DHT11温湿度OLED显示方案:从时序驱动到局部刷新避坑指南

发布时间:2026/10/20 4:53:12
DHT11温湿度OLED显示方案:从时序驱动到局部刷新避坑指南 简介这份资源面向嵌入式与单片机初学者及课程设计、毕业设计开发者围绕DHT11数字温湿度传感器与OLED显示模块提供一套可直接编译运行的完整工程解决温湿度采集与屏幕实时显示的实现问题。压缩包共190个文件约6.82MB以C源码与头文件为核心包含34个c、36个h并配有Keil工程文件、启动汇编、链接脚本及编译中间产物覆盖STM32标准外设库的定时器、串口、I2C、ADC等驱动模块便于按模块阅读与二次修改。资源已有3710人学习下载说明其在同类教程中具备一定参考价值。读者可从中获得传感器单线制串行通信的驱动思路、OLED显示刷新逻辑以及完整的工程目录组织方式适合作为入门练手或项目移植的基础模板。1. 从一次“数据跳变”说起DHT11温湿度OLED显示方案要解决什么做过嵌入式的人都遇到过这种场景DHT11明明接好了串口打印也正常可一接到OLED上数值就开始抽风——要么显示85.0这种经典错误值要么刷新几次之后整块屏直接黑掉。问题往往不在传感器本身而在于时序、刷新节奏和供电被搅在了一起。这套DHT11温湿度OLED显示方案就是围绕“单总线传感器采集 OLED本地显示”这条最小闭环来设计的适合正在做嵌入式课设、单片机毕设或者需要给某个环境监测节点加一块本地屏的开发者。它不追求联网、不上云核心目标只有一个让温湿度数据稳定、可读地出现在屏幕上并且这套代码你能直接改、直接烧、直接验收。2. 需求拆解与技术选型为什么是DHT11加OLED2.1 功能需求与非功能需求先把需求写清楚不然后面选型全是拍脑袋。这个项目的功能需求其实就四条第一周期性采集环境温度和相对湿度第二把两个数值实时显示在本地屏幕上第三数值刷新时不能闪烁、不能残留旧字符第四系统上电后能自恢复不需要人工复位。非功能需求往往被忽略但恰恰是翻车重灾区。采集周期通常定在1到2秒因为DHT11本身响应就慢手册里写得很清楚采样间隔低于1秒容易读到上一次的残留数据。显示刷新率反而可以高一些但没必要跟着采集节奏走就行。供电方面DHT11对电压波动比较敏感3.3V和5V都能跑但如果你用的是5V单片机系统数据引脚上拉电阻别省。还有一个隐性需求代码要能移植换一块同尺寸OLED或者换一个引脚改几行宏定义就能跑而不是重写整个驱动。2.2 主控、传感器与显示器的选型逻辑主控这块STM32F103C8T6和GD32这类Cortex-M3内核的芯片是这个场景里最稳的选择。原因不复杂GPIO翻转速度足够跑单总线时序硬件I2C或者软件模拟I2C都吃得开而且资料多、踩坑记录全。如果你手头只有51单片机也能做但DHT11的微秒级时序对51的晶振和中断响应要求更苛刻调试时间会明显拉长。传感器选DHT11而不是DHT22或者SHT30核心原因是成本和教学场景的匹配度。DHT11精度是湿度±5%RH、温度±2℃分辨率1℃对于“看个大概”的环境监测完全够用。它的单总线协议虽然时序要求死板但正好是嵌入式课设里练GPIO和延时函数的经典素材。你要是做毕设答辩时老师问“为什么不用I2C的SHT30”你可以从成本、协议教学价值、项目定位三个角度回答这比单纯说“便宜”要有说服力。显示器选0.96寸OLEDSSD1306驱动I2C接口128×64分辨率。选它的理由很实际第一I2C只要两根线不占太多IO第二SSD1306的显存结构清晰页地址模式写起来不绕第三0.96寸这个尺寸在课设实物里看起来足够“像个成品”比LCD1602的观感好而且不需要背光调节。唯一要注意的是I2C地址市面上常见的是0x78和0x7A两种买回来先用扫描程序确认一下别硬编码。2.3 通信协议与引脚分配DHT11用的是单总线一根数据线完成双向通信。主机拉低至少18ms作为起始信号然后释放DHT11响应拉低80微秒再拉高80微秒接着连续输出40位数据。前16位是湿度整数和小数中间16位是温度整数和小数最后8位是校验和。校验规则是前四个字节相加取低8位和校验字节比对。这个协议的特点是没有时钟线全靠延时卡时间窗口所以中断干扰是最大的敌人。OLED这边走I2CSCL和SDA两根线加上VCC和GND四根线搞定。引脚分配我一般这样安排DHT11数据脚接PA0OLED的SCL接PB6、SDA接PB7用硬件I2C。如果你用软件模拟I2C那就随便挑两个普通GPIO但延时函数要调准。下面这张表是典型接线参考实际项目里按你的板子改。模块引脚主控引脚说明DHT11DATAPA0单总线需4.7k上拉DHT11VCC3.3V供电DHT11GNDGND共地OLEDSCLPB6I2C时钟OLEDSDAPB7I2C数据OLEDVCC3.3V供电OLEDGNDGND共地注意DHT11的数据引脚上拉电阻不能省尤其是杜邦线较长的时候。我见过太多“读出来全是85”的案例最后查出来就是上拉没接或者接触不良。3. 总体架构设计三层结构与两个核心模块3.1 分层结构驱动层、服务层、应用层这套方案我习惯按三层来切。最底下是驱动层包含DHT11的时序驱动和SSD1306的I2C驱动这一层只负责“把电平变成字节”和“把字节写到屏幕”不关心业务。中间是服务层负责把原始字节转成带单位的温湿度值做校验、做异常过滤比如连续读到85就标记为无效。最上面是应用层管调度什么时候采、什么时候刷、刷新时先清哪块区域。这样分的好处是调试的时候能快速定位。屏幕不亮查驱动层数值不对查服务层刷新节奏乱查应用层。很多课设代码把这三层揉在一个main函数里出了问题只能一行行看效率极低。3.2 数据流与刷新策略数据流很直白应用层触发采集服务层调用DHT11驱动拿到40位原始数据校验通过后转成温度和湿度两个浮点数或者整型然后交给OLED驱动显示。这里有个细节DHT11的温度小数位在DHT11上其实恒为0湿度小数位也是0所以显示的时候直接按整数处理就行别被某些库里的浮点转换绕进去。刷新策略上我一般用“局部刷新”而不是“整屏清空重画”。整屏清空会导致肉眼可见的闪烁尤其是刷新周期短的时候。具体做法是温湿度数值固定显示在屏幕的某两行每次刷新只覆盖数值区域前面的标签“Temp:”和“Humi:”只写一次。SSD1306的页地址模式支持按页写你只要算好坐标每次只更新那几页的显存就行。3.3 关键数据结构与状态机DHT11的读取过程适合用一个简单的状态机来描述而不是一路delay到底。状态机大概分四个状态空闲、起始信号、等待响应、接收数据。每个状态里做有限的事超时就跳回空闲并标记错误。这样即使某次读取失败系统也不会卡死。数据结构上定义一个结构体存温湿度值和有效性标志typedef struct { uint8_t temperature; // 温度整数部分单位℃ uint8_t humidity; // 湿度整数部分单位%RH uint8_t valid; // 1本次数据有效0无效 uint32_t last_read_ms; // 上次成功读取的时间戳 } dht11_data_t;这个结构体在服务层和应用层之间传递应用层只认valid标志不关心底层时序。valid为0的时候屏幕上保持上一次的有效值或者显示“--”而不是把85.0直接怼上去。4. 模块实现DHT11时序驱动与OLED显示驱动4.1 DHT11单总线时序的代码实现DHT11的时序核心就三件事拉低、释放、按窗口读位。下面这段是基于STM32 HAL库的微秒级延时实现关键点在于读位的时候要在高电平持续时间内判断是0还是1。// 微秒级延时基于SysTick或者DWT这里用简单的循环延时示意 static void delay_us(uint32_t us) { uint32_t count us * (SystemCoreClock / 1000000) / 5; while (count--) { __NOP(); } } // 读取一个字节MSB先出 static uint8_t dht11_read_byte(void) { uint8_t byte 0; for (int i 0; i 8; i) { // 等待50us低电平结束 while (DHT11_PIN_READ() 0); delay_us(40); // 40us后采样高电平仍为1则是数据1 byte 1; if (DHT11_PIN_READ() 1) { byte | 1; // 等待剩余的高电平结束 while (DHT11_PIN_READ() 1); } } return byte; } // 完整读取一次返回0成功-1失败 int dht11_read(dht11_data_t *out) { uint8_t buf[5] {0}; // 主机拉低至少18ms DHT11_PIN_OUT(); DHT11_PIN_LOW(); HAL_Delay(20); DHT11_PIN_HIGH(); delay_us(30); DHT11_PIN_IN(); // 等待DHT11响应 if (DHT11_PIN_READ() 1) return -1; while (DHT11_PIN_READ() 0); while (DHT11_PIN_READ() 1); // 读40位 for (int i 0; i 5; i) { buf[i] dht11_read_byte(); } // 校验 if ((uint8_t)(buf[0] buf[1] buf[2] buf[3]) ! buf[4]) { out-valid 0; return -1; } out-humidity buf[0]; out-temperature buf[2]; out-valid 1; return 0; }这段代码里delay_us(40)那个采样点是关键。DHT11的位数据里0的高电平持续26到28微秒1的高电平持续70微秒。40微秒的时候采样0已经变低1还是高刚好能区分。这个值不能随便改改了就容易读错。另外while等待循环里没有加超时实际项目里建议加一个计数器防止传感器掉线时死循环。4.2 SSD1306的I2C写命令与写数据SSD1306的I2C协议里每次传输的第一个字节是控制字节0x00表示后面跟的是命令0x40表示后面跟的是数据。写命令和写数据分开封装后面初始化的时候会反复用到。// 写一个命令字节 void oled_write_cmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 100); } // 写一个数据字节 void oled_write_data(uint8_t data) { uint8_t buf[2] {0x40, data}; HAL_I2C_Master_Transmit(hi2c1, OLED_ADDR, buf, 2, 100); } // 设置显示坐标页地址模式 void oled_set_pos(uint8_t page, uint8_t col) { oled_write_cmd(0xB0 page); // 设置页地址 oled_write_cmd(col 0x0F); // 列低4位 oled_write_cmd(0x10 | (col 4)); // 列高4位 }OLED_ADDR一般是0x78但如果你买到的模块背面电阻跳到了0x7A就得改。写命令和写数据之间不需要额外延时SSD1306的I2C速率支持到400kHzHAL库默认100kHz完全够用。初始化序列里0xAE关显示、0xD5设时钟、0xA8设复用率、0x8D开电荷泵这几条顺序不能乱尤其是电荷泵不开的话屏幕永远不亮。4.3 温湿度数值的格式化与局部刷新显示部分我一般把温度和湿度分两行放字体用6x8或者8x16。局部刷新的逻辑是先算出数值字符串比如“25C”和“60%”然后只更新这两行对应的显存区域。// 在指定页和列显示一个字符串 void oled_show_str(uint8_t page, uint8_t col, char *str) { oled_set_pos(page, col); while (*str) { // 这里调用字库取模函数把字符点阵逐字节写入 for (int i 0; i 6; i) { oled_write_data(font6x8[*str - ][i]); } str; } } // 应用层刷新 void refresh_display(dht11_data_t *data) { char temp_str[8], humi_str[8]; if (data-valid) { sprintf(temp_str, %dC, style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询