打印
[研电赛技术支持]

【GD32F303红枫派使用手册】第二十四讲 DHT11温湿度传感器检测实验

[复制链接]
2759|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 聚沃科技 于 2024-6-24 09:59 编辑


24.1 实验内容
通过本实验主要学习以下内容:
• DHT11操作原理
单总线GPIO模拟操作原理
24.2 实验原理
HT11是一款已校准数字信号输出的温湿度一体化数字传感器。该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点信号,传输距离可达20米以上。
其具体参数如下:
工作电压:3.3V-5.5V
工作电流:0.5A
控制方式:单总线
输出方式:数字量
湿度精度:±5%
温度精度:±2℃
湿度量程:5%~95%
温度量程:-20℃~+60℃
DHT11引脚定义和封装如下图所示
DHT11采用单总线的方式进行数据传输,下面对其通信时序以及传输数据构成进行介绍。
DHT11通信时序可分为:建立连接、数据接收两部分。
建立连接部分时序如下图所示,共包括:主机动作和从机响应两部分。主机动作:DHT11温湿度传感器上电后,数据线处于空闲状态(配置数据线空闲状态为高电平)。主机发送建立连接动作,动作内容为:拉低数据线再拉高数据线(数据线拉低时间>18s、拉高时间在2040us之间)然后释放总线。从机响应:从机接收到主句发送的建立连接动作后,先拉低数据线,再拉高数据线,表示连接建立成功(拉低数据线4050us,拉高数据线4050us)。
在连接成功建立后,从机发送数据,主机进行数据的接收。数据接收:每一bit数据都以50us低电平开始,通过判断低电平后的高电平时间来决定接收数据的种类。接收高电平时间为26us28us表示0,接收高电平为70us表示1。当最后一bit数据传送完毕后,从机拉低数据线50us,随后从机释放总线,总线进入空闲状态(高电平)。
主机一次接收40bit数据,共5字节,每个字节的含义如下图所示。从高到低依次为:湿度整数h(0)、湿度小数h()、温度整数h()、温度小数h()、校验和h()。检验和等于前四字节数据之和。在一次数据接收完成后,验证h()是否等于h()h()h()h()。若等式成立则表示数据成功接收,然后进行数据更新,否则不进行数据更新。
DHT11数据格式举例如下。
24.3 硬件设计
DHT11硬件电路图如下所示。DHT11为单信号线通信,因而仅使用了一个IO进行数据通信,且该IO使用了RC进行滤波并采用4.7K进行上拉,保证了数据通信的稳定性,电源地通过100nf电容滤波。
24.4 代码解析
24.4.1 DHT11初始化
DHT11初始化函数实现如下,主要包括DHT11数据通信引脚的初始化以及DHT11 resetcheck操作。
C
uint8_t dht11_init(void)
{
          driver_gpio_general_init(&DHT11_DA);
    /* DHT11_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
    dht11_reset();
    return dht11_check();
}
在该函数中,将DHT11数据引脚配置为开漏模式,使用上可以不用切换引脚模式以及方向,使用比较方便。
dht11_reset()为复位DHT11操作函数,其实现如下,如原理中介绍,首先拉低数据引脚20ms,之后拉高30us,实现对DHT11的复位操作。
C
static void dht11_reset(void)
{
    DHT11_DA_OUT(0);    /* 拉低DQ */
    delay_ms(20);       /* 拉低至少18ms */
    DHT11_DA_OUT(1);    /* DQ=1 */
    delay_us(30);       /* 主机拉高10~35us */
}
dht11_check()check DHT11是否正常连接以及工作,其实现如下,其主要通过判断DHT11是否能够响应主机的复位请求操作来判断DHT11是否连接以及工作正常,如果工作正常,该函数返回值为0,否则返回1.
C
uint8_t dht11_check(void)
{
    uint8_t retry = 0;
    uint8_t rval = 0;

    while (DHT11_DA_IN && retry < 100)  /* DHT11会拉低83us */
    {
        retry++;
        delay_us(1);
    }

    if (retry >= 100)
    {
        rval = 1;
    }
    else
    {
        retry = 0;

        while (!DHT11_DA_IN && retry < 100) /* DHT11拉低后会再次拉高87us */
        {
            retry++;
            delay_us(1);
        }
        if (retry >= 100) rval = 1;
    }
   
    return rval;
}
24.4.2 DHT11温湿度读取
DHT11温湿度读取函数如下,通过该函数将会连续读取5个字节数据,前两个为湿度数据,之后两个为温度数据,最后一个数据为校验和。读取正确且校验完成后相关湿度数据将会放在对应的形参指定的变量内,该函数将会返回0.
C
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
    uint8_t buf[5];
    uint8_t i;
    dht11_reset();

    if (dht11_check() == 0)
    {
        for (i = 0; i < 5; i++)     /* 读取40位数据 */
        {
            buf[i] = dht11_read_byte();
        }

        if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
        {
            *humi = buf[0];
            *temp = buf[2];
        }
    }
    else
    {
        return 1;
    }
   
    return 0;
}
24.4.3 主函数
DHT11温湿度检测实验主函数如下所示,首先对systick延迟函数进行初始化,之后对DHT11初始化,最后在主循环中没间隔1s读取一次温湿度数据。
C
int main(void)
{
          uint8_t temperature;
    uint8_t humidity;
        
    delay_init();

    while (dht11_init()) /* DHT11初始化 */
                {
                }
               
        while (1)
        {
        dht11_read_data(&temperature, &humidity);             /* 读取温湿度值 */
        delay_ms(1000);
        }
}
24.5 实验结果
DHT11温湿度读取实验烧录到红枫派开发板中,并连接串口,运行程序会,将会每秒钟打印一次温湿度数据。向着DHT11吹口热气,将会观察到打印的温湿度数值都将会增加。

本教程由GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网,GD32MCU技术交流群:859440462




使用特权

评论回复
沙发
rickluo| | 2025-3-22 11:31 | 只看该作者
#define GET_I2C_SDA()             GPIO_ReadDataBit(GPIOF,GPIO_Pin_7)    // 读取SDA端口
#define SET_I2C_SCL()             GPIO_SetBits(GPIOF,GPIO_Pin_6)          // 时钟线SCL输出高电平
#define CLR_I2C_SCL()             GPIO_ResetBits(GPIOF,GPIO_Pin_6)        // 时钟线SCL输出低电平
#define SET_I2C_SDA()             GPIO_SetBits(GPIOF,GPIO_Pin_7)          // 数据线SDA输出高电平
#define CLR_I2C_SDA()             GPIO_ResetBits(GPIOF,GPIO_Pin_7)        // 数据线SDA输出低电平
#define I2C_DELAY           10            
static void GpioInit(void)
{
         /*配置I2C管脚的功能 */
         GPIO_InitTypeDefGPIO_InitStructure;//定义一个GPIO_InitTypeDef类型的结构体
         GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6 | GPIO_Pin_7;//选择要控制的GPIO引脚
         GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OutOD;//设置引脚模式为
         GPIO_InitStructure.GPIO_Pull= GPIO_Pull_NoPull;//模式
         GPIO_Init(GPIOF,&GPIO_InitStructure);
         
         
//GPIOF GPIO_Pin_7 I2C0 SDA
//GPIOF GPIO_Pin_6 I2C0 SCL
}
/**
*******************************************************************
* @function 产生IIC起始时序,准备发送或接收数据前必须由起始序列开始
* @param
* @brief   SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据
*          生成下图所示的波形图,即为起始时序
*                1 2    3    4   
*                    __________     
*          SCL : __/          \_____
*                 ________         
*          SDA :         \___________
*******************************************************************
*/
static void I2CStart(void)
{
         SET_I2C_SDA();          // 1#数据线SDA输出高电平
         SET_I2C_SCL();          // 2#时钟线SCL输出高电平   
         DelayNus(I2C_DELAY);            // 延时4us
         CLR_I2C_SDA();          // 3#数据线SDA输出低电平
         DelayNus(I2C_DELAY);            // 延时4us
         CLR_I2C_SCL();          // 4#时钟线SCL输出低电平,保持I2C的时钟线SCL为低电平,准备发送或接收数据
         DelayNus(I2C_DELAY);            // 延时4us
}
/**
*******************************************************************
* @function 产生IIC停止时序  
* @param
* @return
* @brief   SCL为高电平时,SDA由低电平向高电平跳变,结束传输数据
*         生成下图所示的波形图,即为停止时序
*                1 2   3 4   
*                       _______________     
*         SCL : ______/         
*                __        ____________  
*         SDA:    \______/
*******************************************************************
*/
static void I2CStop(void)
{
         CLR_I2C_SDA();          //2#数据线SDA输出低电平
         DelayNus(I2C_DELAY);            //延时4us
         SET_I2C_SCL();          //3#时钟线SCL输出高电平
         DelayNus(I2C_DELAY);  
         SET_I2C_SDA();          //4#数据线SDA输出高电平,发送I2C总线结束信号
}
/**
*******************************************************************
* @function 发送一字节,数据从高位开始发送出去
* @param   byte
* @return
* @brief   下面是具体的时序图
*                1 2     3     4
*                         ______
*          SCL: ________/      \______   
*                ______________________   
*          SDA: \\\___________________
*******************************************************************
*/
static void I2CSendByte(uint8_t byte)
{                          
         for(uint8_t i = 0; i < 8; i++)   // 循环8次,从高到低取出字节的8个位
         {     
                   if((byte & 0x80))            // 2#取出字节最高位,并判断为‘0’还是‘1’,从而做出相应的操作
                   {
                            SET_I2C_SDA();            // 数据线SDA输出高电平,数据位为‘1
                   }
                   else
                   {  
                            CLR_I2C_SDA();           // 数据线SDA输出低电平,数据位为‘0
                   }
                  
                   byte<<= 1;                              // 左移一位,次高位移到最高位
                  
                   DelayNus(I2C_DELAY);                     // 延时4us
                   SET_I2C_SCL();                // 3#时钟线SCL输出高电平
                   DelayNus(I2C_DELAY);                     // 延时4us
                   CLR_I2C_SCL();                            // 4#时钟线SCL输出低电平
                   DelayNus(I2C_DELAY);                  // 延时4us  
         }  
}
/**
*******************************************************************
* @function 读取一字节数据
* @param   
* @return  读取的字节
* @brief   下面是具体的时序图
*                       ______
*          SCL: ______/      \___        
*                ____________________   
*          SDA: \\\\______________\\\
*******************************************************************
*/
static uint8_t I2CReadByte(void)
{
         uint8_tbyte = 0;                             // byte用来存放接收的数据
         SET_I2C_SDA();                      // 释放SDA
         for(uint8_t i = 0; i < 8; i++)     // 循环8次,从高到低读取字节的8个位
         {
                   SET_I2C_SCL();                        //时钟线SCL输出高电平
                   DelayNus(I2C_DELAY);                          //延时4us
                   byte<<= 1;                               // 左移一位,空出新的最低位
                   if(GET_I2C_SDA())                     // 读取数据线SDA的数据位
                   {
                            byte++;                                   //SCL的上升沿后,数据已经稳定,因此可以取该数据,存入最低位
                   }
                   CLR_I2C_SCL();                        //时钟线SCL输出低电平
                   DelayNus(I2C_DELAY);                          //延时4us
         }
         returnbyte;                                               // 返回读取到的数据
}
/**
*******************************************************************
* @function 等待接收端的应答信号
* @param   
* @return  1,接收应答失败;0,接收应答成功
* @brief   SDA拉低后,表示接收到ACK信号,然后,拉低SCL
*          此处表示发送端收到接收端的ACK
*                _______|____     
*          SCL:        |    \_________   
*                _______|     
*          SDA:         \_____________
*******************************************************************
*/
static bool I2CWaitAck(void)
{
         uint16_terrTimes = 0;
         
         SET_I2C_SDA();             // 释放SDA总线,很重要
         DelayNus(I2C_DELAY);               // 延时4us
         
         SET_I2C_SCL();             // 时钟线SCL输出高电平
         DelayNus(I2C_DELAY);               // 延时4us
         while(GET_I2C_SDA())      // 读回来的数据如果是高电平,即接收端没有应答
         {
                   errTimes++;            // 计数器加1
                   if(errTimes > 250)    // 如果超过250次,则判断为接收端出现故障,因此发送结束信号
                   {
                            I2CStop();         // 产生一个停止信号
                            returnfalse;      // 返回值为1,表示没有收到应答信号
                   }
         }
         CLR_I2C_SCL();             // 表示已收到应答信号,时钟线SCL输出低电平
         DelayNus(I2C_DELAY);               // 延时4us
         
         returntrue;               // 返回值为0,表示接收应答成功  
}
/**
*******************************************************************
* @function 发送应答信号
* @param   
* @return  
* @brief   下面是具体的时序图
*                 1 2     3     4      5     
*                         ______
*          SCL: ________/     \____________   
*                __                     ______
*          SDA:   \___________________/        
*******************************************************************
*/
void I2CSendAck(void)
{
         CLR_I2C_SDA();          // 2#数据线SDA输出低电平
         DelayNus(I2C_DELAY);            // 延时4us
         SET_I2C_SCL();          // 3#时钟线SCL输出高电平,SCL上升沿前就要把SDA拉低,为应答信号
         DelayNus(I2C_DELAY);            // 延时4us
         CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
         DelayNus(I2C_DELAY);            // 延时4us
         SET_I2C_SDA();          // 5#数据线SDA输出高电平,释放SDA总线,很重要
}
/**
*******************************************************************
* @function 发送非应答信号
* @param   
* @return  
* @brief   下面是具体的时序图
*               1 2     3     4
*                        ______
*         SCL: ________/      \______   
*               __ ___________________   
*         SDA: __/
*******************************************************************
*/
void I2CSendNack(void)
{
         SET_I2C_SDA();          // 2#数据线SDA输出高电平
         DelayNus(I2C_DELAY);            // 延时4us
         SET_I2C_SCL();          // 3#时钟线SCL输出高电平,在SCL上升沿前就要把SDA拉高,为非应答信号
         DelayNus(I2C_DELAY);            // 延时4us
         CLR_I2C_SCL();          // 4#时钟线SCL输出低电平
         DelayNus(I2C_DELAY);            // 延时4us
}
#define NSHT30_DEV_ADDR                            0x44 //NSHT30的设备地址
#define NSHT30_I2C_WR                                       0                //写控制bit
#define NSHT30_I2C_RD                1                // 读控制bit
#define TRIG_TEMP_MEASUREMENT_HM          0xE3   // command trig. temp meas. hold master
#define TRIG_HUMI_MEASUREMENT_HM                0xE5    // command trig. humiditymeas. hold master
#define TRIG_TEMP_MEASUREMENT_POLL            0xF3    // command trig. tempmeas. no hold master
#define TRIG_HUMI_MEASUREMENT_POLL    0xF5    // command trig. humiditymeas. no hold master
#define TRIG_TEMP_HUMI_MEASUREMENT    0x2C06   // command trig.humidity  temp meas
#define NSHT30_SOFT_RESET                0x30A2    // command soft reset
#define NSHT30_RESOLUTION_REG                         0xE6   // 设置分辨率寄存器地址
#define NSHT30_RESOLUTION_VAL                         0x83   // 设置分辨率bit7 = 1,bit0 = 0,对应湿度10bit,温度13bit
#define NSHT30_READ_REG                                              0XE7
//NSHT30驱动代码
#define CMD_MEAS_SINGLE_H 0x2400 //measurement: SINGLE Mode high repeatability
#define CMD_MEAS_SINGLE_M 0x240B //measurement: SINGLE Mode medium repeatability
#define CMD_MEAS_SINGLE_L 0x2416 //measurement: SINGLE Mode low repeatability
#define CMD_MEAS_PERI_05_H 0x2032 //measurement: periodic Mode 0.5 mps high repeatability
#define CMD_MEAS_PERI_05_M 0x2024 //measurement: periodic Mode 0.5 mps medium repeat[1]ability
#define CMD_MEAS_PERI_05_L 0x202F //measurement: periodic Mode 0.5 mps low repeatability
#define CMD_MEAS_PERI_1_H 0x2130 //measurement: periodic Mode 1 mps high repeatability
#define CMD_MEAS_PERI_1_M 0x2126 //measurement: periodic Mode 1 mps medium repeatability
#define CMD_MEAS_PERI_1_L 0x212D //measurement: periodic Mode 1 mps low repeatability
#define CMD_MEAS_PERI_2_H 0x2236 //measurement: periodic Mode 2 mps high repeatability
#define CMD_MEAS_PERI_2_M 0x2220 //measurement: periodic Mode 2 mps medium repeatability
#define CMD_MEAS_PERI_2_L 0x222B //measurement: periodic Mode 2 mps low repeatability
#define CMD_MEAS_PERI_4_H 0x2334 //measurement: periodic Mode 4 mps high repeatability
#define CMD_MEAS_PERI_4_M 0x2322 //measurement: periodic Mode 4 mps medium repeatability
#define CMD_MEAS_PERI_4_L 0x2329 //measurement: periodic Mode 4 mps low repeatability
#define CMD_MEAS_PERI_10_H 0x2737 //measurement: periodic Mode 10 mps high repeatability
#define CMD_MEAS_PERI_10_M 0x2721 //measurement: periodic Mode 10 mps medium repeat[1]ability
#define CMD_MEAS_PERI_10_L 0x272A //measurement: periodic Mode 10 mps low repeatability
static bool Nsht30SoftReset(void)                    
{
         I2CStart();
         I2CSendByte((NSHT30_DEV_ADDR<<1)| NSHT30_I2C_WR);
         if(!I2CWaitAck())
         {
                   gotoi2c_err;
         }
         
         
         I2CSendByte((NSHT30_SOFT_RESET&0xFF00)>>8);
         if(!I2CWaitAck())
         {
                   gotoi2c_err;
         }
         
         I2CSendByte(NSHT30_SOFT_RESET&0xFF);
         if(!I2CWaitAck())
         {
                   gotoi2c_err;
         }
         
         
         I2CStop();
         returntrue;
i2c_err:          // 命令执行失败后,要发送停止信号,避免影响I2C总线上其他设备
         I2CStop();
         returnfalse;
}
static bool Nsht30SetResolution(uint8_t*pBuffer)
{
         uint16_tnumToRead=5;
         
         I2CStart();
         I2CSendByte((NSHT30_DEV_ADDR<<1)| NSHT30_I2C_WR);
         if(!I2CWaitAck())
         {
                   gotoi2c_err;
         }
         
         I2CSendByte((CMD_MEAS_SINGLE_L&0xFF00)>>8);
         if(!I2CWaitAck())
         {
                   gotoi2c_err;
         }
         
         I2CSendByte(CMD_MEAS_SINGLE_L&0xFF);
         if(!I2CWaitAck())
         {
                   gotoi2c_err;
         }
         
         
         I2CStop();
         
         DelayNms(I2C_DELAY);
         I2CStart();                                        // 发送起始信号         
         I2CSendByte((NSHT30_DEV_ADDR<<1)| NSHT30_I2C_RD);      // 发送器件地址和读写模式,1 0 1 0 x  x  x R/~W  0xA1           
         if(!I2CWaitAck())                                // 等待应答
         {
                   gotoi2c_err;
         }
         
         while(numToRead--)                                // 数据未读完
         {
                   *pBuffer++= I2CReadByte();                   // 逐字节读出存放到数据数组
                   I2CSendAck();
         }
         *pBuffer= I2CReadByte();                        // 最后一个字节发送非应答
         I2CSendNack();  
         
         I2CStop();
i2c_err:          // 命令执行失败后,要发送停止信号,避免影响I2C总线上其他设备
         I2CStop();
         returnfalse;
}
bool TempHumizhuanhuan(uint8_t *dat,double*pot)
{
         uint16_ttem,hum;
         
         tem= ((uint16_t)dat[0]<<8) | dat[1];
         hum= ((uint16_t)dat[3]<<8) | dat[4];
         if((crc8(dat,2)== dat[2]) && (crc8(dat+3,2) == dat[5]))
         {
                   pot[0]=(175.0*(double)tem/65535.0-45.0) ;// T = -45 + 175 * tem / (2^16-1)
                   pot[1]=(100.0*(double)hum/65535.0);// RH = hum*100 / (2^16-1)
                   return1;
         }
         else
         {
                   return0;
         }
}
float tempData, humiData;
uint8_t buffer[10];
double rth[2];
void TempHumiDrvTest(void)
{
         
         Nsht30SetResolution(buffer);
         printf("Get%x %x %x %x %x%x\n",buffer[0],buffer[1],buffer[2],buffer[3],buffer[4],buffer[5]);


         TempHumizhuanhuan(buffer,rth);
         printf("%3.4f,%3.6f%%\r\n",rth[0],rth[1]);
}




使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

170

主题

190

帖子

10

粉丝