STM32F103驱动DHT11温湿度传感器+0.96寸OLED显示完整工程详解(单总线时序+源码解析)
·
前言
最近做了一套基于 STM32F103C8T6 的 DHT11 采集工程,搭配 I2C 0.96 寸 OLED 实时显示温湿度,踩了很多单总线时序、IO 方向切换、数据校验的坑,整理完整原理、硬件接线、通信时序、底层驱动代码逻辑,零基础也能看懂,工程文件分层清晰,可直接移植使用。
一、硬件整体方案
1. 使用器件
- 主控:STM32F103C8T6
- 传感器:DHT11 数字温湿度模块(内置 8 位单片机、湿敏 / 测温元件,自带校准)
- 显示:0.96 寸 I2C OLED 屏幕

2. 核心特点
- DHT11 采用单总线通信,仅 1 根 DATA 数据线完成主机下发指令、从机回传数据
- 纯数字输出,无需 ADC 采集,单次通信输出固定 40bit 数据
- 数据自带校验和,有效规避干扰导致的读数错误
- 推荐采集周期≥1s,频繁读取会造成传感器响应异常
3. 硬件接线(本工程固定引脚)
| DHT11 引脚 | STM32 引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 可接5V 供电,具体需看说明书 |
| GND | GND | 共地必须稳定 |
| DATA | PA11 | 单总线通信引脚,代码全局宏定义 |
OLED 屏幕接线:
- SCL → PB8
- SDA → PB9
- VCC/GND 接 3.3V 与系统地
接线注意事项
- 成品 DHT11 模块内部自带 4.7K 上拉电阻,无需额外焊接;裸片传感器必须在 DATA 与 VCC 之间接 4.7k~10k 上拉电阻
- 时序依赖微秒级精准延时,
delay_us()延时函数必须校准准确,否则会出现读不到应答、数据乱码 - 走线尽量短,长距离传输容易受电磁干扰导致校验失败
二、工程文件分层结构(Keil MDK 工程)
OLED.uvprojx // Keil工程文件
├─ USER
│ ├─ main.c // 主循环,周期读取DHT11、OLED温湿度/错误显示
├─ HARDWARE
│ ├─ DHT11
│ │ ├─ dht11.c // 复位、应答检测、位/字节读取、数据解析
│ │ └─ dht11.h // IO输入输出宏、引脚定义
│ └─ OLED
│ └─ oled.c // I2C OLED底层显示驱动
三、DHT11 单总线通信核心原理
单总线最大特性:同一根数据线双向通信,因此程序中需要动态切换 PA11 的 GPIO 输入 / 输出模式。
- STM32 发送启动信号:PA11 配置为推挽输出
- 等待 DHT11 回传数据:PA11 切换为上拉输入
dht11.h IO 模式宏解析
// PA11设为输入模式
#define DHT11_IO_IN() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00008000;}
// PA11设为50MHz推挽输出
#define DHT11_IO_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00003000;}
#define DHT11_DQ_OUT PAout(11) // 输出电平控制
#define DHT11_DQ_IN PAin(11) // 读取输入电平
PA11 属于 GPIOA 高 8 位,操作寄存器CRH,bit15~bit12 控制 PA11 模式:
- 0x8:上拉输入;0x3:50MHz 推挽输出
四、完整通信时序拆解
1. 主机启动复位时序 DHT11_Rst ()
STM32 主动下发唤醒信号,步骤:
- PA11 切换输出,拉低 DATA 20ms(大于 18ms 标准启动时长)
- 拉高 DATA,保持 30us
- IO 切换输入,等待 DHT11 应答
void DHT11_Rst(void)
{
DHT11_IO_OUT();
DHT11_DQ_OUT=0;
delay_ms(20);
DHT11_DQ_OUT=1;
delay_us(30);
}
2. 从机应答检测 DHT11_Check ()
复位完成后 DHT11 自动回复应答波形:
- 先拉低 DATA 40~80us,再拉高 40~80us
- 函数返回 0 = 应答正常;返回 1 = 未检测到传感器(接线 / 供电故障)
3. 单 bit 数据读取逻辑 DHT11_Read_Bit ()
DHT11 每一位数据起始固定低电平 50us,通过高电平持续时间区分 0/1:
- 高电平短 = 数据 0
- 高电平长 = 数据 1 读取逻辑:等待电平下降→等待电平上升→延时 40us 采样当前电平判断数值
4. 单字节读取 DHT11_Read_Byte ()
单次读取 8bit,高位先行,循环读取 8 次 bit,左移拼接成完整字节。
五、40bit 数据帧格式与校验机制
DHT11 一次传输固定 5 字节(40bit),数据定义:
| 字节 | 含义 |
|---|---|
| buf[0] | 湿度整数 |
| buf[1] | 湿度小数(DHT11 固定为 0) |
| buf[2] | 温度整数 |
| buf[3] | 温度小数(DHT11 固定为 0) |
| buf[4] |
校验和 |
校验规则:buf[4] = buf[0]+buf[1]+buf[2]+buf[3] 校验不匹配代表本次数据受干扰,直接判定读取失败,主程序会重新初始化传感器重试。
六、驱动初始化流程 DHT11_Init ()
- 开启 GPIOA 外设时钟
- PA11 初始化为推挽输出,默认高电平
- 发送复位启动信号
- 检测传感器应答,返回初始化状态
dht11.c
#include "dht11.h"
#include "delay.h"
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
delay_us(30); //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN();//SET INPUT
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *humiH,u8 *humiL,u8 *tempH,u8 *tempL)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()!=0)
{
return 1;
}
for(i=0;i<5;i++)
{
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])!=buf[4])
{
return 1;
}
*humiH=buf[0];
*humiL=buf[1];
*tempH=buf[2];
*tempL=buf[3];
return 0;
}
// Init DHT11 IO and check presence.
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO口
GPIO_SetBits(GPIOA,GPIO_Pin_11); //PA11 输出高
DHT11_Rst(); //复位DHT11
return DHT11_Check();//等待DHT11的回应
}
dht11.h
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"
//IO方向设置
#define DHT11_IO_IN() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00008000;}
#define DHT11_IO_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=0X00003000;}
////IO操作函数
#define DHT11_DQ_OUT PAout(11) //数据端口 PA11出方向
#define DHT11_DQ_IN PAin(11) //数据端口 PA11入方向
u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *humiH,u8 *humiL,u8 *tempH,u8 *tempL);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif
七、主程序运行逻辑 main.c
主循环 1s 采集一次 DHT11 数据:
- 读取温湿度数据,校验校验和
- 读取成功:OLED 打印温湿度数值
- 读取失败:OLED 打印报错信息,重新初始化 DHT11 重试
while(1)
{
if(DHT11_Read_Data(&humiH,&humiL,&tempH,&tempL)==0)
{
Show_DHT11_Data(humiH,humiL,tempH,tempL);
}
else
{
Show_DHT11_Error();
DHT11_Init();
}
delay_ms(1000);
}
main.c
// Function: show DHT11 temperature and humidity on SSD1306 OLED.
// Wiring:
// OLED SCL -> PB8
// OLED SDA -> PB9
// DHT11 DATA -> PA11
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "dht11.h"
#define SENSOR_REFRESH_MS 1000
static void Show_DHT11_Data(u8 humiH,u8 humiL,u8 tempH,u8 tempL);
static void Show_DHT11_Error(void);
int main(void)
{
u8 humiH=0;
u8 humiL=0;
u8 tempH=0;
u8 tempL=0;
delay_init();
delay_ms(1000);
NVIC_Configuration();
OLED_Init();
OLED_ColorTurn(0);
OLED_DisplayTurn(0);
DHT11_Init();
while(1)
{
if(DHT11_Read_Data(&humiH,&humiL,&tempH,&tempL)==0)
{
Show_DHT11_Data(humiH,humiL,tempH,tempL);
}
else
{
Show_DHT11_Error();
DHT11_Init();
}
delay_ms(SENSOR_REFRESH_MS);
}
}
static void Show_DHT11_Data(u8 humiH,u8 humiL,u8 tempH,u8 tempL)
{
OLED_Clear();
OLED_ShowString(20,0,(u8 *)"DHT11 PA11",16);
OLED_ShowString(0,18,(u8 *)"Temp:",16);
OLED_ShowNum(48,18,tempH,2,16);
OLED_ShowString(64,18,(u8 *)".",16);
OLED_ShowNum(72,18,tempL,1,16);
OLED_ShowString(88,18,(u8 *)"C",16);
OLED_ShowString(0,34,(u8 *)"Humi:",16);
OLED_ShowNum(48,34,humiH,2,16);
OLED_ShowString(64,34,(u8 *)".",16);
OLED_ShowNum(72,34,humiL,1,16);
OLED_ShowString(88,34,(u8 *)"%",16);
OLED_ShowString(0,52,(u8 *)"Read OK",12);
OLED_Refresh();
}
static void Show_DHT11_Error(void)
{
OLED_Clear();
OLED_ShowString(20,0,(u8 *)"DHT11 PA11",16);
OLED_ShowString(8,22,(u8 *)"Sensor Error",16);
OLED_ShowString(0,46,(u8 *)"Check DATA/VCC",12);
OLED_Refresh();
}
OLED 正常显示内容:
DHT11 PA11
Temp: xx.x C
Humi: xx.x %
Read OK

故障报错页面:
DHT11 PA11
Sensor Error
Check DATA/VCC
八、常见踩坑总结
- 延时不准:微秒延时误差大,直接读不到应答,必须校准 delay_us
- IO 忘记切换模式:一直输出无法接收传感器数据,一直输入无法下发启动信号
- 采集间隔过短:小于 1s 频繁读取,传感器无响应、数据漂移
- 忽略校验和:干扰下会出现离谱温湿度数值,校验可过滤无效数据
更多推荐



所有评论(0)