这是我的物联网开发系列文章,将介绍如何从嵌入式开发、云平台开发、Android端开发来实现一个简单的物联网应用开发,体验物联网全栈开发的过程,积累开发的经验。
本篇文章为系列文章第三篇,主要介绍如何通过STM32来获取传感器(DHT11和光敏电阻传感器)的相关数据,开发工具是Keil MDK5和STM32CubeMX。
系列文章第一篇:物联网介绍和系统初步设计
系列文章第二篇:配置阿里云物联网平台及设备端连接测试
预警:本文内容比较长,有点罗嗦,包含较多图片,熟悉操作的小伙伴根据需要看。
STM32CubeMX是一个非常好用的STM32配置初始化代码的工具。我们可以使用它来对芯片的时钟、引脚和外设进行初始化配置,然后再去Keil里面进行更深入的开发。
由于STM32F103C8T6的外设和引脚比较少,而我们使用的硬件又比较多,所以必须先对引脚的使用做好安排。根据我们的设计需要,我们的STM32F103C8T6需要配置的引脚如下表。表格里有些引脚和外设是在后续的文章中使用的,在这里也一并配置好。
GPIO口 | 功能 | 备注 |
---|---|---|
PA9 | USART1_TX | CH340的RX,连接串口调试助手 |
PA10 | USART1_RX | CH340的TX,连接串口调试助手 |
PB10 | USART3_TX | ESP-12S的RX |
PB11 | USART3_RX | ESP-12S的TX |
PC13 | GPIO | STM32板上的LED |
PA0 | GPIO | STM32板上的按键S2 |
PA1 | GPIO | 蜂鸣器 |
PA2 | GPIO | 风扇电机 |
PB0 | ADC1_IN8 | 光敏电阻传感器 |
PA8 | OLED_RES | OLED |
PB15 | SPI2_MOSI | OLED |
PB14 | OLED_CS | OLED |
PB13 | SPI2_SCK | OLED |
PB12 | OLED_DC | OLED |
PA13 | SYS_JTMS-SWDIO | ST-Link |
PA14 | SYS_JTCK-SWCLK | ST-Link |
PB9 | GPIO | DHT11温湿度传感器 |
除了上述的引脚要配置之外,还要配置TIM时钟、NVIC中断、RCC的外部晶振和时钟树。
注意:STM32CubeMX在使用之前,需要安装好Java环境和STM32的MCU芯片包。具体的可以查看这篇博客:链接。
打开STM32CubeMX,然后选择从MCU新建一个工程。在左侧选择 STM32F1,然后在右边找到STM32F103C8选中,点击 Start Project。
注意:我在配置的时候并不是按照下文中的顺序,所以截图内容并不是按照我实际顺序来的。以下的外设(除了NVIC)都有配置就行,不用过分在意顺序。
来到Project Manager,填写好工程名称和文件位置,选择IDE为MDK-ARM,选择最低版本为V5.27或者5.0都行。
点击左侧的Code Generator,将下图所示的两个选项选上。第一个选项表示只将我们用到的库函数代码保存到工程,可以节省工程的空间;第二个选项是表示生成代码时将各种外设的代码按照头文件和源文件的方式分开放,如果这个没勾的话,代码全都挤在main文件里面。
最后,点击右上角的GENERATE CODE就可以生成初始化的代码了。生成代码成功之后就可以在文件位置找到我们的工程了。
在STM32CubeMX生成的工程,我们需要自己将启动文件添加进工程,如下图添加到MDK-ARM分组。另外顺便将DHT11的相关驱动程序(dht11.c、dht11.h、delay.c、delay.h的代码详见本文后面的《DHT11驱动程序》一节)放到工程的文件夹中,同样也要将dht11.c和delay.c添加到工程的分组之中。
提醒:如果驱动程序放置的位置不在工程的头文件路径中,需要将放置驱动程序的位置添加到工程的头文件路径中。
由于我们需要使用串口将传感器数据输出打印到电脑,所以在这里先对用于调试的串口(USART1)进行简单配置。
USART1_TX_BUF[USART1_MAX_SEND_LEN]
,然后添加一个用于串口输出的函数u1_printf(char* fmt,...)
,其中需要使用到头文件"string.h"
、"stdio.h"
和"stdarg.h"
,记得要include。uint8_t USART1_TX_BUF[USART1_MAX_SEND_LEN];
void u1_printf(char* fmt,...)
{
uint8_t i,j;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART1_TX_BUF,fmt,ap);
va_end(ap);
i=strlen((const char*)USART1_TX_BUF);//此次发送数据的长度
for(j=0;j<i;j++)//循环发送数据
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR=USART1_TX_BUF[j];
}
}
注意:
1.使用STM32CubeMX生成的工程代码有严格的格式,很多的注释,我们自己在Keil上添加的代码一定要放在带有USER CODE BEGIN
和USER CODE END
的注释之间的位置。没放在这些位置的代码,下次使用STM32CubeMX对本工程修改时会被清除掉。
2.在.c文件中添加的函数,如果是其他的文件也需要使用到的函数,比如上面说的u1_printf(char* fmt,...)
,需要在对应的头文件中添加函数的声明。
以上两点后面不再赘述。
USART1_MAX_SEND_LEN
,这个是表示缓存数组的长度的。另外,要将上面定义的缓存数组USART1_TX_BUF[USART1_MAX_SEND_LEN]
给extern,方便别的文件可能会使用到。#define USART1_MAX_SEND_LEN 256
extern uint8_t USART1_TX_BUF[USART1_MAX_SEND_LEN];
DHT11是一款有已校准数字信号输出的温湿度传感器。 其精度湿度±5%RH, 温度±2℃,量程湿度20-90%RH, 温度0~50℃。
DHT11应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。1
引脚 | 注释 |
---|---|
VDD | 供电 3-5.5VDC |
DATA | 串行数据,单总线 |
NC | 空脚,请悬空 |
GND | 接地,电源负极 |
注:我这里使用的DHT11是经过再次封装的,在板子上已经将NC悬空,所以只有三个引脚(VDD、DATA、GND)。
DATA引脚用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右。数据分小数部分和整数部分,具体格式如下:
DATA引脚的单总线通信需要使用到微秒级延时,基本过程如下图。后面的驱动程序也是要按照通信的过程原理进行拉高或者拉低,从而读取数据。
DHT11的驱动程序以及使用的延时程序来自于正点原子的程序,并修改了一些内容。
#include "dht11.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F103开发板
//DHT11驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/19
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved
//
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //设置为输出
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
delay_us(30); //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN(); //设置为输出
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()==0)
{
for(i=0;i<5;i++)//读取40位数据
{
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi=buf[0];
*temp=buf[2];
}
}else return 1;
return 0;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
GPIO_Initure.Pin=GPIO_PIN_9; //PB9
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure); //初始化
DHT11_Rst();
return DHT11_Check();
}
#ifndef __DHT11_H
#define __DHT11_H
#include "delay.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F103开发板
//DHT11驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/19
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved
//
//IO方向设置
#define DHT11_IO_IN() {GPIOB->CRH &= 0xFFFFFF0F;GPIOB->CRH |= (8<<4);}
#define DHT11_IO_OUT() {GPIOB->CRH &= 0xFFFFFF0F;GPIOB->CRH |= (3<<4);}
//IO操作函数
#define DHT11_DQ_IN PBin(9) //数据端口 PB9
#define DHT11_DQ_OUT PBout(9)//数据端口 PB9
u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif
#include "delay.h"
//
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/17
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
//
static u32 fac_us=0; //us延时倍乘数
#if SYSTEM_SUPPORT_OS
static u16 fac_ms=0; //ms延时倍乘数,在os下,代表每个节拍的ms数
#endif
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
// delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
// delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
// delay_ostimedly:用于OS延时,可以引起任务调度.
//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef OS_CRITICAL_METHOD //OS_CRITICAL_METHOD定义了,说明要支持UCOSII
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OS_TICKS_PER_SEC //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
#endif
//支持UCOSIII
#ifdef CPU_CFG_CRITICAL_METHOD //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OSCfg_TickRate_Hz //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNestingCtr //中断嵌套级别,即中断嵌套次数
#endif
//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
OS_ERR err;
OSSchedLock(&err); //UCOSIII的方式,禁止调度,防止打断us延时
#else //否则UCOSII
OSSchedLock(); //UCOSII的方式,禁止调度,防止打断us延时
#endif
}
//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
OS_ERR err;
OSSchedUnlock(&err); //UCOSIII的方式,恢复调度
#else //否则UCOSII
OSSchedUnlock(); //UCOSII的方式,恢复调度
#endif
}
//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
OS_ERR err;
OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err); //UCOSIII延时采用周期模式
#else
OSTimeDly(ticks); //UCOSII延时
#endif
}
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
}
#endif
//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
reload=SYSCLK; //每秒钟的计数次数 单位为K
reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右
fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
SysTick->LOAD=reload; //每1/OS_TICKS_PER_SEC秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
//延时nus
//nus:要延时的us数.
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
delay_osschedlock(); //阻止OS调度,防止打断us延时
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
delay_osschedunlock(); //恢复OS调度
}
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u16 nms)
{
if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
delay_ostimedly(nms/fac_ms); //OS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
#else //不用ucos时
//延时nus
//nus为要延时的us数.
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}
#endif
#ifndef __DELAY_H
#define __DELAY_H
#include "main.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2019/9/17
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
//
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
typedef const int32_t sc32;
typedef const int16_t sc16;
typedef const int8_t sc8;
typedef __IO int32_t vs32;
typedef __IO int16_t vs16;
typedef __IO int8_t vs8;
typedef __I int32_t vsc32;
typedef __I int16_t vsc16;
typedef __I int8_t vsc8;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef const uint32_t uc32;
typedef const uint16_t uc16;
typedef const uint8_t uc8;
typedef __IO uint32_t vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t vu8;
typedef __I uint32_t vuc32;
typedef __I uint16_t vuc16;
typedef __I uint8_t vuc8;
void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif
在main文件中,先定义temperature和humidity两个整数(uint_32)变量。
在main函数中,首先使用delay_init(72)
对延时进行初始化,然后使用一个while循环来不断尝试将DHT11初始化直到成功。
delay_init(72);
u1_printf("启动传感器\r\n");
while(DHT11_Init()) //DHT11初始化
{
HAL_Delay(200);
u1_printf("DHT11故障\r\n");
}
在while(1)循环中,使用DHT11_Read_Data(&temperature, &humidity)
读取DHT11的温湿度数据到变量temperature和humidity中,然后通过串口输出来。
while(1)
{
HAL_Delay(1000);
DHT11_Read_Data(&temperature, &humidity);
u1_printf("temperature:%d, humidity:%d \r\n", temperature, humidity);
HAL_Delay(2000);
}
程序文件完成后就进行编译和下载。将串口连接上,并打开调试助手进行接收。返回如下图的数据就是成功了。(温度29℃,湿度95%)
光敏电阻传感器中最简单的电子器件是光敏电阻,它能感应光线的明暗变化,输出微弱的电信号,通过简单电子线路放大处理,可以控制LED灯具的自动开关。
该模块主要有以下的特点:
光敏电阻传感器模块对环境光线最敏感,一般用来检测周围环境的光线的亮度,触发单片机或继电器模块等。模块在环境光线亮度达不到设定阈值时,DO 端输出高电平,当外界环境光线亮度超过设定阈值时,DO 端输出低电平;模拟量输出 AO 可以和 AD 模块相连,通过 AD 转换,可以获得
环境光强更精准的数值。我这里使用的是它的模拟量输出 AO 。
在adc.c文件中添加read_adc()
和get_light()
两个函数。read_adc()
是读取ADC口的电压数值的函数,get_light()
是按照公式将读取的电压转换为光照强度(单位:勒克斯)。每个光照强度传感器的转换公式都存在一些差异,我这里的公式根据实验自己简单测算的。具体测算的方式可以查看这里:使用拟合方法实现光敏电阻传感器数值与光照强度的近似转换。如果不想要那么准的话,也可以直接使用我这里的公式。
uint16_t read_adc(void)
{
uint16_t temp;
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 50);
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
{
temp = HAL_ADC_GetValue(&hadc1);
}
return temp;
}
uint16_t get_light(void)
{
uint16_t adc = read_adc();
uint16_t light = 200000000 * pow(adc, -2.086);
return light;
}
在main函数中使用get_light()
函数读取光照强度,然后通过串口输出。
while(1)
{
HAL_Delay(1000);
DHT11_Read_Data(&temperature, &humidity);
light = get_light();
u1_printf("temperature:%d, humidity:%d, light:%d \r\n", temperature, humidity, light);
HAL_Delay(2000);
}
内容主要来自百度百科:DHT11_百度百科 ↩︎