1. 无线通信入门为什么选择STM32和NRF24L01如果你玩过单片机肯定遇到过需要把数据从一个设备传到另一个设备的情况。用线缆连接当然最简单但很多实际场景里线缆会成为累赘——比如遥控小车、无线传感器网络或者智能家居设备之间的通信。这时候无线模块就成了必需品。在众多无线方案里NRF24L01是个非常经典的选择。我十年前刚接触嵌入式时就用过它到现在很多项目里依然能看到它的身影。它工作在2.4GHz频段最高传输速率能达到2Mbps支持6个数据通道最关键的是价格非常亲民一个模块也就几块钱。对于学习无线通信原理或者做原型验证来说再合适不过了。STM32作为ARM Cortex-M内核的代表资源丰富、性能强大关键是它的SPI接口配置起来很灵活正好能和NRF24L01的SPI通信完美配合。我常用的是STM32F103C8T6这款“蓝色药丸”开发板价格便宜引脚也够用。实际项目中用STM32驱动NRF24L01实现点对点通信传输一些传感器数据或者控制指令稳定性和速度都完全够用。不过说实话NRF24L01的寄存器配置对新手来说可能有点头疼。网上的例程很多但如果不理解底层原理一旦遇到问题就很难调试。我刚开始用的时候就踩过不少坑比如地址配置不对导致收不到数据或者SPI时序搞错让模块根本无响应。这篇文章我就结合自己的实战经验从最底层的寄存器配置讲起手把手带你实现可靠的数据传输。2. 硬件连接电路设计要点与避坑指南先来看看硬件怎么连接。NRF24L01模块通常有8个引脚VCC、GND、CE、CSN、SCK、MOSI、MISO、IRQ。和STM32的连接其实很简单但有几个细节不注意就容易出问题。电源部分是最容易踩坑的地方。NRF24L01的工作电压是1.9V到3.6V绝对不能直接接5V很多STM32开发板的3.3V输出电流有限如果NRF24L01在发射时功率较大可能导致电压被拉低模块工作不稳定。我的经验是最好单独给NRF24L01供电或者在3.3V电源上加个100uF的电解电容并联一个0.1uF的瓷片电容用来滤除高频噪声。引脚连接方面SPI的四根线SCK、MOSI、MISO、CSN直接接到STM32的SPI接口上。CE和IRQ可以接任意GPIO我习惯用PA2和PA4。注意CSN引脚在SPI通信期间必须保持低电平通信结束后再拉高这个时序很重要。IRQ是中断引脚模块收到数据或发送完成时会触发用中断方式能大大减轻CPU负担。这里有个实际电路图可以参考STM32F103C8T6 NRF24L01 PA5 (SPI1_SCK) ---- SCK PA6 (SPI1_MISO) ---- MISO PA7 (SPI1_MOSI) ---- MOSI PA4 (GPIO) ---- CSN PA2 (GPIO) ---- CE PA3 (GPIO) ---- IRQ 3.3V ---- VCC GND ---- GND天线部分也很关键。NRF24L01模块有带PCB天线和带外接天线两种版本。如果通信距离要求不高10米内用PCB天线版本就行如果需要更远距离一定要选带SMA接头的外接天线版本。我实测过在开阔地带外接天线版本能稳定传输50米以上。布线时要注意把NRF24L01尽量远离电机、继电器等干扰源。如果板上还有其他2.4GHz设备比如WiFi模块最好错开频道使用。NRF24L01有125个频道可选默认是频道76你可以根据实际情况调整。3. SPI通信基础STM32如何与NRF24L01对话NRF24L01的所有配置和数据传输都通过SPI接口完成。SPI是同步串行通信需要四根线时钟线SCK、主机输出从机输入MOSI、主机输入从机输出MISO、片选CSN。STM32作为主机NRF24L01作为从机。首先要在STM32上初始化SPI。我用的是SPI1配置成全双工模式、8位数据帧、时钟极性低电平、相位第一个边沿采样。分频系数我一般设为8这样在72MHz系统时钟下SPI时钟是9MHz正好在NRF24L01支持的10MHz上限之内。void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; // SCK和MOSI GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; // MISO GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(GPIOA, GPIO_InitStructure); // 配置CSN引脚软件控制 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); // 初始高电平 // SPI配置 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }SPI读写函数是关键。NRF24L01的SPI时序要求在CSN拉低后第一个字节是指令后面跟着数据。写寄存器时先发送写指令0x20 | 寄存器地址再发送要写入的数据。读寄存器时先发送读指令0x00 | 寄存器地址然后发送一个空字节0xFF来读取数据。// 写寄存器 u8 NRF24L01_Write_Reg(u8 reg, u8 value) { u8 status; NRF24L01_CSN 0; // 使能SPI传输 status SPI1_ReadWriteByte(reg); // 发送寄存器地址 SPI1_ReadWriteByte(value); // 写入数据 NRF24L01_CSN 1; // 禁止SPI传输 return status; } // 读寄存器 u8 NRF24L01_Read_Reg(u8 reg) { u8 reg_val; NRF24L01_CSN 0; SPI1_ReadWriteByte(reg); // 发送读指令 reg_val SPI1_ReadWriteByte(0xFF); // 读取数据 NRF24L01_CSN 1; return reg_val; }这里有个细节每次SPI传输时NRF24L01都会返回状态寄存器的值。状态寄存器包含了发送完成、接收就绪、达到最大重发次数等重要信息。我在调试时经常通过读取状态寄存器来判断模块的工作状态这比盲目猜测要高效得多。4. 核心寄存器详解配置NRF24L01的每一个细节NRF24L01有25个寄存器但常用的就十来个。理解这些寄存器的作用是掌握NRF24L01的关键。我把它分成几类来讲这样更容易理解。配置寄存器CONFIG - 0x00这是最重要的寄存器决定了模块的基本工作模式。bit0PRIM_RX设为1是接收模式0是发送模式。bit1PWR_UP必须设为1才能上电工作。bit2和bit3控制CRC校验我一般启用16位CRCbit31, bit21。bit4到bit6是中断使能位可以根据需要开启。自动应答寄存器EN_AA - 0x01这个寄存器控制6个数据通道的自动应答功能。自动应答是个很实用的功能发送端发出数据后接收端会自动回复一个ACK确认包。如果发送端没收到ACK会自动重发。我通常只启用通道0的自动应答EN_AA 0x01其他通道保持关闭。接收地址使能寄存器EN_RXADDR - 0x02用来启用6个数据通道的接收地址。每个通道可以有自己的接收地址实现一对多通信。对于简单的点对点通信只启用通道0就够了EN_RXADDR 0x01。地址宽度寄存器SETUP_AW - 0x03设置地址长度可以是3、4或5字节。5字节地址的冲突概率最低我一般都设为5字节SETUP_AW 0x03。自动重发寄存器SETUP_RETR - 0x04高4位设置重发延迟250us的倍数低4位设置最大重发次数。我常用的配置是0x1A表示延迟500us0x1最大重发10次0xA。这个值需要根据实际环境调整干扰大的地方可以增加重发次数。射频频道寄存器RF_CH - 0x05设置工作频率公式是频率 2400 RF_CH (MHz)。RF_CH范围0-125对应2.400GHz到2.525GHz。注意避开WiFi常用的频道比如1、6、11我一般用频道402.440GHz。射频设置寄存器RF_SETUP - 0x06这个寄存器控制发射功率和数据速率。bit3RF_DR设为0是1Mbps设为1是2Mbps。bit2-bit1RF_PWR设置发射功率00是-18dBm01是-12dBm10是-6dBm11是0dBm。功率越大传输距离越远但耗电也越多。室内通信用-6dBm就够了。状态寄存器STATUS - 0x07这是只读寄存器反映模块的当前状态。bit6RX_DR表示收到数据bit5TX_DS表示发送成功bit4MAX_RT表示达到最大重发次数。每次处理完中断后需要写1到对应位来清除中断标志。发送/接收地址寄存器发送地址TX_ADDR - 0x10和接收地址RX_ADDR_P0 - 0x0A等必须设置正确而且发送端的接收地址要和接收端的发送地址一致这样才能实现自动应答。地址可以是任意5字节数据但两端要匹配。5. 初始化流程一步步让模块就绪初始化NRF24L01需要按照特定顺序配置寄存器。我总结了一个可靠的初始化流程你照着做基本不会出错。首先是要检测模块是否存在。方法很简单往TX_ADDR寄存器写5个特定值比如0xA5再读回来比较。如果一致说明SPI通信正常模块在线。u8 NRF24L01_Check(void) { u8 buf[5] {0xA5, 0xA5, 0xA5, 0xA5, 0xA5}; u8 read_buf[5]; u8 i; SPI1_SetSpeed(SPI_BaudRatePrescaler_32); // 降低SPI速度到9MHz NRF24L01_Write_Buf(NRF_WRITE_REG TX_ADDR, buf, 5); NRF24L01_Read_Buf(TX_ADDR, read_buf, 5); for(i 0; i 5; i) { if(buf[i] ! read_buf[i]) { return 1; // 检测失败 } } return 0; // 检测成功 }检测通过后开始正式初始化。我习惯先配置发送模式因为很多参数发送和接收模式是共享的。void NRF24L01_TX_Mode(void) { NRF24L01_CE 0; // CE先拉低 // 设置发送地址5字节 NRF24L01_Write_Buf(NRF_WRITE_REG TX_ADDR, (u8*)TX_ADDRESS, TX_ADR_WIDTH); // 设置接收地址用于接收ACK NRF24L01_Write_Buf(NRF_WRITE_REG RX_ADDR_P0, (u8*)RX_ADDRESS, RX_ADR_WIDTH); // 启用通道0自动应答 NRF24L01_Write_Reg(NRF_WRITE_REG EN_AA, 0x01); // 启用通道0接收地址 NRF24L01_Write_Reg(NRF_WRITE_REG EN_RXADDR, 0x01); // 设置自动重发延迟500us最大重发10次 NRF24L01_Write_Reg(NRF_WRITE_REG SETUP_RETR, 0x1A); // 设置射频频道402.440GHz NRF24L01_Write_Reg(NRF_WRITE_REG RF_CH, 40); // 设置发射参数0dBm增益2Mbps速率 NRF24L01_Write_Reg(NRF_WRITE_REG RF_SETUP, 0x0F); // 配置基本工作模式上电、启用CRC、16位CRC、发送模式 NRF24L01_Write_Reg(NRF_WRITE_REG CONFIG, 0x0E); NRF24L01_CE 1; // 启动发送 delay_us(10); // CE高电平至少10us }接收模式的初始化类似但有些参数不同void NRF24L01_RX_Mode(void) { NRF24L01_CE 0; // 设置接收地址 NRF24L01_Write_Buf(NRF_WRITE_REG RX_ADDR_P0, (u8*)RX_ADDRESS, RX_ADR_WIDTH); // 启用通道0自动应答 NRF24L01_Write_Reg(NRF_WRITE_REG EN_AA, 0x01); // 启用通道0接收地址 NRF24L01_Write_Reg(NRF_WRITE_REG EN_RXADDR, 0x01); // 设置射频频道必须和发送端一致 NRF24L01_Write_Reg(NRF_WRITE_REG RF_CH, 40); // 设置接收数据宽度最大32字节 NRF24L01_Write_Reg(NRF_WRITE_REG RX_PW_P0, RX_PLOAD_WIDTH); // 设置发射参数 NRF24L01_Write_Reg(NRF_WRITE_REG RF_SETUP, 0x0F); // 配置基本工作模式上电、启用CRC、16位CRC、接收模式 NRF24L01_Write_Reg(NRF_WRITE_REG CONFIG, 0x0F); NRF24L01_CE 1; // 进入接收模式 }初始化完成后建议读一下CONFIG寄存器确认配置是否生效。有时候SPI通信受干扰配置可能没写进去。6. 数据发送实战从填充缓冲区到确认接收发送数据的流程比接收要复杂一些因为涉及重发机制和状态判断。我写了一个比较健壮的发送函数你可以直接拿去用。u8 NRF24L01_TxPacket(u8 *txbuf) { u8 sta; u16 retry 0; // 提高SPI速度到9MHz SPI1_SetSpeed(SPI_BaudRatePrescaler_8); NRF24L01_CE 0; // 先拉低CE // 写数据到TX FIFO NRF24L01_Write_Buf(WR_TX_PLOAD, txbuf, TX_PLOAD_WIDTH); NRF24L01_CE 1; // 启动发送 // 等待发送完成IRQ变低或超时 while(NRF24L01_IRQ ! 0) { retry; if(retry 10000) { // 超时判断 NRF24L01_CE 0; return 0xFF; // 超时错误 } delay_us(10); } // 读取状态寄存器 sta NRF24L01_Read_Reg(STATUS); // 清除中断标志 NRF24L01_Write_Reg(NRF_WRITE_REG STATUS, sta); if(sta MAX_TX) { // 达到最大重发次数 NRF24L01_Write_Reg(FLUSH_TX, 0xFF); // 清空TX FIFO return MAX_TX; } if(sta TX_OK) { // 发送成功 return TX_OK; } return 0xFF; // 其他错误 }这个函数有几个关键点。首先发送前要把CE拉低写完数据后再拉高CE启动发送。CE高电平至少要维持10us模块才会开始发送。我实测发现如果CE拉高时间太短可能导致发送不启动。其次等待发送完成有两种方式查询IRQ引脚或者查询状态寄存器。我用的是查询IRQ引脚因为更直接。NRF24L01的IRQ引脚在发送完成或达到最大重发次数时会变低平时是高电平。发送完成后一定要读取状态寄存器判断是发送成功还是达到了最大重发次数。如果是MAX_RT中断需要清空TX FIFO否则下次发送可能出问题。清空FIFO用FLUSH_TX命令0xE1。数据长度方面NRF24L01的FIFO是32字节但实际能发送的有效数据最多31字节因为最后一个字节被用作CRC。我一般定义32字节的缓冲区但只使用前31字节。自动重发机制是NRF24L01的一大亮点。发送端发出数据后会切换到接收模式等待ACK。如果收到ACK就产生TX_DS中断如果没收到会在设定的延迟后重发直到达到最大重发次数然后产生MAX_RT中断。这个机制在干扰环境下特别有用能大大提高传输可靠性。7. 数据接收实战中断与轮询两种方式接收数据可以用轮询方式也可以用中断方式。轮询简单但占用CPU中断效率高但配置稍复杂。我两种都用过根据实际需求选择。轮询方式就是不断检查状态寄存器的RX_DR位或者检查IRQ引脚。代码很简单u8 NRF24L01_RxPacket(u8 *rxbuf) { u8 sta; // 读取状态寄存器 sta NRF24L01_Read_Reg(STATUS); // 清除中断标志 NRF24L01_Write_Reg(NRF_WRITE_REG STATUS, sta); if(sta RX_OK) { // 接收到数据 // 从RX FIFO读取数据 NRF24L01_Read_Buf(RD_RX_PLOAD, rxbuf, RX_PLOAD_WIDTH); // 清空RX FIFO NRF24L01_Write_Reg(FLUSH_RX, 0xFF); return 0; // 接收成功 } return 1; // 没收到数据 }在主循环里不断调用这个函数就行。但这样CPU一直在忙等如果系统还有其他任务要处理就不太合适了。中断方式更优雅。把IRQ引脚接到STM32的外部中断引脚上配置为下降沿触发。当NRF24L01收到数据时IRQ会变低触发STM32中断在中断服务函数里读取数据。// 初始化外部中断 void EXTI_IRQ_Init(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 配置PA3为中断输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, GPIO_InitStructure); // 连接EXTI到PA3 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3); // 配置EXTI EXTI_InitStructure.EXTI_Line EXTI_Line3; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; // 下降沿触发 EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 配置NVIC NVIC_InitStructure.NVIC_IRQChannel EXTI3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0x01; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0x01; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); } // 中断服务函数 void EXTI3_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line3) ! RESET) { if(NRF24L01_IRQ 0) { // 确认是NRF24L01的中断 u8 rx_buf[32]; NRF24L01_RxPacket(rx_buf); // 读取数据 // 处理数据... } EXTI_ClearITPendingBit(EXTI_Line3); // 清除中断标志 } }中断方式要注意中断服务函数里不能做太耗时的操作。我一般只是把数据读到缓冲区设置一个标志位在主循环里处理数据。接收数据时还有个细节NRF24L01有6个数据通道PIPE0到PIPE5每个通道可以设置不同的地址。PIPE0和PIPE1是40位地址PIPE2到PIPE5只有最低字节不同。这个特性可以用来实现一对多通信比如一个主机对应多个从机。8. 高级功能与优化技巧基本的收发功能实现后可以进一步优化性能和可靠性。这里分享几个我实际用过的技巧。动态载荷长度默认情况下NRF24L01的载荷长度是固定的1-32字节。但实际应用中数据长度可能变化。启用动态载荷功能后接收端能自动识别数据长度。要启用这个功能需要设置FEATURE寄存器的EN_DPL位以及DYNPD寄存器对应通道的位。// 启用动态载荷 NRF24L01_Write_Reg(NRF_WRITE_REG FEATURE, 0x04); // EN_DPL 1 NRF24L01_Write_Reg(NRF_WRITE_REG DYNPD, 0x01); // 启用PIPE0动态载荷启用后发送数据时不用指定长度接收端会自动获取。但要注意动态载荷和自动应答不能同时启用。多通道接收NRF24L01可以同时监听6个通道。比如PIPE0用地址0x1122334455PIPE1用0x11223344AA这样同一个模块就能接收两个不同地址的数据。这在组网应用中很有用。// 设置PIPE0地址 u8 addr0[5] {0x11, 0x22, 0x33, 0x44, 0x55}; NRF24L01_Write_Buf(NRF_WRITE_REG RX_ADDR_P0, addr0, 5); // 设置PIPE1地址 u8 addr1[5] {0x11, 0x22, 0x33, 0x44, 0xAA}; NRF24L01_Write_Buf(NRF_WRITE_REG RX_ADDR_P1, addr1, 5); // 启用两个通道 NRF24L01_Write_Reg(NRF_WRITE_REG EN_RXADDR, 0x03);接收数据后可以通过状态寄存器的RX_P_NO位bit1-bit3判断是哪个通道收到的数据。低功耗模式NRF24L01有几种功耗模式掉电模式PWR_UP0、待机模式CE0、接收模式、发送模式。不通信时可以进入掉电模式节省功耗。从掉电模式切换到发送或接收模式需要约1.5ms的启动时间设计时要考虑这个延迟。天线匹配优化如果通信距离不够可以检查天线匹配电路。NRF24L01模块通常已经有匹配电路但如果你是自己画板需要注意PCB天线或外接天线的匹配。用矢量网络分析仪调一下匹配电路距离能提升不少。干扰处理2.4GHz频段很拥挤WiFi、蓝牙、微波炉都在这个频段。如果遇到干扰可以尝试换频道或者降低数据速率从2Mbps降到1Mbps。1Mbps模式抗干扰能力更强但传输速率减半。9. 调试与故障排除调试无线通信最头疼的就是不知道问题出在哪里。我总结了一套调试流程能快速定位问题。第一步检查硬件连接用万用表量一下VCC是不是3.3VGND是否连通。SCK、MOSI、MISO、CSN、CE、IRQ这六根线有没有接错。特别是CE和CSN接反了模块根本不工作。第二步检测模块是否存在用前面提到的NRF24L01_Check函数如果能通过说明SPI通信正常。如果失败检查SPI时序和引脚配置。可以用逻辑分析仪抓一下SPI波形看看时钟和数据对不对。第三步检查寄存器配置初始化后把所有重要寄存器读出来和预期值对比。我常用的检查清单CONFIG应该是0x0E发送模式或0x0F接收模式EN_AA和EN_RXADDR根据需求设置SETUP_RETR设置重发参数RF_CH确认频道一致RF_SETUP确认功率和速率STATUS寄存器在空闲时应该是0x0E第四步监听空中数据如果有频谱仪或SDR软件定义无线电可以看看NRF24L01有没有在发射。调到设置的频道比如2.440GHz发送数据时应该能看到信号。这是判断模块是否正常工作的最直接方法。第五步分段测试先让发送端固定发一个数据包用逻辑分析仪抓IRQ引脚看是否有下降沿表示发送完成。然后让接收端监听看IRQ是否有下降沿表示收到数据。这样能确定问题出在发送端还是接收端。常见问题及解决方法问题1发送端IRQ一直为高没有发送完成中断可能原因CE引脚没拉高或者拉高时间不够。确保CE拉高至少10us。也可能是SPI配置错误模块根本没收到发送命令。问题2接收端收不到数据但发送端显示发送成功可能原因两端频道不一致或者地址不匹配。检查RF_CH寄存器确保两端相同。检查发送地址和接收地址是否对应。问题3通信距离很短稍微远点就丢包可能原因电源噪声大或者天线匹配不好。在电源上加滤波电容检查天线是否接触良好。也可以尝试降低数据速率到1Mbps。问题4偶尔能收到但不稳定可能原因环境干扰大。换个频道试试避开WiFi常用的1、6、11频道。或者增加自动重发次数。问题5模块发热严重可能原因电源电压过高或者模块损坏。NRF24L01绝对最大电压是3.6V超过就可能损坏。也可能是SPI时钟太快超过10MHz。调试时我习惯用串口打印关键信息比如状态寄存器值、发送/接收的计数器等。有了这些信息定位问题就快多了。10. 实际项目应用构建无线温度监测系统理论讲完了来看一个实际项目用STM32NRF24L01做无线温度监测。系统包含一个发送端带DS18B20温度传感器和一个接收端带OLED显示。发送端每5秒读取一次温度通过NRF24L01发送。接收端显示当前温度和信号强度通过重发次数估算。发送端代码框架int main(void) { u8 tx_buf[32]; u8 temp[2]; // 初始化 DS18B20_Init(); NRF24L01_Init(); USART1_Init(115200); // 检测模块 while(NRF24L01_Check()) { printf(NRF24L01 Check Failed!\r\n); delay_ms(500); } printf(NRF24L01 Ready!\r\n); // 配置为发送模式 NRF24L01_TX_Mode(); while(1) { // 读取温度 float temperature DS18B20_GetTemp(); temp[0] (u8)((int)temperature); temp[1] (u8)(((int)(temperature * 100)) % 100); // 填充发送缓冲区 tx_buf[0] 0xAA; // 帧头 tx_buf[1] 0x55; tx_buf[2] temp[0]; // 整数部分 tx_buf[3] temp[1]; // 小数部分 tx_buf[4] 0x00; // 预留 // 发送数据 u8 status NRF24L01_TxPacket(tx_buf); if(status TX_OK) { printf(Send OK: %.2f C\r\n, temperature); } else if(status MAX_TX) { printf(Max Retry: %.2f C\r\n, temperature); } else { printf(Send Failed!\r\n); } delay_ms(5000); // 5秒发送一次 } }接收端代码框架int main(void) { u8 rx_buf[32]; u8 rssi 0; // 初始化 OLED_Init(); NRF24L01_Init(); USART1_Init(115200); // 检测模块 while(NRF24L01_Check()) { OLED_ShowString(0, 0, NRF24L01 Error!, 16); delay_ms(500); } // 配置为接收模式 NRF24L01_RX_Mode(); OLED_ShowString(0, 0, NRF24L01 Ready!, 16); while(1) { // 尝试接收数据 if(NRF24L01_RxPacket(rx_buf) 0) { // 校验帧头 if(rx_buf[0] 0xAA rx_buf[1] 0x55) { float temperature rx_buf[2] rx_buf[3] / 100.0; // 估算信号强度通过重发次数 u8 obs NRF24L01_Read_Reg(OBSERVE_TX); rssi 100 - ((obs 0x0F) * 10); // 简单估算 // 显示到OLED char disp[32]; sprintf(disp, Temp: %.2f C, temperature); OLED_ShowString(0, 2, disp, 16); sprintf(disp, RSSI: %d%%, rssi); OLED_ShowString(0, 4, disp, 16); printf(Received: %.2f C, RSSI: %d%%\r\n, temperature, rssi); } } delay_ms(100); // 100ms检查一次 } }这个项目虽然简单但涵盖了无线通信的主要环节初始化、发送、接收、错误处理、数据处理。你可以在此基础上扩展比如增加多个传感器、实现双向通信、加入数据加密等。我在实际部署时还加了数据校验CRC16、数据重传机制、心跳包检测连接状态。工业环境下干扰大这些措施能显著提高可靠性。另外如果传输距离要求超过50米可以考虑换用PALNA版本的NRF24L01模块或者加功率放大器。最后给个建议刚开始调NRF24L01时先用两块开发板在桌面上近距离测试确保基本通信正常。然后再逐步增加距离测试不同环境下的表现。遇到问题别急着换模块大概率是配置或代码问题。多读几遍数据手册理解每个寄存器的作用调试起来会顺利很多。