拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于STC89C51的双DHT11温湿度采集与LCD1602显示实战解析

简介这是一个基于STC89C51单片机的双通道DHT11温湿度实时显示系统面向电子工程初学者、嵌入式开发学习者及课程设计/毕业设计参考人群。系统以STC89C51为控制核心通过单总线读取两路DHT11传感器数据并利用LCD1602液晶屏实时显示温度与湿度覆盖MCU最小系统、传感器时序读取、字符液晶驱动、双通道数据管理等关键知识点。压缩包共22个文件约68KB主要包含C源码、H头文件、Keil工程文件、HEX烧录文件以及编译过程生成的lst/obj/m51等辅助文件其中HARDWARE文件夹可协助理解电路与硬件设计结构。目前已有1370人学习下载。借助这份资料读者可快速获得一套可直接编译烧录的完整工程参考双DHT11轮询处理与LCD1602显示逻辑还能从工程备份和编译报告中了解常见开发流程与排错方式适合作为单片机应用、传感器检测类实验的入门动手素材。 很多同学拿“基于STC89C51单片机双DHT11LCD1602”这个题目来找我课程设计有毕业设计凑内容的也有。老实说这类项目在网上一搜一大把“能跑”的参考代码但大多数人抄完代码、烧进板子LCD1602上要么一直ERR要么湿度温度疯狂跳变甚至屏幕直接花掉。问题通常不是代码抄错了而是写代码的人自己也没把三个关键点吃透DHT11的单总线时序怎么读、LCD1602的4线初始化到底在干什么、双通道应该怎么轮流采集才不互相干扰。这篇文章就把这三个隐藏难点挨个拆开讲顺带给出可以直接套用的代码框架和排错思路。正在做课程设计、毕业设计或者单纯想搞懂51单片机怎么驱动外设的朋友这篇文章值得你保存下来慢慢看。1. 双通道的坑不在DHT11本身而在“时序”和“分工”1.1 双通道并不是把两个传感器接到一个引脚上先说一个最常见的误区有人觉得两个DHT11都是开漏输出把两个数据脚并到一个IO上读一次就能同时拿到两路数据。这个想法很危险。DHT11走的是类单总线协议它的数据是“主机问一次、从机答一整帧”从机靠拉低总线来响应主机。如果两个传感器挂在同一根数据线上主机发出起始信号之后两个从机都会尝试应答总线上谁在拉低、谁在拉高完全没法区分最后读回来的一定是乱码。双通道的正确做法是让两个DHT11各占一个独立IO引脚读取时分别发起始信号、分别读数据帧、分别做校验从硬件上隔离彼此的信号。这才叫真正的“双通道”。1.2 很多人抄代码翻车的地方模拟时序的精度51单片机不像STM32那样有现成的HAL库DHT11驱动基本靠GPIO模拟时序。模拟时序最怕的是延时不准。很多教程里delay_us(30)写得很随意但30us这个时间点恰恰是区分数据位0和1的采样窗口差一点就会误判。再加上每个人用的晶振、编译优化等级、单片机型号12T还是1T都不一样同一个延时函数在不同环境下实际延时差别很大。这就是为什么同一份代码别人板子上跑得好好的换到你手里就ERR。1.3 做这个项目我建议你准备这些东西除了主角STC89C51、两个DHT11、一块LCD1602之外我建议你准备12MHz晶振如果用成品板就忽略、10k电位器调LCD对比度、4.7k到10k的上拉电阻两只、0.1uF和10uF电容各两只去耦用、面包板或洞洞板、杜邦线若干、STC-ISP下载线、Keil C51开发环境。如果手里有逻辑分析仪或示波器就更好了后面排查ERR问题的时候能帮你省下大量时间。没有也别慌用万用表一步步量也能定位大部分问题。2. DHT11单总线时序先看懂波形再写代码2.1 一次完整握手起始信号、响应、40bitDHT11的协议可以简化成“一问一答”。主机先把数据线拉低持续至少18ms这个低电平就是“读请求”的起始信号。DHT11识别到这个信号后会把数据线拉低80us表示“我收到了”再拉高80us表示“准备发数据”然后就按位输出40bit数据。这里有一个细节主机起始信号不能太短太短DHT11会把它当成总线上的毛刺而忽略也不能无限长太长会让传感器误判为异常状态。我习惯控制在20ms左右实测比较稳。2.2 区分0和1的关键在于30us采样点40bit数据里每一位都是以50us低电平开始的区别在于后续高电平的持续时间高电平约26到28us表示“0”高电平约70us表示“1”。所以读数据位的技巧是先等50us低电平结束然后延时大约30us再去采样引脚电平。如果此时引脚是高电平这个数据位就是“1”如果此时已经回到低电平说明是“0”。这个30us采样点的把握就是整个DHT11驱动的核心。延时早一点或晚一点读出来的数据位就容易出错表现就是校验不过或者数值跳变。2.3 把延时函数校准到“能用”的标准STC89C51是12T单片机12MHz晶振下1个机器周期正好1us理论上写延时函数很直观。但Keil C51里函数调用、循环变量自增、条件判断都有额外开销纯C的延时函数实际延时会比理论值偏大。建议先写一个空循环延时然后用逻辑分析仪或示波器实测IO翻转的周期把delay_us和delay_ms校准到尽量贴近目标。下面给一个常用模板void delay_us(unsigned int us) { while (us--) { _nop_(); } } void delay_ms(unsigned int ms) { while (ms--) { delay_us(1000); } }注意这个delay_us(1000)实际可能不止1000us因为有循环和函数调用开销。如果你手里有逻辑分析仪实测一下最稳妥。如果没有可以先用这个模板如果DHT11读取偶尔失败再往两个方向微调起始信号拉低时间加长到25ms或者采样延时从30us改成25us试试。2.4 多少秒读一次才合理DHT11手册明确写了采样周期是2秒左右。也就是说一次完整读取之后传感器需要大约2秒时间完成新一轮测量。如果你在2秒内反复读取它返回的很可能是上一次缓存的数据甚至干脆不响应。所以主循环里的延时至少要给到2秒。很多人的项目“看起来在实时显示”其实是2秒刷新一次这本来就是DHT11的天花板追求更快没有意义。3. 硬件连线双传感器与LCD1602的细节取舍3.1 最小系统先跑起来别急着接传感器如果用的是成品开发板这一步可以跳过。如果是自己焊洞洞板务必先把最小系统跑起来接好晶振、复位电路、电源先用一个LED测试IO是否能正常控制。我见过太多人上来就把传感器、LCD、蜂鸣器全接上结果一上电乱成一锅粥最后发现是晶振没起振或者复位电容虚焊。硬件调试最忌讳“大爆炸式”一次接完分层验证才是正道。3.2 双DHT11的电源、上拉和去耦两个DHT11的VCC统一接5VGND统一接GNDDATA分别接两个不同IO口。每个DATA口到VCC之间都要接一只4.7k到10k的上拉电阻。DHT11的数据线是开漏结构没有外部上拉时高电平不够稳定近距离偶尔能工作但数据线稍微一长、或者旁边有电机继电器干扰就必出问题。电源方面还有一个容易被忽略的点LCD1602背光开启瞬间电流不小如果电源线太细或稳压器余量不足VCC会被瞬间拉低导致DHT11复位或数据错误。建议在DHT11的VCC和GND之间就近加0.1uF陶瓷电容和10uF电解电容LCD背光串联一个100到330欧的限流电阻别直接裸接5V。3.3 LCD1602用4线还是8线取决于IO如果项目只驱动一个LCD8线模式当然更简单直接。但一旦加上两个DHT11、按键、蜂鸣器之类的外设IO就会紧张。4线模式只需要DB4到DB7、RS、RW、E这7个引脚能省4个IO出来。代价是初始化时要按特定顺序给LCD“喂”半字节这也是很多人抄代码失败的重灾区。我个人的建议是只要不是IO实在不够用优先用4线因为省下的IO在后期的功能扩展里太宝贵了。3.4 接线对照表模块引脚接STC89C51LCD1602RSP2.0LCD1602RW直接接地或P2.1建议接地LCD1602EP2.2LCD1602DB4-DB7P0.4-P0.7LCD1602Vo10k电位器中间脚电位器两端接5V和GNDLCD1602LED背光串联220欧电阻接5VLEDK接地DHT11_ADATAP1.0DHT11_BDATAP1.1DHT11_A/BVCC统一接5VDHT11_A/BGND统一接GND上拉电阻DATA到VCC两只4.7k到10k注意数据线DB4-DB7接到P0.4-P0.7时初始化代码里半字节的摆放方式也要对应。如果在你的板子上DB4-DB7接的是P0.0-P0.3那代码里的nibble赋值要相应改到低四位务必和实际接线一一对应。4. 代码框架双通道轮流读取与LCD1602四线驱动4.1 主程序的结构2秒一轮的循环主程序的逻辑不复杂初始化LCD然后进入一个大循环每轮分别读取A、B两个通道的温湿度分别做校验把结果格式化后显示到LCD第一行和第二行最后延时2秒进入下一轮。关键是要把“读取”和“显示”解耦开——读取失败时不能把上一次的脏数据直接显示出来而是要显示一个ERR提示下一次循环再重试。4.2 DHT11读取函数单通道和双通道的写法差异C51的sbit是编译期常量不方便像STM32那样把引脚当作函数参数传递所以双通道工程里最常见的做法是写两个几乎一模一样的读取函数只是里面用的引脚不同。这确实有点啰嗦但胜在逻辑清晰、不会出错。下面以通道A为例给出核心读取代码sbit DHT11_A P1^0; unsigned char DHT11_Read_Byte_A(void) { unsigned char i, dat 0; for (i 0; i 8; i) { while (DHT11_A 0); // 等待50us低电平结束 delay_us(30); // 关键采样点 dat 1; if (DHT11_A 1) { dat | 0x01; } while (DHT11_A 1); // 等待高电平结束 } return dat; } unsigned char DHT11_Read_Data_A(unsigned char *hum, unsigned char *temp) { unsigned char buf[5]; unsigned char i; DHT11_A 0; // 主机起始信号 delay_ms(20); DHT11_A 1; delay_us(30); // 释放总线后短暂等待 if (DHT11_A 1) return 1; // 传感器无响应 while (DHT11_A 0); // 等待80us低电平 while (DHT11_A 1); // 等待80us高电平 for (i 0; i 5; i) { buf[i] DHT11_Read_Byte_A(); } if (((buf[0] buf[1] buf[2] buf[3]) 0xFF) ! buf[4]) { return 2; // 校验失败 } *hum buf[0]; *temp buf[2]; return 0; }通道B只需把DHT11_A全部替换成DHT11_B即可。校验和的算法很简单湿度整数、湿度小数、温度整数、温度小数四个字节相加取低8位应该等于第5个字节。校验不通过的数据直接丢弃这是防止“幽灵数据”上屏的最后一道防线。4.3 LCD1602四线初始化为什么前面要三次0x03LCD1602上电之后默认工作在8位模式。如果直接用4位模式发命令0x28LCD会把0x28当成8位指令的高8位来解析结果就是花屏。正确做法是用一个“同步”技巧连续向高4位发送三次0x03每次间隔5ms以上这样LCD会慢慢意识到“主机在用4位模式跟我说话”然后再发一次0x02正式确认进入4位模式。这一步是整个LCD1602四线驱动里最容易被忽略、也最容易导致花屏的地方。#define LCD_DATA P0 sbit LCD_RS P2^0; sbit LCD_RW P2^1; sbit LCD_EN P2^2; void LCD_WriteNibble(unsigned char nibble) { LCD_RS 0; LCD_RW 0; LCD_DATA (LCD_DATA 0xF0) | (nibble 0x0F); LCD_EN 1; delay_us(2); LCD_EN 0; } void LCD_WriteCmd(unsigned char cmd) { LCD_RS 0; LCD_RW 0; LCD_DATA (LCD_DATA 0xF0) | ((cmd 4) 0x0F); LCD_EN 1; delay_us(2); LCD_EN 0; LCD_DATA (LCD_DATA 0xF0) | (cmd 0x0F); LCD_EN 1; delay_us(2); LCD_EN 0; } void LCD_Init(void) { LCD_RS 0; LCD_RW 0; delay_ms(15); LCD_WriteNibble(0x03); delay_ms(5); LCD_WriteNibble(0x03); delay_ms(5); LCD_WriteNibble(0x03); delay_ms(5); LCD_WriteNibble(0x02); delay_ms(5); LCD_WriteCmd(0x28); // 4位模式2行5x7点阵 LCD_WriteCmd(0x0C); // 开显示关光标 LCD_WriteCmd(0x06); // 写入后地址自动加1 LCD_WriteCmd(0x01); // 清屏 delay_ms(2); }字符写入函数和显示字符串函数也应该一起准备好void LCD_WriteChar(unsigned char ch) { LCD_RS 1; LCD_RW 0; LCD_DATA (LCD_DATA 0xF0) | ((ch 4) 0x0F); LCD_EN 1; delay_us(2); LCD_EN 0; LCD_DATA (LCD_DATA 0xF0) | (ch 0x0F); LCD_EN 1; delay_us(2); LCD_EN 0; } void LCD_WriteString(unsigned char *str) { while (*str) { LCD_WriteChar(*str); } } void LCD_SetCursor(unsigned char row, unsigned char col) { LCD_WriteCmd(0x80 | (row ? 0x40 : 0x00) | col); }4.4 显示格式化的取舍STC89C51的代码空间只有8KB而Keil C51里的sprintf是个“代码空间吞噬者”一用就很容易超过64KB限制而且运行时还慢。所以显示格式化我建议用手工拼字符的方式别碰sprintf。比如第一行显示“A:25C 60%”第二行显示“B:26C 58%”可以这样拼void FormatLine(char *buff, char ch, unsigned char temp, unsigned char hum) { buff[0] ch; buff[1] :; buff[2] temp / 10 0; buff[3] temp % 10 0; buff[4] C; buff[5] ; buff[6] hum / 10 0; buff[7] hum % 10 0; buff[8] %; buff[9] \0; }主程序里每轮读取后根据返回值决定是拼正常数据还是拼“ERR”然后调用LCD_SetCursor和LCD_WriteString刷到对应行。注意LCD写入过程中要等一下DHT11读取和LCD显示之间不要有太紧的耦合否则显示耗时过长会影响下一次读取。5. 实测排错从ERR到乱码完整排查链路5.1 ERR的排查顺序LCD显示ERR说明DHT11读取函数返回了非0值可能是传感器无响应也可能是校验失败。排查顺序建议从硬件到代码一步步来。先用万用表量VCC和GND之间是不是5V确认没接反再量DATA引脚对VCC的上拉电阻是否焊好。之后单独写一个只读一个字节的测试程序用LED指示DHT11是否返回了响应信号。如果LED一直不亮基本可以断定是起始信号没发对或者传感器供电有问题。如果响应正常但校验不过那就是时序采样点偏移用逻辑分析仪抓一下波形看每个数据位里高电平持续时间是对应26us还是70us一目了然。5.2 数据跳变时的电源检查温度湿度偶尔跳一下然后又恢复正常这种问题十有八九出在电源上而不是传感器坏了。DHT11在数据帧变更时会在数据线上产生不小的电流波动如果电源纹波本来就大加上LCD背光开启瞬间拉低VCC传感器就可能复位或者输出错误位。处理办法很直接VCC和GND之间加0.1uF和10uF电容传感器连接线尽量短避免和数据线并行走线。还有一个容易忽略的点如果你用USB转TTL模块给板子供电劣质USB线压降很大换一根粗短的线往往就好了。5.3 花屏乱码时的初始化检查四线模式下花屏90%是初始化序列没走对。重点看三次0x03之间是不是真的隔了5ms很多人的延时函数实际只有1msLCD还没反应过来就进入下一步了。其次查RW引脚如果RW没有接地而是接了IO要确保程序里确实把它拉低了。最后检查对比度电位器Vo电压大概调到1V左右调得太低屏幕全黑太高会有残影或乱码。建议先把电位器从最小往最大慢慢拧找到显示最清晰的那一小段。5.4 两个传感器读数不一致怎么办DHT11的精度是±2%RH和±0.5℃两个传感器放在同一环境下读数略有差异是完全正常的。但如果偏差过大先检查是不是其中一个传感器贴着发热元件比如晶振、稳压芯片、LCD背光附近。其次检查是不是其中一个传感器已经老化DHT11寿命一般在几年左右长期在粉尘或潮湿环境下使用也会漂移。实在不行可以做软件补偿把差值存到EEPROM里显示时自动加上校准值。现象可能原因定位方法处理方案一直显示ERR传感器无响应或校验失败万用表量供电和上拉LED测试程序检查VCC/GND/上拉延长起始信号到20ms偶尔跳变电源纹波大、线路过长观察背光开启瞬间是否出现故障加去耦电容缩短信号线读取间隔2秒LCD花屏4线初始化时序不对逐步检查0x03三次同步延时确保5ms间隔RW接地或置低数值偏差大传感器个体误差同一环境下交叉对比远离热源软件校准补偿6. 再做一版从课程设计到能用的环境监测系统6.1 换OLED省IO如果觉得LCD1602太占地方也太费引脚可以换成0.96寸I2C接口OLEDSCL和SDA两根线就能驱动省下的IO可以接按键、接继电器整个系统的实用度会立刻高一个档次。OLED的驱动库网上很多移植过来之后只需要把显示部分换成OLED相关的函数读传感器的核心代码完全不用动。6.2 串口上报变成一个物联网节点STC89C51自带UART串口把温湿度数据按固定帧格式通过串口发出去再接一块ESP8266或者蓝牙模块就成了一个简单的物联网节点。电脑上用串口助手就能看数据配合PC端工具画曲线也很方便。帧格式建议用“帧头通道号温度湿度校验”的结构方便上位机解析。6.3 加越限报警和控制双通道在这个场景下特别有价值一个通道监控室内一个通道监控设备柜内部分别用不同阈值判断。当某一路温度或湿度超过设定范围时驱动蜂鸣器报警或者通过继电器控制风扇、加湿器、加热器的开关。按键用来设置上下限参数掉电保存到EEPROM里。这一套做下来就不再是单纯的课程设计而是一个可以长期运行的小型环境监控系统了。6.4 负温度与小数位的处理DHT11返回的温度整数位本身就是补码形式的当温度低于0度时最高位为1。处理时要先判断符号位转换成对应的负数值并在LCD上显示负号。湿度的小数位精度其实有限数字看着像那么回事实际参考意义不大建议直接显示整数部分就好没必要把小数位也摆上屏省得误导人。做这个项目我自己最大的体会是DHT11看起来是个“很入门”的传感器但它的单总线协议里藏了不少时序敏感细节。强烈建议你们在调试时哪怕手里只有一台几十块钱的逻辑分析仪也要抓一次完整的读时序波形看看起始信号、80us响应、50us低电平分隔、以及那26us和70us高电平之间的差异。看过一次波形你对“模拟时序”这四个字的理解会远超抄十份代码。项目做完之后还想进一步深入的话去研究用定时器做精确延时或者把同样的时序移植到其他单片机的HAL库里你会发现底层原理完全相通——这大概就是做单片机项目最值得投入的地方。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门