简介:基于51单片机的远程仓库湿度监测系统仿真设计资料,是一套面向51单片机学习者和嵌入式初学者的完整项目包,适用于课程设计、毕业设计及物联网环境监测入门。系统以51单片机为控制核心,结合湿度传感器与无线通信模块,实现仓库湿度采集、处理和远程上报,覆盖硬件组成、软件逻辑与仿真验证,并包含初始化、数据采集、数据处理、无线传输等核心功能的代码实现。压缩包共20个文件、约65KB,主要包含C源文件、Keil工程文件(uv2/plg/opt)、编译生成文件(hex/obj/lst/m51)和Proteus仿真文件(DSN/DBK),另含启动与备份文件,便于直接查看代码或运行仿真。目前已有163人学习下载,借助源码与仿真可直观掌握湿度采集、无线通信及程序设计等关键环节,节省搭建与排错时间,便于扩展报警、显示等功能。
1. 远程仓库湿度监测为何要用51单片机仿真闭环
看到“仿真设计资料”这类标题,很多人第一反应是“是不是只要打开Proteus跑一下就行”。实际做一遍会发现,真正的价值不在那个 .DSN 文件,而是源程序怎么组织、DHT11 时序怎么在仿真模型里对得上、串口输出和 LCD 显示怎么并行不冲突。基于51单片机的远程仓库湿度监测系统,本质上不是“读湿度”这一个点,而是“单总线时序读取 + 数据显示 + 超限处理”三件事的合体。仓库场景要求 7×24 小时工作,芯片选 51 除了成本低,更因为这类环境不需要复杂操作系统,单片机裸机轮询就能稳定覆盖。
适合谁?一种是准备课程设计或电赛基础题的人,需要一份能讲清楚原理的完整工程;另一种是想把 Proteus 仿真当作上位机联调手段的工程师。仿真不是玩具,它可以先把协议、时序、代码分支验证掉,再移植到实物板子,减少烧录次数。
这篇直接围绕 Keil C51 编写源程序、Proteus 搭建仿真文件、两者联调排错展开,整个过程不依赖特定开发板,核心代码和仿真思路可以完整落地到 STC89C52、AT89C51 等常见芯片上。
2. 系统拆解与传感器、采样链路选型
2.1 DHT11在仿真里的角色:不是模拟量而是单总线时序器件
远程仓库湿度监测,传感器是最先要定下来的部分。常见的湿度方案有电容式模拟输出(HS1101)、I2C 数字输出(SHT30)和单总线数字输出(DHT11)。在 51 单片机上,I2C 和单总线都需要软件模拟,而 DHT11 是其中结构最简单、Proteus 支持最成熟的器件,绝大多数 Proteus 8 以上版本自带 DHT11 模型。
很多第一次做仿真的人会走弯路:以为 DHT11 输出的是 0~5V 模拟电压,于是把它接到 ADC0809 前面。这是错误的。DHT11 内部是电阻式感湿元件加 8 位单片机,输出的是 40 bit 数字帧,低位先出。51 单片机读 DHT11,不是读电平高低,而是“测量引脚被拉低的持续时间长短”来区分 0 和 1。
在 Proteus 里,DHT11 模型已经封装好了这些时序行为,你只要按照数据手册时序去操作引脚,就能读回正确数据。如果某版本 Proteus 的 DHT11 模型表现异常,常见处理方法是给数据引脚外接一个 10kΩ 上拉电阻,并在程序中放宽拉高采样窗口。
2.2 51单片机资源规划:晶振、引脚、串口与显示
选型不是只看传感器,单片机的引脚资源和外设要一起算。做一个仓库湿度监测,最少需要:
- 1 个普通 I/O 口读 DHT11
- 1 个串口(TXD/RXD)用于远程上报
- 1 个 I/O 控制声光告警或排风扇
- 可选:LCD1602 占 11 个 I/O 口
51 单片机共 32 个 I/O 口,即使接上 LCD1602 和告警灯,资源仍然充足。我一般用 STC89C52RC 做实物,因为它的内部时钟可以掉电不丢失,但在 Proteus 里更常用 AT89C51,模型稳定且加载 HEX 后立即运行。
晶振选 12MHz 还是 11.0592MHz?如果只接 DHT11 和 LCD,两个都可以。如果需要和上位机串口通信,建议直接用 11.0592MHz,这样串口波特率 9600 的误差接近零。DHT11 时序对微秒级延时敏感,Keil 编译后汇编指令周期会因优化级别变化,所以延时函数最好用_nop_()加循环实测调参,不要裸等。
2.3 仿真的边界:哪些能仿真,哪些不能仿真
美化Proteus 说得再万能,也是有边界的。仓库湿度监测系统里,有三个方面仿真和实物明显不同,必须在设计资料里提前标注出来,避免移植到实物时出错。
| 项目 | Protesu 仿真行为 | 实物行为 |
|---|---|---|
| DHT11 数据 | 模型立即返回固定湿度值,通常不随时间变化 | 随环境湿度缓慢变化,响应时间约 2 秒 |
| 上拉电阻 | 缺失时可能仍能工作,因为模型内部理想化 | 必须接 10kΩ 上拉,否则时序不稳定 |
| 串口输出 | 用 Virtual Terminal 显示,无电气噪声 | 需要 USB-TTL 模块,注意共地 |
| 超限告警 | GPIO 拉高/拉低直接可见 | 继电器驱动需要三极管或 ULN2003 |
记住这个边界不是让你放弃仿真,而是要设计出“仿真和实物代码尽量一致”的工程结构。DHT11 的读取函数、串口发送函数、阈值判断逻辑,这三部分在仿真和实物中应该完全复用。只有 I/O 口定义和延时参数可能不同。
3. 源程序的结构与编写:用Keil C51实现数据采集和超限处理
3.1 Keil工程划分与HEX生成设置
源程序不是把全部逻辑塞进一个 main.c。51 单片机项目虽然不大,但结构化文件有利于仿真阶段单独调试某一部分。我建议工程至少包含四个文件:
仓库湿度监测/ ├── main.c // 主流程、初始化、循环 ├── dht11.c // 单总线时序读取 ├── dht11.h // 引脚定义和函数声明 ├── uart.c // 串口初始化与发送 ├── uart.h └── lcd1602.c // 显示驱动(可选)dht11.h 里用宏定义引脚,这样换板子时只改一行。示例:
#ifndef __DHT11_H__ #define __DHT11_H__ #include <reg52.h> sbit DHT11_DATA = P2^0; unsigned char dht11_read_byte(void); unsigned char dht11_read_data(unsigned char *humidity, unsigned char *temperature); #endif在 Keil 里建工程时,芯片选 AT89C51 还是 STC89C52 取决于你的 Proteus 元件库。两者寄存器基本兼容,但reg52.h比reg51.h多了一些 SFR 定义,而且定时器 2 在 STC89C52 上有独立模式。我通常直接用 AT89C51 建工程,编译出的 HEX 在 Proteus 里加载最快。
生成 HEX 文件时有一个必改项:点击 Options for Target,在 Output 选项卡勾选 Create HEX File。ROM 大小选择默认即可,不需要对 51 做分页。
3.2 DHT11时序:起始信号、响应信号和位解析
DHT11 的读操作分为三步:主机拉低总线发起始信号、DHT11 拉低响应、连续输出 40 bit 数据。用代码表示如下:
#include <reg52.h> #include <intrins.h> sbit DHT11_DATA = P2^0; void delay_us(unsigned int t) { while (t--) { _nop_(); } } unsigned char dht11_read_byte(void) { unsigned char i, value = 0; for (i = 0; i < 8; i++) { while (!DHT11_DATA); // 等待低电平结束 delay_us(40); // 40us后判断信号电平 if (DHT11_DATA) { value |= 0x80 >> i; // 高电平持续为1 } while (DHT11_DATA); // 等待高电平结束 } return value; }这段代码的关键在delay_us(40)。DHT11 协议里,一位数据用高电平持续 26~28us 表示 0,用 70us 表示 1。主机在读总线时,先等低电平结束,然后延迟 40us 采样。如果此刻引脚为高,说明是 1;如果恢复低电平,说明是 0。这个延迟不能改得太小,否则会把 1 误判成 0。
完整读取函数还需要处理起始信号和应答超时:
unsigned char dht11_read_data(unsigned char *humidity_l, unsigned char *humidity_h, unsigned char *temp_l, unsigned char *temp_h) { unsigned char check, i; DHT11_DATA = 0; delay_us(25000); // 主机拉低 25ms,发送起始信号 DHT11_DATA = 1; delay_us(30); // 拉高 30us 后释放总线 if (DHT11_DATA == 0) { // DHT11 拉低响应 while (DHT11_DATA == 0); // 等待响应低电平结束 while (DHT11_DATA == 1); // 等待高电平结束 for (i = 0; i < 5; i++) { *humidity_l = dht11_read_byte(); *humidity_h = dht11_read_byte(); *temp_l = dht11_read_byte(); *temp_h = dht11_read_byte(); check = dht11_read_byte(); } if (check == (*humidity_l + *humidity_h + *temp_l + *temp_h)) { return 1; // 校验通过 } } return 0; }读取的数据顺序是:湿度整数、湿度小数、温度整数、温度小数、校验和。湿度小数和温度小数在 DHT11 上固定为 0,但代码里仍然要按位接收,因为后续更换 DHT22 或 AM2302 时,同样的时序可以复用。
3.3 串口输出与LCD1602双路显示
仓库监测不只是本地看,还需要把数据远程传回。在仿真里,串口输出用 Proteus 的 Virtual Terminal 显示。初始化串口的关键是正确计算波特率重载值。
void uart_init(void) { SCON = 0x50; // 串口模式1,8位UART,允许接收 TMOD = 0x20; // 定时器1工作在模式2(8位自动重装) TH1 = 0xFD; // 9600波特率,晶振11.0592MHz TL1 = 0xFD; TR1 = 1; // 启动定时器1 ES = 1; // 开串口中断 EA = 1; // 开总中断 }TH1 = 0xFD对应 11.0592MHz 晶振下 9600 波特率。如果你的仿真文件用 12MHz 晶振且不改这个值,串口输出会出现乱码。这是远程仓库监测系统仿真中最常见的坑之一。
发送字符串可以写成简单轮询函数,不用中断:
void uart_send_string(unsigned char *str) { while (*str) { SBUF = *str++; while (!TI); TI = 0; } }LCD1602 显示则建议只显示两行:第一行是 HUM: 65%RH,第二行是 TEMP: 26C。在仿真文件里,LCD 初始化时序比很多外围设备苛刻,需要连续延时等待模块内部复位。如果 LCD 显示花屏,优先检查 P0 口是否接上拉电阻,Proteus 的 LCD 模型对 P0 开漏行为非常敏感。
3.4 编译参数:内存模型与优化级别
Keil C51 的默认编译参数对小型 DHT11 项目不会出大问题,但有两个参数需要主动确认。第一个是 Memory Model,默认 Small 模式下变量默认放在 DATA 段,51 的内部 RAM 只有 128 字节,DHT11 的临时变量加上串口缓冲区很容易超过。我一般把串口缓冲区定义为unsigned char code(存放到程序区),或改为 Large 模式,但 Large 模式下所有变量都走外部 RAM,仿真速度会稍微变慢。
第二个是优化级别。Keil 默认编译优化是 Level 8,会重新调整指令顺序,这种优化对 DHT11 这类微秒级延时函数影响极大。一个 40us 的延时在优化后可能只剩十几个微秒,导致读到的数据全部为 0xFF。遇到这种情况,把 dht11.c 文件的优化级别单独设为 Level 0,同时勾选“在文件级别使用优化选项”。
4. Proteus仿真文件的搭建与联调排错
4.1 新建DSN并放置元件清单
仿真文件的核心是 Proteus 的 DSN 工程。从零搭一个仓库湿度监测仿真,需要放置的元件并不多,但少了任何一个都会导致工作状态不对。我通常按下列清单摆放:
| 元件 | 库中名称 | 数量 | 作用 |
|---|---|---|---|
| 单片机 | AT89C51 | 1 | 主控 |
| 湿度传感器 | DHT11 | 1 | 湿度数据源 |
| 虚拟终端 | VIRTUAL TERMINAL | 1 | 串口数据分析 |
| LCD | LM016L | 1 | 本地显示 |
| 电阻 | RES | 3 | P0上拉、DHT11上拉、复位 |
| 电容 | CAP | 2 | 晶振负载电容 |
| 晶振 | CRYSTAL | 1 | 11.0592MHz |
| 发光二极管 | LED-RED | 1 | 超限告警 |
放置元件后,先连线再设置属性。AT89C51 的 XTAL1 和 XTAL2 接晶振两端,每个引脚再接一个 30pF 电容到地。RST 引脚接 10uF 电容到 VCC,再接 10kΩ 电阻到地,这是 51 最常用的上电复位电路。
DHT11 的 VCC 接 5V,GND 接地,DATA 引脚直接连 P2.0,同时在 DATA 和 VCC 之间放一个 10kΩ 上拉电阻。Proteus 的 DHT11 模型内部有弱上拉,但外部上拉更接近真实电路。
4.2 加载HEX、设置时钟频率与运行
HEX 文件是 Proteus 和 Keil 之间的连接点。双击 AT89C51 芯片,在 Program File 一栏选择 Keil 编译输出的 .hex 文件。这里有一个细节:如果 Keil 工程和 Proteus 工程不在同一目录,加载后改过代码重新编译,Proteus 里不会自动更新,需要再次手动指定文件或点击 Load 按钮刷新。
改变量设置时钟频率位置在同一个对话框里,Edit Component 的 Clock Frequency 默认是 1MHz,必须改成 11.0592MHz。很多人只改了晶振属性,忘了改芯片的 Clock Frequency,结果程序时序完全不对。正确做法是两者都改成 11.0592MHz。
运行仿真前先按暂停键,确认虚拟终端和 LCD 都处于激活状态。然后点击运行,观察 DHT11 的引脚波形。如果 DHT11 正确响应,P2.0 引脚上会出现明显的连续窄脉冲,这个脉冲在示波器上可以清楚看到。
4.3 仿真不出的几个典型原因
仿真不出结果时,不要急着改代码,按照从硬件到软件的思路排查。
第一个典型原因是“湿度显示全为 255”。这种情况基本是 DHT11 时序读取失败,程序每次都走到超时分支。先检查 dht11_read_data 里的起始信号延时,Proteus 模型对 25ms 拉低是严格要求的,如果改成 18ms,模型会不响应。另外确认单总线引脚定义和 Proteus 连线一致,比如代码里是 P2.0,连线却接到了 P2.1。
第二个典型原因是“Virtual Terminal 不显示任何内容”。先看串口使能代码有没有加TR1 = 1,再看晶振频率和波特率计算值是否匹配。如果晶振是 12MHz,TH1 就应该是 0xE8 而不是 0xFD。可以用 IDE 的调试器查看串口发送函数的行号,配合 Proteus 示波器观察 TXD 引脚波形。
第三个典型原因是“LCD 亮但不显示字”。多半是 P0 口没有加上拉电阻。Proteus 的 LM016L 模型要求 P0 口有上拉,否则高电平无法正确传输数据。在 P0.0~P0.7 各接一个 4.7kΩ 上拉电阻到 VCC 即可。
还有一类问题是仿真速度实时性。Proteus 在 Debug 菜单里可选择 Run at Real Time,如果你发现 Virtual Terminal 接收速度非常快且数据错乱,可能是当前处于 Maximum Simulation Speed,此时串口时序会被压缩。
5. 让仿真更接近现场:阈值告警与数据校验的进阶设置
5.1 湿度超限控制继电器/风扇
仓库湿度监测的分水岭在看阈值告警。只读湿度不算完,要能在超过 70%RH 时自动拉高一个 I/O 口,用来驱动继电器或风幕机。在源程序里加一个结构体保存湿度上下限,每次读取成功后判断,并留出回差防止继电器频繁抖动。
#define HUMIDITY_ALARM_SETPOINT 70 void process_humidity(unsigned char humidity) { if (humidity > HUMIDITY_ALARM_SETPOINT) { ALARM_PIN = 1; // P3.3 拉高,告警或开启排风扇 } else { ALARM_PIN = 0; } }在 Proteus 里,ALARM_PIN 连接一个 LED 和一个 1kΩ 限流电阻到 VCC,用 LED 亮灭模拟排风扇启停。如果需要仿真完整的继电器动作,可以加一个 RELAY 元件,让单片机输出驱动继电器线圈,再用继电器触点控制交流负载模型。
5.2 用虚拟终端校验协议
远程监测的核心是“数据出去要能对得上”。实物项目里上位机可能会发 Modbus-RTU 或自定义帧,仿真阶段必须先用虚拟终端验证帧格式。DHT11 读到的湿度是一字节整数,包装成 ASCII 帧更利于排错。
unsigned char tx_buf[32]; sprintf(tx_buf, "H:%02dRH T:%02dC\r\n", humidity, temperature); uart_send_string(tx_buf);sprintf 在 Keil C51 中默认会占用较多代码空间,如果遇到L107函数未定义错误,需要在工程设置里把 printf 重定向为输出到串口。更轻量的做法是手动拼帧:
tx_buf[0] = 'H'; tx_buf[1] = 'R'; tx_buf[2] = (humidity / 10) + '0'; tx_buf[3] = (humidity % 10) + '0'; tx_buf[4] = '%'; tx_buf[5] = '\r'; tx_buf[6] = '\n';建议连一个“帧尾校验”逻辑:在发送内容后追加一个字节的异或校验值,虚拟终端上能看到每帧最后一个字符会随数据内容变化。这样将来接真实上位机时,只需把校验字节解析逻辑保留即可,不用重写发送模块。
5.3 快速验证清单
| 验证项 | 预期结果 |
|---|---|
| DHT11 初始读取 | 虚拟终端显示固定湿度值,无 FF 字符 |
| 串口波特率 | 终端每秒输出 1 帧,无乱码 |
| LCD 显示 | 第二行温度值与 DHT11 数值一致 |
| 湿度阈值调整 | 上限改为 50 后 LED 应立即点亮 |
| 起始信号时序 | 示波器可见约 25ms 的低电平脉冲 |
| 上拉电阻移除 | DHT11 数据异常或温度显示全零 |
把温度阈值也做成宏定义,便于观察。仓库环境是季节变化很大的场景,夏季 26℃、冬季可能低于 10℃,仿真里虽然 DHT11 模型不温度波动,但代码结构上要把温度数据同时传给上位机和显示缓冲区,而不是只处理湿度。这一步做到,后续扩展成“温湿度双告警”或者改用 SHT30 传感器,都只需要修改底层读取函数。
本文还有配套的精品资源,点击获取