《嵌入式-STM32开发指南》第三部分 外设篇 - 第1章 温湿度传感器DHT11
时间:2022-11-08 21:00:00
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 机一台;STM一套32开发板; 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的简书