
簡介基于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 以上測試結果更穩定。把這三個判斷條件寫進固件收到邊界警告后自動降速或加大重發間隔比手動改參數調距離高效得多。本文還有配套的精品資源點擊獲取