STM32F103C8T6与NRF24L01无线收发实战:从SPI到RX_TX联调 📅 发布时间:2026/9/12 17:08:43 👁 浏览次数: 简介基于NRF24L01与STM32F103C8T6的无线收发完整工程面向嵌入式初学者与物联网开发者解决短距离无线通信中发送端与接收端的代码实现、调试和移植问题适用于智能家居、遥控遥测及多点组网等场景。压缩包共331个文件约14.54MB以74个.h头文件、72个.c源文件为主同时包含Keil工程文件.uvproj/.uvprojx、汇编与链接脚本、编译输出的.hex/.axf/.map及调试辅助文件属于打开即可编译的完整MDK5工程。已有2076人学习下载。工程内提供NRF24L01的SPI驱动、收发模式配置、频道与地址设置、1Mbps/2Mbps速率选择、中断响应以及CRC校验等核心代码并完整展示发送端与接收端的调用流程通过研读和复用这些代码可快速掌握2.4GHz无线通信的初始化与数据交互机制为后续自组网或多节点通信开发节省大量时间。1. 从 STM32F103C8T6 最小系统板到 NRF24L01 无线模块RX_TX 收发程序的核心是什么把 NRF24L01 模块插到 STM32F103C8T6 最小系统板上很多人第一反应是抄一份寄存器初始化代码但实际卡住的往往是“为什么 SPI 读回来全是 0xFF”“为什么一发数据就进 MAX_RT 重发”“为什么两块板子明明都在发却谁也收不到”。标题里的 NRF24L01_STM32F103C8T6_RX_TX 程序本质就是一对工程一块板子做接收端 RX另一块做发送端 TXMCU 通过 SPI 配置射频芯片内部状态机完成无线收发名称里的“seriou”通常指串口透传版本也就是把无线收到的数据通过 USART 送到 PC 端或者把 PC 串口发出的数据通过 NRF24L01 发出去。对 5 年以上的工程师来说真正值得留意的不是 SPI 读写本身而是 Enhanced ShockBurst 的自动 ACK、自动重发和 CE 引脚拉高时长这三件事的配合。2. NRF24L01 的 Enhanced ShockBurst 原理与 STM32F103C8T6 的最小接线2.1 收发前先搞清 Enhanced ShockBurst 状态机TX、RX、ACK 与重发NRF24L01 不是一颗简单的 SPI 射频收发器它内部集成了一套 Enhanced ShockBurst 协议包结构包含前导码、地址、控制位、0 到 32 字节有效载荷和 CRC。MCU 只需要把数据写进 TX FIFO芯片会自动组包、发出去、等待对方 ACK如果没收到 ACK芯片会按照 SETUP_RETR 寄存器里配置的间隔和次数自动重发。这个“硬件自动完成 ACK 与重发”的机制决定了 MCU 端的驱动写法和串口透传完全不同你不能发完一个字节就去干别的必须轮询 STATUS 寄存器里的 TX_DS、MAX_RT 位来判断这一包到底发出去了没有。对应到接收端芯片收到合法数据包后会自动回 ACK同时把 payload 放进 RX FIFO并把 STATUS 寄存器的 RX_DR 置 1。如果开了自动 ACK 且双方地址配置一致ACK 是可以携带数据的也就是所谓的 ACK payload这给双向通信省掉了频繁切换 TX/RX 模式的麻烦。网上很多教程包括江协那套用 GPIO 模拟 SPI 的讲法走的是翻转电平一个个 bit 打时序虽然慢但对理解协议有帮助工程上更推荐直接用硬件 SPI 配合 HAL 库下面整套封装就是按这个思路写的。2.2 NRF24L01 模块 8 根引脚怎么接到 STM32F103C8T6 最小系统板NRF24L01 模块是 8 脚VCC、GND、CE、CSN、SCK、MOSI、MISO、IRQ。VCC 必须接 3.3V不能接 5V否则芯片直接烧掉。模块发射瞬间电流会到 20mA 左右最小系统板的 LDO 如果同时给显示屏和传感器供电电压会跌落导致复位所以模块电源引脚旁边建议并一个 10uF 电解电容和一个 0.1uF 陶瓷电容。下面是我常用的引脚映射SPI1 的 SCK、MISO、MOSI 分别对应 PA5、PA6、PA7其余三根控制线分配到空闲 GPIO。模块引脚STM32F103C8T6 引脚功能说明VCC3.3V模块供电需并联去耦电容GNDGND共地尽量短CEPA8芯片使能TX 触发发送RX 使能接收CSNPA9SPI 片选低电平有效SCKPA5SPI1 时钟MOSIPA7SPI1 主机输出MISOPA6SPI1 主机输入模块输出IRQPB10低电平有效中断输出可不上拉CE 和 CSN 是很多人搞混的地方。CE 控制收发模式的切换CSN 是 SPI 片选两者必须独立控制。IRQ 是开漏输出内部没有上拉CubeMX 里配成输入模式即可如果想用外部中断记得在代码里使能内部上拉否则 IRQ 浮空会误触发。CSN 初始状态必须是高电平否则芯片一上电就误以为 SPI 被选中后续所有通信都会乱掉。2.3 SPI 速率、时钟极性和相位读不到寄存器时先查这三项NRF24L01 的 SPI 从机模式支持的最高时钟是 10MHzSTM32F103C8T6 的 SPI1 挂在 APB2 总线上PCLK2 默认 72MHz所以分频系数不能低于 8。我用 SPI 预分频 8得到 9MHz SCK留了余量。时钟极性 CPOL 和相位 CPHA 通常配置为 0 和 0也就是空闲时钟为低、第一个时钟沿采样有些库函数默认设置不一样改完寄存器发现读出来全是 0x00 或者 0xFF先看这一项。写完驱动后第一个验证动作是读 CONFIG 寄存器也就是地址 0x00uint8_t config NRF_ReadReg(0x00); while (config 0x00 || config 0xFF) { // 大概率是 SPI 时序或接线问题检查 CSN 是否拉低、CPOL/CPHA 是否匹配 config NRF_ReadReg(0x00); }这里读 CONFIG 的目的不是拿具体值而是确认 SPI 链路通没通。如果读回 0xFF说明 MISO 上没有数据回来优先排查接线和 SPI 复用配置如果读回 0x00说明芯片虽然响应了但配置还没生效检查 PWR_UP 位有没有写进去。还有一个隐蔽问题SPI 的 NSS 引脚如果被硬件模式占用CSN 用 GPIO 控制时可能出现争用CubeMX 里把 SPI1 的 NSS 配成 Disable 或者软件管理然后 CSN 用普通 GPIO。3. 用 STM32CubeMX 在 STM32F103C8T6 上初始化 SPI、GPIO 和 USART3.1 CubeMX 配置项SPI1 全双工主机、GPIO 输出、USART1 透传NRF24L01 驱动不依赖操作系统和复杂外设CubeMX 只需要配四个东西RCC 的 HSE 晶体振荡器、SYS 的 Debug Serial Wire、SPI1 全双工主机、USART1 异步通信。Debug 不选 Serial Wire 的话PA13/PA14 会被占用后面接 ST-Link 下载可能会有麻烦。SPI1 参数按下表设置配置项值说明ModeFull-Duplex Master收发各用一根线用 TransmitReceive 封装Hardware NSS SignalDisableCSN 完全交给 GPIO 控制Prescaler8 分频72 / 8 9MHz低于模块 10MHz 上限Clock Polarity CPOLLow空闲时钟低电平Clock Phase CPHA1 Edge第一个时钟边沿采样CRCDisable无线协议自带 CRCSPI 链路不需要USART1 波特率 1152008 数据位、无校验、1 停止位用于“seriou”版本的串口透传。CubeMX 生成的代码默认把 USART1 的收发都配好了但 printf 重定向需要自己在 fputc 里调用 HAL_UART_Transmit否则调试时看不到收发状态。3.2 初始化 CE、CSN、IRQ 引脚输出推挽与输入模式的区别CE 和 CSN 配成 GPIO_MODE_OUTPUT_PP 推挽输出速度选 LOW 就够了毕竟这两根信号频率不高。IRQ 配成 GPIO_MODE_INPUT同时使能内部上拉避免浮空导致中断误触发。引脚宏定义放在头文件里方便不同核心板改引脚#define NRF_CE_GPIO_Port GPIOA #define NRF_CE_Pin GPIO_PIN_8 #define NRF_CSN_GPIO_Port GPIOA #define NRF_CSN_Pin GPIO_PIN_9 #define NRF_IRQ_GPIO_Port GPIOB #define NRF_IRQ_Pin GPIO_PIN_10 #define NRF_CE_LOW() HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_RESET) #define NRF_CE_HIGH() HAL_GPIO_WritePin(NRF_CE_GPIO_Port, NRF_CE_Pin, GPIO_PIN_SET) #define NRF_CSN_LOW() HAL_GPIO_WritePin(NRF_CSN_GPIO_Port, NRF_CSN_Pin, GPIO_PIN_RESET) #define NRF_CSN_HIGH() HAL_GPIO_WritePin(NRF_CSN_GPIO_Port, NRF_CSN_Pin, GPIO_PIN_SET)初始化函数里必须先把 CSN 拉高、CE 拉低然后再配置 GPIO 模式。原因是 NRF24L01 上电后 CSN 为高进入等待状态如果 GPIO 初始化瞬间输出低电平芯片会误以为收到命令后续状态机就乱了。初始化完成后做一次 NOP 命令确认 CSN 拉低和拉高的宏没有反这个问题在自制底板上特别常见。3.3 生成工程后的三处改动时基、上拉与 HAL_SPI 句柄CubeMX 生成代码后默认的时基是 SysTick如果后面要移植 FreeRTOS 或者做了串口超时判断SysTick 会被操作系统抢走所以建议把 SYS 里的 Timebase Source 改成 TIM1。这个改动在生成前做生成后改会比较麻烦。然后检查 hspi1 句柄是不是全局变量SPI 初始化函数有没有在 main 里被调用这两项缺失会导致 HAL_SPI_TransmitReceive 返回 HAL_ERROR。GPIO 初始化生成的代码只设了输出模式IRQ 上拉需要手动补一行GPIO_InitStruct.Pin NRF_IRQ_Pin; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(NRF_IRQ_GPIO_Port, GPIO_InitStruct);如果不想用中断只靠轮询 STATUS 寄存器IRQ 甚至可以不接。不过建议还是接上因为后续测链路质量时IRQ 的下降沿可以作为发送完成或接收到达的事件信号比在 while 循环里反复读 SPI 省 CPU。串口这边要注意 CubeMX 生成的 HAL_UART_Receive 是阻塞式的串口透传主循环里要用带超时版本的调用否则收不到数据时程序会卡死在等待里。4. NRF24L01 的 HAL 驱动最小实现寄存器命令、SPI 读写封装与 TX/RX 代码4.1 常用寄存器与命令速查表NRF24L01 的操作本质是“发命令 传数据”。读寄存器用 R_REGISTER写寄存器用 W_REGISTER收发 payload 用 W_TX_PAYLOAD 和 R_RX_PAYLOAD。下面这张表是我做驱动时最常用的一组其他的如 DYNPD、FEATURE 只在动态负载时才碰寄存器地址关键位说明CONFIG0x00bit0 PRIM_RX、bit1 PWR_UP、bit3 EN_CRCEN_AA0x01管道自动 ACK 使能常用 0x01 只开管道 0EN_RXADDR0x02接收管道使能点对点用 0x01SETUP_AW0x03地址宽度0x03 表示 5 字节地址SETUP_RETR0x04高 4 位重发间隔低 4 位重发次数RF_CH0x05射频信道范围 0 到 125RF_SETUP0x06通信速率与发射功率STATUS0x07bit6 RX_DR、bit5 TX_DS、bit4 MAX_RTOBSERVE_TX0x08高 4 位丢包计数低 4 位重发计数TX_ADDR0x10发送地址5 字节RX_ADDR_P00x0A管道 0 地址自动 ACK 时需与 TX_ADDR 一致RX_PW_P00x11管道 0 接收载荷宽度最大 32命令字里需要注意写寄存器是 0x20 与寄存器地址做或运算读寄存器是 0x00 与地址做或运算因为寄存器地址本身只有低 5 位有效。W_TX_PAYLOAD 是 0xA0R_RX_PAYLOAD 是 0x61FLUSH_TX 是 0xE1FLUSH_RX 是 0xE2。这些命令都必须把 CSN 拉低后逐字节发送CSN 在整个命令期间保持低电平结束后拉高。4.2 基于 HAL_SPI_TransmitReceive 的读写封装HAL 库的 SPI 读写可以一次同时收一个字节NRF24L01 同样是“发送一个字节同时读回一个字节”的全双工机制所以封装一个 spi_rw 函数就够了uint8_t NRF_SPI_RW(uint8_t byte) { uint8_t ret 0; HAL_SPI_TransmitReceive(hspi1, byte, ret, 1, 100); return ret; } uint8_t NRF_ReadReg(uint8_t reg) { uint8_t val 0; NRF_CSN_LOW(); NRF_SPI_RW(reg 0x1F); // R_REGISTER 命令地址取低 5 位 val NRF_SPI_RW(0xFF); // 发送 NOP读回寄存器值 NRF_CSN_HIGH(); return val; } void NRF_WriteReg(uint8_t reg, uint8_t val) { NRF_CSN_LOW(); NRF_SPI_RW((reg 0x1F) | 0x20); // W_REGISTER 命令 NRF_SPI_RW(val); NRF_CSN_HIGH(); }读寄存器时第二个字节必须发送 0xFF 也就是 NOP 命令因为 SPI 是边发边收不发送时钟就没有数据回来。这里对 reg 做 0x1F 是为了防止调用时误把命令位带进地址比如把 0x20 传给 ReadReg地址位会错乱。超时时间 100ms 通常够用但如果在中断服务函数里调用这个封装建议改成不等待版本。4.3 初始化顺序、发送一包与接收一包的最小函数初始化函数的顺序有讲究先用 CE 拉低让芯片进入待机状态然后依次写 CONFIG、EN_AA、EN_RXADDR、RF_CH、RF_SETUP 等参数最后再写 TX_ADDR 和 RX_ADDR_P0。CONFIG 的 PWR_UP 位必须在其他参数之前先置位并等待 2ms芯片射频部分上电需要时间写完后立刻发数据大概率失败。下面是一个点对点场景的初始化函数void NRF_Init(void) { NRF_CE_LOW(); NRF_WriteReg(0x00, 0x0A); // PWR_UP1, EN_CRC1, PRIM_RX0 HAL_Delay(2); NRF_WriteReg(0x01, 0x01); // EN_AA 只开管道 0自动 ACK NRF_WriteReg(0x02, 0x01); // EN_RXADDR 只开管道 0 NRF_WriteReg(0x03, 0x03); // 5 字节地址 NRF_WriteReg(0x04, 0x0F); // 重发间隔 250us重发 15 次 NRF_WriteReg(0x05, 0x40); // 射频信道 64对应 2464MHz NRF_WriteReg(0x06, 0x06); // 1Mbps0dBm 发射功率 NRF_WriteReg(0x11, 0x20); // 接收载荷宽度 32 字节 NRF_WriteReg(0x07, 0x70); // 清三个中断标志 } uint8_t NRF_Send(const uint8_t *data, uint8_t len) { uint8_t status 0; uint16_t timeout 0; NRF_CE_LOW(); NRF_WriteReg(0x00, 0x0A); // 确保处于 TX 模式 NRF_CSN_LOW(); NRF_SPI_RW(0xA0); // W_TX_PAYLOAD for (uint8_t i 0; i 32; i) { NRF_SPI_RW(i len ? data[i] : 0x00); // 不足 32 字节补 0 } NRF_CSN_HIGH(); HAL_Delay(1); NRF_CE_HIGH(); HAL_Delay(1); // CE 高电平至少 10us这里放宽到 1ms NRF_CE_LOW(); while (timeout 0xFFFF) { status NRF_ReadReg(0x07); if (status 0x20) { // TX_DS发送成功等到了 ACK NRF_WriteReg(0x07, 0x20); // 写 1 清标志 return 0; } if (status 0x10) { // MAX_RT达到最大重发次数 NRF_WriteReg(0x07, 0x10); NRF_CSN_LOW(); NRF_SPI_RW(0xE1); // FLUSH_TX 清掉没发出去的包 NRF_CSN_HIGH(); return 1; } } return 2; // 超时多数情况是配置异常 }发送逻辑里 CE 的时序最容易出错。CE 拉低后写 FIFO写完把 CE 拉高至少 10 微秒再拉低芯片才会真正把数据发出去。如果 CE 一直保持高电平芯片会认为 FIFO 里还有数据会连续发同一包串口端看到的就是重复数据。返回 1 的时候除了清 MAX_RT还要执行 FLUSH_TX否则之前组好的包残留在 FIFO 里下一轮发送会把旧数据一起发出去。接收端代码更短关键是先读 STATUS 判断 RX_DR再读 payload最后清标志uint8_t NRF_Recv(uint8_t *buf) { uint8_t status NRF_ReadReg(0x07); if ((status 0x40) 0) { // 没有 RX_DR 置位 return 0; } NRF_CSN_LOW(); NRF_SPI_RW(0x61); // R_RX_PAYLOAD for (uint8_t i 0; i 32; i) { buf[i] NRF_SPI_RW(0xFF); } NRF_CSN_HIGH(); NRF_WriteReg(0x07, 0x40); // 清 RX_DR此时 FIFO 已读空 return 32; }清 RX_DR 一定要在读 payload 之后因为 RX_DR 只要 FIFO 里还有未读数据就会重新置位。如果先清标志再读循环里判断标志位的逻辑会被干扰有时候一包数据会触发两次接收。接收模式切换只需要把 CONFIG 的 PRIM_RX 置 1也就是写 0x0B然后把 CE 拉高芯片就开始监听信道了。5. 双板 RX_TX 联调一块板发一块板收无线数据通过串口输出5.1 一套源码编译出 TX 和 RX 两个角色避免维护两套工程NRF24L01 点对点工程最常见的坑是两套代码各自独立地址、信道、载荷宽度改了一边忘了另一边。我的做法是在主程序里用一个宏切换角色CubeMX 生成的外设初始化代码完全不动只改编译宏#define BOARD_ROLE 1 // 1 为发送端 TX0 为接收端 RX这个宏放在 nrf_config.h 里两个板子共用同一份源码。角色切换的本质只是初始化时配置不同的寄存器发送端在 NRF_Init 后把 TX_ADDR 和 RX_ADDR_P0 写成同一个地址接收端把 RX_ADDR_P0 写成同样地址并且把 CONFIG 写成 0x0B 进入接收模式。两个板子射频信道必须一致地址必须一致负载宽度必须一致这三项只要差一个字节就完全连不上。用宏还有一个好处调试时把一个板子改成对端角色可以快速确认是硬件故障还是配置不一致。主循环里按角色走不同分支while (1) { #if BOARD_ROLE 1 uint8_t ch; if (HAL_UART_Receive(huart1, ch, 1, 10) HAL_OK) { uint8_t frame[2] { 2, ch }; // 首字节真实长度第二字节是数据 uint8_t ret NRF_Send(frame, 2); uint8_t obs NRF_ReadReg(0x08); printf(send ret%d obs%02X\r\n, ret, obs); } #else uint8_t buf[32]; if (NRF_Recv(buf) 0) { HAL_UART_Transmit(huart1, buf 1, buf[0], 100); // 按首字节长度发串口 } #endif HAL_Delay(1); }串口透传模式下发送端从 PC 串口收到一个字节立即组一个 2 字节帧通过 NRF24L01 发出接收端收到无线数据后按照 first 字节里的长度把有效数据从 UART 发到 PC。这样在 PC 上打开两个串口助手就能看到双向字节回显验证链路是否打通。printf 输出调试信息只接在发送端避免收发两端抢串口。5.2 自动 ACK 与双向传输ACK payload 代替频繁切换角色很多人在做完单向收发后想改成双向第一反应是让两块板子定时切换 RX/TX 模式。这样确实能工作但切换时机和丢包处理很麻烦。nRF24L01 原生支持 ACK payload接收端在回 ACK 时可以把一包数据一起带回发送端这样一次通信完成两个方向的数据交换。接收端需要使用 W_ACK_PAYLOAD 命令命令格式是 0xA8 或管道号例如往管道 0 回传就发 0xA8void NRF_SendAckPayload(const uint8_t *data, uint8_t len) { NRF_CSN_LOW(); NRF_SPI_RW(0xA8); // W_ACK_PAYLOAD管道 0 for (uint8_t i 0; i 32; i) { NRF_SPI_RW(i len ? data[i] : 0x00); } NRF_CSN_HIGH(); }发送端这边不需要做任何特殊处理正常情况下等 ACK 时芯片会把对方携带的数据放进 RX FIFO发送端读 R_RX_PAYLOAD 就能取到回传字节。最典型场景是遥控器发送端每秒发一次遥控指令接收端在 ACK 里捎带电池电压和传感器数据整条链路没有一帧是“空跑”的。要注意 ACK payload 也会占用 RX FIFO如果对方每次 ACK 都带 32 字节发送端必须及时读走否则 FIFO 满了会丢包。RX_PW_P0 最好配置成实际回传长度而不是一律填 32。5.3 三个关键参数信道、发射功率和重发次数的实际推荐值联调阶段最常见的现象是“离得近能通离远一点就疯狂重发”这往往不是硬件能力不行而是参数没配合好。下面这组参数适合室内 10 米左右的点对点场景参数设置说明RF_CH0x40即 2464MHz避开 2.4G WiFi 常用信道 1 和 6RF_SETUP0x061Mbps 0dBm速度和距离的平衡点SETUP_RETR0x0F重发间隔 250us重发 15 次EN_AA0x01管道 0 自动 ACKRX_PW_P032固定 32 字节兼容性最好如果你在实验室里 1 米都通不了先别调参数把上面三个地址相关寄存器打印出来看。TX_ADDR、RX_ADDR_P0 和 EN_AA 只要有一处不匹配芯片收不到完整地址就不会回 ACK发送端会一直进 MAX_RT。近距离测试还把发射功率调到最高没有意义反而增加功耗建议把 RF_SETUP 改成 0x04 即 -12dBm如果这样还能通信说明链路余量很足再调回 0x06 做距离验证。6. NRF24L01 链路质量验证读 OBSERVE_TX 重发计数与丢包率测试6.1 用 OBSERVE_TX 读取重发次数和丢包统计STATUS 寄存器只能告诉你“这包发没发出去”不能说明链路质量。OBSERVE_TX 寄存器地址 0x08低 4 位是最近一次发送的重发次数高 4 位是累计丢包计数。发送成功后读这个寄存器数值越小说明链路越稳uint8_t obs NRF_ReadReg(0x08); uint8_t arc_cnt obs 0x0F; uint8_t plos_cnt (obs 4) 0x0F; printf(ARC%d, PLOS%d\r\n, arc_cnt, plos_cnt);ARC 为 0 表示一次成功ARC 经常在 10 以上说明距离已经接近边界或者信道干扰很强。PLOS 是累计值需要自己记录两次读数的差值来判断增量。这个寄存器在连续重发时并不清零所以不要只看数值大小要看单位时间内两次采样之间的变化趋势。调试时每次发送后把 ARC 和 PLOS 一起打印就能直观看到链路余量。6.2 固定序号测丢包率避免用串口吞吐倒推无线质量有些工程师喜欢用串口收发多少字节来估算无线链路好不好这个办法容易被协议栈和缓冲区干扰不够直接。我一般用序号法发送端每发一包第一字节是递增的序号接收端收到后检查序号连续性连续序号不一致就说明中间丢了包。// 发送端 static uint8_t seq 0; uint8_t pkt[32]; pkt[0] seq; memset(pkt 1, 0xAA, 31); NRF_Send(pkt, 32); HAL_Delay(10);// 接收端 static uint8_t last_seq 0; if (buf[0] ! (uint8_t)(last_seq 1)) { uint8_t lost (uint8_t)(buf[0] - last_seq - 1); total_lost lost; } last_seq buf[0];测试方法发送间隔 10ms持续 1000 包接收端统计丢包数和 ARC 均值。10 米空旷环境丢包率应小于 1%如果高于 5%先看信道是否和周边 WiFi 重叠再降速率到 250kbps 验证。注意 250kbps 只有 nRF24L01 支持老版本芯片不支持。6.3 距离边界怎么判断重发比例比信号强度更靠谱NRF24L01 没有 RSSI 寄存器不能像 CC1101 那样直接拿信号强度判断距离。工程上可以统计 N 包内 MAX_RT 的比例当发送端连续 5 次以上触发 MAX_RT而且 ARC 计数达到 15说明接收端已经收不到任何 ACK这就是通信边界。此时把天线方向转 90 度、调低速率通常能拉回一段距离。板载天线对着人是衰减最大的姿势测试时人站侧面地面也会反射 2.4G 信号模块离地面 20cm 以上测试结果更稳定。把这三个判断条件写进固件收到边界警告后自动降速或加大重发间隔比手动改参数调距离高效得多。本文还有配套的精品资源点击获取