简介:这是一份51单片机无线多路温度采集系统设计的完整工程资料,面向嵌入式初学者、电子设计爱好者及课程设计人员,可用于工业自动化、环境监控等多点测温场景,主要解决多路温度实时采集、无线传输与主机显示等问题。压缩包共22个文件,包含10个C源文件、10个H头文件及2个SCH原理图,代码与硬件设计对应,便于理解通信控制机与采集节点的整体架构。资源仅36KB,体积小巧,重点在于代码逻辑与原理图参考,覆盖温度传感器驱动、无线通信协议、多路采集调度等核心代码。已有365人浏览学习,适合需要快速上手51单片机多路采集与无线通信开发的读者。从中可获取通信控制机与采集节点的完整程序框架、传感器接口驱动及主机与节点原理图,并参考其数据处理与显示逻辑,为实际项目或课程设计提供直接借鉴。
1. 51 单片机无线多路温度采集:拆开这套设计后,重点不是读温度,而是把多路数据对上号
用 51 单片机做无线温度采集,真正卡人的往往不是 DS18B20 读不出来,而是多路数据到了接收端之后分不清谁是谁。很多人在 Proteus 里仿真单点测温很顺利,一到真实无线模块就开始丢包、串数,甚至把 8 个节点的温度全部显示成同一个值。这套设计把采集节点代码、通信控制机代码和节点.Sch、主机.Sch 放在同一个压缩包里,正好把无线传感器网络的“终端 + 网关 + 上位机”三段关系压缩到了两个工程里。
通信控制机是接收端,也是调度端;采集节点是终端,负责把温度打包成无线帧发出去。51 单片机在这里承担本地控制,真正值得学的是单总线时序、无线收发参数还有轮询调度。适合正在做 51 单片机课程设计、基于 51 单片机的项目,以及准备从裸板点灯往无线组网方向迈一步的人。这套思路换到 STM32、ESP32 上依然复用,只是换一套寄存器而已。
2. 系统拆解与器件选型:通信控制机、采集节点和无线链路的分工
拿到压缩包后,第一件事不是打开 Keil 工程,而是先分清两个工程分别烧给哪块板子。从目录命名上已经能看出分工:code 下面有通信控制机代码和采集节点代码,sch 下面有节点.Sch 和主机.Sch。这里的通信控制机就是主机,放在接收端;采集节点放在现场测温位置。两者的关系不是对等的,主机主动发起查询,节点被动应答。
| 工程目录 | 角色 | 主要工作 |
|---|---|---|
| code/采集节点代码 | 终端节点 | 读取 DS18B20,打包温度帧,通过无线发送 |
| code/通信控制机代码 | 接收/控制端 | 无线接收,轮询节点,LCD 显示,串口上报 |
| sch/节点.Sch | 节点原理图 | 单片机最小系统、DS18B20、无线模块接口 |
| sch/主机.Sch | 主机原理图 | 单片机、无线模块、显示电路、串口电路 |
这种一问一答的轮询模式,比所有节点主动上报要稳得多。主动上报的问题是 2.4G 同频段多节点同时发送会产生碰撞,轻则 CRC 错包,重则主机长时间收不到有效数据。而主机每次只查询一个节点,节点收到查询帧后返回本机温度,从协议层面就规避了冲突问题。后期如果要加节点,只需要在主机端把查询循环的数组长度改大,再给新节点分配一个不重复的 ID。
2.1 为什么温度传感器选 DS18B20 而不是 DHT11
这套系统标的是温度采集,传感器选型应该优先考虑接口简单和可重复性。DS18B20 是数字单总线器件,只占一个 I/O 口,支持多片并联,并且每片芯片内置 64 位唯一序列号。这个序列号在实际工程里可以用来做多路地址识别,即使同一个采集节点上挂多片传感器,也能通过 ROM 匹配区分每一路。DHT11 虽然便宜且能读湿度,但温度精度只有 ±2℃,采样周期最低 1 秒,做环境监控还够,做稍微精细点的曲线记录就差一些。
DS18B20 在 12 位分辨率下能把温度量化到 0.0625℃,对机房、仓库、温室这类场景足够用。51 单片机用普通 I/O 模拟单总线时序,只依赖定时器和几条延时指令,并不会给主频造成压力。唯一要注意的是单总线对时序敏感,初学者经常把 60us 读时隙和 15us 写时隙搞混,后面在代码章节会专门展开。
2.2 无线模块:2.4G 短距通信里的低成本方案
无线部分最常用的组合不是蓝牙,也不是 Zigbee,而是 nRF24L01+。它便宜、SPI 接口、功耗低,51 单片机用软件模拟 SPI 就能跑起来。蓝牙需要配对流程,从机要广播,主机要扫描,51 上面写起来比较绕;Zigbee 虽然组网能力强,但协议栈太重,对课程设计来说性价比不高。nRF24L01 更像一个无线管道,只要把数据帧写好,发送端和接收端频率、地址一致,就能稳定收发。
需要注意,nRF24L01 模块引脚电平是 3.3V,而普通 51 单片机 I/O 是 5V。很多现成模块自带电平转换,但如果是裸模块,CE、CSN、MOSI、SCK 这几个由单片机输出的引脚需要串联电阻或加电平转换,否则长期运行会烧模块。无线通信要对齐的参数包括射频频率、发射地址、接收地址、数据速率和 SPI 地址宽度,收发两端任何一个不匹配都会表现为收不到数据。
2.3 多路寻址:轮询比主动上报更适合 51 做主机
这套系统的“多路”既指多个采集节点,也指一个节点上可能挂载多个温度探头。主机端维护一个节点表,循环发送查询帧;采集节点收到查询后,上报本机编号和温度值。无线帧格式是两个工程共用的协议,定义方式如下:
/* 无线数据帧:节点与主机按同一份定义解析 */ typedef struct { unsigned char head; /* 0xA5 帧头 */ unsigned char node_id; /* 节点地址 1~255,0 保留 */ unsigned char cmd; /* 0x01 查询, 0x02 应答 */ unsigned char temp_h; /* 温度整数部分 */ unsigned char temp_l; /* 温度小数部分 */ unsigned char crc; /* 从 frame 头到 temp_l 的校验 */ } sys_packet_t;把 node_id 放在 cmd 前面,主机收到帧后可以先过滤帧头,再判断节点号,不用等整帧收完就能丢弃无关广播包。CRC 放在最后,接收端可以边收边算,收完后直接和最后一个字节比较。这个结构体在实际发送时会转换成一个 unsigned char 数组,避免出现字节对齐问题。如果不想用结构体,也可以直接定义成unsigned char tx_buf[6],可读性会差一些,但更容易让新手理解数据在无线链路上真正传输的样子。
3. 从节点.Sch 和主机.Sch 看硬件:引脚映射、电平与电源
原理图文件打开后,不要先看密密麻麻的连线,先看芯片型号和网络标号。节点板上通常是 51 单片机加 DS18B20 加无线模块排针,主机板上多出 LCD 和串口。每个 .Sch 里真正需要确认的是 I/O 口的分配,因为代码里的 sbit 宏定义必须和原理图网络标号一一对应。如果名字不一致,要么改代码,要么在 PCB 上飞线,代价完全不同。
3.1 采集节点电路:DS18B20 接口和最小系统
采集节点最小系统由晶振、复位电路和电源滤波组成,DS18B20 的信号线 DQ 通常接在 P3.7 或者其他带强上拉的 I/O 口。单总线要求 DQ 线上必须有 4.7kΩ 上拉电阻,否则总线无法回到高电平,传感器会频繁超时。很多人在面包板上省掉这个电阻,结果读回来的温度跳变非常严重。DS18B20 供电可以用 5V,也可以用 3.3V,但上拉电阻必须接在同一个电源域下。
| DS18B20 引脚 | 接线位置 | 说明 |
|---|---|---|
| VCC | +5V | 模块供电,兼容 3.3V |
| GND | GND | 与单片机共地 |
| DQ | P3.7 | 经过 4.7kΩ 上拉到 VCC |
如果节点板上要多接几个 DS18B20,就把所有 DQ 并联到同一个 I/O 口,靠 ROM 码区分。实际项目里不要在一个 I/O 口上并联超过 4 个传感器,因为单总线寄生电容会拉低时序边沿,超过 5 米线长时尤其明显。更稳妥的做法是每路单独占用一个 I/O,软件逻辑简单,排查故障也方便。
3.2 无线模块接口:真正需要接的只有 7 根线
无线模块如果采用 nRF24L01,10 个引脚里有一半可以按固定对应关系接到 51 单片机上。IRQ 中断引脚在很多工程里是不接的,主机通过读 STATUS 寄存器判断是否收到数据,这样能少接一根线,同时降低中断程序对 51 有限资源的占用。这里给出常见的引脚映射,等你打开节点.Sch 时可以逐项对照。
| nRF24L01 引脚 | 接 51 | 作用 |
|---|---|---|
| VCC | 3.3V | 模块电源,禁止直接接 5V |
| GND | GND | 必须共地 |
| CSN | P1.3 | SPI 片选,低电平有效 |
| CE | P1.2 | 发送/接收模式切换 |
| SCK | P1.1 | SPI 时钟 |
| MOSI | P1.0 | 单片机输出到模块 |
| MISO | P1.4 | 模块输出到单片机 |
接线表里的引脚宏定义是这段代码的核心,原理图网络标号变了,这里就要同步改。5V 单片机的 I/O 高电平是 5V,nRF24L01 的 I/O 耐压多数只有 3.6V,稳妥做法是在 CE、CSN、MOSI、SCK 四根线上各串联一个 1kΩ 限流电阻,或者直接把系统供电改成 3.3V 的 51 型号。
/* 根据 sch/节点.Sch 里的网络标号修改 */ sbit DQ = P3^7; /* DS18B20 数据线 */ sbit RF_CSN = P1^3; /* 无线模块 SPI 片选 */ sbit RF_CE = P1^2; /* 无线模块模式切换 */ sbit RF_SCK = P1^1; /* SPI 时钟 */ sbit RF_MOSI = P1^0; /* SPI 输出 */ sbit RF_MISO = P1^4; /* SPI 输入 */这段宏定义看起来简单,但很多下载下来的源码里引脚定义和实际原理图不符。拿到资源后,第一件事就是用 Find 功能在 main.c 里搜索sbit,把每个引脚名和原理图上的网络标号对一遍。
3.3 主机板:串口、LCD 和电平转换要匹配
主机板上的核心除了无线模块,还有串口输出和显示。串口芯片如果用的是 CH340,那么和单片机之间是 TTL 电平,可以直接连接;如果是 MAX232,则多出一组 ±12V 电平,连接电脑时要走 DB9 接口。通信控制机代码里的 UART 初始化必须和晶振匹配,最稳妥的是使用 11.0592MHz 晶振,因为 9600 波特率在这个频率下误差最小。用 12MHz 晶振跑 9600,积累到几个字节后就会出现乱码。
3.4 在 Proteus 里仿真时要拆开节点验证
很多人在 Proteus 里仿真 51 单片机 + DS18B20 时会发现读不到温度,原因是虚拟 DS18B20 对单总线时序的容差比真实芯片更严格,一个延时时间偏一点就会失败。更麻烦的是 Proteus 没有成熟的 nRF24L01 模型,硬仿真只能看到串口数据。我一般建议仿真时把采集节点的发送函数临时替换成串口发送,这样能独立验证 DS18B20 驱动和温度换算逻辑,再回到真实硬件上调无线链路。仿真只是辅助,不要指望它覆盖所有硬件问题。
4. Keil 工程组织与关键代码:单总线驱动、无线收发和主机轮询
这部分是整套代码的核心。通信控制机代码和采集节点代码虽然目录不同,但很多文件是成对出现的,尤其是 nRF24L01 底层,发送端和接收端的驱动虽然在寄存器配置上有差异,却必须保持时钟、地址、通道一致。下面从解压开始,把整个代码链路串起来。
4.1 解压后先看工程结构而不是直接编译
把压缩包解压后,先用命令确认目录层级,然后再打开 Keil 工程。很多初学者双击工程文件后报一堆 missing header,原因就是头文件路径没配置。
unzip 51单片机无线多路温度采集系统设计.zip -d temp_system cd temp_system find . -maxdepth 3 -type f | sort命令执行后可以看到码目录下通常会有 main.c、delay.c、ds18b20.c、nrf24l01.c、lcd1602.c 等文件。如果用的是 Keil5,打开工程后要在魔术棒里的 C/C++ 选项下,把 Include Paths 指向对应源码目录。注意 Keil5 必须安装 C51 版本的编译器,如果只装了 MDK,工程文件能打开但编译按钮是灰色的。
code/ ├── 通信控制机代码/ │ ├── main.c # 主机轮询和界面刷新 │ ├── nrf24l01.c # 无线接收模式驱动 │ ├── uart.c # 串口协议上报 │ └── lcd1602.c # 温度显示 └── 采集节点代码/ ├── main.c # 节点主循环 ├── ds18b20.c # 单总线温度驱动 ├── nrf24l01.c # 无线发送模式驱动 └── delay.c # 延时函数从这里就能看出一个工程边界:DS18B20 驱动只出现在采集节点代码里,通信控制机代码里没有;反过来,主机端的 LCD 和 UART 不会出现在节点工程里。如果下载的源码把两边的文件混在一起,先按这个原则拆开,再考虑编译。
4.2 DS18B20 驱动:复位、转换、读暂存器必须按顺序
DS18B20 驱动代码看起来不长,但三个步骤的顺序不能乱:复位、启动温度转换、读暂存器。读取温度时,如果跳过了 0x44 转换指令,读到的就是上一次转换结果,或者上电默认的 85℃。下面这段代码是采集节点工程里的标准读流程:
unsigned char ds18b20_read_temp(int *temp) { if (ds18b20_init()) return 1; /* 返回 1 表示总线无设备 */ ds18b20_write_byte(0xCC); /* 跳过 ROM,因为只有一个传感器 */ ds18b20_write_byte(0x44); /* 启动温度转换 */ delay_ms(750); /* 12 位分辨率典型转换时间 750ms */ ds18b20_init(); /* 第二次复位,准备读暂存器 */ ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); /* 读暂存器,从第 0 字节开始 */ *temp = ds18b20_read_byte(); /* 低字节 */ *temp |= (int)ds18b20_read_byte() << 8; /* 高字节 */ return 0; }这段代码里最容易改动的是delay_ms(750)。12 位分辨率转换时间是 750ms,如果现场节点多,这个等待会拖慢整个轮询周期。常见的优化是写入配置寄存器,把分辨率降到 9 位,转换时间缩短到 93.75ms,但温度量化步进会变成 0.5℃。另一个更实用的做法是让采集节点在空闲时不停循环启动转换,收到主机查询帧时直接读暂存器,这样主机端等待短,节点端温度也一直是最新值。
4.3 nRF24L01 发送:CE 拉高前要检查配置寄存器
无线发送的最小闭环代码通常是发送方把数据写入 TX FIFO,然后拉高 CE 触发发送,发送完成后 CE 拉低,回到待机。nRF24L01 的 SPI 写负载指令是WR_TX_PLOAD,收到 ACK 后会置 TX_DS 位。代码实现如下:
unsigned char rf_send_frame(unsigned char *buf, unsigned char len) { RF_CE = 0; /* 待机模式 */ RF_CSN = 0; /* 拉低片选,进入 SPI 操作 */ spi_write_byte(WR_TX_PLOAD); /* 写发送负载指令 */ for (unsigned char i = 0; i < len; i++) { spi_write_byte(buf[i]); /* 逐字节写入 */ } RF_CSN = 1; /* 结束 SPI 操作 */ RF_CE = 1; /* CE 拉高,切换到发送模式 */ delay_us(150); /* 保持至少 10us,常用 150us 更稳 */ RF_CE = 0; /* 发送完成,回待机 */ return (spi_read_byte(STATUS) & TX_DS) ? 1 : 0; }返回 1 表示发送完成且收到对方自动应答,返回 0 表示自动重发超时。如果自动重发光,说明接收端地址和本端 TX_ADDR 不匹配,或者接收端根本没开自动应答。参数上有个经验值:数据速率不要调到 2Mbps,2Mbps 虽然快但接收灵敏度变差,1Mbps 是性价比最高的挡位。重发延时设 500us,重发次数设 15 次,单包长度控制在 6~10 字节内,这套参数在室内环境通常能做到很低的丢包率。
4.4 通信控制机端的轮询调度:不要阻塞等待
主机端主循环是一张循环查询表。每轮遍历所有节点 ID,一个节点发完查询帧后,主机只等待一小段时间,等不到就标记离线,然后立刻切换下一个节点。这种调度方式避免了某个节点掉线后整条链路卡死。
void main(void) { unsigned char i; init_uart(); init_rf_rx(); /* nRF24L01 初始化成接收模式 */ while (1) { for (i = 1; i <= NODE_COUNT; i++) { send_query(i); /* 发出查询帧 */ if (wait_rf_packet(20)) { /* 最多等 20ms */ process_packet(i); /* 解析并保存该节点温度 */ } else { nodes[i].online = 0; /* 标记离线 */ } } lcd_refresh(); /* 统一刷新显示 */ uart_report(); /* 把所有节点温度打包到串口 */ } }这里有个细节:wait_rf_packet(20)内部不能使用 51 的delay_ms(20)傻等,应该用定时器 1 做一个超时标志,同时持续读 STATUS 寄存器。如果实在不想改,可以把 20ms 拆成 20 次 1ms 的轮询,每毫秒读一次IRQ引脚或 STATUS。这样主循环里还能插入按键扫描和显示刷新,不会出现按一个键没反应的情况。主机端串口上报最好按行输出,比如,node=1,temp=25.3,这样 C# 写无线温度监测系统时按行解析就很轻松。
4.5 把串口协议改成 Modbus 主站时的思路
如果现场需要接组态屏或工控机,主机端的串口协议可以改造成 Modbus 主站。51 单片机当作 Modbus 主站的常见做法是用功能码 03 读取保持在寄存器,把每个节点的温度值放到一个寄存器里。CRC16 计算是半表查还是全表查都行,但高低字节最容易写反,很多工控屏解析乱码都是这个问题。这个改造不用动无线协议,只要把 uart_report 里的纯文本输出改成 Modbus 帧即可,采集节点代码完全不用改。
5. 联调复盘:读 85℃、距离短和数据乱,先查这三个地方
无线温度系统联调时遇到的现象往往很集中,大部分问题可以从三个方向定位。第一个是 DS18B20 显示 85℃。这个现象不是传感器坏了,而是温度寄存器还没有转换完成,读到了上电默认值。DS18B20 上电后暂存器温度位默认是 0x0550,换算过来就是 85℃。只要在写 0x44 之后等够 750ms,或者轮询转换完成位,再读暂存器,数值就会更新。如果所有节点都显示 85,几乎可以断定程序里没有执行完整的转换流程。
第二个是无线模块近距离能通,拉远几米就丢包。常见原因不是天线问题,而是电源压降。nRF24L01 发射瞬间电流能达到几十毫安,如果和 LCD 背光、继电器共用一根 5V 线,电压跌落会直接让模块复位。主控板和无线模块最好分开供电,无线模块用独立 3.3V 的 LDO 供电,模块下方不要铺大片地铜皮,天线附近也不要走平行电源线。还有一点容易被忽略,就是模块排针接触不良,插在面包板上和焊在转接板上完全是两种效果。
第三个是上位机收到的数据和 LCD 显示对不上。出现这种情况时,先用串口助手抓主机原始输出,不要直接连 C# 上位机。以 9600 波特率,8 数据位,1 停止位,无校验打开串口,如果看到帧头不是 0xA5,说明字节错位;如果帧头对但 CRC 不对,说明采集节点和主机的帧定义不一致。串口助手把数据恢复成分行文本后,再用 C# 解析,问题会清楚很多。
string line = sp.ReadLine(); if (line.StartsWith(",")) { string[] fields = line.Split(','); int node = int.Parse(fields[0].Split('=')[1]); double temp = double.Parse(fields[1].Split('=')[1]); Console.WriteLine($"节点{node}: {temp}℃"); }这段解析逻辑的关键是主机按行输出,每条数据以逗号分隔,C# 端用 ReadLine 读取后逐字段拆分。实际项目里建议在节点号前加一个固定的 key,比如node和temp,这样即使以后增加湿度字段,老版本上位机也能跳过未知字段。串口读数据时注意缓冲区要清空,如果上一帧半包滞留,会被误判成新一帧;最简单的办法是串口收到换行符再开始处理,否则要加一帧超时判断。
本文还有配套的精品资源,点击获取