前一篇的DHT22基础篇差不多是一年前写的了,这一年间发生挺多事情,编程篇拖到现在,废话不多说进入正题。
从前面的基础篇也很详细讲了如何去驱动DHT22的步骤(需了解请移步至:https://blog.csdn.net/k1ang/article/details/98789397),这里我们就将这些步骤进行代码化。
1、起始信号:
void DHT22_Start(void) { Set_IO_Output(); //设置IO为输出模式 DHT_DQ_OUT_L(); //输出低电平1ms SysTick_Delay_Ms(1); DHT_DQ_OUT_H(); //释放总线,即输出高电平30us SysTick_Delay_Us(30); }2、应答信号:
应答信号并不是单纯的按照时序编写,中间增加了应答超时判断,详细见以下代码:
uint8_t DHT22_Ack(void) { uint8_t cnt = 0; Set_IO_Input();//设置IO为输入模式 if (DHT_DQ_IN() == Bit_RESET) //检测是否有低电平 { while((!DHT_DQ_IN()) && (cnt <= 85))//计算低电平持续时间,判断低电平是否应答超时,手册最大应答时间是85us { cnt ++; //每1us自加一次 SysTick_Delay_Us(1); } if(cnt > 85) //大于85us则应答超时 { return ACK_OVER_TIME;//响应超时 } else //应答正常 { cnt = 0; } /*低电平应答正常后到高电平 */ while(DHT_DQ_IN() && (cnt <= 85))计算高电平持续时间,判断高电平是否应答超时,最大应答时间也是85us { cnt ++; SysTick_Delay_Us(1); } if(cnt > 85) //响应超时 { return ACK_OVER_TIME; } else { cnt = 0; return ACK_SUCCESS; } } else { return ACK_ERROR;//应答错误,起始信号后没有应答信号返回 } }3、温湿度数据读取:
应答信号过后,DHT22会连续输出40位数据,这40位数据可分为5个字节数据,5个字节数据分别表示湿度高8位、湿度低8位、温度高8位、温度低8位和校验位。校验位等于湿度高8位+湿度低8位+温度高8位+温度低8位,由于校验位也是8位的,4个8位数据加起来基本会溢出了,如果代码是根据校验结果来更新数显温湿度,如果溢出就会导致数据不更新。为了避免这种情况,可以先将4个温湿度数据相加后强制转换为8位,再和DHT22返回的校验位比较,如果相等则表示数据无误,可以更新显示,详细代码如下:
uint8_t Read_DHT22_Data(DHT22_Data_TypeDef *Data) { DHT22_Start();//发送起始信号 if(DHT22_Ack() == ACK_SUCCESS) //接收应答成功 { Data->RH_int = DHT22_Read_Byte(); //读湿度数据高8位 Data->RH_deci = DHT22_Read_Byte(); //读湿度数据低8位 Data->Temp_int = DHT22_Read_Byte(); //读温度数据高8位 Data->Temp_deci = DHT22_Read_Byte(); //读温度数据低8位 Data->check_sum = DHT22_Read_Byte(); //读校验位 } Set_IO_Output();//设置IO为输出模式 GPIO_SetBits(DHT_DQ_Port,DHT_DQ_Pin);//释放总线 Data->sum = (uint8_t)(Data->RH_deci + Data->RH_int + Data->Temp_deci + Data->Temp_int);//将温湿度高低8位累加,并强制转换为uint8_t类型 if(Data->check_sum == Data->sum)//校验正确 { Data->RH = (float)((256 * Data->RH_int + Data->RH_deci) / 10);//转换为湿度数据,除以10是因为传感器出来的值是实际的10倍 Data->Temp = (float)((256 * Data->Temp_int + Data->Temp_deci) / 10);//转换为温度数据 return SUCCESS; //校验通过 } else return ERROR;//校验不过 }4、读一个字节函数
在第3步用到一个读字节函数,下面从代码分析如果根据“0”和“1”时序读取,详细代码如下:
uint8_t DHT22_Read_Byte(void) { uint8_t i; uint8_t DHT22_Byte = 0; for(i = 0; i < 8; i++) { while(DHT_DQ_IN() == Bit_RESET);//等待低电平结束 SysTick_Delay_Us(40); //等待40us,再判断IO口电平状态 if(DHT_DQ_IN() == Bit_SET)// 40 us后仍为高电平则表示数据“1” { /* 等待数据1的高电平结束 */ while(DHT_DQ_IN() == Bit_SET); DHT22_Byte |= (uint8_t)(0x01 << (7-i)); //把第7-i位置1,MSB先行 } else // 40 us后为低电平则表示数据“0” { DHT22_Byte &= (uint8_t)~(0x01 << (7-i)); //把第7-i位置0,MSB先行 } } return DHT22_Byte;//返回当前读取到的字节 }这里解释下为什么以40us来判断1和0信号:
先看下手册中“0”和“1”信号的时序
从上图中可以看到信号“0”和信号“1”的低电平时间都是相同的,信号“0”的高电平时间最大是30us,信号“1”的高电平时间最大是75us,要区分出信号“0”和信号“1”只能通过高电平的时间来判断。从30us和75us之间取一个时间点,我以40us作为临界点,当低电平结束后,经过40us如果还是高电平则说明是信号“1”,如果40us后是低电平则说明是信号“0”。
最后附上张测试结果与小米温湿度计对比的图片: