简介一套基于51单片机的温湿度采集监控完整工程面向物联网环境监测、农业大棚、仓储管理等场景也适合电子类专业学生与嵌入式开发者学习参考。方案以SHT10温湿度传感器和DS1302实时时钟为核心实现温湿度采集、日历计时、12864液晶屏显示以及超限报警还支持手动调时覆盖从底层驱动到上层交互的完整开发链路。压缩包共28个文件以C源程序、头文件和Keil工程文件为主另含列表文件、目标文件及可直接烧录的hex文件整体仅84KB目录分层清晰便于按模块查阅与移植。目前已有353人学习下载。借助该工程可掌握传感器时序读取、RTC芯片读写、液晶屏取模显示和阈值报警处理等关键思路能够直接套用或二次修改快速搭建属于自己的环境监控系统。1. 51单片机做温湿度采集监控百元成本也能做出工业级数据链路做仓库环境监测时某开发者被市售温湿度记录仪几千块的价格劝退随手用库存的STC89C52RC和DHT11搭了一套采集监控板成本不到二十块却能实现秒级刷新、超限报警、串口上报给上位机。51单片机做温湿度采集监控本质上是把传感器时序解析、显示刷新、阈值判断和通信协议四件事压进一颗8位MCU里不涉及操作系统全靠裸机编程的定时和状态机。这套方案特别适合设备改造、实验室监控、种植养殖棚这类对成本敏感、数据量不大但稳定性有要求的场景。只要掌握单总线时序、校验和、去抖和低功耗调度新手也能在三天内跑通一版可靠的小系统。2. 硬件选型与核心电路设计DHT11、DS18B20还是SHT302.1 三类温湿度传感器的精度、时序和成本对比温湿度采集监控的第一步不是写代码而是把传感器选对。市面上最常见的三种方案是DHT11、DS18B20和SHT30它们之间不是简单的价格差异而是测量原理和接口时序的差异。传感器温度精度湿度精度接口类型典型价格适合场景DHT11±2℃±5%RH单总线2元左右低成本入门、环境干燥度粗测DS18B20±0.5℃无湿度单总线3元左右只测温度、需多点测温SHT30±0.3℃±2%RHI2C8元左右精度要求高、长时间漂移小的场景从时序上看DHT11和DS18B20都是单总线占用IO少但时序要求苛刻SHT30走I2C读数据要简单得多但需要自己实现或移植I2C主机驱动。我一般建议新手直接选DHT11起步因为它模块化产品最多引脚间距大面包板好插出问题的点相对少。等项目做到需要校验数据、对比标准仪器时再换SHT30代码结构不用大改因为采集层和业务层已经分开了。2.2 最小系统接线与上拉电阻的三种接法51单片机最小系统本身不复杂但接线细节决定稳定性。晶振选用11.0592MHz是为了串口波特率能整除不产生误差复位电路用10uF电容加10k电阻上电自动复位EA引脚必须接VCC否则MCU会从外部ROM取指令导致完全跑飞。温湿度传感器的数据引脚最好接在P3.7或P2.0这类方便调试的口上。DHT11的数据线需要上拉常见做法有三种模块板上自带贴片上拉电阻直接接线即可裸传感器则需要外接一个4.7k到10k的上拉电阻到VCC还有一种做法是配置单片机引脚为开漏输出靠外部上拉维持高电平。实际测试中5V供电的DHT11模块用10k上拉3.3V供电建议用4.7k原因是上拉电阻越大边沿越缓抗干扰能力会下降。// DHT11数据引脚定义STC89C52RC使用P3.7 sbit DHT11_DQ P3^7;这段代码只是定义数据引脚。注意在51单片机里P3口是准双向口读取外部信号前要先写1否则读到的一直是锁存器里的0。这个细节是新手最多踩的坑后面第5章会详细说。2.3 电源去耦与传感器供电的注意事项温湿度采集监控最容易忽略的是电源质量。传感器的湿度测量本质上是检测电容变化电源纹波大、地线上有毛刺湿度值就会跟着跳。我习惯在MCU电源引脚附近加一个100uF电解电容和一个0.1uF陶瓷电容并联电解电容管低频陶瓷电容管高频。如果传感器线和电机驱动线靠得很近还要在线束上套磁环或用屏蔽线。传感器供电不要直接从MCU的VCC引脚飞线取最好从电源入口单独引一条线中间串一个22欧姆电阻再并一个10uF电容类似一个简单的RC滤波。某开发者在做孵化箱监控时继电器一吸合湿度显示从60%跳到90%排查半天发现是DHT11和继电器共用了一根5V线继电器线圈的感应电动势直接耦合进了传感器。把供电分开后数据立刻稳定。另外51单片机IO口驱动能力有限不要指望用IO口给传感器供电除非你明确算过电流。3. 驱动代码与数据解析从时序到字节校验3.1 单总线时序的延时实现用NOP还是定时器DHT11的数据通信依赖严格的单总线时序读一位的时间窗口是50到70微秒。51单片机跑在11.0592MHz下一个机器周期约1.085微秒用NOP填充延时可以做到微秒级控制但可读性差。更推荐的方式是用软件延时函数循环里去调整NOP数量直至示波器测到的波形满足时序要求。// 微秒级延时11.0592MHz下参数us最大约125 void delay_us(unsigned char us) { while (us--) { _nop_(); _nop_(); _nop_(); } }这个函数的逻辑是用循环加空操作消耗时间。注意参数us是unsigned char最大255实际会因为函数调用自身消耗几个周期所以要实测校准。我一般会拿逻辑分析仪抓DHT11起始信号后的第一个高电平脉宽调整NOP数量把脉宽调到26到28微秒因为DHT11的0或1正是靠高电平持续时间区分的。3.2 DHT11数据解析与校验和的边界检查DHT11一次通信返回40位数据湿度整数、湿度小数、温度整数、温度小数、校验和。校验和是前四个字节的和低8位。解析流程是先拉低起始信号至少18ms然后释放总线读取响应和40位原始数据最后把每个字节按位拼出来。unsigned char dht11_data[5]; unsigned char read_byte(void) { unsigned char i, value 0; for (i 0; i 8; i) { while (!DHT11_DQ); // 等待低电平结束 delay_us(40); // 40us后采样落在一个bit中间 if (DHT11_DQ) { value | (0x80 i); // 高电平持续较长判为1 } while (DHT11_DQ); // 等待高电平结束 } return value; }这段代码的关键在delay_us(40)后采样。DHT11的0信号是26到28微秒的高电平1信号是70微秒高电平40微秒处采样能正确区分两者。读取完40位后必须做校验和判断否则一个翻转位可能让温度显示成-40℃。校验通过后还要检查数据范围比如湿度大于100%、温度大于80℃就丢弃本次数据保守的做法是连续三次失败后才报传感器故障。3.3 把采集频率和滤波写进主循环DHT11的响应时间是毫秒级的但每次读完数据后传感器需要至少1秒的间隔才能保证内部电容放电完成。所以采集频率不要超过1Hz否则读到的温度会越来越偏高因为连续读取会让传感器发热。主循环里我习惯用软件定时器累加作为节拍每50ms提供一个时基20个时基执行一次采集。采集得到的数据不直接显示而是先做滑动平均滤波#define FILTER_LEN 5 unsigned char temp_buf[FILTER_LEN]; unsigned char temp_index 0; unsigned int temp_sum 0; unsigned char filter_add(unsigned char new_value) { temp_sum - temp_buf[temp_index]; temp_sum new_value; temp_buf[temp_index] new_value; temp_index (temp_index 1) % FILTER_LEN; return (unsigned char)(temp_sum / FILTER_LEN); }滑动平均的目的是把偶发的干扰尖峰抹平。注意长度选择5而不是8是因为DHT11本身数据刷新就慢滤波窗口太大反而会让数值变化迟钝用户会以为系统卡住了。这个滤波器只处理整数和小数分离后的整数部分小数部分直接丢弃或保留原值。4. 显示、报警与上位机联动从数码管到串口协议4.1 数码管与LCD1602刷新方式避免闪烁与碎码温湿度采集监控必须有人机界面最简单的是4位数码管动态显示复杂一点用LCD1602两行显示。数码管动态扫描的坑是刷新频率不够时肉眼可见闪烁我一般让每位点亮时间在3ms左右扫描周期12ms以内人眼就感知不到。关键是不能在定时器中断里做长时间延时否则中断被堵死主循环里的采集和串口都会卡。void display_loop(void) { unsigned char disp[4]; disp[0] temp / 10; disp[1] temp % 10; disp[2] humi / 10; disp[3] humi % 10; // 依次位选低位在前 for (i 0; i 4; i) { P2 segment_code[disp[i]]; // 查表得到段码 P1 (unsigned char)(1 i); // 位选 delay_us(300); } }注意扫描代码不能放在主循环死跑否则采集函数的delay_us会阻塞显示导致数码管出现残影。更好的做法是把显示刷新放在定时器中断里中断每2ms切换一位主循环只负责改缓冲区。LCD1602则不需要动态扫描但写指令时要注意忙标志检测否则在慢速单片机下容易丢数据。4.2 阈值报警与按键去抖的参数设计监控系统离不开发报警报警阈值不能硬编码在显示逻辑里。我用三个按键调整上限和下限设置键进入参数模式加减键修改当前选中项长按设置键退出。按键扫描必须去抖常见手法是10ms延时判断两次更可靠的是用定时器每5ms扫描一次连续两次状态一致才算按下。sbit KEY_SET P3^2; sbit KEY_UP P3^3; sbit KEY_DOWN P3^4; unsigned char key_scan(void) { static unsigned char last_state 0; unsigned char now_state; now_state (KEY_SET 2) | (KEY_UP 1) | KEY_DOWN; if (now_state ! last_state) { delay_us(10000); // 10ms去抖 now_state (KEY_SET 2) | (KEY_UP 1) | KEY_DOWN; last_state now_state; return now_state; } return 0; }这个函数的返回值是按键状态0表示无变化。注意去抖延时期间不能读取DHT11否则单总线时序会乱掉。报警输出我建议加滞回比如上限设为30℃那么报警触发在30.1℃解除要在29.9℃避免温度在阈值边界抖动时继电器反复吸合。4.3 串口输出与上位机的轻量级协议51单片机把数据传给上位机最省事的方法是串口打印格式化字符串但商用监控系统需要结构化的协议。我常用的是一个极简的JSON行协议每条记录末尾带换行上位机按行解析{t:25.4,h:62.3,al:0}下面的代码负责通过串口发送一帧数据波特率9600void uart_send_frame(unsigned char temp_integer, unsigned char temp_decimal, unsigned char humi_integer, unsigned char humi_decimal, unsigned char alarm_flag) { char buf[32]; sprintf(buf, {\t\:%d.%d,\h\:%d.%d,\al\:%d}\r\n, temp_integer, temp_decimal, humi_integer, humi_decimal, alarm_flag); for (unsigned char i 0; buf[i] ! \0; i) { SBUF buf[i]; while (!TI); TI 0; } }注意sprintf在51上会占用大量代码空间如果ROM紧张可以改用分批发送字符串常量。串口波特率误差是由晶振直接决定的11.0592MHz下9600波特率的误差不到0.1%如果换成12MHz晶振误差会到2%长时间通信就会积累出乱码。5. 温湿度采集监控避坑指南5个最常见翻车现场5.1 现象湿度跳变到99%或温度显示-40℃某开发者在组装完板子后第一次上电湿度直接显示99%温度-40℃第一反应是传感器坏了。换了个新模块依旧如此。用示波器抓数据引脚发现起始信号后根本没有低电平响应波形。原因是代码里读引脚前没有先把引脚置高。P3口在作为输入使用前需要向端口寄存器写1否则引脚始终被锁存器拉低DHT11响应了但单片机读不到。解决方法是读数据前执行DHT11_DQ 1;并且延时几个微秒让引脚稳定。另一个常见原因是传感器供电电压掉到4.5V以下DHT11内部逻辑紊乱直接串万用表量电压即可判断。5.2 现象显示值一直不变像卡死但实际上程序在跑如果数码管正常扫描、按键有反应但湿度温度始终是上一次的值说明采集流程卡在读时序的死循环里。我见过最隐蔽的坑是delay_us被编译器优化掉因为函数参数是unsigned char循环体是空的开着-O2优化后整个循环直接删除延时变成0时序完全错误。解决方法是关闭这个文件的优化或者给延时变量加volatile修饰让编译器不敢删循环。还有就是在调试时临时把循环体改成_nop_();_nop_();这样至少能保证语义。5.3 现象数据偶发错误加打印信息反而更频繁采集10次有1次湿度偏差很大但又查不出规律。用串口打印每次原始校验和发现错误刚好出现在串口中断发送字节期间。单片机的定时器和串口中断都在跑DHT11单总线的时序是微秒级的中断一插入时序窗口就错位了。解决思路有三个读取DHT11之前关中断读完再开或者把DHT11的读取放在定时器中断里并且串口只发数据不接收又或者改用带DMA的增强型51单片机。最稳妥的是关中断读取因为一次读取最多几十微秒对系统实时性影响很小。5.4 现象报警继电器反复吸合、触点打火温度在阈值附近波动时继电器触点频繁切换除了打火还有可能烧掉IO口。原因是没有加滞回比较直接把阈值硬比较。我设定的滞回区间是±0.5℃并且报警输出后至少保持1秒不变防止瞬间抖动。另一个坑是继电器线圈没有续流二极管断电瞬间产生高压反电动势反灌进IO口。必须在线圈两端反向并联一个1N4007或1N4148阴极接正极。这是硬件上最便宜也最必要的保护省掉一个二极管轻则单片机复位重则烧IO口。5.5 现象批量焊接后部分板子采集异常同样是DHT11、同样代码几十块板子中总有几块湿度偏高。用万用表对比发现采集异常的模块供电电压正常但数据引脚在空闲时的高电平只有2.8V正常板子是4.9V。问题出在手工焊接时电烙铁温度过高或者DHT11引脚附近焊锡残留导致轻微漏电。解决方法是把焊接温度调低到320℃左右焊接时间控制在2秒内焊完后用洗板水清洁引脚。另外如果用了插座还要检查插座弹片是否氧化。这排问题最难定位因为原理图完全一样只能靠对比测量。6. 进阶用定时器状态机代替延时做低功耗的采集监控6.1 状态机驱动单总线读取到这一步你已经能稳定采集数据了但主循环里到处是delay_us一旦塞进更多功能就会互相干扰。进阶做法是用定时器产生1ms时基把DHT11的通信过程拆成几个状态节点空闲、起始、等待响应、读位、完成。每个状态在定时器回调里执行主循环只查询状态标志。enum { STATE_IDLE, STATE_START_LOW, STATE_WAIT_RESPONSE, STATE_READ_BITS, STATE_DONE }; unsigned char dht_state STATE_IDLE; unsigned char dht_raw[5]; unsigned char dht_bit_index 0; unsigned char dht_byte_index 0; void dht_tick(void) { switch (dht_state) { case STATE_IDLE: DHT11_DQ 0; delay_us(20000); // 起始信号至少18ms DHT11_DQ 1; dht_state STATE_WAIT_RESPONSE; break; case STATE_WAIT_RESPONSE: if (!DHT11_DQ) { dht_state STATE_READ_BITS; dht_byte_index 0; } break; // 后续状态按位读取 } }这样的好处是读取过程不阻塞主循环系统还能同时处理按键、串口和显示。注意delay_us在状态机里依然要精确只是不占满整个任务周期。6.2 低功耗模式的唤醒策略野外或移动场景对功耗敏感51单片机的掉电模式能让电流降到微安级。思路是每10秒醒来一次读取一组温湿度并缓存再进入掉电模式。唤醒源用外部中断把定时器溢出信号接到INT0引脚或者用带唤醒功能的增强型51。void enter_sleep(void) { PCON 0x02; // 进入掉电模式仅允许外部中断唤醒 }掉电唤醒后第一件事不能是读DHT11因为传感器断电后需要1秒稳定时间。应该在上电等待2秒后再采集否则第一次数据一定是错的这就是为什么很多低功耗设计第一次上报的数据异常。经验做法是唤醒后先初始化串口和GPIO延时2秒然后丢弃第一次采集结果再真正记录。6.3 长时间运行的验证方法做完软硬件调试最后一步是验证整机可靠性。我把板子和一只标准温湿度记录仪放在同一个密封箱内每隔一分钟记录一次数据持续24小时。对比两组数据的偏差DHT11在温度30℃附近的误差通常控制在±1.5℃湿度在30%到80%范围内误差±4%。如果偏差超过传感器规格多半是安装位置不对传感器靠近了发热元件或阳光直射。更细致的验证是看数据曲线的连续性把串口数据导入表格工具画曲线出现尖刺的点大概率是偶发干扰可以对比当时是否有继电器动作。这让我养成了一个好习惯每次改完代码先跑48小时连续记录再动手接上真实业务系统。希望这些血泪经验能让你的温湿度采集监控少走弯路一次就把数据做准。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?