目录
一、DHT11说明:
1.典型电路:
-
在使用DHT11传感器时,其DATA数据线通过一个上拉电阻与微处理器的I/O端口相连,这是为了在数据线空闲时保持一个稳定的高电平状态。对于短距离通信(线长小于5米),通常推荐使用4.7kΩ的上拉电阻。如果通信距离较长(超过5米),可能需要根据实际情况选择一个更小的电阻值,以确保数据线上的信号能够稳定地到达微处理器。
-
当DHT11使用3.3V电压供电时,推荐保持数据线尽可能短,因为长距离的接线可能会因为线路损耗而导致传感器供电不足,进而影响测量结果的准确性。此外,如果电源部分存在波动,这也可能会影响温度的测量值。特别是当使用开关电源时,电源的不稳定可能会导致温度读数出现跳动。
-
DHT11传感器的一个特性是,它每次读出的温湿度数值实际上是上一次测量的结果。为了获得实时数据,需要连续读取两次,第二次读取的结果通常更接近当前的实时值。但是,不建议连续多次读取传感器,因为这样可能会影响传感器的性能和数据的准确性。每次读取之间应至少间隔2秒钟,这样可以给传感器足够的时间来准备下一次测量,从而获得更准确的数据。
2.串行通信说明(单线双向):
单总线说明:
-
DHT11是一种常用的单总线数字温湿度传感器,它通过一条数据线与微控制器通信。这种单总线的设计意味着所有的数据交换和控制都通过这一根线完成。在这种通信协议中,设备通过一个漏极开路或三态端口连接到数据线,这样当设备不发送数据时,它能够释放总线,允许其他设备使用总线。为了确保在没有数据传输时总线为高电平状态,单总线通常需要外接一个约4.7kΩ的上拉电阻。
-
由于DHT11采用主从结构,只有主机(微控制器)发起请求时,从机(DHT11)才会响应。因此,主机访问DHT11时必须严格遵守单总线通信序列。如果通信序列出现混乱,DHT11将不会响应主机的请求。
单总线传送数据位定义:
-
DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次传送40位数据, 高位先出。
-
数据格式: 8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验位。
-
其中湿度小数部分为0。
校验位数据定义:
“8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据”=8bit校验位。
二、DHT11读取时为啥要切换模式:
使用推挽输出模式来读取电平变化在某些情况下是可行的,但可能会遇到一些问题,特别是在与DHT11这类传感器通信时。以下是一些关键点,解释了为什么在与DHT11通信时,仅使用推挽输出模式可能不够:
1. 通信时序要求
DHT11通信协议对时序有严格的要求。它依赖于精确的高电平和低电平持续时间来传输数据。在推挽输出模式下,GPIO引脚可以驱动电流,但在读取数据时,需要引脚处于浮动状态以准确检测由传感器拉高或拉低的电平变化。
2. 信号检测的灵敏度
在推挽输出模式下,GPIO引脚会尽力保持其状态(高或低),直到程序改变它。这意味着,如果引脚被配置为输出高电平,它可能无法准确检测到微弱的低电平信号,尤其是当这些信号由外部设备(如DHT11)生成时。
3. 浮动输入模式的优势
将GPIO配置为浮动输入模式(GPIO_Mode_IN_FLOATING
)时,引脚会处于高阻抗状态,这意味着它不会尝试驱动信号,而是允许外部信号驱动它。这使得微控制器能够更准确地读取由外部设备设置的电平状态,无论是高还是低。
4. 避免冲突
如果GPIO被配置为推挽输出,并且尝试读取数据,而此时外部设备(如DHT11)也在尝试通过同一引脚驱动信号,可能会导致冲突。这种冲突可能导致不稳定的读数或通信失败。
5. 功耗考虑
在某些低功耗应用中,可能希望在不活跃时减少GPIO引脚的功耗。在输出模式下,GPIO引脚会持续消耗电流以维持其电平状态,而在浮动输入模式下,引脚在不活跃时消耗的电流要少得多。
6.结论
虽然在理论上可以使用推挽输出模式来读取电平变化,但在实践中,特别是在需要精确时序和敏感信号检测的应用(如DHT11通信)中,推荐使用浮动输入模式。这样可以确保微控制器能够准确地检测到由外部设备生成的信号,避免通信错误和提高系统的可靠性。
7.切换模式代码:
void DHT11_Mode(uint8_t mode) {
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
if (mode) {
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
} else {
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
//数据线会被上拉电阻拉高,主机通过检测数据线的电平变化来读取DHT11发送的数据。
}
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
三、发送开始准备读取信号:
步骤一:
DHT11上电后(DHT11上电后要等待1s以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时DHT11的DATA数据线由上拉电阻拉高一直保持高电平; 此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。
//初始化PA5引脚
void DHT11_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
DHT11_Mode(1); // 推挽输出
GPIO_SetBits(DHT11_PORT, DHT11_PIN); // 将PA5设置为高电平
}
步骤二:
微处理器的I/O设置为输出同时输出低电平,且低电平保持时间不能小于18ms(最大不得 超过30ms),然后微处理器的I/O设置为输入状态,由于上拉电阻,微处理器的I/O即DHT11的 DATA数据线也随之变高,等待DHT11作出回答信号。发送信号如图所示:
void DHT11_Start(void) {
DHT11_Mode(1); //推挽输出
GPIO_ResetBits(DHT11_PORT, DHT11_PIN);
Delay_ms(20); // 保持20ms起始信号
GPIO_SetBits(DHT11_PORT, DHT11_PIN);
Delay_us(60); // 拉高等待电平被拉低(确保延时完之前,电平被拉低)
}
四、接收DHT11应答信号:
DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的 DATA引脚处于输出状态,输出80微秒的低电平作为应答信号,紧接着输出80微秒的高电平通知外设准备接收数据,微处理器的I/O此时处于输入状态,检测到I/O有低电平(DHT11回应信号)后,等待80微秒的高电平后的数据接收,发送信号如图所示:
uint8_t DHT11_Reply(void) {
DHT11_Start(); //发送准备读取信号
uint8_t timeout = 100;
DHT11_Mode(0); //切换模式为浮空输入,接收电平变化
// 等待低电平结束,DHT11首先输出80微秒的低电平作为应答信号
while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) && timeout > 0) {
timeout--;
Delay_us(1);
}
if (timeout == 0) return 1; // 超时
//等待高电平结束,低电平之后输出80微秒的高电平通知 外设准备接收数据
timeout = 100;//重置timeoutDHT11
while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) && timeout > 0) {
timeout--;
Delay_us(1);
}
return (timeout > 0) ? 0 : 1; // 返回0表示成功,1表示失败
}
五、接收DHT11发送的数据一位:
在DHT11发送应答信号结束之后,由DHT11的DATA引脚输出40位数据,微处理器根据I/O电平的变化接收40位数据,位数据 “0”的格式为:50微秒的低电平和26-28微秒的高电平,位数据“1”的格式为:50微秒的低 电平加70微秒的高电平。位数据“0”、“1”格式信号如图所示:
读取方法:
可以等待发送位数据“0”的低电平之后的高电平结束之后读取引脚电平,由于位数据“1”的高电平时长比位数据“0”长,如果读取到的为1,则输出的信号为位数据“1”,否则为零。
uint8_t DHT11_ReadBit(void) {
//DHT11_Reply(); //可用于调试
int8_t timeout = 100;
// 等待低电平结束,50微秒的低电平
while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) && timeout > 0) {
timeout--;
Delay_us(1);
}
Delay_us(50); //等待超过位数据0值的高电平时间,26-28微秒的高电平
timeout = -5;
//检测高电平持续时间,直到高电平结束,或超时timeout>=100
while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) && timeout <100) {
timeout++;
Delay_us(1);
}
return (timeout > 0) ? 1 : 0;
}
六,接收DHT11发送的数据字节:
DHT11传感器通过单总线传输数据,每次通信传输40位,包括湿度的整数部分、湿度的小数部分、温度的整数部分、温度的小数部分以及校验和。每个位的持续时间不同,表示0和1的高低电平持续时间不同,因此需要逐个位地读取并解析。
接收到的40位数据为:
0011 0101(湿度高8位)0000 0000 (湿度低8位)0001 1000(温度高8位 )0000 0100(温度低8位) 0101 0001( 校验位)
校验位=湿度高位+湿度低位+温度高位+温度低位
计算:
00110101+00000000+00011000+00000100=01010001
接收数据正确:
湿度:00110101(整数)=35H=53%RH00000000=00H=0.0%RH=>53%RH+0.0%RH=53.0%RH
温度:00011000(整数)=18H=24℃00000100=04H=0.4℃=>24℃+0.4℃=24.4℃
特殊说明:
当温度低于0℃时温度数据的低8位的最高位置为1。
示例:
-10.1℃表示为0000101010000001
温度:00001010(整数)=0AH=10℃,00000001(小数)=01H=0.1℃
完整时序图:
//读取一个字节
uint8_t DHT11_ReadByte(void) {
uint8_t byte = 0; // 初始化一个变量来存储最终的字节数据,初值为0
for (int i = 0; i < 8; i++) {
// 循环8次,因为1个字节等于8个位(bit)
byte <<= 1;
// 将byte变量的当前值左移1位,为新的位数据腾出位置(相当于乘以2)
byte |= DHT11_ReadBit();
// 读取一个位的数据,使用逻辑或操作(|=)更新byte变量的最低位
}
return byte; // 返回组合好的字节数据
}
//温度湿度数据读取
float DHT11_Read_Temperature_Humidity(uint8_t *temp_H, uint8_t *temp_L, uint8_t *humi_H, uint8_t *humi_L) {
uint8_t buf[5]; // 定义一个数组来存储从DHT11读取的5个字节的数据
uint8_t i; // 循环计数器
if (DHT11_Reply() != 0) { // 检测DHT11的应答信号
return -1; // 如果应答失败,返回-1
}
for (i = 0; i < 5; i++) { // 循环读取5次,因为DHT11每次传输40位数据,即5个字节
buf[i] = DHT11_ReadByte(); // 调用DHT11_ReadByte函数读取单个字节数据,并存储到buf数组中
}
// 检查数据的校验和是否正确
if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4]) {
*humi_H = buf[0]; // 将湿度的高位部分存储到指向的变量中
*humi_L = buf[1]; // 将湿度的低位部分存储到指向的变量中
*temp_H = buf[2]; // 将温度的高位部分存储到指向的变量中
*temp_L = buf[3]; // 将温度的低位部分存储到指向的变量中
return 0; // 如果校验和正确,返回0表示成功读取数据
} else {
return -2; // 如果校验和不正确,返回-2表示校验失败
}
}
七、示例代码及结果:
通过网盘分享的文件:16-DHT11温湿度模块读取
链接: https://pan.baidu.com/s/10ZGZvjZaA7SQtP8yX2gGpQ?pwd=1gbr 提取码: 1gbr