荣陶陶 发表于 2025-3-8 21:55

STM32 + DHT11 温湿度传感器详解

一、DHT11 温湿度传感器概述
DHT11 是一款常用的数字式温湿度传感器,由电阻式感湿元件和 NTC 测温元件组成。它能够将环境中的温度和湿度转换为数字信号输出,具有成本低、使用方便等优点,广泛应用于智能家居、气象监测等领域。

1. 主要特性
单总线通信:采用单总线协议与微控制器进行通信,仅需一根数据线即可完成数据传输。
测量范围:湿度测量范围为 20 - 90%RH,温度测量范围为 0 - 50°C。
精度:湿度测量精度为 ±5%RH,温度测量精度为 ±2°C。
数据输出:输出 40 位数据,其中包括 8 位湿度整数部分、8 位湿度小数部分、8 位温度整数部分、8 位温度小数部分和 8 位校验和。
2. 引脚说明
VCC:电源引脚,接 3 - 5.5V 电源。
DATA:数据引脚,用于与微控制器进行数据通信。
GND:接地引脚。
二、STM32 与 DHT11 的硬件连接
将 DHT11 与 STM32 进行连接时,需要注意以下几点:

电源连接:将 DHT11 的 VCC 引脚连接到 STM32 的 3.3V 或 5V 电源引脚,GND 引脚连接到 STM32 的地引脚。
数据连接:将 DHT11 的 DATA 引脚连接到 STM32 的一个 GPIO 引脚,这里假设连接到 PA0 引脚。
上拉电阻:在 DATA 引脚和 VCC 引脚之间连接一个 4.7K - 10K 的上拉电阻,确保数据传输的稳定性。
三、DHT11 通信协议
DHT11 采用单总线通信协议,通信过程分为以下几个步骤:

1. 起始信号
STM32 向 DHT11 发送一个至少 18ms 的低电平信号,然后拉高 20 - 40μs,通知 DHT11 准备发送数据。

2. 响应信号
DHT11 接收到起始信号后,会拉低总线 80μs 作为响应信号,然后拉高 80μs 表示准备发送数据。

3. 数据传输
DHT11 依次发送 40 位数据,每位数据以 50μs 的低电平开始,高电平的持续时间决定了数据是 0 还是 1。如果高电平持续时间为 26 - 28μs,则表示数据 0;如果高电平持续时间为 70μs,则表示数据 1。

4. 校验和
最后 8 位为校验和,用于验证数据的准确性。校验和等于湿度整数部分、湿度小数部分、温度整数部分和温度小数部分的和的低 8 位。

四、STM32 代码实现
1. 初始化 GPIO 引脚
#include "stm32f10x.h"

// 初始化 PA0 引脚
void DHT11_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}


2. 发送起始信号
// 发送起始信号
void DHT11_Start(void) {
    GPIO_SetBits(GPIOA, GPIO_Pin_0);
    Delay_ms(20);// 确保 DHT11 处于稳定状态
    GPIO_ResetBits(GPIOA, GPIO_Pin_0);
    Delay_ms(20);// 至少 18ms 低电平
    GPIO_SetBits(GPIOA, GPIO_Pin_0);
    Delay_us(30);// 20 - 40μs 高电平
}


3. 等待响应信号
// 等待 DHT11 响应信号
uint8_t DHT11_Wait_Response(void) {
    uint8_t retry = 0;

    // 等待 DHT11 拉低总线
    while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) && retry < 100) {
      retry++;
      Delay_us(1);
    }
    if (retry >= 100) return 1;// 响应超时
    else retry = 0;

    // 等待 DHT11 拉高总线
    while (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) && retry < 100) {
      retry++;
      Delay_us(1);
    }
    if (retry >= 100) return 1;// 响应超时

    return 0;
}



4. 读取一位数据
// 读取一位数据
uint8_t DHT11_Read_Bit(void) {
    uint8_t retry = 0;

    // 等待低电平结束
    while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) && retry < 100) {
      retry++;
      Delay_us(1);
    }
    retry = 0;

    // 等待高电平
    while (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) && retry < 100) {
      retry++;
      Delay_us(1);
    }
    Delay_us(40);// 等待 40μs 判断数据位

    if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) return 1;
    else return 0;
}



5. 读取一个字节数据
// 读取一个字节数据
uint8_t DHT11_Read_Byte(void) {
    uint8_t i, dat = 0;
    for (i = 0; i < 8; i++) {
      dat <<= 1;
      dat |= DHT11_Read_Bit();
    }
    return dat;
}


6. 读取温湿度数据
// 读取温湿度数据
uint8_t DHT11_Read_Data(uint8_t *humi_int, uint8_t *humi_dec, uint8_t *temp_int, uint8_t *temp_dec) {
    uint8_t buf;
    uint8_t i;

    DHT11_Start();
    if (DHT11_Wait_Response()) return 1;// 响应失败

    for (i = 0; i < 5; i++) {
      buf = DHT11_Read_Byte();
    }

    // 校验和验证
    if ((buf + buf + buf + buf) == buf) {
      *humi_int = buf;
      *humi_dec = buf;
      *temp_int = buf;
      *temp_dec = buf;
      return 0;// 读取成功
    } else {
      return 1;// 校验和错误
    }
}



7. 主函数
int main(void) {
    uint8_t humi_int, humi_dec, temp_int, temp_dec;
    DHT11_GPIO_Init();

    while (1) {
      if (!DHT11_Read_Data(&humi_int, &humi_dec, &temp_int, &temp_dec)) {
            // 数据读取成功,可进行后续处理
            // 例如通过串口打印数据
            printf("Humidity: %d.%d%%, Temperature: %d.%d°C\n", humi_int, humi_dec, temp_int, temp_dec);
      }
      Delay_ms(2000);// 每 2 秒读取一次数据
    }
}


注意事项
由于 DHT11 的数据传输对时序要求较高,因此在编写代码时需要确保延时函数的准确性。
为了避免干扰,建议在读取数据时关闭中断。
DHT11 不适合频繁读取数据,建议读取间隔不小于 2 秒。
通过以上步骤,我们可以实现 STM32 与 DHT11 温湿度传感器的通信,获取环境的温度和湿度数据。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/duierrorshuobu/article/details/146083370

rickluo 发表于 2025-3-22 11:34




#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* @return * @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   * @return1,接收应答失败;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.humiditytemp 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 repeatability#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 repeatability#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 xxx R/~W0xA1                  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<<8) | dat;         hum= ((uint16_t)dat<<8) | dat;         if((crc8(dat,2)== dat) && (crc8(dat+3,2) == dat))         {                   pot=(175.0*(double)tem/65535.0-45.0) ;// T = -45 + 175 * tem / (2^16-1)                   pot=(100.0*(double)hum/65535.0);// RH = hum*100 / (2^16-1)                    return1;          }         else         {                   return0;         }}float tempData, humiData;uint8_t buffer;double rth;void TempHumiDrvTest(void){                   Nsht30SetResolution(buffer);         printf("Get%x %x %x %x %x%x\n",buffer,buffer,buffer,buffer,buffer,buffer);          TempHumizhuanhuan(buffer,rth);         printf("%3.4f,%3.6f%%\r\n",rth,rth);}

页: [1]
查看完整版本: STM32 + DHT11 温湿度传感器详解