资讯动态

51单片机DS18B20温度采集与NRF905无线传输实战

发布时间:2026/10/21 3:09:08 来源:尧图企业网站定制
简介这是一套面向单片机爱好者、电子类专业学生与DIY玩家的温度采集与无线传输实践方案基于51单片机搭配NRF905无线模块解决短距离温度数据采集与无线收发的问题适合具备一定电子技术基础、能看懂基础电路图并了解C语言设计原理的读者。压缩包共34个文件约80KB以C语言源文件、头文件、Keil工程文件uvproj、uvopt及编译产物hex、obj、lst、m51为主另含大量bak备份文件完整保留了发射机与接收机两套工程。资源围绕DS18B20温度采集与LCD1602显示展开配合NRF905完成无线数据发送与接收读者可通过查看电路学习电路设计原理通过阅读代码理解代码编写逻辑并借助编译输出文件快速验证程序运行效果。目前已有173人学习适合作为课程设计、毕业设计或电子DIY项目的参考素材。1. 从一块“哑巴”温度节点说起51单片机配NRF905到底能跑出什么效果前阵子帮一个做农业大棚环境监测的朋友看方案他手里有一批老式温度采集节点主控是51单片机无线部分用的是NRF905。问题很典型节点能采到温度但数据传不回来串口助手上一片空白。我拿过来一看程序里NRF905的收发模式切换根本没做对配置寄存器写进去的值和实际读出来的对不上。这个项目就是解决这类问题的——一套完整的51单片机温度采集加NRF905无线传输程序包含发送端和接收端两套代码能直接把DS18B20采到的温度通过NRF905发出去接收端还原成可读数据。适合正在做无线传感节点、课程设计或者想搞懂NRF905底层时序的开发者。它不依赖复杂协议栈全是寄存器级操作跑通之后你对无线模块的理解会扎实很多。2. 硬件链路与工程结构先看清数据从传感器到空中的完整路径2.1 发送端与接收端的硬件组成发送端核心是51单片机最小系统加DS18B20温度传感器再挂一片NRF905模块。DS18B20走单总线接在任意一个IO口上程序里用位操作模拟时序。NRF905和单片机之间是SPI接口但51没有硬件SPI所以用普通IO口模拟这点很关键后面代码里会看到。接收端同样是51单片机加NRF905区别在于它不需要传感器而是把收到的数据通过串口送到电脑方便用串口助手看结果。供电部分要注意NRF905模块对电源纹波比较敏感我一般会在模块VCC和GND之间并一个10uF电解电容加一个0.1uF瓷片电容。很多“发送正常但接收距离短”的问题根源就在电源上。天线也要匹配模块自带的弹簧天线在空旷环境能到几十米室内穿墙会打折扣这是物理规律不是程序能解决的。2.2 工程目录与文件职责拿到源码包目录结构通常是这样组织的NRF905_Temp/ ├── Sender/ │ ├── main.c // 主循环初始化、读温度、发数据 │ ├── ds18b20.c // 单总线时序与温度换算 │ ├── nrf905.c // NRF905寄存器配置与收发 │ └── delay.c // 短延时与毫秒延时 ├── Receiver/ │ ├── main.c // 主循环初始化、收数据、串口输出 │ ├── nrf905.c // 与发送端一致保证配置匹配 │ ├── uart.c // 串口初始化与发送 │ └── delay.c └── Common/ └── nrf905_reg.h // 寄存器地址与命令字定义发送端和接收端的nrf905.c内容基本一致因为NRF905的配置必须完全对称才能通信。常见做法是把配置参数抽到一个头文件里两边都包含避免改了一边忘了另一边。我一般会把频道、地址、有效数据宽度这几个参数单独拎出来放在nrf905_reg.h顶部改的时候一眼就能看到。2.3 数据流与关键参数整个链路的数据流是DS18B20转换温度 - 单片机读取 - 格式化成一个字节数组 - 写入NRF905发送缓冲区 - 拉高TRX_CE触发发送 - 接收端NRF905收到后拉低DR引脚 - 单片机读缓冲区 - 串口打印。NRF905有几个参数必须两边一致频道频率、接收地址、有效数据宽度。频道频率决定载波地址相当于“门牌号”数据宽度决定一次传几个字节。温度数据我一般用4个字节两个字节放温度整数部分和小数部分另外两个字节放节点ID和校验。这样接收端能区分是哪个节点发的也能判断数据有没有出错。3. 核心模块拆解DS18B20时序与NRF905寄存器配置3.1 DS18B20单总线时序实现DS18B20最容易被忽略的是延时精度。51单片机在不同晶振下机器周期不同延时函数必须根据实际晶振调整。下面这段是11.0592MHz晶振下的典型实现// ds18b20.c #include ds18b20.h #include delay.h // 复位DS18B20检测存在脉冲 bit ds18b20_reset(void) { bit presence; DQ 0; // 拉低总线 delay_us(480); // 保持至少480us DQ 1; // 释放总线 delay_us(60); // 等待15-60us presence DQ; // 读取存在脉冲低电平表示存在 delay_us(420); // 等待复位完成 return presence; } // 写一个字节低位先写 void ds18b20_write_byte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { DQ 0; delay_us(2); DQ dat 0x01; // 写数据位 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) { DQ 0; delay_us(2); DQ 1; // 拉高准备读 delay_us(2); dat 1; if(DQ) dat | 0x80; // 读到1则置高位 delay_us(60); } return dat; }逻辑说明复位函数先拉低总线480us以上再释放如果DS18B20存在会拉低总线60us左右读DQ为0说明器件在线。写字节时每个位先拉低2us然后放数据位保持60us最后释放。读字节时拉低2us后释放在2us内采样总线电平。参数上delay_us的精度直接决定成败如果晶振换了这三个延时值都要重新算。我见过有人用12MHz晶振但延时按11.0592MHz写结果温度读出来永远是85度这就是时序错位的典型翻车现场。3.2 NRF905寄存器配置与收发切换NRF905通过SPI接口配置51用IO模拟SPI。下面这段是配置和发送的核心代码// nrf905.c #include nrf905.h #include nrf905_reg.h // 模拟SPI写一个字节 void nrf905_spi_write(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { MOSI dat 0x80; // 高位先出 SCK 0; dat 1; SCK 1; // 上升沿写入 } } // 写配置寄存器 void nrf905_config(void) { CSN 0; // 片选拉低 nrf905_spi_write(W_CONFIG); // 写配置命令 nrf905_spi_write(0x00); // 配置字节根据实际设置 // 依次写入频道、地址、数据宽度等 nrf905_spi_write(CHANNEL); nrf905_spi_write(0xE7); // 接收地址两边必须一致 nrf905_spi_write(0xE7); nrf905_spi_write(0xE7); nrf905_spi_write(0xE7); nrf905_spi_write(PAYLOAD_WIDTH); // 有效数据宽度 CSN 1; } // 发送一包数据 void nrf905_tx_packet(unsigned char *buf, unsigned char len) { unsigned char i; CSN 0; nrf905_spi_write(W_TX_PAYLOAD); // 写发送缓冲区命令 for(i 0; i len; i) { nrf905_spi_write(buf[i]); } CSN 1; TRX_CE 1; // 触发发送 delay_ms(1); // 等待发送完成 TRX_CE 0; }逻辑说明nrf905_spi_write在SCK上升沿把数据移出高位先出。配置时先发W_CONFIG命令然后依次写配置字节、频道、地址、数据宽度。地址四个字节必须和接收端完全一致否则收不到。发送时先写TX_PAYLOAD命令再把数据逐个写入最后拉高TRX_CE触发。参数上PAYLOAD_WIDTH要和实际发送字节数匹配我一般设成4对应温度数据包。TRX_CE拉高后不要立刻拉低给模块一点时间1ms是保守值实际可以更短但没必要省这点时间。3.3 接收端中断与串口输出接收端用DR引脚判断是否有数据可以用查询也可以用中断。下面是用查询方式的接收代码// Receiver/main.c 片段 void main(void) { unsigned char rx_buf[4]; unsigned char i; uart_init(); // 串口初始化波特率9600 nrf905_config(); // 配置NRF905与发送端一致 while(1) { if(DR 1) // 收到数据 { CSN 0; nrf905_spi_write(R_RX_PAYLOAD); // 读接收缓冲区命令 for(i 0; i 4; i) { rx_buf[i] nrf905_spi_read(); } CSN 1; // 串口打印温度 uart_send_byte(rx_buf[0]); // 整数部分 uart_send_byte(.); uart_send_byte(rx_buf[1]); // 小数部分 uart_send_byte(\r); uart_send_byte(\n); } } }逻辑说明DR为高表示接收缓冲区有数据先发R_RX_PAYLOAD命令然后连续读4个字节。串口部分把整数和小数分开打印中间加点号。参数上串口波特率要和电脑端一致9600是最常用的。如果DR一直不拉高先检查发送端是否真的发出去了再检查两边地址和频道是否一致。我一般会在发送端加一个LED闪烁指示发送动作接收端加一个LED指示接收这样调试时不用看串口也能判断链路是否通。4. 避坑与排查NRF905调试中最容易翻车的五个点4.1 现象发送端正常但接收端毫无反应原因最常见的是两边地址或频道不一致。NRF905的地址是4个字节配置时如果只改了发送端忘了改接收端或者频道值写错模块根本不会响应。另一个可能是PAYLOAD_WIDTH设置不同发送端设4接收端设2数据对不上。解决把地址和频道参数抽到头文件里两边包含同一个头文件。调试时先用固定地址0xE7E7E7E7和频道0x01确认通了再改。用示波器看DR引脚如果发送时接收端DR有脉冲但读不到数据就是PAYLOAD_WIDTH的问题。4.2 现象温度读出来永远是85度原因DS18B20复位后默认输出85度如果时序不对单片机读到的就是默认值。常见于延时函数按12MHz写但实际用11.0592MHz晶振或者delay_us精度不够。解决用示波器测DQ线上的复位脉冲宽度确保拉低时间在480us到960us之间。延时函数用定时器做不要用循环计数循环计数在不同优化等级下时间不一样。我一般会在复位后读一次温度如果连续三次都是85度直接报传感器故障。4.3 现象通信距离短隔一堵墙就断原因电源纹波大、天线匹配差、发射功率配置低。NRF905的发射功率可以通过配置寄存器调整默认可能是最低档。另外模块供电如果和单片机共用一路电机或继电器动作时电压跌落会导致模块复位。解决模块VCC并10uF加0.1uF电容供电单独走一路LDO。配置寄存器里把发射功率调到最大具体值看模块手册。天线尽量远离金属和人体弹簧天线竖直放置效果最好。如果还不行降低空中速率速率越低距离越远这是无线通信的基本规律。4.4 现象数据偶尔出错串口打印乱码原因没有校验机制NRF905本身有CRC但配置时可能没使能。另外电源波动导致某个字节翻转。解决在数据包里加一个校验字节比如前三个字节异或。接收端算一下不对就丢弃。NRF905的CRC使能位在配置寄存器里建议打开。串口乱码还要检查波特率误差11.0592MHz晶振配9600波特率误差为012MHz晶振会有误差尽量用11.0592MHz。4.5 现象程序烧进去后单片机不跑原因51单片机的P0口作为普通IO时没有内部上拉如果NRF905的SPI线接在P0口没有外接上拉电阻电平拉不高SPI通信失败。另外看门狗没关程序跑飞后不断复位。解决P0口接4.7K到10K上拉电阻或者换到P1、P2口。看门狗在初始化时先关掉调试完成再决定是否开启。烧录时注意晶振频率设置有些下载软件默认12MHz实际板子是11.0592MHz烧进去延时全错。5. 二次开发与改造从固定节点到可配置传感网络5.1 增加节点ID与多节点区分原始程序是点对点发送端固定接收端固定。要改成多节点最直接的办法是在数据包里加一个字节的节点ID。发送端从拨码开关或EEPROM读ID接收端根据ID区分不同节点。下面是一个改造示例// 发送端数据包结构 typedef struct { unsigned char node_id; // 节点编号 unsigned char temp_int; // 温度整数 unsigned char temp_dec; // 温度小数 unsigned char checksum; // 校验 } temp_packet_t; // 填充数据包 temp_packet_t pkt; pkt.node_id read_dip_switch(); // 从拨码开关读ID pkt.temp_int temp 4; // DS18B20原始值转换 pkt.temp_dec (temp 0x0F) * 10 / 16; pkt.checksum pkt.node_id ^ pkt.temp_int ^ pkt.temp_dec; nrf905_tx_packet((unsigned char *)pkt, sizeof(pkt));逻辑说明结构体打包后直接发送接收端按同样结构体解析。node_id用拨码开关读取3位拨码可以支持8个节点。checksum用异或简单有效。参数上sizeof(pkt)是4和PAYLOAD_WIDTH一致。如果节点多了可以改成轮询发送每个节点分配不同时间片避免空中碰撞。5.2 接收端增加数据解析与上位机协议接收端收到数据后除了串口打印还可以按一定协议打包发给上位机。我一般用简单的帧头加数据加帧尾格式// 接收端串口输出协议 void uart_send_packet(temp_packet_t *pkt) { uart_send_byte(0xAA); // 帧头 uart_send_byte(pkt-node_id); uart_send_byte(pkt-temp_int); uart_send_byte(pkt-temp_dec); uart_send_byte(pkt-checksum); uart_send_byte(0x55); // 帧尾 }逻辑说明帧头0xAA和帧尾0x55用于上位机同步中间是有效数据。上位机收到后先找0xAA再读5个字节最后验证0x55和校验和。这样即使串口有噪声也能恢复。参数上帧头帧尾可以换成其他值只要不和有效数据冲突就行。我一般会在上位机加一个超时重同步机制超过一定时间没收到完整帧就重新找帧头。5.3 低功耗改造思路如果节点用电池供电低功耗是必须的。51单片机本身功耗不低但可以通过休眠模式加定时唤醒降低平均功耗。NRF905也有掉电模式发送间隙可以关掉。改造步骤把主循环改成定时器唤醒唤醒后读温度、发数据、然后进入休眠。NRF905在发送完成后拉低TRX_CE和PWR_UP进入掉电模式。注意DS18B20转换需要时间可以在转换期间让单片机休眠转换完成用外部中断唤醒。5.4 验证方法与调试习惯改造完成后怎么验证我一般分三步第一步用串口助手看接收端输出确认数据格式正确第二步用另一个NRF905模块做嗅探看空中数据是否和发送端一致第三步长时间跑看有没有丢包或校验错误。调试时我会在关键位置加LED指示比如发送时闪一下接收时闪一下这样不用接串口也能判断程序有没有跑到那里。从那以后我每次调NRF905都强制先跑一遍地址和频道一致性检查再确认电源纹波最后才看代码逻辑。这个顺序帮我省了很多来回改代码的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑