仓库温湿度监测系统设计:基于STC89C52+DHT11+PyQt5上位机实战

发布时间:2026/9/6 6:31:45
仓库温湿度监测系统设计:基于STC89C52+DHT11+PyQt5上位机实战 简介基于STM32与C语言的仓库温湿度监测系统完整设计实例面向具备一定单片机基础、从事嵌入式或物联网开发的技术人员解决传统人工巡检效率低、易出错的问题适用于仓储物流、药品仓库、食品冷链、电子产品仓库等对温湿度有严格要求的场景。系统以STM32单片机配合高精度DHT22传感器实现温湿度实时采集、处理、存储与超限报警并兼顾低功耗、云平台上传等多终端扩展。压缩包内含1个DOCX文档整体大小65KB按章节完整梳理了项目背景、目标意义、技术挑战及解决方案、系统特点与应用领域同时给出硬件电路设计、传感器选型与连接、程序模块划分、GUI界面设计和核心代码说明对芯片选型、电源管理和稳定性测试亦有具体分析。已有47人学习可供需要落地仓库环境监测项目的开发者参考复用。 仓库温湿度监测系统这个题目几乎是每年单片机课程设计和毕业设计里出现频率最高的几个之一。很多人一上来就找现成代码结果不是跑不通就是看不懂答辩的时候一问三不知。这篇文章我想以“仓库温湿度监测系统”这个完整项目为例从头到尾拆一遍硬件电路设计、C语言程序设计、上位机GUI设计和代码实现把每个环节为什么这么做、实际踩过哪些坑都说清楚。这套系统用STC89C52RC单片机做主控DHT11采集温湿度LCD1602本地显示再加一个PC端上位机图形界面做远程监控功能覆盖了“采集——显示——报警——上报”的完整链路。无论你是做课程设计、毕业设计还是单纯想练手嵌入式开发这套方案都足够典型跑通之后你基本就掌握了51单片机裸机开发的整套套路。1. 项目概述与整体设计思路1.1 需求拆解仓库环境监测到底要解决什么问题仓库里存放的货物特别是食品、药品、电子元件、纺织原料这一类对温度和湿度都很敏感。温度过高会加速变质湿度过大会导致受潮发霉湿度过低在静电敏感场合也是隐患。所以仓库管理的核心诉求就是实时知道当前环境温湿度超限能提醒最好还能留下一段时间的记录方便追溯。落到具体功能上这套系统需要做到四件事。第一实时采集温度和湿度数据这是基础第二在本地显示当前数值方便巡检人员直接看第三超过设定阈值时要有声光报警提醒人员采取措施第四把数据上传到PC端上位机通过图形界面展示趋势让管理员在办公室里就能看到仓库状态。1.2 方案选型为什么是51单片机DHT11串口上位机主控芯片我选了STC89C52RC。原因很简单51内核的资料和例程在网络上是最全的遇到问题基本都能搜到答案这对初学者来说是最大的优势。用STM32当然性能更强但对这个项目属于杀鸡用牛刀而且开发环境配置、调试门槛都比51高一个台阶。传感器用的是DHT11。虽然很多人吐槽它精度一般、采样周期长但放到仓库监测这个场景里其实是够用的。温度精度±2℃湿度精度±5%RH仓库环境监测又不是精密实验室这个误差完全可以接受。更重要的是DHT11是单总线协议代码实现简单恰好适合用来学习时序驱动。显示用LCD1602这是51课程设计里的经典搭配。显示区域够用两行每行16个字符能同时显示温湿度和报警状态代码成熟资料多。上位机GUI我选择用Python的PyQt5实现。原因很现实Python写界面代码量小pyserial库处理串口通信非常方便加上pyqtgraph画趋势曲线比C#或者Qt C省事得多一个上午就能把界面搭起来。对刚接触上位机开发的人来说这是上手成本最低的路线。1.3 系统总体架构与数据流向整个系统的数据链路是单向的DHT11传感器把温湿度数据通过单总线协议传给单片机单片机做数据处理后一路发给LCD1602显示另一路通过串口发给PC上位机。按键负责设置报警阈值蜂鸣器和LED负责超限报警。这里有个设计上的细节值得说一下。下位机和上位机之间我用的是串口通信数据帧格式是自定义的带了帧头和校验字节。为什么不用现成协议因为自定义协议更简单直接而且自己定协议的过程本身就是学习的一部分。后面我会详细讲这个数据帧是怎么设计的。2. 硬件电路设计最小系统、传感器与显示电路2.1 最小系统与电源设计晶振频率的选择很关键任何51系统都离不开最小系统包括晶振电路、复位电路和电源滤波。STC89C52RC的晶振电路由两个30pF电容和一个晶振组成复位电路用10uF电解电容加10k电阻。晶振频率这块我必须单独拎出来说因为这个坑埋了太多人。做串口通信的项目请直接用11.0592MHz晶振不要用12MHz。原因很简单串口波特率发生器是用定时器对晶振频率做分频得到的115200、9600这些标准波特率都是按11.0592MHz设计好的用12MHz晶振会产生波特率误差。9600波特率下用12MHz误差能到2%以上短时间通信可能没问题数据量一多就会出现随机乱码。电源部分仓库场景通常有220V供电我建议用一个5V/2A的电源适配器通过USB口或者DC座供电。如果想要系统更皮实加一个AMS1117-3.3稳压也没什么问题但要注意DHT11的供电范围是3.3V到5.5V电压不同读出来的湿度值会有一点点偏差实际测试时尽量统一用5V。2.2 DHT11温湿度采集电路上拉电阻必须要有DHT11有3个引脚VCC接5VGND接地DATA接单片机IO口。最关键的电路细节是DATA线上要接一个4.7kΩ到10kΩ的上拉电阻到VCC。为什么必须接上拉电阻因为DHT11的数据引脚是开漏输出的可以拉低电平但没法主动输出高电平。没有上拉电阻高电平状态就悬空了读回来的数据就是乱码。我见过太多人买的DHT11模块上其实已经集成了上拉电阻但有些裸传感器是没有的直接用杜邦线连到单片机然后死活读不出数据。DATA引脚接到单片机的P2.0口程序里用宏定义方便修改。实际接线时传感器和单片机之间的距离尽量短杜邦线控制在20cm以内太长会引入干扰导致读取失败。这个问题在后面的常见问题章节我还会再提。2.3 LCD1602显示电路与声光报警电路LCD1602是16脚接口实际使用中数据线可以只接4根D4到D7用4位模式驱动省下4个IO口。关键的几个引脚是RS接P2.5RW接P2.6EN接P2.7VO引脚接一个10k电位器中间抽头用来调节对比度。VO这个脚是新手翻车重灾区。默认情况下VO接地对比度最高屏幕上会显示一个深色方块像是没有初始化。需要调节电位器让对比度到一个合适的程度字迹清晰且没有背景方块才算调好。很多人在Alpha板上用可调电阻原理是一样的拧到字清楚就行。报警电路我用了一个5V有源蜂鸣器加一个红色LED。蜂鸣器不能直接接IO口因为51单片机的IO口驱动能力很弱高电平输出时电流只有几十微安带不动蜂鸣器。正确做法是用NPN三极管S8050做开关管IO口通过1k电阻接三极管基极蜂鸣器接在集电极和VCC之间。IO口输出高电平三极管导通蜂鸣器响输出低电平三极管截止蜂鸣器不响。LED报警灯同理串一个330Ω限流电阻再接IO口。2.4 串口通信电路连接PC上位机的桥梁单片机要跟PC通信中间的桥梁是USB转TTL模块最常用的是CH340方案。电路连接很简单CH340模块的TXD接单片机RXDP3.0RXD接单片机TXDP3.1两边GND必须共地。有一个常见错误是TXD接TXD、RXD接RXD这种“直连”方式是错的串口通信要交叉连接。还有一点要注意有些USB转TTL模块上有3.3V和5V两个电平选项必须拨到5V否则电平不匹配会导致通信不稳定。3. 下位机C语言程序设计从时序驱动到业务逻辑3.1 程序模块划分与主循环设计写单片机程序最忌讳的就是把所有代码堆在main函数里。我把程序分成几个模块dht11.c负责温湿度采集lcd1602.c负责屏幕显示key.c处理按键输入uart.c负责串口发送main.c里做综合调度。每个模块配一个头文件接口清晰以后调试也方便。主循环的逻辑可以概括为采集温湿度数据刷新LCD显示判断是否超限并触发报警读取按键状态处理阈值设置串口上报数据然后延时等待下一次采集。这个循环是一个典型的“前台轮询”架构51单片机裸机开发最常用的方式。DHT11的采样周期不能太频繁数据手册要求两次读取间隔至少1秒。实际测试下来每2秒采集一次比较合适也能减轻屏幕闪烁的视觉疲劳。我用一个滴答计数器来做这个2秒定时而不是直接延时2秒这样主循环在处理按键和报警时不会被卡住。3.2 DHT11单总线时序驱动延时精度是核心DHT11的通讯协议是单总线主机和传感器之间只有一根数据线。读取一次数据的完整时序是主机拉低数据线至少18ms然后释放并保持高电平20到40usDHT11收到开始信号后会拉低80us再拉高80us作为响应之后连续发送40位数据。每一位数据都以50us低电平开始接着的高电平时间如果为26到28us表示0如果为70us表示1。这段时序里的延时必须准确尤其是判断0和1的那一步。我用的12MHz晶振下一段延时代码的实测时间是关键。下面是DHT11读取函数的核心代码// 延时N微秒12MHz晶振下测试可用 void Delay_us(unsigned int us) { while(us--) { _nop_(); _nop_(); } } // 从DHT11读取一个字节 unsigned char DHT11_ReadByte(void) { unsigned char i, dat 0; for(i 0; i 8; i) { while(!DHT11_DATA); // 等待低电平结束 Delay_us(40); // 延时40us if(DHT11_DATA) // 如果仍然是高电平 { dat | (0x80 i); // 则该位为1 } while(DHT11_DATA); // 等待高电平结束 } return dat; }这段代码里最容易出问题的是Delay_us(40)的延时精度。50us低电平结束、高电平开始后如果在40us的时刻数据线还是高电平这一位就是1如果是低电平就是0。延时太长会把0误判成1太短则可能还没到判断点。所以在不同晶振频率下这个延时值是需要微调的。3.3 LCD1602显示驱动与数据显示逻辑LCD1602的驱动核心是发送命令和数据两个函数。初始化时要按照时序依次发送功能设置0x284位模式双行显示、显示开关0x0C开显示不显示光标、清屏0x01和输入模式设置0x06。每次操作都要先读忙标志或者用延时等待的方式保证命令被接收。显示函数的核心是设置光标位置然后写字符。我封装了一个LCD_ShowString函数接收行列号和字符串指针这样在主程序里显示温湿度就非常简洁sprintf(disp_buf, Temp: %d.%d C, temp_int, temp_dec); LCD_ShowString(1, 1, disp_buf); // 第1行显示温度 sprintf(disp_buf, Humi: %d.%d RH, humi_int, humi_dec); LCD_ShowString(2, 1, disp_buf); // 第2行显示湿度有一个细节经验用sprintf格式化字符串虽然方便但会占用较大的代码空间。STC89C52只有8KB Flash如果你还想加更多的功能字符串格式化很容易把flash撑爆。我的做法是单独封装一个LCD_ShowNum函数直接往指定位置写数字字符这样能省下不少字节。3.4 阈值设置、报警控制与串口上报阈值设置我用3个独立按键实现KEY1进入设置模式并切换设置项温度上限、温度下限、湿度上限、湿度下限KEY2数值加KEY3数值减。设置模式下LCD显示当前正在修改的参数和数值按键操作后立即保存到变量中掉电后重新设置。想做成掉电保存也不难外挂一个AT24C02就可以但这个项目我为了控制代码量没有加。报警逻辑是纯粹的判断语句当温度大于温度上限或者温度小于温度下限或者湿度大于湿度上限任一个条件成立就让蜂鸣器响、LED亮LCD上显示ALARM字样。蜂鸣器是持续响还是间歇响我建议用间歇响500ms响500ms停这样更符合实际报警器给人的提示感也不会太吵。串口上报我用了最简单的帧格式。每一帧固定6个字节帧头0xAA、帧头0x55、温度整数部分、温度小数部分、湿度整数部分、湿度小数部分。没有加校验字节因为短距离通信出错概率低加校验会让代码复杂不少。如果你追求可靠性可以在帧尾加一个累加和校验字节上位机解析时做校验就行。4. 上位机GUI设计用PyQt5搭建监控界面4.1 界面功能规划与布局设计上位机GUI虽然不是这个题目的强制要求但加上之后整个项目的档次和实用价值完全不同。评审老师看到你有一个能实时显示数据曲线的PC端界面印象分会高很多。界面布局我的规划是最上方是串口连接区包含端口选择下拉框、波特率选择、连接/断开按钮中间是实时数据区用大字号标签显示当前温度和湿度右侧是报警状态指示正常时绿色超限时红色闪烁底部是趋势曲线区温度一条曲线湿度一条曲线随着数据不断刷新滚动显示。顶层的设计原则就一个信息层次清晰一眼能看到当前状态。实际操作中还要考虑界面刷新频率串口数据每2秒来一组曲线的刷新频率和它保持一致不需要用定时器高频刷新否则白白消耗CPU。4.2 串口通信协议与数据帧解析下位机发送的6字节帧结构在上位机解析时要做的第一件事是寻找帧头。具体做法是从缓冲区逐字节读读到0xAA之后再看下一个字节是不是0x55如果两个字节都匹配就认为找到了一帧的开始然后接着读4个数据字节。使用pyserial库读取串口数据时我建议用read(6)阻塞方式读取固定字节数。注意一个细节下位机发送一帧是完整的6个字节但上位机接收时可能分成几次到达尤其在Windows系统下串口接收缓冲区的刷新时机不确定。稳妥的做法是把读取到的数据追加到一个环形缓冲区然后从缓冲区里按帧结构解析。温度合成是温度整数 温度小数 / 10这样能还原出带一位小数的实际温度值。比如接收到温度和湿度的整数部分都是0这种数据帧可能有问题要做异常值过滤数值明显不在合理范围温度-40到80湿度0到100的直接丢弃。4.3 PyQt5核心代码实现串口读取与曲线绘制下面是上位机串口读取和界面更新的核心代码框架用PyQt5加pyserial实现import sys import serial from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QComboBox from pyqtgraph import PlotWidget class MainWindow(QMainWindow): def __init__(self): super().__init__() self.ser None self.temp_curve [] # 温度曲线数据 self.humi_curve [] # 湿度曲线数据 def open_serial(self): port self.port_combo.currentText() self.ser serial.Serial(port, 9600, timeout2) def read_data(self): # 在定时器里周期调用 if self.ser and self.ser.in_waiting 6: data self.ser.read(6) if data[0] 0xAA and data[1] 0x55: temp data[2] data[3] / 10.0 humi data[4] data[5] / 10.0 self.update_labels(temp, humi) self.update_curve(temp, humi)有一点需要特别提醒PyQt的界面更新必须在主线程执行串口读取如果放在子线程不能直接操作界面控件。我在上面代码里简化了线程处理实际项目里建议用QTimer定时读取或者用QThread加信号槽机制把数据传到主线程。这个话题展开又是一篇文章这里只提示方向初学者可以先跑通单线程版本。5. 联调、常见问题与避坑指南5.1 典型故障速查表系统跑不通的时候不要急着怀疑代码先按电源、接线、硬件、软件的顺序排查。故障现象可能原因排查方法LCD背光亮但无字符VO对比度没调好调节电位器直到字迹清晰LCD显示乱码初始化时序不对或D4-D7接错检查初始化代码和接线顺序温度湿度始终显示0DHT11数据线没接上拉电阻加4.7k-10k上拉电阻到VCC温度湿度读出来是固定的25/60DHT11未响应时序延时不对用示波器或逻辑分析仪看时序串口收到乱码晶振用了12MHz波特率不准换11.0592MHz晶振或降低波特率到2400烧录后无反应冷启动时序不对复位电路有问题STC单片机要断电再上电不能只点下载Keil编译提示程序超出内存代码量超过8KB Flash减少字符串常量用code关键字存查表数据5.2 几个值得注意的实操细节第一个细节单片机IO口的驱动能力。51单片机的P0口是开漏输出必须接上拉电阻才能输出高电平P1、P2、P3口有内部上拉但驱动能力也很有限。驱动LCD1602、蜂鸣器这类外设最好都用三极管或者直接把P0口加上拉电阻不要直接硬驱动。第二个细节中断优先级和DHT11时序的关系。DHT11读取过程中绝对不能被中断打断我实际测试时发现一旦在读取DHT11的40us窗口内进了定时器中断读出来的数据就会变成乱码。解决办法是在读取DHT11之前关闭总中断EA 0读完再打开。如果你的系统加了串口中断接收这个冲突会更明显更要做好临界区保护。第三个细节Keil编译告警UNREFERENCED LABEL。这个其实是编译器在提示某个标签没有被使用通常是因为删除了代码但goto标签还留着或者函数里定义了没用的标号。解决方法是找到对应的标签删除就行不影响程序运行但答辩前尽量把警告清理干净印象分会好很多。5.3 从课程设计到真正可用的系统扩展思路做完整套系统你可以想想如果真正部署到一个几千平米的仓库这套方案够不够用答案是不够的但这正是扩展思路的起点。单点采集换成多点采集可以用Modbus协议挂多个传感器节点用485总线连接一套上位机轮询所有节点。数据存储用SQLite上位机记录历史数据并生成日报表。报警方式从本地声光报警扩展成短信报警或者微信推送。如果你想继续深入这几个方向每一个都够写一篇完整的技术文章。我个人在实际调这套系统时最深的体会是硬件电路设计得再完善代码逻辑写得再漂亮最后能不能跑通还是看细节。上拉电阻有没有接、晶振频率选没选对、IO口号和程序里写的一不一致这些点任何一个出错都会让你抓狂半天。所以我的习惯是先用万用表量一遍所有电源引脚再对照原理图检查一遍接线最后才上电写程序。这套排错思路比任何一条具体经验都更值钱。本文还有配套的精品资源点击获取