51单片机+NRF905无线温度采集系统:DS18B20与LCD1602完整实现

📅 发布时间:2026/10/17 7:22:22
51单片机+NRF905无线温度采集系统:DS18B20与LCD1602完整实现
简介基于51单片机的温度采集与NRF905无线传输完整工程包含发射机与接收机两套程序面向单片机爱好者、电子类专业学生及DIY人群可系统理解温度采集、无线收发、单片机编程的综合设计。资源包共34个文件以Keil工程文件、C源程序、DS18B20与LCD1602头文件、编译产物hex为主并保留大量bak备份与中间文件压缩包仅80KB可直接用Keil打开对照学习。已有172人学习使用建议使用者具备一定电子技术基础能看懂基础电路图。通过阅读发射与接收两端代码可掌握DS18B20温度传感器驱动、LCD1602显示控制、NRF905无线模块的发送接收流程结合代码中对外围器件的控制逻辑还能熟悉三极管、数码管、稳压器等常用器件的实际应用是一份结构清晰、便于复现的实战参考。1. 为什么用NRF905给51单片机做温度采集三个绕不开的取舍做51单片机温度采集堆功能不难难在把温度数据无线传出去。很多同学一开始会想用蓝牙或者WiFi模块但真把这套方案做进课程设计里就会发现模块初始化繁琐、透传协议啰嗦51这颗芯片的资源根本撑不起复杂的协议栈一旦误码调试起来完全是黑匣子。换用NRF905这类的433MHz无线收发芯片情况会简单很多SPI接口直连单片机收发一帧数据只要几十行C语言不依赖操作系统也不依赖串口透传固件。这套程序适合两类场景一类是课程设计或毕业设计要求展示“传感器采集—无线传输—接收显示”的完整链路另一类是实验室或小型仓库的温度点监控现场不方便拉线用两个51节点就能把温度从采集端搬到显示端。整个系统的核心思路很清晰发送节点用DS18B20采温度组帧后通过NRF905发射接收节点用另一片NRF905接收做帧校验后再送到LCD1602显示。下面按设计和落地顺序把每一块拆开讲。2. 总体架构设计双节点链路的硬件接线与NRF905配置要点无线通信项目最怕一开始就埋头写代码等板子焊完才发现引脚分配不合理或者模块配置不清楚。做这套方案第一步应该是把数据流向和引脚关系定死后面写程序就只是把流程图翻译成C代码。2.1 系统组成与数据流向一个采集节点一个接收显示节点整套系统拆成两个独立节点。发送节点也叫采集节点由三部分组成51单片机最小系统、DS18B20温度传感器、NRF905无线模块。接收节点则是51最小系统加NRF905模块外接一块LCD1602液晶屏。数据流向是一个单向链条DS18B20把温度读进发送节点的RAM组帧后交给NRF905发出接收节点的NRF905通过SPI把载荷读进单片机校验后格式化到LCD1602上。这里有一个值得注意的设计决定发送节点不需要LCD调试时用串口打印就够了。原因是无线调试时你真正关心的是“发出的帧内容对不对”把温度值直接通过串口发到电脑上看比在采集端加液晶更直观也能省掉一个模块的成本。接收节点放在手边液晶屏实时刷新天然就承担了人机交互的角色。如果后续要扩展成多点采集只需要在发送帧里加一个设备ID字段接收端按ID分别刷新显示区域即可这套结构不用推倒重来。2.2 硬件接线NRF905的SPI引脚与DS18B20/LCD1602的分配NRF905是一个SPI从机51单片机作为主机控制它。软件模拟SPI比硬件SPI更适合51这类主频不高的芯片因为你可以完全掌控时钟极性和采样点。引脚分配建议遵循一个原则把NRF905的SPI四根线放在同一个P口上方便批量操作DR引脚接外部中断或者普通IO都行轮询方案用普通IO就够了。NRF905引脚功能接51单片机SCKSPI时钟P1.0MOSI主机输出P1.1MISO主机输入P1.2CSN片选低有效P1.3TRX_CE收发模式切换P1.4TX_EN发送使能P1.5PWR_UP上电模式P1.6DR数据就绪P3.2DS18B20是单总线器件数据线接P2.0注意这根线上要接一个4.7kΩ上拉电阻否则时序完全读不出温度。LCD1602的分工是P0口做8位数据总线RS接P2.1RW接P2.2EN接P2.3。需要提醒的是NRF905模块供电是3.3V而51单片机是5V多数NRF905模块的IO引脚能容忍5V电平可以直接连接但保险起见最好确认一下你手上模块的说明书有些廉价的二手模块IO引脚没有做电平转换长期直接驱动会有隐患。2.3 NRF905寄存器配置10字节配置表如何决定频点和发射功率NRF905内部有一份配置寄存器总共10个字节负责频段、发射功率、CRC校验、地址宽度这些关键参数。程序上电后第一件事就是把这份配置写进去写配置的命令是0x00后面顺序跟10个配置字节。下面的代码是发送和接收节点共用的配置段。unsigned char code nrf905_cfg[10] { 0x18, // CH_NO频点选择433MHz 频段内的一个通道 0x04, // 关闭自动重发方便逐帧观察 0x0F, // 发射功率设为最高档 0xE0, // 地址宽度 4 字节使能 8bit CRC 校验 0x40, // 外部晶振 4MHz时钟输出开启 0x00, // 剩余字节按模块手册默认值保留 0x00, 0x00, 0x00, 0x00 }; void nrf905_write_config(void) { unsigned char i; CSN 0; // 拉低片选启动一次 SPI 事务 spi_write_byte(0x00); // WC 命令表示写配置寄存器 for (i 0; i 10; i) { spi_write_byte(nrf905_cfg[i]); } CSN 1; // 拉高片选结束事务 }写配置的命令是0x00十个字节必须按顺序写不能跳字节。CH_NO这个字节决定载波频率如果你的环境里433MHz频段干扰严重改这个字节就能换频点两个节点必须保持一致否则收不到。0xE0这一字节同时管了地址宽度和CRC收发两端的配置必须完全一致差一个bit都白搭。配置表写进芯片之后最好再读一遍验证。读配置的命令是0x10后面跟10个空字节时钟从MISO上读回数据逐字节比对。很多同学遇到“写进去的配置没生效”的问题实际上就是SPI时序有问题写进去的就是错误数据读回比对是定位这类问题最快的办法。2.4 通信协议起点地址、帧长、CRC和ShockBurst收发流程NRF905用的是ShockBurst模式它的工作方式可以理解成发射端先把地址和载荷通过SPI写入芯片内部FIFO然后拉高TRX_CE芯片自动完成加前导码、加CRC、调制发射接收端收到有效帧后硬件自动做地址匹配和CRC校验匹配通过就把DR引脚拉高通知单片机来读。对开发者来说你不需要关心射频调制细节但要理解两个状态机的切换。发送状态机的顺序是PWR_UP拉高TX_EN置1TRX_CE置0先写目标地址再写载荷数据最后TRX_CE拉高触发发射等待DR拉高表示这一帧已经发出。接收状态机则简单很多PWR_UP拉高TX_EN置0TRX_CE置1然后一直等DR上升沿。注意DR引脚在两种模式下都有效发送完成和接收完成都会拉高它程序里要根据自己处于什么模式去解读这个信号。帧长方面NRF905单帧最大载荷是32字节这套方案只用了6字节完全够用。地址匹配是硬件完成的配置成4字节地址后只有地址完全相同的两个模块才能互通这本身就是第一道过滤。下一章开始写发送节点的具体代码。3. 发送节点实现DS18B20采集、数据组帧与ShockBurst发射发送节点是整套系统的数据源头。这一章按数据流动的顺序拆成四步先把DS18B20的时序搞定再读温度然后组帧最后通过NRF905发出去。任何一步出问题接收端显示的都是无意义数据。3.1 DS18B20单总线时序复位、写位、读位的精确控制DS18B20这种单总线器件对时序非常敏感。它的通信基础是复位脉冲加存在脉冲然后通过读写时隙来交换数据。51单片机没有硬件单总线控制器全部靠延时函数模拟。这里的关键是关闭中断如果延时过程中被定时器打断时序就会拉长传感器直接不响应。sbit DQ P2^0; // 单总线数据引脚 // 复位操作拉低 480us 以上然后释放总线等待存在脉冲 bit ds18b20_reset(void) { bit presence; DQ 0; delay_us(500); // 主机拉低复位脉冲 DQ 1; delay_us(60); // 释放总线等待传感器应答 presence DQ; // 读到 0 表示传感器存在 delay_us(400); // 等待存在脉冲结束 return ~presence; } // 写一个字节写 1 和写 0 的时序宽度不同 void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; if (dat 0x01) { delay_us(60); // 写 1拉低后立即释放 DQ 1; } else { delay_us(60); // 写 0持续拉低 60us DQ 1; } dat 1; } delay_us(10); }读写时序的窗口很窄一般要求在拉低后的15us到60us之间采样。上面的代码用的是最稳妥的写法拉低后立即做延时再判断引脚电平。这里最容易翻车的点是晶振频率同样是延时60us12MHz和11.0592MHz的机器周期不一样如果代码里延时函数是按其中一个频率写的换板子之后必须重新校准。我一般会在延时函数里留一个宏定义方便根据实际晶振调整。3.2 温度读取流程跳过ROM、启动转换、读暂存器DS18B20的读取流程是固定的复位后先发ROM指令单点采集时直接用0xCC跳过ROM寻址再发0x44启动温度转换转换需要时间12位精度下典型值是750ms然后再次复位发0xBE读暂存器连续读两个字节得到温度原始值。unsigned int ds18b20_read_temperature(void) { unsigned char low, high; unsigned int raw; if (!ds18b20_reset()) { return 0xFFFF; // 传感器不存在返回特殊标记 } ds18b20_write_byte(0xCC); // 跳过 ROM单点采集 ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(750); // 等待转换完成切勿缩短 ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器 low ds18b20_read_byte(); high ds18b20_read_byte(); raw ((unsigned int)high 8) | low; return raw; }返回0xFFFF作为传感器掉线的哨兵值发送端主循环里必须判断这个值不能把错误数据也组帧发出去。温度换算公式是实际温度 原始值 × 0.0625。比如原始值0x0190换算成十进制是400400乘以0.0625就是25.0℃。如果是负温度寄存器里存的是补码需要先按有符号数处理再换算这部分放到接收端显示的时候一并处理。3.3 发送帧格式设计帧头、设备ID、温度值和累加和校验无线传输不能直接裸发温度两个字节因为接收端无法判断这一帧从哪里开始、数据有没有被干扰。所以要在应用层定义一帧格式。这套方案的帧结构是2字节帧头1字节设备ID2字节温度原始值1字节累加和校验总共6字节。void nrf905_send_temperature(unsigned int temp_raw) { unsigned char frame[6]; unsigned char sum 0; unsigned char i; frame[0] 0xAA; // 帧头固定值 frame[1] 0x55; frame[2] 0x01; // 设备 ID多节点时区分来源 frame[3] (unsigned char)(temp_raw 8); frame[4] (unsigned char)(temp_raw 0xFF); for (i 0; i 5; i) { sum frame[i]; // 累加和接收端重算比对 } frame[5] sum; nrf905_set_tx_mode(); // PWR_UP1, TX_EN1, TRX_CE0 nrf905_write_tx_payload(frame, 6); // 写载荷到发送 FIFO TRX_CE 1; // 触发发射 while (DR 0); // 等待芯片完成发射 TRX_CE 0; // 拉低回到待机 }帧头选0xAA55是因为它二进制交替出现0和1误码时不容易伪装成有效数据。设备ID这一字节在单点方案里是固定的但多点采集时就能派上用场接收端按照ID分别刷新不同区域的数据。累加和校验不复杂就是把前面5个字节加起来得到一个8位值接收端做同样的加法然后比对任何一bit翻转都会导致校验失败。注意NRF905硬件本身有CRC校验这里再加一层累加和是“双保险”防止数据通过了射频CRC但被单片机缓存读到错误的场景。3.4 NRF905发射程序从组帧到DR标志的完整发送状态机发射端的初始化顺序很重要很多人上来就写TRX_CE1结果模块根本没进入工作模式。正确的顺序是先把PWR_UP拉高让芯片上电稳定再写配置寄存器和目标地址最后才允许收发切换。初始化的代码放在main函数开头主循环里每秒读一次温度并发一帧。void main(void) { unsigned int temp_raw; nrf905_init(); // 内部完成 PWR_UP1、写配置、写目标地址 while (1) { temp_raw ds18b20_read_temperature(); if (temp_raw ! 0xFFFF) { // 传感器在线才发送 nrf905_send_temperature(temp_raw); } delay_ms(1000); // 1 秒发送一帧方便观察 } }每秒一帧的节奏是根据接收端LCD刷新需求定的太快液晶显示会闪烁太慢温度变化看起来不连续。NRF905本身支持更高的发送频率但51单片机跑DS18B20转换就要750ms加上组帧和SPI写入1秒周期已经占用了大部分时间没必要过度压榨。调试这个发送端时有一个很实用的手段在组帧之后、调用发射函数之前把frame数组通过串口打印出来看。这样你能明确区分是“采集温度不对”还是“发射链路没通”而不是一上来就扎进无线模块的SPI细节里。4. 接收节点实现帧校验、设备过滤与LCD1602显示恢复接收节点做的事情正好是发送端的逆过程让NRF905持续处于接收状态等DR引脚拉高后读出载荷做帧校验和设备ID过滤最后把温度值格式化到LCD1602上。这一章的重点在于如何把校验逻辑写干净以及如何防止液晶上显示过期的旧数据。4.1 接收节点初始化让NRF905进入持续接收模式接收节点初始化时配置寄存器的内容必须和发送节点完全一致地址也必须是同一个否则硬件地址匹配就不通过。初始化完成后把TRX_CE置1模块就进入持续接收状态不需要频繁重配。void nrf905_rx_init(void) { PWR_UP 1; // 先上电 delay_ms(5); // 等待晶振起振 nrf905_write_config(); // 写配置与发送端一致 nrf905_write_rx_address(); // 写本机接收地址 TX_EN 0; // 进入接收模式 TRX_CE 1; // 启动接收状态机 }这里的delay_ms(5)很关键。NRF905上电后内部晶振需要时间稳定如果立刻写配置SPI事务可能在时钟没稳的情况下执行寄存器根本写不进去。这个现象不太容易复现有时候断电重来就好很像“玄学”本质是上电时序问题。放在初始化函数里每次上电都走一遍就能稳定复现。4.2 DR标志轮询与载荷读取接收端主循环做轮询不停检测DR引脚电平。DR拉高说明芯片FIFO里有一帧有效数据等待读取这时通过SPI发0x21命令读取载荷读完后DR会自动清零复位。读取函数的代码和发送端写载荷正好对称。unsigned char nrf905_read_rx_payload(unsigned char *buf, unsigned char len) { unsigned char i; unsigned char rlen; CSN 0; spi_write_byte(0x21); // RTP 命令读接收载荷 rlen spi_read_write_byte(0x00); // 第一个字节是载荷长度 for (i 0; i len; i) { buf[i] spi_read_write_byte(0x00); } CSN 1; return rlen; }读回的第一个字节是帧长度这能用来判断接收到的数据长度是否和发送端一致。如果发送端发的是6字节接收端读回长度却是别的值说明接收到的帧已经错乱这时候直接丢弃比费力解析更明智。4.3 帧校验与设备ID过滤无校验数据只配做实验射频环境里数据出错是常态不做校验的程序在演示时偶尔能跑但一遇到干扰就全部白屏。这套方案的接收逻辑是三层过滤帧头检查、累加和校验、设备ID比对。三层都过了才认为是一帧有效数据。void rx_process(void) { unsigned char rxbuf[6]; unsigned char sum 0; unsigned char i; if (DR 0) return; // 没有新帧直接返回 nrf905_read_rx_payload(rxbuf, 6); if (rxbuf[0] ! 0xAA || rxbuf[1] ! 0x55) { return; // 帧头不对丢弃 } for (i 0; i 5; i) { sum rxbuf[i]; } if (sum ! rxbuf[5]) { return; // 累加和不匹配丢弃 } if (rxbuf[2] ! 0x01) { return; // 设备 ID 不匹配丢弃 } temp_raw (rxbuf[3] 8) | rxbuf[4]; // 有效载荷送显示 frame_valid 1; }三层过滤写完后能进入显示流程的只有真正来自本系统采集节点的温度数据。这里有一个原则值得记下来任何“看起来能用但没校验”的无线程序只配在实验桌上跑真要到现场演示校验层就是你的后悔药。多花十几行代码换来的是一整晚不丢数据的安稳。4.4 LCD1602显示与温度刷新策略温度原始值换算成摄氏度并显示到LCD1602上是接收端的最后一步。LCD1602初始化时序比较固定写命令和数据时注意RS引脚的电平区分即可。换算部分需要考虑负温度和一位小数显示。void lcd_show_temperature(int raw) { unsigned char neg 0; unsigned int abs100; char buf[17]; if (raw 0x8000) { // 最高位为 1 表示负温度 raw ~raw 1; neg 1; } abs100 (unsigned int)raw * 625 / 100; // 温度放大 100 倍 buf[0] neg ? - : ; buf[1] 0 abs100 / 1000 % 10; // 十位 buf[2] 0 abs100 / 100 % 10; // 个位 buf[3] .; buf[4] 0 abs100 / 10 % 10; // 十分位 buf[5] C; buf[6] \0; lcd_line1(buf); }刷新策略上不要每读到一帧就立刻写LCD那样液晶会闪。我习惯的做法是设一个显示标志主循环每100ms检查一次只有温度变化超过0.1℃时重写液晶行内容。再加上一个超时判断如果连续3秒没有收到任何有效帧就在第二行显示“RX:LOST”提示观众无线链路已经断开。这个细节在现场演示时非常加分它说明你的系统有故障提示能力而不是死机了还在傻等。5. NRF905调试避坑五个高频翻车现场与排查手段无线模块调试最折磨人的地方在于出了问题你看不见摸不着。这里把最常见的五个翻车场景按“现象、原因、解决”写清楚基本覆盖51单片机配NRF905调试时90%的问题。5.1 发送端卡死在等待DR拉高现象程序运行到while(DR 0)就再也走不下去串口打印显示前面的组帧都正常但DR引脚始终是低电平。原因最常见的是PWR_UP没有先拉高或者TX_EN状态不对。芯片没有进入发送模式TRX_CE拉高了也不会触发任何射频活动。另一个隐蔽原因是目标地址没有写入芯片不知道往哪发自然不动作。解决先用万用表量PWR_UP引脚电平确认初始化函数确实执行过。然后读回配置寄存器验证写配置成功最后检查发送前是否调用了写地址函数。我一般会在初始化函数里加一个LED指示每完成一个步骤就翻转一次LED电平这样上电后看灯闪几次就知道程序执行到哪一步了。5.2 温度偶尔读回85.0℃现象接收端LCD显示的温度偶尔跳变成85.0℃过一两秒又恢复正常。排除是无线数据错误因为帧校验完全通过。原因DS18B20上电后的默认温度寄存器值就是0x0550换算过来正好是85℃。出现这种情况是因为发送端在某次复位时序后没有等够750ms转换时间就去读暂存器读到的还是上电默认值。解决检查DS18B20读取函数的延时。转换命令0x44发出后必须延迟750ms这个延时不能用定时器中断最好用嵌套循环死等。如果代码里用了中断还要在延时前关闭总中断防止时序被拉长。加一个防御性判断读到的原始值是0x0550时不发送直接重新读取。5.3 数据乱码与校验不过现象接收端偶尔能收到帧但累加和校验失败率特别高用串口打印收到的十六进制数据能看到字节内容歪歪扭扭。原因大概率是发送端和接收端的SPI时钟极性和采样点不一致。软件模拟SPI时如果在SCK上升沿才去读MISO而芯片是在下降沿输出数据读到的就会错位。另外两个模块如果供电不是同一个电源地电位不一致通信距离稍远就会乱码。解决软件SPI统一采用先置MOSI、再拉高SCK、再采样MISO、最后拉低SCK的顺序。两个节点用同一个电源适配器供电或者把两个模块的地用短粗导线连在一起。乱码问题先解决地线再查时序顺序别反。5.4 室内距离只有十来米现象空旷场地标称几百米的模块在室内隔一堵墙就只有十来米再远一点接收端完全收不到。原因天线问题占大头。433MHz频段的波长约69厘米四分之一波长单极天线理论长度约17厘米很多模块出厂带的是弹簧天线或者PCB天线如果天线区域被螺丝、外壳金属或者大面积覆铜遮挡辐射效率会断崖式下降。发射功率寄存器也可能被配成了最低档。解决把配置表里发射功率字节改成最高档然后用一根17厘米左右的导线焊在天线焊盘上注意导线尽量伸直不要蜷成一团。天线周围不要铺铜PCB上对应的净空区域不要走线。换个位置测试很多时候问题不是功率不够而是模块被放在了金属桌面上。5.5 两套系统互相串台现象教室里同时有三组同学做同一个题目A组的接收端偶尔能显示B组发送的温度值数据张冠李戴。原因所有组的模块都工作在同一频点地址如果也配成一样的硬件过滤就形同虚设A组接收端会无差别接收B组的帧。解决把系统分成两层过滤。第一层是射频配置每套设备使用不同的CH_NO频点或者换用不同的4字节地址。第二层是应用层帧里的设备ID各小组设置成不同值接收端校验ID不匹配就直接丢弃。两层都加上串台问题基本绝迹。这个细节在课程设计答辩时还能主动讲出来算是加分项。6. 验收与答辩测试用例、演示脚本和几个高频追问方案写完不等于能交付验收环节需要有据可查。这里给出一套可以直接抄的测试清单和演示脚本照着走一遍基本能覆盖评委八成的问题。6.1 测试用例与通过标准用例编号测试内容操作步骤通过标准T01上电启动先开发送节点再开接收节点3秒内LCD刷新出有效温度T02近距离传输两节点距离1米持续2分钟无乱码累计校验失败少于3次T03中距离传输距离10米无遮挡温度每5秒至少刷新一次T04遮挡穿越中间隔一堵墙距离5米校验失败率小于20%能恢复T05长时间稳定性连续运行8小时不白屏、不死机丢帧率小于1%T06断电恢复拔掉发送节点电源再插回接收端3秒内自动恢复显示测试时最好把每项结果记成表格评委问起来直接翻数据比口头说“我测过没问题”有说服力得多。6.2 现场演示脚本演示顺序决定答辩节奏。我的习惯是先给接收节点上电LCD1602显示“RX:LOST”状态让评委看到故障提示界面再给发送节点上电3秒内温度出现形成对比。然后用手捏住DS18B20的金属外壳温度读数会缓慢上升这说明传感器响应正常。最后拔掉发送节点电源LCD切回“RX:LOST”重新上电又恢复显示。整套演示不超过两分钟但把采集、传输、显示、故障恢复四个点全部覆盖了。6.3 答辩环节的高频追问与应答准备评委最常问的三个问题要提前备好答案。第一个是“NRF905的地址过滤怎么实现的”答射频芯片内部有个接收地址寄存器收到帧后硬件自动对比只有匹配才拉高DR中断通知单片机。第二个是“为什么不用蓝牙或WiFi”答51单片机资源有限433MHz模块配置简单、功耗低、穿透性好适合这个应用场景。第三个是“丢包了怎么办”答应用层有累加和校验校验失败就丢弃LCD上超时显示链路断开后续还可以加自动重传机制。6.4 我做无线调试时的一条习惯以前调NRF905时翻过一次大车板子焊完了才发现天线焊盘被外壳的一个金属螺柱短接信号全被屏蔽排查了整整一晚没找到原因。从那以后我每次做无线方案上电之前先过一遍“天线是否接好、引脚是否对表、地线是否共地”这三项检查再烧程序。这套流程帮我在不少项目里躲过了返工如果你今天要交这份设计建议也把这三项检查写到自己的调试清单里希望帮到你。本文还有配套的精品资源点击获取