简介:这是一套基于Proteus仿真的单片机无线温度采集报警系统设计完整资料,适合单片机学习者、电子爱好者以及正在准备课程设计或毕业设计的学生使用。系统以51单片机为控制核心,配合DS18B20温度传感器、LCD1602液晶显示模块和模拟无线传输手段,完成温度采集、实时显示与超阈值报警等功能,帮助读者理解无线温度监测装置的电路构成与程序开发流程。压缩包共包含40个文件,主要提供仿真电路工程、C语言源程序、HEX固件、Keil工程配置、原理图截图以及备份文件等,可对照查看发射端与接收端的电路连接及程序逻辑,类型覆盖了从仿真调试到程序烧录的各个环节。整体大小仅442KB,小巧齐全,便于快速下载和二次修改。目前已有155位学习者下载使用,对需要快速搭建无线温度采集报警系统样例的读者而言,是一份值得参考的实践性资源。
1. 单片机仿真无线温度报警系统,先想清楚要解决什么问题
调试室里要盯的设备柜、实验室烘箱、温室大棚,测温点往往藏在角落或高处,拉线既碍事又难维护。这个标题要解决的正是这类问题:一块板子采集温度,无线发出去,另一块板子接收、显示并报警。所谓“基于单片机仿真的protues无线温度采集报警系统设计”,拆开就是Proteus仿真电路、无线温度收发、Keil C51源代码三件事。标题里把Proteus写成了protues,是这类搜索里非常常见的笔误,按这个写法也能搜到同类资源。这套方案适合单片机课设/毕设、正在学51单片机的入门工程师,以及想快速做出一个带无线链路的原型演示的人。项目启动前先给一句结论:无线收发本身并不难,真正的工时落在DS18B20单总线时序和nRF24L01寄存器配置上,这两块后面会重点展开。
2. proteus无线温度采集电路的元件选型与接线方法
2.1 为什么是DS18B20和nRF24L01,而不是其他方案
做单片机仿真项目选器件,第一原则是:Proteus元件库里有可用模型、Keil里有人验证过、接线能收尾。这个标题里最常用的组合是DS18B20做温度采集、nRF24L01做无线收发,原因很具体。
DS18B20是单总线数字温度传感器,三根线就能工作,内部直接出12位温度结果,分辨率0.0625℃。相比LM35加ADC0832的组合,它省掉了一条ADC采集通道、电压换算和参考电压设置;相比热敏电阻,它省掉了阻值到温度的指数换算。更重要的是,在Proteus里双击DS18B20元件,可以直接拖动温度滑块模拟环境温度变化,这一条对后面验证报警阈值和无线链路非常关键,选其他传感器很难做到这种仿真便利度。
nRF24L01是2.4GHz频段的SPI接口无线收发芯片,Proteus元件库中按NRF24L01搜索能拿到仿真模型。SPI口上的时钟、数据引线在仿真时是实打实翻转的,能清楚看到通信过程。部分资料里推荐的315M/433M无线模块在Proteus中没有对应的射频仿真模型,只能停留在逻辑示意层面;而nRF24L01能完成真实寄存器读写,对学习状态机帮助更大。
MCU选择AT89C51或AT89C52,晶振12MHz,这是51单片机Proteus仿真最稳的组合。Keil C51默认工程模板对这两个芯片兼容性最好,网上绝大多数51单片机例程也是按12MHz机器周期写的,延时函数可以直接套用。报警执行部分用BUZZER蜂鸣器和LED灯指示,显示部分用LM016L,这个型号是Proteus里1602字符液晶的标准模型。
下表是本方案的必备元件清单:
| 元件 | Proteus搜索关键词 | 数量 | 用途 |
|---|---|---|---|
| 单片机 AT89C51 | AT89C51 | 2 | 一块发送,一块接收 |
| 温度传感器 DS18B20 | DS18B20 | 1 | 采集环境温度 |
| 无线模块 nRF24L01 | NRF24L01 | 2 | 无线温度数据收发 |
| 液晶屏 LCD1602 | LM016L | 1 | 接收端显示温度和阈值 |
| 蜂鸣器 | BUZZER | 1 | 超限声音报警 |
| LED指示灯 | LED-RED | 1 | 超限灯光报警 |
| 晶振/电容/电阻排 | CRYSTAL、CAP、RESPACK-8 | 若干 | 最小系统和上拉 |
2.1节的内容就是把元件的选型理由和仿真优势说清楚,接下来看两种板卡的实际接线。
2.2 发送端与接收端仿真电路接线对照
Proteus工程建议建一张图,左右各放一块AT89C51,分别作为发送端和接收端,运行起来能同时看到两边状态,方便判断无线链路是否打通。如果做成两个独立工程,仿真运行时还要来回切换窗口,排查问题效率低。
发送端要接的东西不多:DS18B20的数据脚接P2.0,信号线对VCC接一颗4.7kΩ上拉电阻,保证单总线空闲状态是高电平。nRF24L01用P1口驱动,接法如下表:
| nRF24L01引脚 | 单片机引脚 | 说明 |
|---|---|---|
| CE | P1.0 | 收发模式切换 |
| CSN | P1.1 | SPI片选,低有效 |
| SCK | P1.2 | SPI时钟 |
| MOSI | P1.3 | 主机输出从机输入 |
| MISO | P1.4 | 主机输入从机输出 |
| IRQ | 悬空 | 代码里用查询方式,不接外部中断 |
| VCC / GND | 5V / 地 | Proteus模型接5V,实物模块需3.3V |
接收端除了同样接好nRF24L01之外,还要接LCD1602和报警电路。LCD1602的DB0到DB7接P0口,P0是开漏结构,必须接一个8位排阻上拉,否则屏幕上会出现满屏亮点块。RS接P2.5,RW直接接地,EN接P2.6,蜂鸣器接P2.7,指示灯接P2.4。报警器件放在P2口而不是P0口,是因为P2口是准双向口,输出高电平能力比P0强,驱动蜂鸣器这类器件更可靠。
两边的单片机最小系统也要画全:31脚EA接VCC,19脚和18脚之间接12MHz晶振,两个引脚各接33pF对地电容,9脚复位端接10μF电容到VCC、1kΩ电阻到地。这里最容易漏的是EA引脚,仿真里如果EA浮空,单片机默认从外部程序存储器取指,烧进去的hex压根不会执行。
2.3 Proteus仿真下载的3个必要配置
第一,用Keil编译出hex文件后,在Proteus中双击单片机,把hex路径填到Program File栏。填完之后单片机上会出现一个绿色小方块标记,表示固件已经挂载。
第二,双击晶振元件,确认频率是12MHz。Proteus默认新建工程的晶振频率经常是1MHz,如果不改,DS18B20的480μs复位时序、60μs写时序全部错位,最典型的症状就是温度固定读出85℃。
第三,运行仿真后,双击DS18B20模型,会弹出温度设置滑块,拖动滑块改温度,观察接收端LCD数值和报警输出是否跟随。拖到阈值以上,蜂鸣器应该动作;拖回阈值以下,蜂鸣器解除。
提示:部分版本的nRF24L01模型在Proteus高速动画档位下刷新会滞后,表现为接收端延迟几秒才更新。遇到这种情况,在System菜单的Animated Options里把仿真速度调回中档,不影响调试结论。
3. Keil C51源代码:温度采集与nRF24L01无线收发
3.1 DS18B20单总线时序与读温度源码
DS18B20所有通信都发生在单总线上,任何一次操作流程都是“复位→ROM命令→功能命令→读写数据”。12MHz晶振下,单片机机器周期是1μs,代码用_nop_()和循环变量来生成所需延时。先看复位和应答检测:
#include <reg51.h> #include <intrins.h> sbit DQ = P2^0; // DS18B20 数据线 void delay_us(unsigned int us) { while (us--) _nop_(); // 12MHz 下 _nop_() 约 1us } unsigned char ds18b20_reset(void) { unsigned char presence; DQ = 0; delay_us(500); // 复位脉冲持续 480us 以上 DQ = 1; delay_us(60); // 释放总线,等从设备应答 presence = DQ; // DS18B20 拉低总线表示在线 delay_us(240); return presence; // 返回0表示设备在位 }这段代码的逻辑是:主机先将总线拉低480μs以上,制造一个复位脉冲;然后释放总线,此时总线由上拉电阻拉回高电平。DS18B20检测到这个下降沿之后,会在15μs到60μs之间主动拉低总线60μs到240μs,这个低电平就是应答信号。presence = DQ这条语句读到的值,低电平表示传感器在线,高电平表示没接上或接线错误。
写命令和读温度的代码是时序里最容易出错的部分:
void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); _nop_(); // 拉低约 2us,写时隙开始 DQ = dat & 0x01; // 写 1 就拉高,写 0 保持低 delay_us(60); // 保持 60us DQ = 1; // 结束该时隙 dat >>= 1; } } unsigned char ds18b20_read_byte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { dat >>= 1; DQ = 0; _nop_(); _nop_(); // 读时隙起始信号 DQ = 1; delay_us(5); // 释放总线后等 5us 再采样 if (DQ) dat |= 0x80; delay_us(60); // 等待时隙结束 } return dat; } unsigned int ds18b20_read_temp(void) { unsigned char tl, th; unsigned int temp; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); // 12 位分辨率转换约 750ms ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 tl = ds18b20_read_byte(); // 低字节 th = ds18b20_read_byte(); // 高字节 temp = (th << 8) | tl; return temp; // 实际温度 = temp * 0.0625 }代码里的0xCC是跳过ROM命令,适用于单传感器挂载,省去读64位序列号;0x44启动一次温度转换;0xBE读取暂存器前两个字节。转换时间在12位分辨率下典型值是540μs到750ms不等,代码里固定等待750ms最稳妥。读回的16位结果是带符号数,正温度乘以0.0625得到摄氏温度;负温度需要额外取反加一处理,仿真阶段可以先用正温度验证链路。
写1时隙要求主机拉低总线后1μs到15μs内释放总线,代码里两个_nop_()约2μs;写0时隙要全程拉低60μs。读时序时,主机必须主动拉低总线至少1μs再释放,之后从机驱动总线告诉主机当前bit是0还是1,主机应在15μs内完成采样。delay_us(5)之后读引脚,正好落在从机数据有效窗口内。
3.2 nRF24L01寄存器配置和发送流程
nRF24L01工作在SPI从机模式,51单片机通过4根线控制它。通信前要做的第一件事是把收发双方配置成一致的信道、地址和负载长度。核心寄存器配置对照:
| 寄存器 | 地址 | 配置值 | 作用 |
|---|---|---|---|
| CONFIG | 0x00 | 0x0E | 上电、使能CRC、16位校验 |
| EN_AA | 0x01 | 0x01 | 打开通道0自动应答 |
| EN_RXADDR | 0x02 | 0x01 | 只使能接收通道0 |
| SETUP_AW | 0x03 | 0x03 | 收发地址宽度5字节 |
| SETUP_RETR | 0x04 | 0x1A | 重发延时500μs,最多10次 |
| RF_CH | 0x05 | 40 | 工作信道,2440MHz |
| RF_SETUP | 0x06 | 0x0E | 2Mbps速率,0dBm发射功率 |
| RX_PW_P0 | 0x11 | 4 | 每个数据包有效载荷4字节 |
| STATUS | 0x07 | 0x70 | 读状态后写1清除中断 |
配置函数和发送一包数据的核心代码如下:
sbit CE = P1^0; sbit CSN = P1^1; sbit SCK = P1^2; sbit MOSI = P1^3; sbit MISO = P1^4; #define TX_PLOAD_WIDTH 4 unsigned char code TX_ADDRESS[5] = {0xE7, 0xE7, 0xE7, 0xE7, 0xE7}; unsigned char spi_transfer(unsigned char dat) { unsigned char i, recv = 0; for (i = 0; i < 8; i++) { MOSI = (dat & 0x80) ? 1 : 0; dat <<= 1; SCK = 1; _nop_(); _nop_(); recv <<= 1; if (MISO) recv |= 0x01; SCK = 0; } return recv; } void nrf24l01_write_reg(unsigned char reg, unsigned char val) { CSN = 0; spi_transfer(0x20 | reg); // 写寄存器命令 spi_transfer(val); CSN = 1; } void nrf24l01_config(void) { CE = 0; nrf24l01_write_reg(0x00, 0x0E); // CONFIG 上电 nrf24l01_write_reg(0x01, 0x01); // EN_AA nrf24l01_write_reg(0x02, 0x01); // EN_RXADDR nrf24l01_write_reg(0x03, 0x03); // SETUP_AW nrf24l01_write_reg(0x04, 0x1A); // SETUP_RETR nrf24l01_write_reg(0x05, 40); // RF_CH nrf24l01_write_reg(0x06, 0x0E); // RF_SETUP nrf24l01_write_reg(0x11, TX_PLOAD_WIDTH); // 写入发送地址和接收通道0地址 CSN = 0; spi_transfer(0x20 | 0x10); // TX_ADDR for (i = 0; i < 5; i++) spi_transfer(TX_ADDRESS[i]); CSN = 1; CSN = 0; spi_transfer(0x20 | 0x0A); // RX_ADDR_P0 for (i = 0; i < 5; i++) spi_transfer(TX_ADDRESS[i]); CSN = 1; CE = 1; delay_ms(2); }发送一包温度数据的函数需要按照芯片手册的状态机顺序执行:
unsigned char nrf24l01_send(unsigned char *buf, unsigned char len) { unsigned char status; CE = 0; // 先进入待机模式 CSN = 0; spi_transfer(0xA0); // W_TX_PAYLOAD 写发送载荷 for (i = 0; i < len; i++) spi_transfer(buf[i]); CSN = 1; CE = 1; // 拉高 CE 至少 10us,启动发送 delay_us(15); CE = 0; status = spi_transfer(0xFF); // NOP 读状态 while (!(status & 0x20)) { // 等待 TX_DS 置位 status = spi_transfer(0xFF); if (status & 0x10) { // MAX_RT 重发超限 spi_transfer(0xE1); // FLUSH_TX 清空发送缓冲 break; } } spi_transfer(0x20 | 0x07); // 清 STATUS 中断位 spi_transfer(0x70); return status; }发送状态机的关键在于CE引脚。芯片上电配置期间CE必须为低;写完整包数据后,CE拉高至少10μs,芯片才会从待机进入发送状态;发送完成后CE拉低回到待机,等待下一包。代码里等待TX_DS位(STATUS的第5位)最可靠,它表示数据包已经发射出去;如果等待期间MAX_RT先置位,说明自动重发超过10次,要清空FIFO再继续。发射功率在RF_SETUP里配置成0dBm,2Mbps速率会缩短空中传输时间,减少碰撞概率,适合Proteus仿真这种多节点同时工作的场景。
3.3 接收端主流程:无线接收、LCD显示和报警判定
接收端的任务比发送端多一项报警输出。主循环先读状态寄存器,检测到RX_DR置位后读出4字节有效载荷,温度占前两字节,后两字节可以留作阈值或备用标志。下面是可以直接改用的接收主函数框架:
void main(void) { unsigned char rxbuf[4]; unsigned char status, i; unsigned int temp; unsigned int threshold = 40; // 默认报警阈值 40℃ lcd1602_init(); nrf24l01_config_rx(); // PRIM_RX=1,进入接收模式 CE = 1; // 接收模式 CE 保持高 while (1) { status = spi_transfer(0xFF); // NOP 读状态 if (status & 0x40) { // RX_DR 接收数据就绪 CSN = 0; spi_transfer(0x61); // R_RX_PAYLOAD for (i = 0; i < 4; i++) rxbuf[i] = spi_transfer(0xFF); CSN = 1; spi_transfer(0x20 | 0x07); spi_transfer(0x70); // 清中断 temp = rxbuf[0] | (rxbuf[1] << 8); display_temperature(temp); alarm_scan(temp, threshold); } } }程序进入接收模式前,必须保证CONFIG寄存器的PRIM_RX位是1,并且CE引脚保持高电平。51单片机往CONFIG写0x1E就能完成“上电+CRC16+接收模式”三个动作。读取负载时用0x61命令,每读一个字节主机要往SPI发送一个0xFF占位字节,因为SPI是全双工,主机不发时钟从机就不会输出后续数据。温度数据两字节高位在前,与发送端打包顺序一致;如果收发两端高低字节顺序不同,接收端显示会出现跳变到几千的异常值,排查时先看这一条。
4. 双端仿真联调、报警逻辑与10个排查点
4.1 在Proteus里加载两个hex的联调步骤
发送端和接收端的单片机程序要分别编译成两个hex文件。Proteus图纸上两块AT89C51各自加载固件:发送端烧发送程序,接收端烧接收程序。双击单片机在Program File里选择对应hex,然后点击左下角运行按钮。
联调时先静态验证发送端:双击DS18B20,把温度从25℃拖到80℃,观察发送端程序有没有卡死。如果温度从25℃往上走,接收端LCD没有任何变化,优先检查两块单片机上的nRF24L01三个关键参数:RF_CH的值是否一致、地址数组是否一致、RX_PW_P0和发送端负载长度是否一致。Proteus不会因为无线参数不匹配而报错,现象就是静默收不到。
接收端LCD如果能显示温度,再把发送端温度拖过40℃,观察蜂鸣器和LED。如果只有显示没有报警,问题在阈值判定逻辑,往下看报警去抖。
4.2 报警逻辑必须写的3个细节
第一是去抖。温度在阈值边缘小幅波动时,如果检测到一次超限就立刻报警,蜂鸣器会反复通断,仿真里听起来就像接触不良。常见的做法是连续3次采样都超限才触发报警。
第二是回差。报警触发后,只有温度低于“阈值减回差值”才解除报警。比如阈值40℃,回差2℃,则温度降到38℃以下才停止报警。这样避免温度在39.5℃附近徘徊时报警频繁开关。
第三是蜂鸣器驱动不要用延时函数。蜂鸣器响的频率由定时器中断控制,主循环负责判断条件并改变报警状态,两者分离,否则按键响应或LCD刷新会被阻塞。
#define THRESHOLD 40 #define HYSTERESIS 2 bit alarm_state = 0; unsigned char alarm_count = 0; void alarm_scan(unsigned int temp) { if (alarm_state == 0) { if (temp >= THRESHOLD * 16) { // 温度是0.0625℃精度 if (++alarm_count >= 3) { alarm_state = 1; alarm_count = 0; } } else { alarm_count = 0; } } else { if (temp <= (THRESHOLD - HYSTERESIS) * 16) { alarm_state = 0; // 温度回落解除报警 } } if (alarm_state) { P2_7 = ~P2_7; // 定时器中断里翻转,产生方波 } else { P2_7 = 1; } }这段代码里的阈值乘16,是因为DS18B20的原始返回值是12位定点数,实际温度等于数值乘以0.0625,也就是除以16。写成threshold * 16,避免使用浮点运算,51单片机算浮点会拖慢主循环。蜂鸣器引脚P2_7的翻转动作放在定时器0中断里,当报警状态为1时,中断服务程序里执行BEEP = ~BEEP;,就能输出1kHz左右的方法驱动蜂鸣器响;报警解除后P2_7输出高电平,蜂鸣器停。
4.3 仿真高频故障按优先级排序的排查表
仿真过程中遇到现象和原因对不上的情况,先看下表,按表格顺序检查成本最低。
| 故障现象 | 根本原因 | 处理方式 |
|---|---|---|
| 运行后单片机不工作 | EA引脚浮空 | EA接VCC |
| 温度一直显示85℃ | 晶振频率不是12MHz或延时函数不对 | 检查晶振和复位时序 |
| LCD满屏亮点块 | P0口无上拉 | 接RESPACK-8排阻到VCC |
| 无线完全收不到 | 收发信道、地址、负载长度不一致 | 逐个核对RF_CH、地址数组、RX_PW_P0 |
| TX_DS始终不置位 | CE高电平时间不够 | CE拉高保持15μs以上 |
| 蜂鸣器在阈值附近反复响 | 没有回差逻辑 | 加入HYSTERESIS的判断 |
| 接收端温度跳变到几千 | 高低字节组包顺序不一致 | 检查收发两端字节序 |
| 改完代码仿真没变化 | 没有重新编译或没重新加载hex | Keil重新编译后重新加载 |
85℃这个问题值得单独说明:DS18B20上电后暂存器中的温度寄存器默认值是0x0550,正好对应85℃。如果读到固定85℃,说明传感器通信根本没建立,读到的只是默认值。遇到它不要怀疑传感器坏了,先量复位时序。
5. 验证无线链路与仿真到实物改造的3个实用技巧
5.1 用虚拟示波器看SPI时序,确认发送状态
Proteus左侧工具栏的虚拟仪器里有OSCILLOSCOPE,把SCK、MISO、CE三根线接过去。点击运行,发送端每次发送时观察SCK是否有8个脉冲簇,CE是否拉高超过10μs,MISO线上是否有响应数据。这套方法能直接判断SPI通信是否建立,比盲目改寄存器值快得多。如果SCK完全没有脉冲,检查CSN是否为低、SPI初始化代码是否在main函数开头执行。
5.2 把温度组包成Modbus RTU帧,方便接上位机
这个标题的系统如果只停留在仿真里,价值有限。把温度数据进一步封装成Modbus RTU帧,就能让接收端和组态软件、串口调试助手直接对话。温度寄存器放在30001保持寄存器,功能码03,CRC16校验。下面这段CRC计算代码可以直接复用:
unsigned int crc16_modbus(unsigned char *pdata, unsigned char len) { unsigned int crc = 0xFFFF; unsigned char i, j; for (i = 0; i < len; i++) { crc ^= pdata[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }组帧时按“从机地址+功能码+寄存器地址高字节+低字节+寄存器数量高字节+低字节+CRC低字节+CRC高字节”的顺序发送,8个字节一帧。CRC计算时先算前6个字节,算完把低字节放前面、高字节放后面。帧接收端的判断标准是:地址正确、功能码正确、CRC比对通过,三者满足才解析数据。这是Modbus单片机帧接收数据程序的通用套路。
5.3 实物移植要注意的三个改动点
仿真图里nRF24L01接5V没问题,但实物模块必须3.3V供电,5V供电会烧毁模块。51单片机P1口输出5V电平,和3.3V的模块通信时,MOSI、SCK、CSN、CE四根线各串一个100Ω到1kΩ的电阻限流,MISO是模块输出,可以直连51。第二,实物布局时nRF24L01下方不要铺铜,天线下方的PCB区域留空,否则收发距离明显缩小;仿真里不存在这个问题,最容易在实物阶段被忽略。第三,如果换上STC15系列1T单片机或者STM32,延时函数必须重新标定,DS18B20的单总线时序靠的是微秒级延时,12MHz下用_nop_计数的代码在1T芯片上整体快8到12倍,直接运行会稳定读85℃;改成定时器或示波器标定后的延时函数再继续调试。
本文还有配套的精品资源,点击获取