手头两块 Arduino,想让它们不拉线就互相说话,NRF24L01 大概是成本最低的答案:十几块钱一对,2.4GHz 收发一体,SPI 接口,官方 datasheet 摆在那,社区里的 TMRh20/RF24 库又把封装做得足够薄。这篇文章把 HowToMechatronics 那篇经典教程连同社区里反复出现的坑一起理了一遍——接线怎么接、收发对怎么写、地址和信道到底是什么关系,以及为什么你照抄代码却一个包都收不到。多数时候,问题不在代码,在供电。
先把模块认清楚:2.4GHz 收发一体的小板子
NRF24L01 是 Nordic Semiconductor 的 2.4GHz 无线收发芯片,常见的是带弹簧天线和晶振的转接板版本。工作电压 1.9 到 3.6V,发射电流约 12mA,速率 250kbps 到 2Mbps 可选,空旷环境标称距离 100 米上下。它有 125 个信道,每个信道可以开 6 条地址管道(pipe),也就是说一套网络里能挂 6 个节点同时收。
有个好消息:虽然模块供电是 3.3V,但它的逻辑引脚是 5V 容忍的,接 Arduino UNO 不用加电平转换,直接连。这点 datasheet 写得很清楚,新手可以少操一份心。市售版本里还有加 PA+LNA 放大的远距离版(带 SMA 天线,标称能到 1000 米),引脚定义不变,接线完全一样。


接线:SPI 七根线一次接对
模块 8 个引脚:GND、VCC、CE、CSN、SCK、MOSI、MISO、IRQ。真正要接的是 7 根(IRQ 可悬空)。SPI 三根走硬件 SPI——UNO 上是 D11(MOSI)、D12(MISO)、D13(SCK);CE 和 CSN 是普通数字脚,库的构造函数里自己指定,教程用的是 D7 和 D8。VCC 必须接 3.3V,接 5V 会烧,这不是吓唬人,是芯片绝对最大额定值就到 3.6V。
接线对完之后,两块板子的接法完全一样,一块将来当发射、一块当接收,硬件不用改。

RF24 收发对:两段代码跑通第一个包
库用 Arduino IDE 库管理器搜 RF24 装 TMRh20 的那个就行。收发两端的关键点:地址必须一致,发射端 openWritingPipe 加 stopListening,接收端 openReadingPipe 加 startListening。单次 write 最大 32 字节,这是芯片硬件 FIFO 的限制,超了就得自己分包。
发射端,每秒发一个 Hello World:
/*
* Arduino Wireless Communication Tutorial
* Example 1 - Transmitter Code
*
* by Dejan Nedelkovski, www.HowToMechatronics.com
*
* Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include "SPI.h"
#include "nRF24L01.h"
#include "RF24.h"
RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00001";
void setup() {
radio.begin();
radio.openWritingPipe(address);
radio.setPALevel(RF24_PA_MIN);
radio.stopListening();
}
void loop() {
const char text[] = "Hello World";
radio.write(&text, sizeof(text));
delay(1000);
}接收端,串口 9600 打出来等着收:
/*
* Arduino Wireless Communication Tutorial
* Example 1 - Receiver Code
*
* by Dejan Nedelkovski, www.HowToMechatronics.com
*
* Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include "SPI.h"
#include "nRF24L01.h"
#include "RF24.h"
RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00001";
void setup() {
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_MIN);
radio.startListening();
}
void loop() {
if (radio.available()) {
char text[32] = "";
radio.read(&text, sizeof(text));
Serial.println(text);
}
}烧完打开两边串口,接收端每秒一行 Hello World,链路就算通了。注意 openReadingPipe 第一个参数是管道号,0 到 5,tutorial 里用的是 0。
地址与信道:收不到包先查这两处
可以把信道想成频段,地址想成门牌号。两块模块必须落在同一个信道上,且收发的管道地址一字不差,才能握手。教程里用 5 字符地址 "00001",这个长度正好对应库默认的 5 字节地址宽度。如果一个端改了地址另一个没改,或者两端固件里地址字符串差一个字符,write 会照常返回,但接收端永远静默——这是新手最容易懵的地方,因为它不报错。
多节点时才是 pipe 真正的用武之地:一个接收端开 6 条不同地址的管道,可以同时监听 6 个发射端,不用自己写轮询。

功率与供电:90% 的翻车都在这
先把功率级说清楚。setPALevel 从 RF24_PA_MIN 到 RF24_PA_MAX,拉高功率能拉开距离,但发射电流也随之上涨。第一次调试建议保持 MIN——距离近不影响你验证逻辑,还把电源压力降到最低。带 PA+LNA 的远距离版在 MAX 档瞬时电流能冲到上百毫安级别,这时候 Arduino 板载 3.3V 稳压器那点余量根本兜不住。
然后是全社区口径一致的头部坑:供电噪声。NRF24L01 对 3.3V 电源轨很敏感,USB 供电的 UNO 板载 3.3V 又常常不干净,症状五花八门——radio.begin() 打印全是 0、write 永远失败、时通时断。标准解法简单得不像话:在模块的 VCC 和 GND 之间并一颗 10 到 100 微法的电解电容,大量翻车案例就此终结。如果还不稳,就别指望板载 3.3V 了,外部独立 3.3V 稳压(AMS1117 之类)给模块单独供电,共地即可。

顺带一提天线版本的选择:普通板载天线版够用的场景就别上 PA+LNA,功率越大越考验供电,很多买了远距离版的人反而因为电源跟不上,表现不如普通版。

进阶:双向通信,一块板子又发又收
RF24 允许运行中在收发之间快速切换,做法是给两端各开两条地址,互相把对方的地址挂在自己的读管道上,然后每轮 loop 里 stopListening 发一包、再 startListening 听一包。下面是教程 Example 2 的完整代码:发射端读 A0 电位器映射成舵机角度发过去,接收端驱动舵机,同时把手上的按键状态回传,发射端据此点亮自己的 LED。
发射端:
/*
* Arduino Wireless Communication Tutorial
* Example 2 - Transmitter Code
*
* by Dejan Nedelkovski, www.HowToMechatronics.com
*
* Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include "SPI.h"
#include "nRF24L01.h"
#include "RF24.h"
#define led 12
RF24 radio(7, 8); // CE, CSN
const byte addresses[][6] = {"00001", "00002"};
boolean buttonState = 0;
void setup() {
pinMode(12, OUTPUT);
radio.begin();
radio.openWritingPipe(addresses[1]); // 00002
radio.openReadingPipe(1, addresses[0]); // 00001
radio.setPALevel(RF24_PA_MIN);
}
void loop() {
delay(5);
radio.stopListening();
int potValue = analogRead(A0);
int angleValue = map(potValue, 0, 1023, 0, 180);
radio.write(&angleValue, sizeof(angleValue));
delay(5);
radio.startListening();
while (!radio.available());
radio.read(&buttonState, sizeof(buttonState));
if (buttonState == HIGH) {
digitalWrite(led, HIGH);
}
else {
digitalWrite(led, LOW);
}
}接收端:
/*
* Arduino Wireless Communication Tutorial
* Example 2 - Receiver Code
*
* by Dejan Nedelkovski, www.HowToMechatronics.com
*
* Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include "SPI.h"
#include "nRF24L01.h"
#include "RF24.h"
#include "Servo.h"
#define button 4
RF24 radio(7, 8); // CE, CSN
const byte addresses[][6] = {"00001", "00002"};
Servo myServo;
boolean buttonState = 0;
void setup() {
pinMode(button, INPUT);
myServo.attach(5);
radio.begin();
radio.openWritingPipe(addresses[0]); // 00001
radio.openReadingPipe(1, addresses[1]); // 00002
radio.setPALevel(RF24_PA_MIN);
}
void loop() {
delay(5);
radio.startListening();
if ( radio.available()) {
while (radio.available()) {
int angleV = 0;
radio.read(&angleV, sizeof(angleV));
myServo.write(angleV);
}
delay(5);
radio.stopListening();
buttonState = digitalRead(button);
radio.write(&buttonState, sizeof(buttonState));
}
}
想一次传多路数据(摇杆 XY、按键、旋钮),就把字段打包进一个 struct,两端定义完全一致,radio.write 直接 write 整个结构体,只要 sizeof 不超过 32 字节就行。这是把 NRF24L01 用成小型无线传感器网络的基础。
收尾:这套链路能干什么
跑通收发对之后,往上看一层,这十几块钱的链路能干的事不少:遥控小车、无线传感器节点、多机编队的底层通道,都是同一个套路变着花样用。它不像 ESP32 那样自带 WiFi 和协议栈,但正因为它够裸、够简单、不依赖路由器,延迟和时序都在自己手里,反而是做确定性通信更好的起点。把供电这一课学扎实,NRF24L01 会是你工具箱里性价比最高的一对模块。











请问双向通信那段 delay(5) 是必须的吗,去掉会丢包吗?打算给遥控小车用
前年玩过一阵这模块,最后就是栽在供电上。USB 供电的 UNO 带不动,模块时不时掉线,当时排查了两天没头绪,后来在 VCC 和 GND 之间并了颗 100uF 电容立马就稳了。文章里说 90% 翻车在供电,一点不夸张,这坑我拿血泪填过。
地址差一个字符不报错这个点真的关键,我第一次玩的时候就是发射端写 00001 接收端写成 00002,write 还返回成功,接收端死活没输出,折腾一下午。
电容一颗治百病,这模块老玩家都懂