1.1理论分析
1.1.1 DHT11 介绍
DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。
DHT11传感器包括一个电阻式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个 DHT11 传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在 OTP 内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达 20 米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选则。产品为 4 针单排引脚封装。连接方便,特殊封装形式可根据用户需求而提供。
DHT11 的技术参数如下:
- 工作电压范围: 3.3V-5.5V
- 工作电流 :平均 0.5mA
- 输出:单总线数字信号
- 测量范围:湿度 20~90%RH,温度 0~50℃
- 精度 :湿度±5%,温度±2℃
- 分辨率 :湿度 1%,温度 1℃
1.1.2 STM32读取 DHT11
DHT11 模块的数据管脚用于 MCU 与 DHT11 之间的通讯和同步,采用单总线数据格式,一次通讯时间 4ms 左右,一次完整的数据传输为 40bit,高位先出,数据分小数部分和整数部分,具体格式是:
8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据 +8bit校验和数据传送正确时校验和数据等于 8bit 湿度整数数据+8bit 湿度小数数据+8bi 温度整数数据+8bit 温度小数数据所得结果的末 8 位。
传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。例如,某次从 DHT11 读到的数据如下所示。
由以上数据就可得到湿度和温度的值,计算方法:
湿度= byte4 . byte3=45.0 (%RH)
温度= byte2 . byte1=28.0 ( ℃)
校验= byte4+ byte3+ byte2+ byte1=73(=湿度+温度)(校验正确)
可以看出, DHT11 的数据格式是十分简单的, DHT11 和 MCU 的一次通信最大为 3ms 左右,建议主机连续读取时间间隔不要小于 100ms。
下面,介绍一下 DHT11 的传输时序。
1.主机与DHT11通讯流程
主机MCU 发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,等待主机开始信号结束后,即:拉低数据线,保持至少 18ms时间,然后拉高数据线 20~40us时间。DHT11 发送响应信号,送出 40bit 的数据,并触发一次信号采集,用户可选择读取部分数据。正常情况, DHT11 会拉低数据线,保持80us时间,作为响应信号,然后 DHT11 拉高数据线,保持80us时间后,开始输出数据。DHT11 接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11 不会主动进行温湿度采集。采集数据后转换到低速模式。
2.主机复位信号和 DHT11 响应信号
总线空闲状态为高电平,主机把总线拉低等待 DHT11 响应,主机把总线拉低必须大于 18 毫秒,保证 DHT11 能检测到起始信号。DHT11 接收到主机的开始信号后,等待主机开始信号结束,然后发送 80us 低电平响应信号。主机发送开始信号结束,延时等待 80us 后,读取 DHT11 的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高,如图中黑色线所示
总线为低电平,说明 DHT11 发送响应信号,DHT11 发送响应信号后,再把总线拉高 80us,准备发送数据,每一 bit 数据都以 50us 低电平时隙开始,高电平的长短定了数据位是 0 还是 1。格式如图中灰色线所示。如果读取响应信号为高电平,则 DHT11 没有响应,请检查线路是否连接正常。当最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,随后总线由上拉电阻拉高进入空闲状态。
3.数字‘ 0’信号表示方法
数字 0 信号表示方法如图所示
4.数字‘ 1’信号表示方法
数字 1 信号表示方法如图所示
这里我们可以看出,数字“0”和数字“1”不同的地方在于高电平的时间不同,这也是读取数据的关键所在。
1.2实验详解
1.2.1实验目的
- 通过实验掌握STM32 芯片GPIO 的配置方法
- 掌握温湿度传感器DHT11 的原理与使用
1.2.2实验设备
硬件:PC 机一台;STM32开发板一套; DHT11 一个
软件:Windows 10系统,Keil5集成开发环境、串口助手
1.2.3实验相关电路图
DHT11模块相关电路途如下图所示:
传感器模块是单总线通信,单总线通常要求外接一个4.7kΩ的上拉电阻,这样,当总线闲置时,总线上始终是高电平
【注】VCC接开发板的5V、GND 接开发板的GND、DATA接到开发板的PC5上,当然,也可以接到其他引脚上,修改相应的GPIO代码即可。
1.2.4 Lib_V3.5.0库实现
代码很简单。主要是根据DHT11的操作即可,另外需要高精度延时,这里用到了滴答定时器,这个在前面的章节以已经讲过了,这里就不在细说了。接下来看看DHT11的数据采集实现。
1.首先对GPIO初始化,配置为推挽输出,速度设置为高速。
/**
* @brief 配置DHT11用到的IO口
* @param None
* @retval None
*/
static void DHT11_GPIO_Config ( void )
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启DHT11_Dout_GPIO_PORT的外设时钟*/
DHT11_Dout_SCK_APBxClock_FUN ( DHT11_Dout_GPIO_CLK, ENABLE );
/*选择要控制的DHT11_Dout_GPIO_PORT引脚*/
GPIO_InitStructure.GPIO_Pin = DHT11_Dout_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化DHT11_Dout_GPIO_PORT*/
GPIO_Init ( DHT11_Dout_GPIO_PORT, &GPIO_InitStructure );
}
2.接着就是主机MCU发送一次开始信号,主机把总线拉低必须大于 18 毫秒,保证 DHT11 能检测到起始信号,然后拉高数据线 20~40us时间。
/*输出模式*/
DHT11_Mode_Out_PP();
/*主机拉低*/
DHT11_Dout_0;
/*延时18ms*/
Delay_ms(18);
/*总线拉高 主机延时30us*/
DHT11_Dout_1;
Delay_us(30); //延时30us
3.DHT11 接收到主机的开始信号后,等待主机开始信号结束,然后发送 80us 低电平响应信号。主机发送开始信号结束,延时等待 80us 后, DHT11 开始输出数据。每一 bit 数据都以 50us 低电平时隙开始,高电平的长短定了数据位是 0 还是 1。
DHT11读取一个字节数据的函数如下。
/**
* @brief 从DHT11读取一个字节,MSB先行
* @param None
* @retval byte
*/
static uint8_t DHT11_ReadByte ( void )
{
uint8_t i, byte=0;
for(i=0;i<8;i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(DHT11_Dout_IN()==Bit_RESET);
/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
Delay_us(40); //延时x us 这个延时需要大于数据0持续的时间即可
if(DHT11_Dout_IN()==Bit_SET)/* x us后仍为高电平表示数据“1” */
{
/* 等待数据1的高电平结束 */
while(DHT11_Dout_IN()==Bit_SET);
byte|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”
{
byte&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
}
}
return byte;
}
这个函数就是来读取DHT11的数据,DHT11通过不同的延时来表示“0”和“1”,以26~28us的高电平表示“0”,以40us高电平表示“1”,因此这里通过去一个中间延时来区分。
4.主机MCU读取完40位数据后,需要检查数据的正确性。
下面给出一次温湿度数据的读取代码。
/**
* @brief 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
* @param DHT11_Data:DHT11数据类型
* @retval ERROR: 读取出错
* SUCCESS:读取成功
*/
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{
/*输出模式*/
DHT11_Mode_Out_PP();
/*主机拉低*/
DHT11_Dout_0;
/*延时18ms*/
Delay_ms(18);
/*总线拉高 主机延时30us*/
DHT11_Dout_1;
Delay_us(30); //延时30us
/*主机设为输入 判断从机响应信号*/
DHT11_Mode_IPU();
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(DHT11_Dout_IN()==Bit_RESET)
{
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while(DHT11_Dout_IN()==Bit_RESET);
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while(DHT11_Dout_IN()==Bit_SET);
/*开始接收数据*/
DHT11_Data->humi_int = DHT11_ReadByte();
DHT11_Data->humi_deci = DHT11_ReadByte();
DHT11_Data->temp_int = DHT11_ReadByte();
DHT11_Data->temp_deci = DHT11_ReadByte();
DHT11_Data->check_sum = DHT11_ReadByte();
/*读取结束,引脚改为输出模式*/
DHT11_Mode_Out_PP();
/*主机拉高*/
DHT11_Dout_1;
/*检查读取的数据是否正确*/
if(DHT11_Data->check_sum==DHT11_Data->humi_int+DHT11_Data->humi_deci+DHT11_Data->temp_int+DHT11_Data->temp_deci)
{
return SUCCESS;
}
else
return ERROR;
}
else
return ERROR;
}
这个完整流程如图2所示。
1.2.5 HAL库实现
我们在串口的例子的基础上进行配置。
串口通信(HAL库)
这里只用到了PC5,因此只需要将PC5配置成输出即可。
生成工程即可。
HAL库和标准库的差不多,流程都是一样的,只是使用的函数稍微不同。一个字节数据读取函数如下:
/**
* @brief 从DHT11读取一个字节,MSB先行
* @param None
* @retval byte
*/
static uint8_t DHT11_ReadByte ( void )
{
uint8_t i, byte=0;
for(i=0;i<8;i++)
{
/*每bit以50us低电平标置开始,轮询直到从机发出 的50us 低电平 结束*/
while(DHT11_Dout_IN() == GPIO_PIN_RESET);
/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,
*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时
*/
delay_us(40); //延时x us 这个延时需要大于数据0持续的时间即可
if(DHT11_Dout_IN()==GPIO_PIN_SET)/* x us后仍为高电平表示数据“1” */
{
/* 等待数据1的高电平结束 */
while(DHT11_Dout_IN()==GPIO_PIN_SET);
byte|=(uint8_t)(0x01<<(7-i)); //把第7-i位置1,MSB先行
}
else // x us后为低电平表示数据“0”
{
byte&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行
}
}
return byte;
}
完整的温湿度读取数据实现如下:
/**
* @brief 一次完整的数据传输为40bit,高位先出
* 8bit 湿度整数 + 8bit 湿度小数 + 8bit 温度整数 + 8bit 温度小数 + 8bit 校验和
* @param DHT11_Data:DHT11数据类型
* @retval ERROR: 读取出错
* SUCCESS:读取成功
*/
uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{
/*输出模式*/
DHT11_Mode_Out_PP();
/*主机拉低*/
DHT11_Dout_0;
/*延时18ms*/
HAL_Delay(18);
/*总线拉高 主机延时30us*/
DHT11_Dout_1;
delay_us(30); //延时30us
/*主机设为输入 判断从机响应信号*/
DHT11_Mode_IPU();
/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/
if(DHT11_Dout_IN()==GPIO_PIN_RESET)
{
/*轮询直到从机发出 的80us 低电平 响应信号结束*/
while(DHT11_Dout_IN()==GPIO_PIN_RESET);
/*轮询直到从机发出的 80us 高电平 标置信号结束*/
while(DHT11_Dout_IN()==GPIO_PIN_SET);
/*开始接收数据*/
DHT11_Data->humi_int = DHT11_ReadByte();
DHT11_Data->humi_deci = DHT11_ReadByte();
DHT11_Data->temp_int = DHT11_ReadByte();
DHT11_Data->temp_deci = DHT11_ReadByte();
DHT11_Data->check_sum = DHT11_ReadByte();
/*读取结束,引脚改为输出模式*/
DHT11_Mode_Out_PP();
/*主机拉高*/
DHT11_Dout_1;
/*检查读取的数据是否正确*/
if(DHT11_Data->check_sum==DHT11_Data->humi_int+DHT11_Data->humi_deci+DHT11_Data->temp_int+DHT11_Data->temp_deci)
{
return SUCCESS;
}
else
return ERROR;
}
else
return ERROR;
}
值得注意的是,这里需要实现微秒延时,系统滴答定时器是HAL库初始化的,仅仅实现了毫秒延时,微秒延时如下:
#define CPU_FREQUENCY_MHZ 72 // STM32时钟主频
void delay_us(__IO uint32_t delay)
{
int last, curr, val;
int temp;
while (delay != 0)
{
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;
if (curr >= 0)
{
do
{
val = SysTick->VAL;
}
while ((val < last) && (val >= curr));
}
else
{
curr += CPU_FREQUENCY_MHZ * 1000;
do
{
val = SysTick->VAL;
}
while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
当然还可以使用定时器来实现延时。
1.2.6实验现象
不管是使用何种库,结果都是一样的,每隔1.5s就输出温湿度。
代码获取方法
1.长按下面二维码,关注公众号[嵌入式实验楼]
2.在公众号回复关键词[STM32F1]获取资料
欢迎访问我的网站:
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
转载:https://blog.csdn.net/u013162035/article/details/116972044