ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

51单片机DHT11温湿度报警系统:Proteus仿真与实现

51单片机DHT11温湿度报警系统:Proteus仿真与实现 简介面向51单片机与Proteus仿真学习者的温湿度报警控制系统完整资料包以DHT11为温湿度传感器四位数码管切换显示温度与湿度支持上下限设定、超限声光报警及按键关闭报警。压缩包共41个文件约1.16MB包含Proteus仿真工程、Keil C源码main.c与DHT11.c/h、原理图SchDoc、流程图BMP、元件清单Excel及功能介绍文档覆盖硬件设计到软件调试的完整链路。目前已有267人浏览学习。仿真工程可直接运行直观复现温湿度实时显示、阈值设置与报警联动代码与原理图模块对应便于理解DHT11时序读取、数码管动态扫描、按键消抖等51单片机常用外设编程。整体结构清晰适合课程设计、毕业设计或电子竞赛备赛也可作为新手从基础到综合项目的练手资源。1. 51单片机用DHT11拼数码管报警系统最难的不是代码做温湿度报警控制这类课设或小项目51单片机、DHT11、数码管这三个词组合在一起几乎是固定搭配。硬件上就是一颗STC89C52或者AT89C51接一个DHT11传感器再把温湿度数值扔到数码管上显示超过阈值就驱动蜂鸣器或者继电器。听起来结构简单但真正动手做过的人都知道最折腾人的不是C语言逻辑而是DHT11那根单总线数据线的时序以及数码管动态扫描时亮度与闪烁之间的取舍。Proteus仿真在这个项目里地位很特殊——它既能帮你把数码管段码、位选逻辑提前调通又能让你在不碰烙铁的情况下验证DHT11的时序是否合法。这篇文章就按我平时做这个项目的顺序来讲先立住硬件选型和原理再写代码接着在Proteus里跑仿真最后落到物料清单和工程文件组织这些容易被忽略的细节上。适合看这篇文章的人有两类一类是正在做课程设计的学生需要快速把整个系统跑起来并讲清楚原理另一类是工作中偶尔要搭一个低成本环境监测节点的工程师想确认51方案在什么场景下仍然值得选。无论是哪类读完你至少能回答三个问题DHT11的数据手册时序怎么落到代码上、四位数码管怎么用两个锁存器或者三极管完成动态扫描、Proteus里仿真和实物之间到底差在哪。2. 系统拆分51单片机、DHT11和数码管的接口与选型2.1 为什么这个系统适合51而不是直接上STM32先回答一个很多人会问的问题都什么年代了为什么还用51单片机做温湿度报警原因很简单——这个系统的计算负载和IO需求极其有限。DHT11是一根单总线每秒最多读两次每次40个bit数码管是4位共阴或者共阳动态扫描只需占用8个段选加4个位选报警输出就是一路电平翻转。整个系统加起来不到15个GPIOCPU主频12MHz甚至6MHz都够用。STM32在这个场景下属于杀鸡用牛刀而且Proteus里51的仿真模型比STM32成熟得多教学和演示场景尤其合适。具体型号上最常见的选择是STC89C52RC或者AT89C51。两者都是8051内核区别在于STC支持ISP下载、内置RC时钟Proteus里一般用AT89C51或者AT89C52替代也完全兼容。如果你手里只有STC芯片在Proteus里选AT89C51一样能完成仿真管脚定义完全相同。2.2 DHT11的工作原理和通信协议要点DHT11是盛思锐实际上是非数字温湿度传感器的国产经典件生产的单总线数字温湿度传感器内部包含一个电阻式湿度元件和NTC测温元件通过一个8位MCU把温度和湿度数据打包成40位数据帧输出。数据帧结构是固定的8位湿度整数 8位湿度小数 8位温度整数 8位温度小数 8位校验和。通信是主机主动发起——主机拉低总线至少18ms然后释放并拉高DHT11检测到这个起始信号后响应一个80us的低电平接着拉高80us表示准备发送数据。之后就是高低电平交替的比特流——我一般直接看高电平的持续时间来判断0和1高电平约26us到28us是逻辑0约70us是逻辑1。这里要注意比如DHT11的数据手册上写的规范值Proteus仿真模型会严格按这个时序工作如果你在真实硬件上实现时忽略时序精度就会出现读数恒定或者CRC校验失败。测量范围和精度也要心里有数湿度5%到95%RH精度正负5%温度0到50摄氏度精度正负2摄氏度。这个精度做环境监测和报警足够了但别指望它做精确计量。如果你想做更高精度的方案DHT22会稍好一点但代码时序几乎一样。2.3 数码管的类型选择共阴还是共阳几位合适数码管的选型直接影响驱动电路设计。常见的接法是4位一体数码管共阴或共阳都有。在这个项目中我倾向于用共阴数码管原因后面代码部分会详细说——主要是Proteus中仿真和使用74HC573锁存器驱动时共阴的逻辑更直观。位数的选择取决于你要显示的内容。温湿度报警系统通常要显示两组数据温度和湿度每组至少两位有效数字加上小数点和特殊符号比如H和C的标识最少需要4位。如果你还想显示设定阈值或状态码那5位或6位更稳妥但代码复杂度会上升。我这里按4位一体数码管来展开实际项目中如果做成两个2位分体式数码管也没问题逻辑相同。数码管的驱动电流是另一个关键点。单个LED段的正常工作电流是5到10mA8个段全亮时单个位需要40到80mA如果直接用P0口灌电流驱动单片机引脚会过载。常见做法是段选端加限流电阻220Ω或330Ω位选端加三极管或直接用74HC573锁存器。Proteus仿真中如果忽略驱动电流显示效果看不出问题但做实物时必须按这个思路来。2.4 报警执行机构蜂鸣器、LED还是继电器报警输出从简单到复杂有几种选择方式纯蜂鸣器方案。一支有源蜂鸣器接一个NPN三极管如S8050单片机P2.0输出高电平驱动基极蜂鸣器导通发声。这是最简单也最常见的适合纯提示报警。蜂鸣器加LED方案。LED接在另一个IO口上亮起表示报警状态蜂鸣器响表示触发声音提醒。适合课设展示因为报警状态可视化更好。继电器方案。如果你的应用是控制外部设备比如风扇或加热器就需要用继电器隔离控制交流设备。继电器选5V线圈的用ULN2003驱动是比较稳妥的选择因为ULN2003内部自带续流二极管能吸收继电器断开时的反向电动势。我这里按最简单的蜂鸣器报警来写同时在Proteus里加一个LED作为报警指示灯。如果你要做实物加一个继电器也不影响代码结构。3. 程序框架与核心代码DHT11时序、数码管扫描和报警逻辑3.1 程序的整体工作流程这个系统的软件逻辑非常简单主循环里读传感器、刷新显示、比较阈值、决定是否报警。正常状态下温湿度不超过设定范围时数码管持续显示当前值超出范围后蜂鸣器响、报警LED亮数码管依旧显示实时值。需求指标列表每2秒刷新一次温湿度数据DHT11的最快采样周期是2s实际建议3s到5s数码管0.5ms刷新一次显示保证无闪烁温度阈值和湿度阈值在代码中宏定义方便修改蜂鸣器报警时用延时产生断续音而不是一直响整个程序由三大部分构成DHT11底层驱动、数码管显示驱动、主循环报警控制逻辑。下面分别展开。3.2 DHT11底层驱动C语言实现时序的关键写法3.2.1 引脚定义和初始化先定义连接引脚。DHT11的DATA引脚接P2.1数据线是开漏输出真实硬件上必须接4.7k到10k上拉电阻Proteus仿真模型内部自带弱上拉但为了和实物一致建议在仿真图里也加上。#include reg51.h sbit DHT11_DQ P2^1; // DHT11数据线 void delay_us(unsigned int us) { while (us--) { _nop_(); // 12MHz下约1us } }逻辑说明定义sbit是为了按位寻址方便对单个引脚操作。_nop_()在intrins.h头文件中定义执行时间约为一个机器周期。12MHz晶振下一个机器周期是1us所以delay_us函数近似可以做到微秒级延时。这个延时函数只是个粗略实现精度不高但DHT11的时序容差允许对时序要求有一定自由度关键在于拉低起始信号18ms要足够长这是用延时函数不是用定时器做的最重要原因。3.2.2 起始信号和总线释放接下来是主机发送起始信号然后释放总线读取从机响应。unsigned char DHT11_Start(void) { unsigned char timeout 0; DHT11_DQ 0; // 主机拉低总线 delay_us(20000); // 至少18ms20ms更保险 DHT11_DQ 1; // 释放总线 delay_us(40); // 等待从机响应 if (!DHT11_DQ) { // 从机拉低响应 timeout 0; while (!DHT11_DQ timeout 100) { // 等待80us低电平结束 timeout; delay_us(1); } if (DHT11_DQ) { while (DHT11_DQ timeout 100) { // 等待80us高电平结束 timeout; delay_us(1); } return 1; // 起始成功 } } return 0; // 无响应 }逻辑说明20ms的拉低时间远大于规格书要求的18ms多出来2ms是为了兼容STC单片机IO速度的差异实测有效。从机响应是一个80us低电平80us高电平——响应信号和准备发送数据的间隔两个while循环分别等待这两个阶段结束超时保护防止死循环。这里最关键的一点起始信号之后DHT11会拉低总线80us作为响应然后拉高80us之后才开始传输数据位。这个响应信号容易被忽略导致后续读数据错位。3.2.3 读取一个字节电平宽度判0和1读取数据位是DHT11驱动的核心也是最容易踩坑的地方。按照数据手册描述每个数据位都是由一个低电平时隙50us加一个高电平时隙组成——高电平时隙较短的是0较长的是1。判断方法很简单测高电平持续时间。unsigned char DHT11_ReadByte(void) { unsigned char i, data_byte 0; unsigned int timeout; for (i 0; i 8; i) { timeout 0; // 等待低电平结束 while (!DHT11_DQ timeout 100) { timeout; delay_us(1); } if (timeout 100) return 0; // 超时总线卡死 delay_us(40); // 关键延时40us后再读电平 if (DHT11_DQ) { data_byte | (0x80 i); // 高电平持续超40us则为1 timeout 0; while (DHT11_DQ timeout 100) { // 等待高电平结束 timeout; delay_us(1); } } } return data_byte; }逻辑说明这个函数的关键点在第10行低电平结束后等待40us再读引脚。因为高电平持续时间小于40us的是0大于40us的是1所以在40us这个时间点采样就能区分0和1。位顺序是MSB先行所以(0x80 i)是从最高位开始填数据。每次读完一个bit后要等待高电平结束否则下一个bit的低电平时隙会被误判。这个判别方法是Proteus仿真和实物都能通过的写法。还有另一种写法是测量高电平宽度然后和40us比较但那样需要更精确的计时方式代码也复杂一些。3.2.4 完整读取40位数据并校验有了上面的基础完整读取函数就是把湿度整数、湿度小数、温度整数、温度小数、校验和依次读出来再做一次校验。unsigned char DHT11_ReadData(unsigned char *hum_int, unsigned char *hum_dec, unsigned char *temp_int, unsigned char *temp_dec) { unsigned char hum_i, hum_d, temp_i, temp_d, checksum; if (!DHT11_Start()) return 0; // 起始失败直接返回 hum_i DHT11_ReadByte(); hum_d DHT11_ReadByte(); temp_i DHT11_ReadByte(); temp_d DHT11_ReadByte(); checksum DHT11_ReadByte(); if ((hum_i hum_d temp_i temp_d) ! checksum) { return 0; // 校验失败 } *hum_int hum_i; *hum_dec hum_d; *temp_int temp_i; *temp_dec temp_d; return 1; }参数说明hum_int和hum_dec湿度整数部分和小数部分DHT11小数部分通常为0只有DHT22才有实际小数。temp_int和temp_dec温度整数和小数部分。返回值1表示成功、0表示失败。调用方主循环直接根据返回值决定要不要刷新显示。校验和算法是四个字节相加后取低8位如果等于校验字就通过。这是DHT11协议里唯一的错误检测机制不能省。3.3 数码管显示驱动段码表、动态扫描和消隐3.3.1 段码表和共阴共阳的选择数码管显示的本质是把一个字节映射到8个LED段a到dp共阴数码管的段码是逻辑高电平点亮共阳则是低电平点亮。下面给出共阴数码管0到9的标准段码如果用的是共阳取反即可。字符共阴段码共阳段码字符共阴段码共阳段码00x3F0xC050x6D0x9210x060xF960x7D0x8220x5B0xA470x070xF830x4F0xB080x7F0x8040x660x9990x6F0x903.3.2 动态扫描原理动态扫描的原理是利用人眼视觉暂留——让4位数码管分时轮流点亮每位的点亮时间约1ms到2ms4位一轮总共4ms到8ms刷新频率在125Hz以上就不会有闪烁感。看似是轮流亮但因为切换够快人眼看就是同时亮的。Proteus仿真中有一个仿真速度的问题需要特别提醒Proteus的时序仿真不是实时运行的如果你的扫描切换过快或者过慢都会出现显示异常比如只有一位亮、亮度不均等。我一般把每一位的显示时间设成2ms一轮扫描8ms这个速度在Proteus里和实物上都能稳定工作。3.3.3 P0口加锁存器还是直接用三极管这是一个硬件选型问题。两种常见方案方案AP0口直驱段选P2口低4位接PNP三极管做位选。数码管通常共阴位选用P2口控制三极管的基极——这样低电平选中进行放大让对应位点亮。8个段选各串一个330Ω限流电阻。方案BP0口接74HC573锁存器锁存器输出驱动数码管段选位选通过另一个锁存器或三极管。两个锁存器一个控制段选一个控制位选这样在显示刷新期间锁存器的输出不会被新数据的输入干扰。课设和大部分仿真用方案A足够了。74HC573方案的好处是单片机IO口得以释放而且锁存器的输出驱动能力更强适合驱动大尺寸数码管1.2英寸以上。如果你做实物而且手头有573芯片方案B的电路也不复杂。3.3.4 显示函数段码查表、位选切换、延时和消隐下面这段是核心显示代码unsigned char code seg_code[] {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; sbit BELL P2^0; // 蜂鸣器控制 sbit DHT11_DQ P2^1; // DHT11数据线 // 共阴数码管位选定义 sbit DIG1 P2^2; sbit DIG2 P2^3; sbit DIG3 P2^4; sbit DIG4 P2^5; void Display_Process(unsigned char *disp_buf) { unsigned char i; unsigned char code bit_sel[4] {0x01, 0x02, 0x04, 0x08}; for (i 0; i 4; i) { // 先切断所有位选防止上一位残留 DIG1 DIG2 DIG3 DIG4 0; // 输出段码 P0 seg_code[disp_buf[i]]; // 如果该位需要点亮小数点则 P0 | 0x80 // 选中当前位 switch (i) { case 0: DIG1 1; break; case 1: DIG2 1; break; case 2: DIG3 1; break; case 3: DIG4 1; break; } delay_us(2000); // 每一位显示2ms } }逻辑说明seg_code[]是共阴数码管0到9的段码表code关键字表示存到程序存储器而不是RAM节约宝贵的128字节RAM空间。第一个关键操作是切断所有位选消隐。如果不做这一步上一个数字的残影会叠加到当前位上产生拖影。switch用来选中当前要显示的位。这里用的是共阴高电平选择位的接法对应P2口直接驱动位选。2ms的延时决定了亮度和刷新率。想要更亮就适当加长但超过4ms会出现闪烁这个要把握好。3.4 报警逻辑阈值比较和断续蜂鸣报警逻辑是整个系统里最简单但最容易考虑不周的部分。直接上完整主函数代码void main(void) { unsigned char hum_i, hum_d, temp_i, temp_d; unsigned char disp_buf[4]; unsigned char temp_threshold 30; // 温度报警阈值30度 unsigned char hum_threshold 80; // 湿度报警阈值80%RH bit alarm_state 0; unsigned int alarm_counter 0; while (1) { // 每2秒读取一次DHT11 if (DHT11_ReadData(hum_i, hum_d, temp_i, temp_d)) { // 换算成完整数值 if (temp_i temp_threshold || hum_i hum_threshold) { alarm_state 1; } else { alarm_state 0; } } // 更新显示缓冲区 // 显示格式温度两位 湿度两位例如“28 65” disp_buf[0] temp_i / 10; // 温度的十位 disp_buf[1] temp_i % 10; // 温度的个位 disp_buf[2] hum_i / 10; // 湿度的十位 disp_buf[3] hum_i % 10; // 湿度的个位 // 每秒扫描240次左右共4位数码管 Display_Process(disp_buf); // 报警处理报警时蜂鸣器鸣响0.1秒停0.1秒 if (alarm_state) { alarm_counter; if (alarm_counter % 10 5) { BELL 1; // 蜂鸣器响 } else { BELL 0; // 蜂鸣器停 } } else { BELL 0; alarm_counter 0; } } }参数说明temp_threshold和hum_threshold是报警阈值直接在宏定义里改就行。如果你想在运行时调节可以接两个按键通过中断或者扫描修改这两个变量后面第5章会提这个思路。报警判断条件是大于阈值没有加迟滞。实际应用中温湿度在阈值附近反复横跳会导致蜂鸣器频繁启停改进方案是加一个迟滞区间——比如温度超过30度报警低于28度才解除。这个在代码里就是两个不同的比较条件。报警蜂鸣器采用的是时间片轮转思想alarm_counter每经过一个主循环加1当值在0到4之间时蜂鸣器响5到9之间时停实现0.1秒响、0.1秒停的断续音效果这样比一直响更容易引起注意。3.5 显示缓冲区如何映射到实际温湿度这里有一个很多人容易搞混的点DHT11返回的数据是什么样的格式直接看代码里的数据流处理。处置方式如下如果DHT11返回温度整数为28湿度的整数为65那么disp_buf[0]2, disp_buf[1]8, disp_buf[2]6, disp_buf[3]5数码管就会显示“28 65”。如果你想要在数码管上同时显示“C”和“H”这种单位标识可以在温度显示完后切到对应的带小数点或者特殊字符的段码。但4位数码管通常是这样显示的第2位显示完温度个位后点亮该位的小数点表示分隔。这个操作在Display_Process里可以通过给P0的段码按位或一个0x80来实现。小数部分hum_dec和temp_dec在DHT11中基本都是0所以显示时直接丢弃了。4. Proteus仿真搭建从原理图到流程图和物料清单4.1 Proteus里搭建最小系统的步骤Proteus仿真在这个项目中比实物调试更有优势——DHT11模型可以直接在元件库里找到它的时序反应和真实芯片几乎一致而且你可以随时暂停仿真观察引脚电平这在实物上用示波器才能做到。新建工程后的步骤如下选择AT89C51芯片。不需要外部时钟电路Proteus默认模拟12MHz晶振。放置DHT11元件在元件库中搜索“DHT11”或“DHT11 Humidity-Temperature Sensor”直接拖入画布。放置一个4位一体共阴数码管元件名通常是“7SEG-MPX4-CC”或“7SEG-MPX4-CA”。注意CC是共阴CA是共阳。放置一个蜂鸣器元件和一个LED蜂鸣器不接三极管也可以响Proteus模型不关心驱动电流。放置电阻排用于段选限流简化画法可以放一个RP1排阻阻值设为220Ω。连接方式完全对应代码中的引脚定义P0口 → 段选通过220Ω排阻P2.2到P2.5 → 数码管位选直接连接P2.1 → DHT11的DATA引脚P2.0 → 蜂鸣器正极负极接地P0口其他引脚不需要外接上拉数码管段选本身就有电流路径如果你之前用的是共阳数码管需要做两个调整一是段码表按列取反二是位选控制逻辑要反过来——共阳数码管的公共端是接高电平的所以位选要用低电平选中。代码中的DIGx 1变成DIGx 0。4.2 仿真时常见的时序和速度问题Proteus仿真单片机和实物有一个本质区别Proteus的指令执行是软件模拟的它的推进速度和实际晶振频率基本一致但和编译器的优化、宿主机性能都有关。以下是我在仿真中用得最顺手的经验值参数实物推荐值Proteus推荐值DHT11起始信号拉低时间18ms-20ms18ms-20ms保持一致数据位判定延时40us40us数码管每位显示时间1ms-2ms2ms-5ms主循环周期无要求尽量加一点延时特别要强调的是DHT11的起始信号如果用了delay_ms(20)这种函数要确认编译器的优化不要把这个20ms的延时代码优化掉。我见过有人把起始信号延时写成delay_us(20000)结果被Keil优化成了空操作导致DHT11的时序完全错误——这是因为优化器认为这个循环没有副作用直接丢弃了。解决办法是在delay函数内部加volatile修饰变量或者在循环体内加一个_nop_()让它无法被完全优化。4.3 流程图的作用不仅是文档更是debug工具对这个项目来说流程图至少要画到子程序级。我画流程图的习惯是这样的主程序流程图只画到关键判断DHT11读取子程序单独画一个详细流程包括超时处理和校验分支因为这是最容易出bug的地方。数码管扫描流程可以只画函数内部逻辑不用画主循环上下文。画流程图的方式可以是Visio、draw.io甚至是纸上手绘再拍照贴文档。关键在于流程图的逻辑要和代码一致。很多人流程图里画的是“温度大于阈值就报警”但代码里实际写的是“温度大于阈值且湿度不大于阈值才算报警”这就出现了文档和代码不一致的问题。建议在流程图每个判断框旁边标注代码中对应的行号或变量名这样评审或答辩时一目了然。4.4 物料清单的编写思路物料清单看起来简单但实际写出来很多人会漏项。下面给一份这个项目的标准清单模板序号物料名称规格/型号数量备注1单片机STC89C52RCDIP401可用AT89C51替代2晶振12MHz13电容22pF2晶振匹配电容4电容10uF/16V1复位电路5电阻10kΩ1复位电路6电阻4.7kΩ1DHT11上拉7排阻220Ω x 81数码管段选限流8数码管4位共阴0.36英寸19温湿度传感器DHT11110三极管S80502位选驱动和蜂鸣器驱动11蜂鸣器有源5V112LED红色2电源指示和报警指示13按键轻触型2可选用于调阈值14电源USB转TTL5V或DC插座1这个清单是可以直接拿去采购的。注意有源蜂鸣器和无源蜂鸣器的区别——无源蜂鸣器需要单片机输出一定频率的方波才会响有源蜂鸣器只要通直流电就会响。如果你用的是无源蜂鸣器代码里的BELL 1就要改成BELL输出一个约2kHz的方波信号。4.5 从Proteus到实物的差异点Proteus仿真通过之后烧录到实物板上仍然可能出问题这几点我在实际项目中反复踩过第一个差异是驱动能力。Proteus的数码管模型不考虑功耗和最大电流P0口直接驱动的效果在仿真里看起来很完美但实物的P0口是开漏输出你必须外接上拉电阻才能正常工作。这一点初学者特别容易漏。第二个差异是DHT11的响应时序。Proteus里的DHT11模型是理想化的起始信号时序要求相对宽松。实物上如果延时偏短DHT11可能不响应。因此代码里的起始信号拉低时间在实物上我建议至少20ms。第三个差异是蜂鸣器驱动。Proteus仿真中一个IO口直接驱动蜂鸣器就能响实物上用IO口直接接蜂鸣器电流可能不足以驱动或者造成单片机口损坏必须用三极管放大电流。5. 进阶技巧数据稳定性、按键调阈值和Proteus联合调试5.1 连续多次读取取平均用多数表决滤掉毛刺DHT11传感器偶尔会出现偶发的读错误或者尖峰毛刺——尤其当供电电压不稳或者数据线过长时比较常见。这个问题的根本原因是单总线协议没有重传机制读错一个字节就导致整帧校验失败或者读到异常值。我的处理方式是连续读三次取多数值或平均值。如下是配合现有代码的过滤方案连续调用三次DHT11_ReadData记录三次的温度和湿度值。如果三次结果完全一致直接用如果有两次一致取多数的值如果三次都不同检查校验成功与否再决定如果三帧全部校验失败就沿用上一次的值不加新数据。这个方案的优点是只要代码里DHT11_ReadData返回值非1主循环就不刷新显示保留旧值这样数码管上就不会出现跳动或者闪烁的0或255这种错误值。5.2 加两个按键做阈值调节中断扫描还是轮询扫描有些课设要求阈值可以现场调整这时候至少要两个按键一个切换调节对象温度阈值/湿度阈值一个增加数值。如果再加一个按键做减少就更完整。按键扫描放在主循环里的做法是时间轮询这个方案有一个隐患——主循环里数码管扫描会占用较长时间如果按键按下时间太短比如小于主循环周期可能漏掉按键事件。解决方案有两个一是把按键扫描放到定时器中断里。10ms定时器中断在中断里调用按键扫描函数判断按下和释放状态更新阈值变量。这样做键盘响应准确不受主循环阻塞影响。二是用外部中断把按键接在INT0和INT1上。下降沿触发在中断服务函数里直接增减阈值。这是资源占用最少的方式但如果按键没接硬件去抖电路需要在中断里加软件消抖延时。无论用哪种方式阈值变量都要是全局变量并且注意在Proteus中仿真中断的时序和实物上略有差异中断服务函数里不要放数码管扫描这类耗时过长的操作。5.3 用Proteus的数字示波器验证DHT11时序这个技巧对排错很有用。Proteus虚拟示波器可以挂在DHT11的数据线上直接观察通信时序波形。操作方式在Proteus里放置一个Virtual Terminal或者Digital Oscilloscope工具把DHT11的数据线连接到示波器的A通道运行仿真后就能看到数据传输时的波形。判断标准的波形特征起始信号是一段约20ms的低电平然后拉高。响应信号是80us低电平80us高电平。数据位波形是一组高低电平交替0的高电平短约27us1的高电平长约70us。你用虚拟示波器观察这个波形配合代码逻辑会清楚地看到问题出在主机侧还是从机侧。即使没有示波器也可以通过Proteus运行时的引脚电平颜色来做粗判断引脚上有高电平会显示红色方块低电平是蓝色方块中间过程中的电平变化通过慢速仿真可以看到跳动。5.4 温湿度传感器的替代方案什么时候该换DHT22如果这个项目要升级为一个真正长期运行的节点DHT11的精度和稳定性会成为瓶颈。DHT22AM2302是直接替换方案中的首选它的湿度精度正负2%温度精度正负0.5摄氏度比DHT11整整高一个档次。而且时序协议几乎完全一样唯一区别是数据帧中的小数部分有实际意义需要两个字节才能完整表示。代码上要做三个调整一是hum_dec和temp_dec不再是固定0要参与显示二是读取到的16位数据要先判断最高位是否为1——为1表示负温度实际值要取补码再除以10三是DHT22的起始信号要求至少1ms的拉低时间而不是DHT11的18ms但这两种方案都统一用20ms也没问题。如果你要用I2C接口的高精度传感器比如SHT30或SI7021那就需要改动较大不仅通信协议不同代码结构也要从单总线驱动改成I2C驱动这个改动在51上做成本偏高更适合直接切到STM32平台。最后提醒一个代码习惯调试阶段可以在Keil里用软件仿真单步运行到DHT11的读取函数跑到40us延时那里仔细观察变量变化——这个位置的时序正确性是整个系统能够正确显示和报警的前提。本文还有配套的精品资源点击获取
返回列表