MCU单片机 - STM32F103C8T6 驱动 DHT11 温湿度传感器详解

Peter_Deng. 2025-08-12 12:04:36

DHT11 是一种数字温湿度传感器,采用单总线协议通信,结构简单,易于使用,非常适合STM32微控制器进行学习和开发。本文基于STM32F103C8T6单片机,使用PB3口连接DHT11,详细讲解硬件连接、GPIO配置、单总线通信时序、数据接收与校验,并配备超详细注释代码和自定义延时函数。

一、硬件连接

  • DHT11数据线连接STM32的PB3引脚
  • VCC接5V或3.3V,GND接地
  • 注意:数据线上最好加一个4.7kΩ的上拉电阻,确保总线空闲时处于高电平

二、单总线通信协议简介

DHT11的数据通信只有一根数据线,采用开漏结构,主机和从机通过电平的高低及持续时间传递信息:

在这里插入图片描述

三、关键代码详解(含自定义延时)

DHT11数据时序图如下:

数据时序图

1. 微秒延时函数(基于SysTick)

/*****************************************
函数功能:微秒级延时函数
参    数:xus - 延时的微秒数
返 回 值:无
备    注:使用SysTick计数器,时钟为72MHz/8=9MHz
          计数器每个tick约0.111us,LOAD=9*xus-1实现xus微秒延时
******************************************/
void delay_us(u32 xus)
{
    SysTick->CTRL &= ~(1 << 2);          // 选择内部时钟HCLK/8(9MHz)
    SysTick->LOAD = 9 * xus - 1;         // 设定重装载值,计数9*xus个tick
    SysTick->VAL = 0;                    // 清空当前计数值
    SysTick->CTRL &= ~(1 << 1);          // 禁止SysTick中断
    SysTick->CTRL |= (1 << 0);           // 启动SysTick计数器

    while (!(SysTick->CTRL & (1 << 16))); // 等待计数器计数到0,自动清标志
    SysTick->CTRL &= ~(1 << 0);          // 关闭计数器
}

/*****************************************
函数功能:毫秒级延时函数
参    数:xms - 延时的毫秒数
返 回 值:无
备    注:循环调用delay_us实现毫秒延时
******************************************/
void delay_ms(u32 xms)
{
    while (xms--)
    {
       delay_us(1000);                  // 1ms = 1000us
    }
}

2. GPIO初始化及模式切换

#include "stm32f10x.h"  // STM32F10x 标准库头文件

/*****************************************
函数功能:配置PB3为开漏输出模式(主机驱动数据线)
参    数:无
返 回 值:无
备    注:开漏输出允许总线被多个设备共享,未驱动时线被上拉
******************************************/
void DHT11_SetOutput(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0}; // 初始化结构体清零

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;        // 选择PB3引脚
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出模式
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 最高输出速度50MHz

    GPIO_Init(GPIOB, &GPIO_InitStruct);            // 初始化GPIOB口
}

/*****************************************
函数功能:配置PB3为浮空输入模式(接收数据线电平)
参    数:无
返 回 值:无
备    注:用于检测DHT11拉低或拉高状态,浮空输入避免内部上拉影响电平
******************************************/
void DHT11_SetInput(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0}; // 初始化结构体清零

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;        // 选择PB3引脚
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入模式
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;    // 速度参数无实际意义

    GPIO_Init(GPIOB, &GPIO_InitStruct);            // 初始化GPIOB口
}

3. DHT11初始化函数

/*****************************************
函数功能:DHT11初始化,开启GPIO时钟,配置PB3引脚,默认拉高总线
参    数:无
返 回 值:无
备    注:禁用JTAG复用,释放PB3,保证引脚可用
          使用开漏输出,延时1秒,等待DHT11稳定
******************************************/
void DHT11_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟

    // 禁用JTAG复用,保留SWD调试,释放PB3引脚,避免复用冲突
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

    DHT11_SetOutput();      // 配置PB3为开漏输出
    GPIO_SetBits(GPIOB, GPIO_Pin_3);  // 拉高数据线,释放总线

    delay_ms(1000);         // 延时1秒,等待传感器稳定
}

4. 发送起始信号

/*****************************************
函数功能:主机发送起始信号,通知DHT11开始传输
参    数:无
返 回 值:无
备    注:拉低至少18ms,然后拉高,等待DHT11响应
******************************************/
void DHT11_StartSignal(void)
{
    DHT11_SetOutput();      // 配置为开漏输出

    GPIO_ResetBits(GPIOB, GPIO_Pin_3); // 拉低数据线,主机驱动
    delay_ms(20);            // 保持20ms以上,满足18ms最低要求

    GPIO_SetBits(GPIOB, GPIO_Pin_3);   // 释放总线,拉高数据线
    delay_us(30);            // 等待20~40us,为DHT11响应做准备
}

5. 等待DHT11响应信号

/*****************************************
函数功能:等待DHT11拉低再拉高响应信号
参    数:无
返 回 值:0 - 响应成功
          1 - 无响应(拉低超时)
          2 - 拉低持续超时(等待低电平超时)
          3 - 拉高持续超时(等待高电平超时)
备    注:防止程序死循环卡死
******************************************/
u8 DHT11_WaitResponse(void)
{
    u16 timeout = 0;

    DHT11_SetInput();       // 切换为输入,监听数据线

    // 等待DHT11拉低开始响应,低电平持续约80us
    while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
    {
        delay_us(1);
        if (++timeout > 100) return 1; // 超时,未检测到拉低信号
    }

    timeout = 0;
    // 等待DHT11释放低电平,开始拉高
    while (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
    {
        delay_us(1);
        if (++timeout > 100) return 2; // 低电平持续超时
    }

    timeout = 0;
    // 等待DHT11拉高结束
    while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
    {
        delay_us(1);
        if (++timeout > 100) return 3; // 高电平持续超时
    }

    return 0; // 响应成功
}

6. 接收40位数据(5字节)

/*****************************************
函数功能:接收DHT11传来的40位数据
参    数:data - 长度为5的数组,用于存放接收的5字节数据
返 回 值:无
备    注:通过高电平宽度判别01,高电平持续时间长为1,短为0
          高位优先传输,循环按字节和位读取
******************************************/
void DHT11_ReceiveData(u8* data)
{
    u8 i, j;

    for (i = 0; i < 5; i++)    // 5字节循环
    {
       u8 byte = 0;

       for (j = 0; j < 8; j++) // 8位循环
       {
           // 等待低电平结束(确保是新一位开始)
           while (!GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3));

           delay_us(40); // 延时40us判断电平持续时间

           // 判断当前高电平状态
           if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3))
           {
              // 高电平持续时间长,认为是1
              byte |= (1 << (7 - j)); // 按位设置,高位先传

              // 等待高电平结束,防止干扰下一位
              while (GPIO\_ReadInputDataBit(GPIOB, GPIO\_Pin\_3));
           }
           //40us后低电平,认为0,不需设置位
       }

      data[i] = byte; // 存入对应字节
    }
}

7. 读取温湿度数据

/*****************************************
函数功能:读取温湿度数据(整数部分)
参    数:temp_int - 指向温度整数变量指针
          humi_int - 指向湿度整数变量指针
返 回 值:0 - 读取成功
          1 - 无响应
          2 - 校验失败
备    注:忽略小数部分和负温度,可根据需要扩展
******************************************/
u8 DHT11_Read(u8* temp_int, u8* humi_int)
{
    u8 buf[5];
    u8 sum;

    DHT11_StartSignal();          // 主机发送起始信号

    if (DHT11_WaitResponse())     // 等待DHT11响应,非0为失败
        return 1;

    DHT11_ReceiveData(buf);       // 接收5字节数据

    sum = buf[0] + buf[1] + buf[2] + buf[3];  // 计算校验和
    if (sum != buf[4])            // 校验和错误
        return 2;

    *humi_int = buf[0];           // 湿度整数部分
    *temp_int = buf[2];           // 温度整数部分

    return 0;                     // 成功
}

四、主函数示例:串口实时打印温湿度,2秒刷新

#include "stm32f10x.h"
#include "dht11.h"
#include <stdio.h>

// 重定向printf到串口1
int fputc(int ch, FILE *f)
{
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}

int main(void)
{
    u8 ret;
    u8 temperature = 0, humidity = 0;

    SystemInit();
    delay_init();        // 初始化延时函数
    DHT11_Init();        // 初始化DHT11
    USART1_Init(115200); // 初始化串口1

    printf("System Init Complete, Starting DHT11 Reading...\r\n");

    while (1)
    {
        ret = DHT11_Read(&temperature, &humidity);

        if (ret == 0)
        {
            printf("Temperature: %d C, Humidity: %d%%\r\n", temperature, humidity);
        }
        else if (ret == 1)
        {
            printf("DHT11 no response!\r\n");
        }
        else if (ret == 2)
        {
            printf("DHT11 checksum error!\r\n");
        }

        delay_ms(2000); // 2秒刷新一次
    }
}

五、总结

  • DHT11的通信关键是开漏单线双向控制和严格的时序。
  • GPIO模式切换为开漏输出与浮空输入,实现总线控制与监听。
  • 延时函数保证微秒级准确延时,避免数据传输错误。
  • 采用超时机制防止函数死循环。
  • 校验和验证保证数据正确性。
  • 主函数通过串口实时打印读取结果,方便调试和监控。

这套代码适合STM32F103C8T6开发板,用PB3口接DHT11,实测稳定可靠,代码注释详细,欢迎读者复制粘贴调试


(完)

...全文
109 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

21

社区成员

发帖
与我相关
我的任务
社区管理员
  • c_university_1665
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧