简介:面向单片机初学者的无线温度采集报警系统仿真资料,基于51单片机与DS18B20温度传感器,通过Proteus完成电路设计与无线传输模拟,实现两机温度采集、LCD1602实时显示、阈值超限声光报警等功能。压缩包共40个文件,容量约442KB,涵盖pdsprj仿真工程、Keil C源程序与hex烧录文件、硬件电路截图,并将程序分为温度采集端与温度显示端两个子工程,便于对照理解数据发送与接收的完整流程。已有155人浏览学习。资料结构清晰,可直接用Proteus打开仿真运行,也可参考源代码学习单总线传感器驱动、串口通信模拟、LCD1602显示及报警逻辑,适合课程设计、毕业设计或自学实践使用,亦可作为小型无线测温装置的原型参考。
1. 从机房温度监控说起:Proteus 仿真先行的设计路径
试想一个 20 平米的弱电机房:白天服务器满载,机柜背面温度能冲到 65℃;凌晨负载降下来又回到 28℃。人工巡检盯不住这个变化曲线,直接上 nRF24L01 又要在天线和协议栈上折腾一周。更稳妥的路径是先搭一套“51 单片机 + DS18B20 + LCD1602”的无线温度采集报警系统,在 Proteus 里把采集端、模拟无线链路、显示端全部跑通。仿真阶段最有价值的部分不是省一块开发板,而是能随时改 DS18B20 的温度值,检查无线帧格式、报警阈值和显示逻辑是否自洽,验证完再移植到实体硬件。这套设计适合做课程设计、毕业设计,以及想快速掌握单总线协议和串口组帧的嵌入式工程师。
2. DS18B20 单总线时序与 51 单片机读取实现
2.1 为什么选 DS18B20 而不是 NTC 或 LM75
DS18B20 的测温范围是 -55℃~+125℃,在 -10℃~+85℃ 区间误差为正负 0.5℃,分辨率通过配置可选 9~12 位,默认 12 位时 LSB 对应 0.0625℃。相比之下,NTC 热敏电阻需要搭分压电路再进 ADC,最后还要用查表或拟合法换算温度,标定误差和设备差异会让精度很难控制;而 I2C 接口的 LM75 虽然也能直接读出数字量,但双线协议要占用两个 I/O,且总线上挂多个传感器时地址分配更麻烦。
DS18B20 用的是单总线协议,一根数据线既能供电也能传数据,多个器件可以并联在同一条线上,主机靠芯片内部 64 位 ROM 编码区分它们。对于“一个采集端 + 一个显示端”的无线报警系统,单点测温不需要寻址,直接跳过 ROM 操作即可,逻辑非常简单。在 Proteus 仿真里,DS18B20 模型对时序很敏感,代码里任何延时抖动都可能让它一直返回 85℃——这是 DS18B20 上电后暂存器的默认值,也是后续排错时最典型的线索。
2.2 复位、写位、读位三个基础时序
单总线的通信由主机发起,每一个 bit 都是一个完整的时隙。先看复位和存在脉冲检测:主机把 DQ 拉低至少 480us,释放总线后 DS18B20 等待 15~60us,然后拉低 60~240us 表示“我在线”,主机在这个窗口内采样电平,读到低电平就说明传感器存在。
sbit DQ = P1^0; // DS18B20 复位,返回 0 表示检测到设备 unsigned char ds18b20_reset(void) { unsigned char presence; DQ = 0; delay_us(500); // 主机拉低,持续 480~960us DQ = 1; delay_us(70); // 释放总线,等待器件响应 presence = DQ; // 存在脉冲窗口内采样 delay_us(410); // 等待整个复位时序结束 return presence; }写时隙和读时隙的关键在于时序窗口:写 1 是拉低 1~15us 后释放,写 0 是保持低电平 60us;读时隙则是主机拉低至少 1us 后释放,并在 15us 内采样总线电平。具体实现如下:
void ds18b20_write_bit(unsigned char bit) { DQ = 0; delay_us(2); // 起始低脉冲 if (bit) DQ = 1; // 写 1 则释放总线 delay_us(60); // 位时间 60~120us DQ = 1; } unsigned char ds18b20_read_bit(void) { unsigned char bit; DQ = 0; delay_us(2); // 起始低脉冲 DQ = 1; delay_us(12); // 在 15us 内采样 bit = DQ; delay_us(50); // 等待位时间结束 return bit; } void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { ds18b20_write_bit(dat & 0x01); dat >>= 1; } } unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; if (ds18b20_read_bit()) dat |= 0x80; } return dat; }这里的delay_us()由定时器或空循环实现,在仿真里晶振频率直接决定延时的真实性。如果单片机属性里用的是 12MHz,而代码按 11.0592MHz 计算延时,单总线时序会产生累积漂移,表现就是读出的数据偶尔跳变。所以采集端晶振一上来就要定好,后面串口波特率也依赖同一个频率。
2.3 温度读取流程与负温度换算
读取温度的标准流程是:复位 → 跳过 ROM(0xCC)→ 启动温度转换(0x44)→ 等待 750ms → 复位 → 跳过 ROM → 读暂存器(0xBE)→ 连续读 9 字节,其中第 0、1 字节是温度值。
| 命令字 | 功能 | 说明 |
|---|---|---|
| 0xCC | 跳过 ROM | 单点测温时不需要读取 64 位 ROM 编码 |
| 0x44 | 启动温度转换 | 12 位分辨率下转换时间最长 750ms |
| 0xBE | 读暂存器 | 连续输出 9 字节,前两字节为温度 |
// 返回温度值,单位 0.1℃ int ds18b20_get_temp(void) { unsigned char msb, lsb; int raw; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); // 等待转换完成 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 lsb = ds18b20_read_byte(); // 低字节 msb = ds18b20_read_byte(); // 高字节 raw = (msb << 8) | lsb; // 拼成 12 位有符号数 if (raw & 0x8000) raw -= 65536; // 负温度修正 return raw * 10 / 16; // 0.0625℃/LSB -> 0.1℃ }温度计算里为什么要raw * 10 / 16:默认 12 位分辨率下,每一个 LSB 代表 0.0625℃,换算成 0.1℃ 单位就是乘以 0.625,等价于先乘 10 再整除 16。这样整型运算不会引入浮点,51 单片机也不用加载浮点库。负温度时高字节符号位扩展需要手动处理,raw -= 65536就是把 16 位补码转成有符号整数。仿真中如果想验证负温度,双击 DS18B20 模型把 Temperature 属性改成 -10 即可,无需改代码。
3. Proteus 无线链路模拟:串口帧协议与电路搭建
3.1 仿真里怎样模拟“无线”传输
真实系统中,采集端通过 nRF24L01 或 LoRa 模块把温度帧发出去,显示端用同型号模块接收。Proteus 标准库没有 nRF24L01 模型,课程设计和毕设最常见的替代方案是:用串口加网络标签模拟一条透明无线链路。采集端 AT89C51 的 TXD 通过命名为RF_RX的标签连到显示端 RXD,显示端 TXD 通过RF_TX连回采集端 RXD,两边共地。
这样做的合理性在于,无线模块对应用层本来就是透明的,只要双方约定好波特率、帧格式和校验,串口透传与无线透传在逻辑上等价。我一般把这条标签网络理解成“空中的无线信道”,仿真阶段跑通的协议栈,移植到真实 nRF24L01 时只需替换底层收发函数。网上很多资料把 Proteus 拼成 protues,搜索时两个拼写都能找到同类工程,但读电路图时别认错器件。
3.2 帧格式设计:长度、类型、校验一并在内
无线链路里最容易出问题的不是温度数据本身,而是接收端怎么从字节流里切出完整的一帧。这里用一个 7 字节定长帧:
| 字段 | 字节数 | 取值说明 |
|---|---|---|
| 帧头0 | 1 | 固定 0xAA |
| 帧头1 | 1 | 固定 0x55 |
| 长度 | 1 | 载荷长度,这里为 5 |
| 类型 | 1 | 0x01 温度上报,0x02 心跳 |
| 温度 | 2 | int16 小端,单位 0.1℃ |
| 校验 | 1 | 前 6 字节累加和,溢出丢弃 |
帧头用两个字节 0xAA 0x55 是为了降低误同步概率。接收端只要先找到帧头,再按长度字段收满整帧,最后算校验,就能丢掉噪声和半包。
3.3 采集端发送与显示端接收的关键代码
串口工作在模式 1,8 位 UART,波特率 9600。51 单片机的串口波特率由定时器 1 提供,11.0592MHz 晶振下 TH1 取 0xFD 得到标准 9600;如果换成 12MHz 晶振,波特率会有约 2.1% 的误差,仿真短帧勉强能用,实物连接就很容易出乱码。
void uart_init(void) { SCON = 0x50; // 串口模式 1,允许接收 TMOD &= 0x0F; TMOD |= 0x20; // 定时器 1 模式 2,8 位自动重装 TH1 = 0xFD; // 11.0592MHz 下对应 9600bps TL1 = 0xFD; TR1 = 1; ES = 1; // 打开串口中断 EA = 1; } void rf_send_temp(int temp) { unsigned char buf[7]; unsigned char crc = 0; unsigned char i; buf[0] = 0xAA; buf[1] = 0x55; buf[2] = 5; // 载荷长度 buf[3] = 0x01; // 温度上报 buf[4] = temp & 0xFF; // 低字节 buf[5] = (temp >> 8) & 0xFF; // 高字节 crc = buf[0] + buf[1] + buf[2] + buf[3] + buf[4] + buf[5]; buf[6] = crc; for (i = 0; i < 7; i++) { SBUF = buf[i]; while (!TI); // 等待发送完成 TI = 0; } }发送函数先把温度拆成两个字节,再算累加和,最后按顺序写入 SBUF。while (!TI)是同步等待,发送频率不高时没问题;如果后面要加多节点轮询,最好改成中断发送,避免阻塞主循环。
unsigned char rx_buf[7]; unsigned char rx_cnt = 0; bit frame_ready = 0; int current_temp = 0; void uart_isr(void) interrupt 4 { unsigned char ch; if (RI) { RI = 0; ch = SBUF; if (rx_cnt < 7) rx_buf[rx_cnt++] = ch; // 帧头不对就重新对齐 if (rx_cnt == 2 && (rx_buf[0] != 0xAA || rx_buf[1] != 0x55)) { rx_buf[0] = rx_buf[1]; rx_cnt = 1; } if (rx_cnt >= 7) { // 校验:前 6 字节累加和 if ((unsigned char)(rx_buf[0] + rx_buf[1] + rx_buf[2] + rx_buf[3] + rx_buf[4] + rx_buf[5]) == rx_buf[6]) { current_temp = (int)(rx_buf[4] | (rx_buf[5] << 8)); frame_ready = 1; } rx_cnt = 0; } } }接收中断里做了简易对齐:每收 2 字节检查一次帧头,不对就把后一字节前移。这样即使链路里混入干扰字节,也能在下一个帧头处重新同步。校验通过后更新current_temp,主循环只需要轮询frame_ready标志,再去刷新显示和报警,不在中断里做耗时操作。
3.4 Proteus 电路搭建步骤与器件清单
打开工程文件无线温度传输.pdsprj,可以看到两个独立的单片机子系统。采集端负责读 DS18B20 并通过模拟无线链路发出,显示端负责解析帧、显示温度和驱动报警。整个仿真电路的器件清单整理如下:
| 器件 | Proteus 关键字 | 数量 | 用途 |
|---|---|---|---|
| 单片机 | AT89C51 | 2 | 采集端、显示端各一片 |
| 温度传感器 | DS18B20 | 1 | 采集温度,可改属性 |
| LCD1602 | LM016L | 1 | 显示温度和阈值 |
| 蜂鸣器 | BUZZER | 1 | 超限报警 |
| LED | LED-RED / LED-BLUE | 各 1 | 高限、低限指示 |
| 按键 | BUTTON | 2 | 阈值调节 |
| 电阻 | RES | 若干 | 4.7k 上拉、LED 限流 |
| 晶振 | CRYSTAL | 2 | 11.0592MHz |
从空工程搭建时,按下面几步操作:
- 新建 Proteus 工程,放置两个 AT89C51,双击设置 Clock Frequency 为 11.0592MHz。
- 采集端:P1.0 接 DS18B20 数据脚,数据线到 VCC 之间接 4.7k 上拉电阻;P3.1(TXD)引出标签
RF_RX,P3.0(RXD)引出标签RF_TX。 - 显示端:P2 口接 LM016L 的 D0~D7,P3.5 接 RS,P3.6 接 RW,P3.7 接 E;P3.0 接标签
RF_RX,P3.1 接标签RF_TX。 - 显示端 P1.7 通过 PNP 三极管驱动蜂鸣器,P1.6、P1.5 接红、蓝 LED,P1.0、P1.1 接按键到地。
- 两端分别接 11.0592MHz 晶振和两个 30pF 电容到地。
- 用 Keil 分别编译
温度采集端和温度显示端两个工程,生成 HEX 文件,双击对应单片机加载。 - 运行仿真,双击 DS18B20 模型,修改 Temperature 属性为 40,观察显示端是否出现 40.0℃ 且触发报警。
LM016L 是 Proteus 里的 LCD1602 型号,搜索时直接用 LM016L 找得最快。蜂鸣器模型建议用 BUZZER 并串联三极管驱动,因为单片机的 I/O 灌电流能力不足以直接驱动大电流蜂鸣器,仿真里虽然能响,但实物一定带不动。
4. LCD1602 显示驱动与阈值报警状态机
4.1 LCD1602 初始化:时序比命令更关键
LM016L 是标准 HD44780 控制器,8 位模式下初始化序列固定为:功能设置 0x38(8 位数据、双行、5x7 点阵),显示开关 0x0C(开显示、关光标),输入模式 0x06(地址自动加一),最后清屏 0x01。每个命令之间要留足够间隔,尤其清屏需要 1.6ms 以上。
#define LCD_DB P2 sbit LCD_RS = P3^5; sbit LCD_RW = P3^6; sbit LCD_E = P3^7; void lcd_write_cmd(unsigned char cmd) { LCD_DB = cmd; LCD_RS = 0; // 命令模式 LCD_RW = 0; // 写模式 LCD_E = 1; delay_us(50); LCD_E = 0; // E 下降沿锁存数据 delay_ms(2); } void lcd_write_data(unsigned char dat) { LCD_DB = dat; LCD_RS = 1; // 数据模式 LCD_RW = 0; LCD_E = 1; delay_us(50); LCD_E = 0; delay_ms(2); } void lcd1602_init(void) { lcd_write_cmd(0x38); // 8 位、双行、5x7 lcd_write_cmd(0x0C); // 显示开,光标关 lcd_write_cmd(0x06); // 地址自动加 1 lcd_write_cmd(0x01); // 清屏 }LCD 的 E 引脚是下降沿触发,所以每次写完数据后要把 E 拉低。这里delay_ms(2)是给控制器内部处理留时间,仿真里写太快会丢命令,表现为字符错位或者整屏不刷新。LCD_RW 在纯写应用里可以直接接地,但仿真中还是保留引脚定义更贴近实物。
4.2 温度与阈值显示:整数运算替代 sprintf
显示内容分两行:第一行显示当前温度,第二行显示上限和下限。51 单片机资源有限,尽量不用 sprintf,手工拼字符数组更可靠。
void display_temperature(int temp) { char buf[6]; unsigned char th, tl; // 温度:temp 单位 0.1℃,如 253 表示 25.3℃ th = temp / 10; tl = temp % 10; lcd_write_cmd(0x80); // 第一行行首 lcd_write_data('T'); lcd_write_data(':'); lcd_write_data('0' + th / 10); lcd_write_data('0' + th % 10); lcd_write_data('.'); lcd_write_data('0' + tl); lcd_write_data('C'); lcd_write_cmd(0xC0); // 第二行行首 lcd_write_data('H'); lcd_write_data(':'); lcd_write_data('0' + th_high / 100); lcd_write_data('0' + (th_high % 100) / 10); lcd_write_data('0' + th_high % 10); lcd_write_data(' '); lcd_write_data('L'); lcd_write_data(':'); lcd_write_data('0' + th_low / 100); lcd_write_data('0' + (th_low % 100) / 10); lcd_write_data('0' + th_low % 10); }th_high和th_low的初始值设为 350 和 50,即 35.0℃ 和 5.0℃。显示阈值时按三位整数展开,分别取百位、十位、个位。这样整个显示流程不涉及浮点运算,代码体积小,仿真和实物行为一致。
4.3 报警状态机:上限、下限、正常三态切换
报警逻辑不能只判断“超过阈值就响”,否则温度在阈值附近抖动时蜂鸣器会反复开关。代码里用一个枚举变量保存当前状态,只有状态变化时才更新输出。
| 状态 | 触发条件 | 蜂鸣器 | 红色 LED | 蓝色 LED |
|---|---|---|---|---|
| 正常 | TL ≤ T ≤ TH | 灭 | 灭 | 灭 |
| 超上限 | T > TH | 响 | 亮 | 灭 |
| 低限 | T < TL | 响 | 灭 | 亮 |
typedef enum { ALARM_NONE, ALARM_HIGH, ALARM_LOW } alarm_state_t; alarm_state_t alarm_mode = ALARM_NONE; int th_high = 350; // 上限 35.0℃ int th_low = 50; // 下限 5.0℃ void check_alarm(void) { alarm_state_t new_mode; if (current_temp > th_high) new_mode = ALARM_HIGH; else if (current_temp < th_low) new_mode = ALARM_LOW; else new_mode = ALARM_NONE; if (new_mode != alarm_mode) // 仅在状态变化时刷新输出 { alarm_mode = new_mode; switch (alarm_mode) { case ALARM_NONE: BEEP = 0; LED_RED = 0; LED_BLUE = 0; break; case ALARM_HIGH: BEEP = 1; LED_RED = 1; LED_BLUE = 0; break; case ALARM_LOW: BEEP = 1; LED_RED = 0; LED_BLUE = 1; break; } } }阈值调节用两个按键实现:第一个按键切换正在设置的对象(上限还是下限),第二个按键数值加 1。按键都有机械抖动,消抖用 10ms 延时加电平二次确认。这样设计的优点是报警状态机与按键扫描解耦,后面想接矩阵键盘或者旋转编码器,只需要改 key_scan 这一个函数。
5. 多采集端组网扩展与仿真排错检查表
5.1 从单采集端扩展到多节点:帧结构加地址
单采集端的帧结构里没有地址字段,扩展成多个采集端后,显示端无法分辨数据来自哪一路。常见做法是在原有帧的“类型”字节后增加一个node_id字段,帧长从 7 字节变成 8 字节:
| 字段 | 字节数 | 取值说明 |
|---|---|---|
| 帧头 | 2 | 0xAA 0x55 |
| 长度 | 1 | 这里为 6 |
| 类型 | 1 | 0x10 温度上报,0x11 心跳 |
| 节点地址 | 1 | 1~8,对应不同采集端 |
| 温度 | 2 | int16 小端,单位 0.1℃ |
| 校验 | 1 | 前 7 字节累加和 |
多节点时不能让所有采集端同时往同一条串口线上发数据,TTL 电平多发送器共线会互相驱动。常见做法是显示端作为主机轮询:先发一帧请求0xAA 0x55 0x02 0x80 node_id CRC,对应节点收到请求后延时若干毫秒再上报温度,避免多个节点同时响应。仿真里每个采集端各用一个独立标签通道也是可行方案,但可扩展性不如轮询,实物无线模块本身就是半双工共享信道,轮询反而是更接近真实的设计。
5.2 三个高频仿真故障和检查点
第一个是 DS18B20 始终显示 85.0℃。85℃ 是上电默认值,说明复位或时序根本没走通。按顺序检查:DS18B20 数据脚到 VCC 之间有没有 4.7k 上拉电阻;单片机的晶振频率是否和代码延时假设一致;delay_us(500)这些延时的实际时间是否达标。Proteus 里直接删掉上拉电阻再仿真,温度立即变成 85℃,可以快速验证上拉的作用。
第二个是 LCD1602 屏幕出现一堆黑块。先确认初始化序列是不是严格按 0x38、0x0C、0x06、0x01 顺序发送,中间延时是否足够;再看 LCD_RW 是不是接到了地或低电平,RW 悬空会导致读写状态不确定。对比度引脚 VL 在仿真里接一个 1k 电位器,滑动到中间位置通常显示最清晰。
第三个是无线帧偶发丢字节或显示乱跳。接收端中断里只做收数和置标志,帧解析全部挪到主循环完成;主循环凡是超过 10ms 的延时都要确认串口中断没有因为EA=0被全局关掉。还可以把波特率从 9600 降到 4800 验证是不是误差累积导致,如果降速后正常,那就是晶振频率和 TH1 重装值不匹配。
一个省时间的验证技巧是双击 DS18B20 模型,在 Temperature 属性里分别输入 30、40、0,观察显示端温度变化和报警状态切换是否与表格一致。把多节点帧结构和报警状态机写进自己的模板工程,下次再接 STC 单片机或者加继电器执行机构,只需要替换底层驱动,协议和状态机部分完全不用动。
本文还有配套的精品资源,点击获取