资讯动态

STC89C51+Proteus实现多路DHT11多点测温方案

发布时间:2026/9/13 20:21:18 来源:尧图企业网站定制
简介这套基于STC89C51单片机与DHT11传感器组成的多点测温方案同时提供Proteus仿真工程和完整软件源码适合正在做单片机课程设计、毕业设计或想学习嵌入式开发与传感器应用的读者。方案实现两个测温点的实时温度采集将当前温度与每个点预设的上下限值分别比较并在LCD对应位置显示H高温或L低温报警状态刷新清晰逻辑简单易懂可从中掌握DHT11驱动时序、LCD1602显示控制、阈值设定与报警输出的完整设计方法。压缩包共24个文件包含C语言源文件、头文件、Keil工程文件、Proteus原理图DSN、可直接烧录的HEX文件及Readme说明整体仅72KB轻量便于修改仿真环境可直接运行验证。用户可以通过修改程序中的温度阈值灵活调整报警点二次开发方便。目前已有199人学习使用资料目录结构清晰代码模块划分明确是单片机多点测温项目的高性价比参考也可在此基础上扩展更多点位或联动控制功能。1. 为什么多点测温偏偏选STC89C51DHT11单总线不带节点寻址很多人第一次做多点测温会直接想到DS18B20因为它的1-wire总线协议自带64位ROM地址一条总线理论上能挂几十个节点。DHT11虽然也叫单总线但协议设计里没有节点号这个概念40位数据帧里只有湿度、温度和校验没有地址字段。如果把几个DHT11的DATA脚直接并联在一起主机收到数据后根本分不清哪一路来自哪个位置。所以这个标题下的“多点测温方案”工程上最直接、最不容易翻车的做法是让每一个DHT11独占一个单片机IO口由STC89C51分时轮询采集。用3路传感器时P2口只需占用3根引脚剩余IO足够驱动LCD1602和按键。STC89C51是5V供电的8051内核IO电平与DHT11天然匹配不需要像STM32那样额外加电平转换电路。Proteus自带DHT11元件模型配合Keil C51编译生成的HEX文件从单总线时序到LCD显示全链路都能在仿真环境里跑通。这套电路和源码比较适合课程设计、毕设以及刚接触单总线协议又不想立刻买开发板的读者。2. Proteus搭建STC89C51多路DHT11电路元件搜索、接线与上拉电阻2.1 Proteus元件搜索STC89C51在库里没有用AT89C51替代在Proteus 8 Professional的元件搜索框输入“STC89C51”绝大多数版本直接搜不到。Proteus默认库里的51内核MCU以Atmel的AT89C51、AT89C52为主而STC89C51的指令集和引脚排列与AT89C51高度兼容所以最稳妥的做法是直接用AT89C51占位仿真。编译时在Keil里选择Atmel AT89C51目标芯片生成的HEX文件烧进Proteus里的AT89C51模型执行结果和STC89C51在12T模式下的行为一致。如果你手头有STC官方发布的Proteus插件或者第三方封装好的STC元件库也可以放置“STC89C51RC”这个模型。不过对本文这种只用到普通IO口和外部中断的项目AT89C51在Proteus层面和STC89C51的差异可以忽略。DHT11在Proteus里可以直接搜到位于Sensors分类下元件名就叫“DHT11”。通常封装有4个引脚VCC、DATA、NC、GND。放3个DHT11到画布分别对应3个测温点A、B、C。一个典型的元件清单如下元件Proteus搜索关键词数量用途主控MCUAT89C511STC89C51的替代仿真模型温湿度传感器DHT113测温点A/B/C上拉电阻RES3DHT11 DATA线4.7kΩ上拉排阻RESPACK-81P0口10kΩ上拉LCD1602LM016L1两行温度显示晶振CRYSTAL112MHz电容CAP2晶振负载电容33pF2.2 接线里的三个坑P0开漏、DATA上拉、NC脚悬空STC89C51的P0口内部是开漏结构接LCD1602的数据线DB0DB7时必须外接排阻到VCC否则LCD数据线上的高电平根本拉不上去。Proteus里拖一个RESPACK-8公共端接VCC8个引脚按顺序接P0口。P2口是准双向IO内部自带弱上拉但DHT11的DATA线仍然要外接一个4.7kΩ到10kΩ的上拉电阻。这个上拉跟单片机P2口的内置上拉不是一回事它服务于单总线协议DHT11释放总线的时候DATA线靠这个电阻保持在VCC电平缺了它主机读引脚会读到不定态。DHT11四脚封装上标“NC”的引脚必须悬空既不接VCC也不接GND更不要往其它网络上去连。在Proteus里NC误接到地可能不报错但放到真实的DHT11元器件上虽然NC内部没有连接也容易在画PCB时造成不必要的困惑。VCC接5VGND统一接系统地。2.3 三路测温的布线组织与网络标签分配在Proteus里放3个DHT11加上拉电阻后连线容易交叉。我一般会把三路传感器分成三个区块用网络标号代替物理连线。VCCAT89C51的40脚、DHT11三个VCC、排阻公共端GNDAT89C51的20脚、DHT11三个GNDDHT11_A第一路DATA接P2.0DHT11_B第二路DATA接P2.1DHT11_C第三路DATA接P2.2。放置网络标签时每一路的DATA网络名必须不同否则Proteus会认为它们连在一起仿真时三路数据互相干扰读出来全是同一个值。连线完成后可以跑一遍Proteus的电气规则检查但DHT11的NC脚在某些版本里会被报未连接警告这个可以忽略。2.4 双击单片机加载HEX文件时钟频率一定要同步Proteus不是写代码的地方仿真代码一般用Keil C51写完后编译出HEX文件然后在原理图里双击AT89C51模型在Program File属性中选择这个HEX文件。这里有一个容易忽视的细节Clock Frequency属性需要手动设置为12MHz默认值可能是1MHz或跟元件库走。代码里的延时全部假设在12MHz、12T模式下1个机器周期为1us。如果这个频率不改成12MHz后面DHT11时序全部无法对上。时钟频率本身可以随便调前提是你在代码里同步修改延时参数。更常见的做法是保持12MHz不变这样_nop_()延时最简单。设置完HEX路径和频率后点击Proteus左下角运行按钮仿真器就会开始执行固件。3. DHT11时序驱动源码STC89C51读取40位数据与校验换算3.1 DHT11的三个时间窗口起始、应答、数据位DHT11的通信过程分为三段。首先由主机把DATA线拉低至少18ms然后释放总线等待2040us这是起始信号用来唤醒DHT11。DHT11检测到起始信号后先把总线拉低80us再把总线拉高80us这是一组应答信号表示传感器已经准备好发送数据。应答之后DHT11连续输出40位数据每一位都以50us的低电平开头随后拉高。高电平持续的时间决定了这一位是0还是1数据0的高电平约2628us数据1的高电平约70us。读位时不需要去量高电平的绝对宽度常用策略是在低电平结束之后延时40us再读引脚此时逻辑0已经结束高电平翻回低电平而逻辑1仍然保持高电平。注意DHT11的采样周期至少1秒两次连续读取间隔太短时传感器可能不应答。多点轮询时每路之间的间隔只有几百毫秒但从某一路的视角看它自己的两次读取仍然相隔数秒所以不存在违反最小采样周期的问题。3.2 STC89C51的IO声明与软件延时配置在Keil C51工程里头文件用reg51.h这是Intel 8051和STC89C51通用寄存器定义。把三个测温点的IO口声明为sbit同时定义DHT11数据帧的结构体。C51里sbit不能直接作为函数参数传递所以多路驱动不能用“一个函数、传引脚进去”的写法需要针对每个引脚单独生成读取函数。下面是一段可直接编译的基础代码#include reg51.h #include intrins.h sbit DHT11_POINT_A P2^0; sbit DHT11_POINT_B P2^1; sbit DHT11_POINT_C P2^2; typedef struct { unsigned char humi_h; // 湿度整数部分 unsigned char humi_l; // 湿度小数部分 unsigned char temp_h; // 温度整数部分 unsigned char temp_l; // 温度小数部分 unsigned char crc; // 校验和 } DHT11_Data; void delay_us(unsigned char us) { while (us--) { _nop_(); } } void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 125; j); }delay_us在12MHz、12T模式下一条_nop_()就是一个机器周期1us循环体本身还有几条指令开销所以它更适合30us、40us这种不需要绝对精确的延时。delay_ms里的内层循环125次是根据Keil C51默认优化级别估算出来的不同优化级别下实际毫秒数会有偏差但DHT11只需要保证起始信号“不低于18ms”这个下限量级不出错即可。如果你开-O2优化建议把delay_ms函数里内层循环次数调到180左右并用Proteus虚拟示波器校准一次。3.3 用宏展开生成三路读取函数绕开sbit形参限制标准C51里sbit不能出现在函数形参列表中。所以我一般用宏来生成三个结构相同、引脚不同的读取函数模板只写一份程序体积也不大。完整读取宏如下#define DEFINE_DHT11_READER(PIN, FUNC_NAME) \ unsigned char DHT11_Read_##FUNC_NAME(DHT11_Data *dht) \ { \ unsigned char i, j, buf[5] {0,0,0,0,0}; \ PIN 0; \ delay_ms(20); \ PIN 1; \ delay_us(30); \ if (PIN 0) { \ while (PIN 0); \ while (PIN 1); \ for (i 0; i 5; i) { \ for (j 0; j 8; j) { \ while (PIN 0); \ delay_us(40); \ buf[i] 1; \ if (PIN 1) { \ buf[i] | 1; \ while (PIN 1); \ } \ } \ } \ dht-humi_h buf[0]; \ dht-humi_l buf[1]; \ dht-temp_h buf[2]; \ dht-temp_l buf[3]; \ dht-crc buf[4]; \ if ((buf[0] buf[1] buf[2] buf[3]) buf[4]) \ return 1; \ } \ return 0; \ } DEFINE_DHT11_READER(DHT11_POINT_A, POINT_A) DEFINE_DHT11_READER(DHT11_POINT_B, POINT_B) DEFINE_DHT11_READER(DHT11_POINT_C, POINT_C)展开后得到三个函数DHT11_Read_POINT_A、DHT11_Read_POINT_B、DHT11_Read_POINT_C它们的逻辑完全一样只是操作的引脚不同。每个函数的流程是先拉低20ms发出起始信号再拉高并延时30us然后检测引脚是否为低。如果为低说明DHT11开始应答两个while分别消耗掉应答的低电平80us和高电平80us。进入数据位读取后while(PIN0)等待当前位的50us低电平起始结束延时40us采样。此时如果引脚仍为高说明这个位是逻辑1如果已经变低则这一位是逻辑0。读到1后还要while(PIN1)把剩余高电平消耗掉让下一位从干净的低电平起始状态开始。一旦40位读完5个字节暂存在buf[0]到buf[4]中。buf[4]是校验字节前四个字节之和等于它时才返回1否则整个数据帧判定无效并返回0。3.4 校验和与温湿度换算别把小数位当整数处理DHT11的5个字节依次是湿度整数、湿度小数、温度整数、温度小数、校验和。校验算法是前四个字节相加取结果的低8位。直接比较即可if ((buf[0] buf[1] buf[2] buf[3]) buf[4]) { // 数据有效 }温度换算时整数部分和小数部分要分开处理。假设读出来temp_h25、temp_l6则温度为25.6℃。湿度同理humi_h60、humi_l0表示60.0%RH。还有一位符号位藏在temp_h里bit7为1表示零下温度float temperature; float humidity; temperature buf[2] buf[3] * 0.1f; humidity buf[0] buf[1] * 0.1f; if (buf[2] 0x80) { temperature -temperature; }DHT11的温度精度只有一位小数分辨率是0.1℃。校验失败的帧直接丢弃显示出上一轮的正常值。千万不要在未通过校验的情况下把buf里的数据拿去显示那样会把噪声数据当温度上报。4. 多点测温软件框架轮询调度、超时重试与LCD1602显示4.1 把测温点抽象成传感器节点表加失败计数三个DHT11用三个独立的读取函数主循环如果写三个if感觉还行但如果要扩展到5路、6路代码会变得很长。更清晰的做法是定义传感器节点结构体把每路的数据缓冲、有效标志、失败计数放在一起typedef struct { unsigned char enabled; // 1表示启用该测温点 DHT11_Data value; // 最近一次有效数据 unsigned char valid; // 1表示当前数据可用 unsigned char fail_cnt; // 连续失败次数 } SensorNode; SensorNode nodes[3] { {1, {0, 0, 0, 0, 0}, 0, 0}, {1, {0, 0, 0, 0, 0}, 0, 0}, {1, {0, 0, 0, 0, 0}, 0, 0} };enabled字段用来关闭不需要的测温点比如系统只部署两路传感器时把第三路enabled改成0轮询时跳过。valid表示缓冲区内数据是否经过校验fail_cnt记录连续失败次数这两个字段合起来决定了显示层要不要显示--。三个读取函数的签名相同可以用函数指针数组把路由统一起来unsigned char (*const dht11_reader[3])(DHT11_Data *) { DHT11_Read_POINT_A, DHT11_Read_POINT_B, DHT11_Read_POINT_C };C51对函数指针的支持没有问题代价是每次调用多了几条间接寻址指令对秒级轮询周期来说完全无所谓。4.2 轮询循环与“连续失败5次”才判脱机有了函数指针数组轮询代码就变得非常紧凑。每一路传感器独立读取独立计数互不干扰void poll_sensors(void) { unsigned char i; for (i 0; i 3; i) { if (!nodes[i].enabled) continue; if (dht11_reader[i](nodes[i].value) 1) { nodes[i].valid 1; nodes[i].fail_cnt 0; } else { nodes[i].fail_cnt; if (nodes[i].fail_cnt 5) { nodes[i].valid 0; } } } }读取成功时更新缓冲区并把fail_cnt清零读取失败时不覆盖旧数据只累加失败计数。连续失败5次才把valid置0显示层据此切换成--。之所以不采用一次失败立刻置无效是因为DHT11偶尔会因为供电波动或者线缆干扰返回一次校验错误这种偶发问题不应该让界面闪烁。poll_sensors每2秒调用一次。连续5次失败对应约10秒无有效数据更新这个时间对告警来说不算迟钝对误报来说又有足够的缓冲。首次上电时valid初始为0即使第一轮就读取成功valid也会变成1不会有未初始化数据被显示出去的问题。注意读取宏内部的while(PIN0)和while(PIN1)没有超时保护。在Proteus里如果传感器被误删或者引脚网络悬空这两个循环会永久卡住。排错时先确认三个DHT11都在原理图上、且DATA网络标签没有重复。4.3 LCD1602两行显示三路温度的操作Proteus中的LCD1602用LM016L代替。数据口接P0RS、RW、E分别接P2.7、P2.6、P2.5。显示层只需要提供两个基础函数一个设置光标位置一个显示温度值。先把1602的底层时序驱动写好后按下面方式组织显示逻辑#define LCD1602_RS P2_7 #define LCD1602_RW P2_6 #define LCD1602_EN P2_5 void LCD1602_SetCursor(unsigned char x, unsigned char y) { unsigned char addr; if (y 0) addr 0x80 x; else addr 0xC0 x; LCD1602_WriteCmd(addr); } void LCD1602_ShowTemp(unsigned char point_no, unsigned char x, unsigned char y) { int temp_int, temp_dec; if (!nodes[point_no].valid) { LCD1602_ShowString(x, y, --.-); return; } temp_int nodes[point_no].value.temp_h 0x7F; temp_dec nodes[point_no].value.temp_l; LCD1602_ShowChar(x, y, 1 point_no); LCD1602_ShowChar(x 1, y, :); LCD1602_ShowNum(x 2, y, temp_int); LCD1602_ShowChar(x 4, y, .); LCD1602_ShowChar(x 5, y, 0 temp_dec); LCD1602_ShowChar(x 6, y, C); }LCD1602_ShowTemp(0, 0, 0)会在第一行第0列显示“1:25.6C”。一行16列可以放下两路第二行再显示第三路温度加第一路湿度。需要注意小数位temp_dec的范围是09所以可以直接用0 temp_dec得到对应字符如果DHT11传感器厂商在某些型号里把小数位补成两位则要做一次除以10和取余否则字符会错乱。取温度整数部分时对temp_h做了 0x7F这是为了把符号位mask掉避免负温度显示成大于100的数值。如果项目不涉及零下温度这个mask可以不加但保留它对数据兼容性更好。5. 用Proteus虚拟示波器验证DHT11时序与常见跑飞定位5.1 抓取DATA脚上的“起始-应答-40位数据”三段波形Proteus运行仿真后在虚拟仪器模式下打开Digital Oscilloscope把第一路DHT11的DATA脚信号接入示波器A通道。触发方式选Single或Auto电平触发设置为1.5V左右然后重新运行仿真。示波器画面上应该出现三段清晰的波形。第一段是约20ms的低电平这是主机发出的起始信号。如果这一段异常短说明delay_ms(20)被Keil优化掉了需要把延时函数里的循环变量声明为volatile。第二段是80us低电平和80us高电平这是DHT11的应答。第三段是40个独立的脉冲组每个脉冲组以50us低电平开头后面的高电平宽度决定逻辑值。用示波器的Delta光标量几个脉冲70us左右的是逻辑126us左右的是逻辑0。5.2 两类异常现象的处理顺序第一种异常是LCD全程显示--故障现象是DHT11无应答。先用示波器看波形如果没有起始脉冲就去查代码如果有起始脉冲但后面没有应答信号大概率是DATA线上拉电阻缺失或者Proteus网络标签把某一路的DATA接错到了其它网络。第二种异常是读出的数据恒为0xFF不属于校验错误而是读位采样点太晚。把delay_us(40)改成delay_us(30)让采样点落在数据1高电平的中间区域。调整后重新仿真观察数据位波形是否稳定。提示Proteus里的DHT11是理想模型不会体现真实传感器的上升沿缓变和驱动能力差异。仿真跑通后如果上真机建议把上拉电阻固定为4.7kΩ并在DHT11的VCC和GND之间就近加一颗100nF陶瓷电容否则长线传输时1位数据容易失真。另外如果你需要频繁调整采样延时最好把40us这个值单独定义成宏#define DHT11_SAMPLE_DELAY_US 40在读取宏里把delay_us(40)替换为delay_us(DHT11_SAMPLE_DELAY_US)这样上真机调试时只需要改一个宏定义不用满工程找数字。调整方向是数据1被误判为0时减小这个值数据0被误判为1时增大这个值。每次只改5us改完用示波器对照波形确认。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价