#申请原创#
@21小跑堂
手头有一个DHT22温湿度传感器和CH32V307开发板,可玩性极强。DHT22是已校准的数字温湿度传感器,用于检测环境温湿度,采用DHT22(AM2302),标准单总线接口。拥有比常见的DHT11更高的精度和更大的量程。
DHT22产品主要特性如下:
工作电压: 3.3V-5.5V
湿度分辨率: 0.1%RH
湿度测量范围: 0%RH ~ 99.9%RH
湿度测量误差: ±2%RH (25°C)
温度分辨率: 0.1°C
温度测量范围: -40°C ~ 80°C
温度测量误差: ±0.5℃
DHT22传感器有3个引脚,功能如下:
VCC:电源正(3.3V-5.5V)
GND:电源地
DOUT:通信端口
DHT22器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由数据线完成。单总线通常要求外接一个约 5.1kΩ的上拉电阻,这样,当总线闲置时,其状态为高电平。SDA 用于微处理器与 AM2302 之间的通讯和同步,采用单总线数据格式,一次传送 40 位数据,高位先出。具体通信时序如图所示:
DHT22通信格式说明:
起始信号: 微处理器把数据总线(SDA)拉低一段时间(至少800μs)[1],通知传感器准备数据。
响应信号: 传感器把数据总线(SDA)拉低80μs,再接高80μs以响应主机的起始信号。
数据格式: 收到主机起始信号后,传感器一次性从数据总线(SDA)串出40位数据,高位先出。
湿度: 湿度分辨率是16Bit,高位在前;传感器串出的湿度值是实际湿度值的10倍。
温度: 温度分辨率是16Bit,高位在前;传感器串出的温度值是实际温度值的10倍;温度最高位(Bit15)等于1表示负温度,温度最高位(Bit15)等
于0表示正温度;温度除了最高位(Bit14~Bit0)表示温度值。
校验位=湿度高位+湿度低位+温度高位+温度低位。
单总线通信时序
用户主机(MCU)发送一次起始信号(把数据总线 SDA 拉低至少 800µs)后, DHT22从休眠模式转换到高速模式。待主机开始信号结束后,DHT22发送响应信号,从数据总线 SDA 串行送出 40Bit的数据,先发送字节的高位;发送的数据依次为湿度高位、湿度低位、温度高位、 温度低位、校验位,发送数据结束触发一次信息采集,采集结束传感器自动转入休眠模式,直到下一次通信来临。
设计一个结构体用来存放温湿度数据:
typedef struct
{
float hum;
float temp;
} DHT_data;
typedef struct {
GPIO_TypeDef *DHT_Port;
uint16_t DHT_Pin;
} DHT_sensor;
外设读取步骤
主机和传感器之间的通信可通过如下三个步骤完成读取数据。
步骤一:
DHT22上电后( DHT22上电后要等待 2S 以越过不稳定状态,在此期间读取设备不能发送任何指令),测试环境温湿度数据,并记录数据,此后传感器自动转入休眠状态。 DHT22的 SDA 数据线由上拉电阻拉高一直保持高电平,此时 AM2302 的 SDA 引脚处于输入状态,时刻检测外部信号。
设置CH32V307 IO为输出模式的代码如下:
static void goToOutput(DHT_sensor *sensor)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
lineUp();
GPIO_InitStruct.GPIO_Pin = sensor->DHT_Pin;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(sensor->DHT_Port, &GPIO_InitStruct);
}
设置CH32V307 IO为上拉输入模式的代码如下:
static void goToInput(DHT_sensor *sensor)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = sensor->DHT_Pin;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(sensor->DHT_Port, &GPIO_InitStruct);
}
步骤二:
微处理器的 I/O 设置为输出, 同时输出低电平,且低电平保持时间不能小于 800us,典型值是拉低 1MS,然后微处理器的 I/O 设置为输入状态,释放总线,由于上拉电阻,微处理器的 I/O 即DHT22的 SDA 数据线也随之变高,等主机释放总线后,DHT22发送响应信号,即输出 80 微秒的低电平作为应答信号,紧接着输出 80 微秒的高电平通知外设准备接收数据,信号传输如图所示:
步骤三:
DHT22 发送完响应后,随后由数据总线 SDA 连续串行输出 40 位数据,微处理器根据 I/O 电平的变化接收 40 位数据。 位数据“0”的格式为: 50 微秒的低电平加 26-28 微秒的高电平; 位数据“1”的格式为: 50 微秒的低电平加 70 微秒的高电平; 位数据“0”、位数据“1”格式信号如图 所示:
DHT22的数据总线 SDA 输出 40 位数据后,继续输出低电平 50 微秒后转为输入状态,由于上拉电阻随之变为高电平。同时 DHT22内部重测环境温湿度数据,并记录数据,测试记录结束,单片机自动进入休眠状态。单片机只有收到主机的起始信号后,才重新唤醒传感器,进入工作状态。
温湿度数据读取函数设计如下:
DHT_data DHT_getData(DHT_sensor *sensor)
{
DHT_data data = {-128.0f, -128.0f};
goToOutput(sensor);
lineDown();
Delay(18);
lineUp();
goToInput(sensor);
uint16_t timeout = 0;
while(getLine())
{
timeout++;
if (timeout > DHT_TIMEOUT)
{
return data;
}
}
timeout = 0;
while(!getLine())
{
timeout++;
if (timeout > DHT_TIMEOUT)
{
return data;
}
}
timeout = 0;
while(getLine())
{
timeout++;
if (timeout > DHT_TIMEOUT)
{
return data;
}
}
uint8_t rawData[5] = {0,0,0,0,0};
for(uint8_t a = 0; a < 5; a++)
{
for(uint8_t b = 7; b != 255; b--)
{
uint16_t hT = 0, lT = 0;
while(!getLine() && lT != 65535) lT++;
timeout = 0;
while(getLine()&& hT != 65535) hT++;
if(hT > lT) rawData[a] |= (1<<b);
}
}
if((uint8_t)(rawData[0] + rawData[1] + rawData[2] + rawData[3]) == rawData[4])
{
data.hum = (float)(((uint16_t)rawData[0]<<8) | rawData[1])*0.1f;
if(!(rawData[2] & (1<<7)))
{
data.temp = (float)(((uint16_t)rawData[2]<<8) | rawData[3])*0.1f;
}
else
{
rawData[2] &= ~(1<<7);
data.temp = (float)(((uint16_t)rawData[2]<<8) | rawData[3])*-0.1f;
}
}
return data;
}
DHT22传感器读单总线的流程图示意图如图所示:
主程序设计,初始化串口打印,初始化IO结构体:
int main(void)
{
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n",SystemCoreClock);
DHT_sensor dht = {GPIOE, GPIO_Pin_6};
while(1)
{
DHT_data d = DHT_getData(&dht);
printf("Temp %2.1f°C, Hum %2.1f%%\r\n", d.temp, d.hum);
Delay_Ms(1000);
}
线路连接,将DHT22数据线连接到PE6管脚:
运行结果:
完整dht.h代码:
#ifndef DHT_H_
#define DHT_H_
#include "debug.h"
#define DHT_TIMEOUT 100000
typedef struct
{
float hum;
float temp;
} DHT_data;
typedef struct {
GPIO_TypeDef *DHT_Port;
uint16_t DHT_Pin;
} DHT_sensor;
DHT_data DHT_getData(DHT_sensor *sensor);
#endif
完整dht.c代码:
#include "dht.h"
#define lineDown() GPIO_ResetBits(sensor->DHT_Port, sensor->DHT_Pin)
#define lineUp() GPIO_SetBits(sensor->DHT_Port, sensor->DHT_Pin)
#define getLine() GPIO_ReadInputDataBit(sensor->DHT_Port, sensor->DHT_Pin)
#define Delay(d) Delay_Ms(d)
static void goToOutput(DHT_sensor *sensor)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
lineUp();
GPIO_InitStruct.GPIO_Pin = sensor->DHT_Pin;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(sensor->DHT_Port, &GPIO_InitStruct);
}
static void goToInput(DHT_sensor *sensor)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = sensor->DHT_Pin;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(sensor->DHT_Port, &GPIO_InitStruct);
}
DHT_data DHT_getData(DHT_sensor *sensor)
{
DHT_data data = {-128.0f, -128.0f};
goToOutput(sensor);
lineDown();
Delay(18);
lineUp();
goToInput(sensor);
uint16_t timeout = 0;
while(getLine())
{
timeout++;
if (timeout > DHT_TIMEOUT)
{
return data;
}
}
timeout = 0;
while(!getLine())
{
timeout++;
if (timeout > DHT_TIMEOUT)
{
return data;
}
}
timeout = 0;
while(getLine())
{
timeout++;
if (timeout > DHT_TIMEOUT)
{
return data;
}
}
uint8_t rawData[5] = {0,0,0,0,0};
for(uint8_t a = 0; a < 5; a++)
{
for(uint8_t b = 7; b != 255; b--)
{
uint16_t hT = 0, lT = 0;
while(!getLine() && lT != 65535) lT++;
timeout = 0;
while(getLine()&& hT != 65535) hT++;
if(hT > lT) rawData[a] |= (1<<b);
}
}
if((uint8_t)(rawData[0] + rawData[1] + rawData[2] + rawData[3]) == rawData[4])
{
data.hum = (float)(((uint16_t)rawData[0]<<8) | rawData[1])*0.1f;
if(!(rawData[2] & (1<<7)))
{
data.temp = (float)(((uint16_t)rawData[2]<<8) | rawData[3])*0.1f;
}
else
{
rawData[2] &= ~(1<<7);
data.temp = (float)(((uint16_t)rawData[2]<<8) | rawData[3])*-0.1f;
}
}
return data;
}
|
DHT22是较为常用单总线(1-Wire)传感器,对速度要求较低且可节约IO资源,在实际设计中较为常用。作者使用CH32V307开发板轻松采集DHT22的温湿度并进行串口打印,整体实现效果较好,继续加油