当前位置: 首页 > news >正文

STM32 环境监测项目笔记(一):DHT11 温湿度传感器原理与驱动实现

本系列笔记是笔者学习 B 站 up 主 “技术探索者” STM32 系列视频所作的记录,不理解的地方推荐观看视频~

目录

  • 一、前言
  • 二、DHT11 模块核心认知
    • 2.1 模块特性与接线
    • 2.2 单总线协议时序图解析
  • 三、CubeMX 工程配置
    • 3.1 基础配置(芯片 / 时钟 / Debug)
    • 3.2 TIM1 配置(1μs 高精度延时)
    • 3.3 串口 1 配置(数据打印)
  • 四、DHT11 驱动代码实现(带详细注释)
    • 4.1 头文件(dht11.h):宏定义与结构体
    • 4.2 核心函数:延时 / IO 模式切换 / 数据读取
    • 4.3 串口重定向(usart.c)
  • 五、测试验证与结果分析
  • 六、总结

一、前言

大家好,我是 Hello_Embed。上一系列我们完成了智能垃圾桶项目,从模块驱动到功能整合,掌握了嵌入式开发的基础流程。本次开启新系列 ——环境监测项目,核心目标是实现 “温湿度 + 光照强度” 的实时采集与 OLED 显示,既复习定时器、串口等旧知识,也将学习单总线、ADC、IIC 等新协议。

本系列第一篇聚焦 DHT11 温湿度传感器—— 这是嵌入式开发中最常用的入门级温湿度模块,通过单总线协议实现数据传输,接线简单但对时序精度要求高。本次将从模块原理、时序分析、CubeMX 配置到驱动代码,完整实现 DHT11 的温湿度采集功能。

二、DHT11 模块核心认知

2.1 模块特性与接线

2.1.1 核心参数

DHT11 是低成本数字式温湿度传感器,性能满足日常环境监测需求,关键参数如下:

参数规格说明
温度测量范围 0~50℃,精度 ±2℃无负温测量能力,适合常温场景
湿度测量范围 20%~80% RH,精度 ±5% RH低湿 / 高湿环境精度会下降
通信协议单总线(1-Wire)仅需 1 根数据线实现双向通信
供电电压3.3V~5V兼容 STM32 3.3V/5V 供电
响应时间≤2s每次采集间隔建议 ≥2s
2.1.2 接线说明

DHT11 共 3 个引脚(部分模块带 4 引脚,其中 1 个为空脚),接线原则如下(本次选用 PA7 作为数据线):

DHT11 引脚功能连接对象(STM32)备注
VCC电源正极3.3V/5V 引脚勿接反,否则可能烧毁模块
GND电源负极GND 引脚必须与 STM32 共地
DATA单总线数据PA7 引脚需配置为双向 IO(输入 / 输出切换)

2.2 单总线协议时序图解析

DHT11 与 STM32 的通信完全依赖 “时序”,需严格遵循 “起始信号→应答信号→数据传输” 三步流程,时序图如下:

请添加图片描述

2.2.1 1. 起始信号(STM32 → DHT11)

STM32 主动发送起始信号,告知 DHT11 “准备采集数据”,时序要求:

  1. 数据线(PA7)拉低 18ms(必须≥18ms,否则 DHT11 不响应);
  2. 数据线拉高 20~40μs(等待 DHT11 应答);
  3. 此时 STM32 需将数据线切换为 输入模式,准备接收 DHT11 的应答信号。
2.2.2 2. 应答信号(DHT11 → STM32)

DHT11 检测到起始信号后,主动发送应答信号,时序特征:

  1. 数据线拉低 80μs(表示 “已收到起始信号”);
  2. 数据线拉高 80μs(表示 “准备发送数据”);
  3. 应答信号结束后,进入数据传输阶段。
2.2.3 3. 数据传输(DHT11 → STM32)

DHT11 一次传输 40 位二进制数据(共 5 字节),数据格式与解析规则如下:

  • 数据格式:8 位湿度整数 → 8 位湿度小数 → 8 位温度整数 → 8 位温度小数 → 8 位校验和;
    • 例:若 5 字节为 0x40, 0x00, 0x19, 0x00, 0x59,则湿度 = 64% RH,温度 = 25℃,校验和 = 64+0+25+0=89=0x59(校验通过);
  • 位数据区分:DHT11 通过 “高电平持续时间” 区分 0 和 1:
    • 数据 0:低电平 50μs → 高电平 26~28μs;
    • 数据 1:低电平 50μs → 高电平 70μs;
  • 校验规则:前 4 字节之和 = 第 5 字节(校验和),若不相等则数据无效。

三、CubeMX 工程配置

本次使用 STM32F103C8T6 最小系统板,新建 CubeMX 工程,配置步骤如下:

3.1 基础配置(芯片 / 时钟 / Debug)

  1. 芯片选择:搜索并选择 STM32F103C8T6
  2. Debug 配置:进入 System Core → SYS,Debug 选择 Serial Wire(必须设置,否则无法烧录);
  3. 时钟配置
    • 进入 System Core → RCC,High Speed Clock(HSE)选择 Crystal/Ceramic Resonator(外部晶振);
    • 进入 Clock Configuration,将 HCLK 配置为 72MHz(STM32F103 最高主频),配置如下:
      请添加图片描述
      请添加图片描述

3.2 TIM1 配置(1μs 高精度延时)

DHT11 时序对时间精度要求到 μs 级,需用定时器实现 1μs 延时,选择 TIM1(16 位定时器,满足延时需求):

  1. 进入 Timers → TIM1,模式选择 Internal Clock
  2. 参数配置:
    • Prescaler(预分频值):72 - 1(72MHz 时钟 / 72 = 1MHz,即 1 次计数 = 1μs);
    • Counter Period(ARR):65535(16 位定时器最大计数,避免频繁溢出);
  3. 配置截图如下:
    请添加图片描述

3.3 串口 1 配置(数据打印)

通过串口 1 打印温湿度数据,配置如下:

  1. 进入 Connectivity → USART1,模式选择 Asynchronous(异步通信);
  2. 基本参数:波特率 115200,数据位 8,停止位 1,校验位 None(默认配置,无需修改);
  3. 引脚:默认 PA9(TX)、PA10(RX),无需手动调整。
工程生成
  1. 进入 Project Manager → Code Generator,勾选 Generate peripheral initialization as a pair of .c/.h files per peripheral
  2. 选择工程路径,Toolchain/IDE 设为 MDK-ARM,点击 Generate Code 生成工程。

四、DHT11 驱动代码实现(带详细注释)

新建 driver 文件夹,创建 dht11.cdht11.h,并将 driver 文件夹添加到 Keil 工程路径(Options for Target → C/C++ → Include Paths)。

4.1 头文件(dht11.h):宏定义与结构体

定义数据线引脚、数据类型别名、存储温湿度的结构体,声明核心函数:

#ifndef __DHT11_H
#define __DHT11_H#include "main.h"// 数据类型别名(简化代码)
#define u8  unsigned char
#define u16 unsigned short
#define u32 unsigned int// ------------- DHT11 数据线引脚宏定义 -------------
#define DATA_PIN        GPIO_PIN_7    // 数据线对应引脚:PA7
#define DATA_GPIO_Port  GPIOA         // 数据线对应端口:GPIOA// 数据线电平控制宏(简化代码)
#define DATA_SET()      HAL_GPIO_WritePin(DATA_GPIO_Port, DATA_PIN, GPIO_PIN_SET)   // 拉高数据线
#define DATA_RESET()    HAL_GPIO_WritePin(DATA_GPIO_Port, DATA_PIN, GPIO_PIN_RESET) // 拉低数据线
#define DATA_READ()     HAL_GPIO_ReadPin(DATA_GPIO_Port, DATA_PIN)                  // 读取数据线电平// ------------- 温湿度数据存储结构体 -------------
typedef struct
{u8 Data[5];    // 存储 DHT11 传输的 40 位数据(5 字节)u8 index;      // 数据计数标志(可选,用于统计采集次数)u8 temp;       // 解析后的温度值(整数部分)u8 humidity;   // 解析后的湿度值(整数部分)
} DHT11_DATA;// 全局变量声明(供外部文件调用,如 main.c)
extern DHT11_DATA DHT11_data;// 函数声明
void DHT11_Task(void);  // DHT11 采集任务(对外接口)#endif

4.2 核心函数:延时 / IO 模式切换 / 数据读取

dht11.c 中实现 5 个核心函数:μs 延时、数据线输入 / 输出模式切换、位数据读取、完整数据采集、采集任务封装。

4.2.1 1μs 高精度延时函数(基于 TIM1)
#include "dht11.h"// 声明 TIM1 句柄(CubeMX 自动生成在 tim.c 中)
extern TIM_HandleTypeDef htim1;
// 定义全局结构体变量(存储温湿度数据)
DHT11_DATA DHT11_data;/*** @brief  1μs 高精度延时函数* @param  us:目标延时时间(单位:μs,最大 65530μs,避免溢出)* @retval 无* @note   基于 TIM1 实现,通过设置计数起始值补偿代码执行时间*/
void Delay_us(uint16_t us)
{u16 differ = 0xffff - us - 5;  // 计数起始值:-5 用于补偿函数调用耗时__HAL_TIM_SET_COUNTER(&htim1, differ);  // 设置 TIM1 计数起始值HAL_TIM_Base_Start(&htim1);  // 启动 TIM1 计数// 等待计数到接近 0xffff(避免定时器溢出)while (differ < 0xffff - 5){differ = __HAL_TIM_GET_COUNTER(&htim1);  // 实时读取计数值}HAL_TIM_Base_Stop(&htim1);  // 停止 TIM1 计数
}
4.2.2 数据线输出模式切换(STM32 发送信号)
/*** @brief  设置数据线为输出模式,并控制电平* @param  flag:0=拉低数据线,1=拉高数据线* @retval 无* @note   单总线需频繁切换 IO 模式,此函数封装输出模式配置*/
static void DATA_OUTPUT(u8 flag)
{GPIO_InitTypeDef GPIO_InitStruct = {0};  // GPIO 初始化结构体// 配置 PA7 为推挽输出模式GPIO_InitStruct.Pin = DATA_PIN;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出GPIO_InitStruct.Pull = GPIO_NOPULL;          // 无上下拉(输出模式无需)GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;// 高速模式HAL_GPIO_Init(DATA_GPIO_Port, &GPIO_InitStruct);// 根据 flag 控制电平if (flag == 0){DATA_RESET();  // 拉低数据线}else{DATA_SET();    // 拉高数据线}
}
4.2.3 数据线输入模式切换(STM32 接收信号)
/*** @brief  设置数据线为输入模式,并读取电平* @param  无* @retval 0=数据线低电平,1=数据线高电平* @note   配置为上拉输入,避免引脚悬空导致误判*/
static u8 DATA_INPUT(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};u8 flag = 0;  // 存储读取到的电平状态// 配置 PA7 为上拉输入模式GPIO_InitStruct.Pin = DATA_PIN;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;     // 输入模式GPIO_InitStruct.Pull = GPIO_PULLUP;         // 上拉电阻(防止悬空)HAL_GPIO_Init(DATA_GPIO_Port, &GPIO_InitStruct);// 读取数据线电平并返回if (DATA_READ() == GPIO_PIN_RESET){flag = 0;  // 低电平}else{flag = 1;  // 高电平}return flag;
}
4.2.4 读取 1 字节数据(8 位)
/*** @brief  读取 DHT11 发送的 1 字节数据(8 位二进制)* @param  无* @retval 读取到的 1 字节数据* @note   通过高电平持续时间区分 0 和 1,加入超时机制防止程序卡死*/
static u8 DHT11_Read_Byte(void)
{u8 ReadDat = 0;  // 存储最终读取的字节数据u8 temp = 0;     // 存储每一位的二进制值(0/1)u8 retry = 0;    // 超时计数(防止死循环)u8 i = 0;        // 循环变量(8 位数据)// 循环 8 次,读取 8 位数据for (i = 0; i < 8; i++){// 1. 等待 DHT11 拉低数据线(每一位数据前先拉低 50μs)while (DATA_READ() == 0 && retry < 100){Delay_us(1);retry++;}retry = 0;  // 重置超时计数// 2. 延时 40μs:数据 0 的高电平(26~28μs)会在此期间结束,数据 1 仍为高电平Delay_us(40);// 3. 判断当前电平:高电平则为 1,低电平则为 0if (DATA_READ() == 1){temp = 1;}else{temp = 0;}// 4. 等待 DHT11 拉低数据线(当前位数据传输结束)while (DATA_READ() == 1 && retry < 100){Delay_us(1);retry++;}retry = 0;  // 重置超时计数// 5. 数据封装:左移 1 位空出最低位,将当前位值存入ReadDat <<= 1;ReadDat |= temp;}return ReadDat;  // 返回读取到的 1 字节数据
}
4.2.5 完整数据采集(起始→应答→读取→校验)
/*** @brief  完整的 DHT11 数据采集函数* @param  无* @retval 1=采集成功,0=采集失败(校验不通过或无应答)* @note   整合起始信号、应答信号、数据读取、校验逻辑*/
static u8 DHT11_Read(void)
{u8 retry = 0;  // 超时计数u8 i = 0;      // 循环变量(5 字节数据)// 1. 发送起始信号(STM32 → DHT11)DATA_OUTPUT(0);  // 数据线输出模式,拉低HAL_Delay(18);   // 拉低 18ms(必须≥18ms)DATA_OUTPUT(1);  // 拉高数据线Delay_us(20);    // 拉高 20μs(等待应答)// 2. 切换为输入模式,接收应答信号(DHT11 → STM32)DATA_INPUT();Delay_us(20);    // 等待应答信号稳定// 3. 判断是否收到应答(先低后高)if (DATA_READ() == 0){// 3.1 等待应答低电平结束(80μs)while (DATA_READ() == 0 && retry < 100){Delay_us(1);retry++;}retry = 0;// 3.2 等待应答高电平结束(80μs)while (DATA_READ() == 1 && retry < 100){Delay_us(1);retry++;}retry = 0;// 4. 读取 5 字节数据(40 位)for (i = 0; i < 5; i++){DHT11_data.Data[i] = DHT11_Read_Byte();}Delay_us(50);  // 数据读取后延时,确保稳定}// 5. 校验数据(前 4 字节之和 = 第 5 字节)u32 sum = DHT11_data.Data[0] + DHT11_data.Data[1] + DHT11_data.Data[2] + DHT11_data.Data[3];if (sum == DHT11_data.Data[4]){// 校验通过,解析温湿度(仅取整数部分,小数部分通常为 0)DHT11_data.humidity = DHT11_data.Data[0];  // 湿度整数DHT11_data.temp = DHT11_data.Data[2];      // 温度整数return 1;  // 采集成功}else{return 0;  // 校验失败,数据无效}
}
4.2.6 采集任务封装(对外接口)
/*** @brief  DHT11 采集任务(供 main.c 调用)* @param  无* @retval 无* @note   简化外部调用,加入采集次数统计(可选)*/
void DHT11_Task(void)
{if (DHT11_Read())  // 若采集成功{DHT11_data.index++;  // 采集次数+1if (DHT11_data.index >= 128)  // 防止 index 溢出{DHT11_data.index = 0;}}
}

4.3 串口重定向(usart.c)

usart.c 中实现 fputc 函数,让 printf 通过串口 1 打印温湿度数据:

#include <stdio.h>  // 包含 printf 所需头文件/*** @brief  串口1 重定向函数,printf 输出到串口* @param  ch:要输出的字符* @param  f:文件指针(标准输出,无需关注)* @retval 输出的字符*/
int fputc(int ch, FILE *f)
{// 发送 1 个字符到串口1,超时时间 1000msHAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 1000);return ch;
}
Keil 配置(支持 printf)
  1. 打开工程 Options for Target → Target,勾选 Use MicroLIB(启用微库);
  2. 进入 Debug → Settings → Flash Download,勾选 Reset and Run(下载后自动运行)。

五、测试验证与结果分析

main.c 中调用 DHT11 采集任务,周期性打印温湿度数据:

#include "dht11.h"int main(void)
{//主循环:每 3 秒采集并打印一次温湿度while (1){DHT11_Task();  // 采集温湿度// 打印数据(仅整数部分,DHT11 小数部分通常为 0)printf("Temp is %d ℃\r\n", DHT11_data.temp);printf("Hum is %d %%RH\r\n", DHT11_data.humidity);printf("------------------------\r\n");HAL_Delay(3000);  // 间隔 3 秒(≥DHT11 响应时间 2s)}
}
测试结果
  1. 硬件接线:DHT11 VCC→3.3V、GND→GND、DATA→PA7;
  2. 串口工具设置:波特率 115200、数据位 8、停止位 1、校验位 None;
  3. 预期结果:串口每 3 秒打印一次温湿度,示例如下:
    请添加图片描述

六、总结

本次笔记完整实现了 DHT11 温湿度传感器的驱动开发,核心收获包括:

  1. 理解单总线协议的时序逻辑:起始信号、应答信号、数据传输的时间要求是采集成功的关键;
  2. 掌握 IO 模式动态切换:单总线需频繁在 “输出(发信号)” 和 “输入(收信号)” 之间切换;
  3. 学会数据校验与异常处理:通过校验和判断数据有效性,加入超时机制防止程序卡死。

下一篇我们讲解IIC协议并用OLED屏幕实时显示信息,请关注 Hello_Embed,持续更新环境监测项目!

http://www.dtcms.com/a/474647.html

相关文章:

  • C++ 完全背包
  • 【Linux】理解链接过程
  • 广州做网站多少钱怎么做简单的网站首页
  • 【机器人学中的状态估计】7.5.2习题证明:(Cu)^=(2cos(phi)+1)u^-u^C-C^Tu^公式证明
  • Flask、Nginx 与 Docker 的分工与协作
  • 怎么建立一个公司的网站吗ui界面设计作品模板
  • 网站浮动广告怎么做qq开放平台网站开发申请不通过的原因
  • redis中的list命令
  • 对网站建设课程的心得体会北京旅游网页设计
  • 碎片化知识整理利器:NoteGen——AI驱动的免费开源笔记工具使用指南
  • 网站的建设方法包括什么问题高端网站建设大概多少费用
  • RabbitMQ Exchange类型与绑定规则详解
  • 太平洋建设官方网站wordpress 显示分类
  • 比特币私钥位数范围动态估计源代码
  • 随机游走:从布朗运动到PageRank算法的数学之旅
  • 机器学习周报十七
  • DeepCode:从论文到完整软件开发的全自动AI工具
  • 深入探索现代前端开发:从基础到架构的完整指南
  • Sora2高级玩法:超越基础生成的创意新世界(FL去水印送邀请码)
  • 自己怎样优化网站wordpress博客位置
  • 大型购物网站服务器h5页面制作工具易企秀
  • ESP32 + Arduino IDE 开发的 MQTT 通信程序
  • 网站策划哪里找WordPress访问确认
  • Kubernetes YAML配置入门
  • 淘宝网站官网东莞微网站建设多少钱
  • leetcode 118. 杨辉三角 python
  • 中级软件设计师考试选择题——计算机网络典型真题
  • 互联网个人用户网站WordPress移动站
  • ArrayList和LinkedList的区别是什么?(高频)
  • 建设网站的费用属于资产吗广州百度快速排名优化