部品 · 読了まで19分

nRF24L01とArduinoのチュートリアル:配線、RF24のコード、通信距離、対処法

nRF24L01とArduinoのガイド:配線、3.3 Vの電源と10 µFのコンデンサでの対策、RF24の送信・受信のコード、PA+LNAで最大1 kmの通信距離、ジョイスティックのリモコン、ESP32とPico。

nRF24L01とArduinoの組み合わせは、ケーブルを引かずに2つのボードの間でデータを送る、最も安く実用的な方法です。この爪ほどの大きさのモジュールは2.4 GHz帯で動き、最大で毎秒2メガビットを運び、送信機にも受信機にもなれます。ラジコンカー、無線のジョイスティック、庭のセンサーの値を家の中に送るステーション、互いに知らせ合うアラームのノードに最適です。標準版は見通しのよい場所で約100メートル届き、外部アンテナ付きのnRF24L01+PA+LNA版は、条件がよければ最大1キロメートル届きます。

このガイドでは、モジュールのしくみ、使うSPIのピン、3.3 Vで給電しなければならない理由、10 µFのコンデンサが最も多い問題を解決するしくみを学びます。続いて、RF24ライブラリで送信機と受信機のコードを書き、アドレスとチャンネルを理解し、ジョイスティックのリモコンを作り、モジュールをESP32とRaspberry Pi Picoにつなぎ、最後に「radio.begin() false」と「データが届かない」の問題をステップごとに解決します。

nRF24L01 2.4 GHz無線通信モジュール
標準のnRF24L01+モジュール:基板の端のジグザグの配線がPCBアンテナです。8ピンのヘッダーを通してSPIでつなぎます。

nRF24L01とは?そのしくみ

nRF24L01は、Nordic Semiconductorが作る2.4 GHzの無線トランシーバーのチップです。今売られているモジュールのほとんどには、改良版のnRF24L01+か互換のクローンのチップが載っています。モジュールはSPIでArduinoと通信します。ArduinoがSPIで設定と送るデータを書き込めば、あとはチップがやってくれます。Enhanced ShockBurstと呼ばれるNordicのハードウェアのプロトコルは、各パケットをアドレスとCRC(エラーチェック)で包みます。受信機がパケットを正しく受け取ると、自動で送信機に確認応答(ACK)を返し、ACKが来なければ送信機は自分で再送します。つまり、コードは「送れ」と言うだけで、再送とエラーチェックはハードウェアで行われます。

1つのパケットが運べるデータは最大32バイトです。少なく聞こえるかもしれませんが、ジョイスティックの2つの軸、いくつかのボタン、複数のセンサーの値なら楽に入ります。モジュールは126のチャンネルのどれかを使い、チャンネルの周波数は2400 + チャンネル番号(MHz)です。たとえばチャンネル76は2476 MHzです。受信機は6つの異なるアドレス(パイプと呼びます)を同時に聞けるので、1つのハブで6台の送信機からデータを集められます。

項目値
周波数2.400〜2.525 GHz(126チャンネル、1 MHz間隔)
データレート250 kbps、1 Mbps、2 Mbps
電源電圧1.9〜3.6 V(実際には3.3 V)
入力ピン5 Vトレラント(CE、CSN、SCK、MOSI)
出力電力0、−6、−12、−18 dBm
電流(送信中、0 dBm)約11.3 mA
電流(受信中、2 Mbps)約13.5 mA
パワーダウン約900 nA
受信感度−94 dBm(250 kbps)、−85 dBm(1 Mbps)、−82 dBm(2 Mbps)
ペイロードのサイズ1〜32バイト
インターフェースSPI、最大10 MHz
パイプの数6

nRF24L01とnRF24L01+PA+LNAの違い

市場には2種類のモジュールがあります。標準のモジュールでは、アンテナは基板に印刷されたジグザグの銅の配線です。nRF24L01+PA+LNA版は、同じトランシーバーの前にRFフロントエンドのチップを加えています。PA(パワーアンプ)は送り出す信号を強め、LNA(低雑音アンプ)は届いた弱い信号をチップに入る前に増幅します。ねじ込み式の外部アンテナはSMAコネクタに取り付けます。どちらのモジュールもピン配置が同じで同じコードを使い、互いに問題なく通信できます。違いは通信距離と消費電流です。下の数値はおおよそのもので、環境によって変わります。

アンテナ付きのnRF24L01+PA+LNA RFモジュール
nRF24L01+PA+LNA:送信と受信のアンプとSMAアンテナが付いた版です。同じコードで動きますが、消費電流はずっと大きくなります。
項目nRF24L01+(PCBアンテナ)nRF24L01+PA+LNA
アンテナ基板に印刷外部のSMAアンテナ
出力電力最大0 dBm(1 mW)最大+20 dBm(100 mW)、型番による
送信時のピーク電流約12 mA最大115 mA
受信時のピーク電流約13.5 mA最大45 mA
見通しでの通信距離(250 kbps)50〜100 m800〜1100 m
屋内での通信距離10〜30 m、壁1〜2枚数フロア、数十メートル
おすすめの電源3.3Vピン+10 µFのコンデンサ別の3.3 Vレギュレーター+コンデンサ

nRF24L01のピン配置とArduinoへの配線

モジュールには2×4のヘッダーに並んだ8本のピンがあります。四角いはんだのパッドのピンが1番ピンで、GNDです。VCCはそのすぐ横にあります。CE(Chip Enable)はモジュールを受信か送信のモードに切り替え、CSN(Chip Select Not)はSPIバスで「今このチップと話している」と伝えるためにLOWにします。SCK、MOSI、MISOは標準のSPIの線で、ボードの決まったSPIのピンにつなぐ必要がありますが、CEとCSNはどのデジタルピンにもつなげます。IRQピンは任意で、パケットが届いたときや送信が終わったときにLOWになり、割り込みを使いたいときに使います。SPIのしくみはSPIとは?を、ボードの全体のピン配置はピン配置ツールをご覧ください。

nRF24L01のピン役割Arduino Uno / NanoArduino MegaESP32Raspberry Pi Pico
1 GNDグラウンドGNDGNDGNDGND
2 VCC電源(3.3 V)3.3V3.3V3V33V3(OUT)
3 CEモードの選択D9D9GPIO 4GP6
4 CSNSPIのチップセレクトD10D10GPIO 5GP5
5 SCKSPIのクロックD13D52GPIO 18GP2
6 MOSIボードからモジュールへのデータD11D51GPIO 23GP3
7 MISOモジュールからボードへのデータD12D50GPIO 19GP4
8 IRQ割り込み(任意)つながないつながないつながないつながない
VCCを決して5 Vにつながないでください。チップが耐えられるのは最大3.6 Vで、5 Vでは永久に壊れることがあります。信号のピンは5 Vトレラントなので、UnoのSPIのピンに直接つなげますが、電源は必ず3.3 Vにします。モジュールをブレッドボードに直接差すのもやめましょう。2×4のヘッダーのため、向かい合うピンが同じ列に入り、ショートしてしまいます。代わりにメス-オスのジャンパーワイヤーを使いましょう。

最も多い失敗:電源と10 µFのコンデンサ

nRF24L01の問題のほとんどは、コードではなく電源が原因です。モジュールは送信する瞬間に、短く鋭い電流のパルスを流します。長いジャンパーワイヤーと弱い3.3 Vの電源はこのパルスに追いつけず、電圧が一瞬下がり、チップは壊れたパケットを送ったり、ACKを取りこぼしたり、自分でリセットしたりします。公式の資料によると、Arduino Unoの3.3Vピンが出せるのは最大50 mAです。標準のモジュールには足りますが、最大115 mAを流すPA+LNA版には足りません。一部のNanoの互換品では、3.3Vピンの電源がUSBシリアル変換チップから来ていて、さらに弱くなっています。症状に心当たりがあるでしょう。write()が成功したりしなかったりする、触るとよく動く、通信距離が数メートルで止まる、などです。

  • 10 µFのコンデンサ:モジュールのVCCとGNDのピンの間に、できるだけ近くで10 µFの電解コンデンサを入れます。長い足(+)をVCCに、帯のある側(−)をGNDにつなぎます。10〜100 µFならどれでも効きます。効く理由はコンデンサとは?で読めます。
  • 100 nFのセラミックコンデンサ:電解コンデンサに並列に小さなセラミックコンデンサ(104の表示)を入れると、高周波のノイズを除去できます。コンデンサの表示はコンデンサの表記ツールで読み解けます。
  • ソケットアダプター:nRF24L01用に売られている8ピンのアダプター基板には、AMS1117-3.3のレギュレーターとコンデンサが最初から付いています。アダプターのVCCに5 Vを入れれば、モジュールにはきれいな3.3 Vが届きます。
  • 別のレギュレーター:PA+LNA版は、Arduinoの3.3Vピンではなく、5 Vから給電する別のAMS1117-3.3レギュレーターから給電し、すべてのGNDをつなぎます。
  • 短い線:ジャンパーワイヤーはできれば10 cm未満にしましょう。長くゆるい線は、電源とSPIの信号の両方を乱します。
AMS1117 3.3 V電圧レギュレーター
AMS1117-3.3レギュレーター:5 Vを3.3 Vに下げ、PA+LNAモジュールが送信中に流す大きな電流も楽にまかないます。

RF24ライブラリのインストール

  1. Arduino IDEで「ツール」>「ライブラリを管理」を開くか、左のライブラリのアイコンをクリックします。
  2. 検索欄にRF24と入力し、TMRh20のRF24ライブラリをインストールします。RF24Network、RF24Mesh、RF24Ethernetは別のライブラリで、始めるには必要ありません。
  3. インストール後、「ファイル」>「スケッチ例」>「RF24」にGettingStartedの例があります。この記事のコードは、RF24のバージョン1.5以降の書き方に沿っています。
  4. ライブラリのインストールが初めてなら、Arduinoライブラリの追加方法の手順に沿って進めましょう。
ネットの古い例では、radio.openWritingPipe(address)のあとにradio.stopListening()を書いています。RF24のバージョン1.5からは、1行のradio.stopListening(address)がすすめられています。古い書き方もコンパイルできますが、「deprecated」(非推奨)の警告が出ることがあります。この記事のコードがコンパイルできないなら、RF24ライブラリを更新しましょう。

アドレス、チャンネル、データレート:2つのモジュールの設定をそろえる

2つのモジュールが互いに聞き取れるには、いくつかの設定が両側でまったく同じでなければなりません。トランシーバーでの会話を思い浮かべてください。同じ周波数(チャンネル)、同じ話す速さ(データレート)、同じ呼び出し名(アドレス)に合わせていなければ、相手の声は聞こえません。アドレスは5バイトの配列で、コードでは"00001"のような5文字の文字列で書くのが一番簡単です。アドレスはデバイスの身分証ではなく、2つのデバイスの間の共有の通り道だと考えましょう。送信機はその通り道にパケットを置き、その通り道を聞いている受信機がそれを拾います。ペイロードのサイズも大切です。RF24は初期設定で固定サイズのペイロードを使い、受信機が送信機と同じサイズを期待していないと、パケットはまったく見えません。

設定RF24の関数この記事での値メモ
チャンネルsetChannel(76)76(2476 MHz)0〜125。両側で同じにする
データレートsetDataRate(RF24_250KBPS)250 kbps速度が低いほど通信距離が長い
アドレスstopListening(address) / openReadingPipe(1, address)"00001"送信機の宛先 = 受信機のパイプ
ペイロードのサイズsetPayloadSize(sizeof(data))32バイト(ジョイスティックの例では5)固定サイズなら両側で同じにする
出力電力setPALevel(RF24_PA_LOW)低そろえなくてよい。通信距離を決める
自動ACKとCRC初期設定でオン16ビットCRCオフにするなら両側で

送信機のコード(TX)

最初のテストでは、2台のArduinoを別々のUSBポートにつなぎ、モジュールの間を1〜2メートルあけます。片方に下の送信機のコードを書き込みます。1秒ごとに番号付きのメッセージを送り、受信機が受け取りを確認したかをシリアルモニタに表示します。シリアルモニタを115200ボーにするのを忘れないでください。

C++ (Arduino IDE)
1#include <SPI.h>
2#include <RF24.h>
3
4RF24 radio(9, 10);                        // CE, CSN pinleri / CE, CSN pins
5const uint8_t address[6] = "00001";       // iki tarafta aynı adres / same address on both sides
6char message[32];                         // en fazla 32 bayt / 32 bytes max
7uint16_t counter = 0;
8
9void setup() {
10  Serial.begin(115200);
11  if (!radio.begin()) {                   // modül cevap vermiyor / module not responding
12    Serial.println(F("nRF24L01 bulunamadi, kablolari kontrol et!"));
13    while (true) {}
14  }
15  radio.setChannel(76);                   // kanal 76 = 2476 MHz / channel 76 = 2476 MHz
16  radio.setDataRate(RF24_250KBPS);        // en uzun menzil / longest range
17  radio.setPALevel(RF24_PA_LOW);          // masa testi için düşük güç / low power for desk tests
18  radio.setPayloadSize(sizeof(message));  // paket boyu 32 bayt / 32-byte payload
19  radio.stopListening(address);           // verici modu + hedef adres / TX mode + target address
20}
21
22void loop() {
23  snprintf(message, sizeof(message), "Merhaba BlueGrays #%u", counter++);
24  bool ok = radio.write(message, sizeof(message));   // onay (ACK) geldi mi? / was it acknowledged?
25  if (ok) {
26    Serial.print(F("Gonderildi: "));
27    Serial.println(message);
28  } else {
29    Serial.println(F("Onay gelmedi"));
30  }
31  delay(1000);
32}

受信機のコード(RX)

C++ (Arduino IDE)
1#include <SPI.h>
2#include <RF24.h>
3
4RF24 radio(9, 10);                        // CE, CSN pinleri / CE, CSN pins
5const uint8_t address[6] = "00001";       // vericiyle aynı adres / same address as the transmitter
6char message[32];
7
8void setup() {
9  Serial.begin(115200);
10  if (!radio.begin()) {
11    Serial.println(F("nRF24L01 bulunamadi, kablolari kontrol et!"));
12    while (true) {}
13  }
14  radio.setChannel(76);                   // ayarlar vericiyle aynı / settings match the transmitter
15  radio.setDataRate(RF24_250KBPS);
16  radio.setPALevel(RF24_PA_LOW);
17  radio.setPayloadSize(sizeof(message));
18  radio.openReadingPipe(1, address);      // 1 numaralı boruyu aç / open pipe 1
19  radio.startListening();                 // alıcı modu / RX mode
20}
21
22void loop() {
23  if (radio.available()) {                // paket geldi mi? / has a packet arrived?
24    radio.read(message, sizeof(message));
25    Serial.print(F("Gelen: "));
26    Serial.println(message);
27  }
28}

radio.write()はパケットを送り、受信機が確認すればtrueを返します。初期設定では、約1.5 ms間隔で15回再送してから、falseを返します。受信機では、openReadingPipe(1, address)でパイプ1を送信機のアドレスに設定し、startListening()でモジュールを受信モードにし、available()でパケットが待っているかを確認し、read()でそれを変数にコピーします。受信機のシリアルモニタに「Gelen: Merhaba BlueGrays #0」「#1」「#2」と数が増えていく行が表示されれば、通信はできています。

テストではモジュールをすぐ隣に置かず、出力電力を低くしておきましょう。特にPA+LNAのモジュールは、数センチの距離で最大出力で動くと互いの受信機を飽和させてしまい、パケットが失われ、故障のように見えます。近距離ではRF24_PA_LOWかRF24_PA_MINを使い、実際の距離になってから出力を上げましょう。

通信距離:100メートルから1キロメートルまで

通信距離を決めるのは、出力電力、受信感度、環境の3つです。データレートが低いほど、受信機はより弱い信号を読み解けます。感度は250 kbpsで−94 dBmですが、2 Mbpsでは−82 dBmです。この12 dBの差は、見通しのよい場所で約4倍の通信距離にあたります。壁、木、人の体、金属の面は2.4 GHzの信号を大きく弱め、Wi-Fiルーター、Bluetoothの機器、電子レンジも同じ帯域を使っています。

データレート受信感度相対的な通信距離使う場面
250 kbps−94 dBm最も長いリモコン、離れたセンサー
1 Mbps−85 dBm中くらい一般的な用途(RF24の初期設定)
2 Mbps−82 dBm最も短い高速、短い距離

チャンネルの選び方も大切です。よく使われるWi-Fiのチャンネル1、6、11は、およそ2401〜2473 MHzを占めます。nRF24L01のチャンネル74〜83はその上にあり、近くのルーターがWi-Fiのチャンネル12や13を使っていなければ、より静かです。ヨーロッパ(とトルコ)では、免許のいらない2.4 GHz帯は2400〜2483.5 MHzなので、83より上のチャンネルは使わないでください。ネットの例で見かける100以上のチャンネルはこの帯域の外で、正確な範囲は国の規則によって決まります(日本でも範囲はほぼ同じですが、技適マークのない無線モジュールの使用は電波法で制限されています)。通信距離を伸ばすには、次のことを試しましょう。

  • 250 kbpsを使う:リモコンやセンサーのデータに高い速度は必要なく、低いレートで通信距離が得られます。
  • まず電源を直し、それから出力を上げる:RF24_PA_MAXが意味を持つのは、安定した3.3 Vとコンデンサがあるときだけです。弱い電源で出力を上げると、かえって悪くなります。
  • アンテナを高く、同じ向きに取り付ける:地面から1〜2メートルの高さで、両方のアンテナを同じように立てると、通信距離が目に見えて伸びます。
  • 金属から離す:モジュールを金属の箱の中や、大きな電池パックのすぐ横に置かないでください。
  • シールドのないPA+LNAの基板を包む:金属のカバーのないPA+LNAのモジュールをビニールテープで包み、その上をアルミホイルで包むと、自分自身への干渉が減ります。ホイルがピンやアンテナのコネクタに触れないようにしてください。
  • アンテナなしで送信しない:アンテナを付けずにPA+LNAのモジュールを最大出力で動かすと、パワーアンプに負担がかかります。

ジョイスティックで無線のリモコン

では、本物のプロジェクトを作りましょう。1台のArduinoのジョイスティックで、2台目のArduinoのサーボモーターとLEDを無線で制御します。送信機では、ジョイスティックのVRxピンをA0に、VRyをA1に、ボタン(SW)をD2につなぎます。詳しくはジョイスティックモジュールをご覧ください。受信機では、サーボの信号線をD3に、LEDを220 Ωの抵抗を通してD4につなぎます。両側のnRF24L01の配線は、上の表と同じです。

1つのパケットで複数の値を送るのに一番すっきりした方法はstruct(構造体)です。ここで大切な点があります。int16_tやuint8_tのような固定サイズの型を使いましょう。intはArduino Unoでは2バイトですが、ESP32では4バイトで、さらにESP32のコンパイラは整列のために構造体の最後に詰め物のバイトを加えます。同じ構造体がUnoでは5バイト、ESP32では6バイトになると、ペイロードのサイズが合わず、データがまったく届きません。__attribute__((packed))はその詰め物を取り除き、どのボードでも構造体を5バイトにします。まずは送信機です。

C++ (Arduino IDE)
1#include <SPI.h>
2#include <RF24.h>
3
4struct __attribute__((packed)) JoystickData {   // her kartta 5 bayt / 5 bytes on every board
5  int16_t x;
6  int16_t y;
7  uint8_t button;
8};
9
10RF24 radio(9, 10);                        // CE, CSN pinleri / CE, CSN pins
11const uint8_t address[6] = "JOY01";       // kumanda adresi / remote address
12JoystickData data;
13
14void setup() {
15  pinMode(2, INPUT_PULLUP);               // joystick butonu (SW) / joystick button (SW)
16  if (!radio.begin()) {
17    while (true) {}                       // modül yok / no module
18  }
19  radio.setChannel(76);
20  radio.setDataRate(RF24_250KBPS);
21  radio.setPALevel(RF24_PA_HIGH);         // orta-yüksek güç / medium-high power
22  radio.setPayloadSize(sizeof(data));
23  radio.stopListening(address);           // verici modu / TX mode
24}
25
26void loop() {
27  data.x = analogRead(A0);                // VRx ekseni 0-1023 / VRx axis 0-1023
28  data.y = analogRead(A1);                // VRy ekseni 0-1023 / VRy axis 0-1023
29  data.button = (digitalRead(2) == LOW);  // basılıysa 1 / 1 when pressed
30  radio.write(&data, sizeof(data));
31  delay(50);                              // saniyede 20 paket / 20 packets per second
32}

続いて、サーボとLEDを動かす受信機です。

C++ (Arduino IDE)
1#include <SPI.h>
2#include <RF24.h>
3#include <Servo.h>
4
5struct __attribute__((packed)) JoystickData {   // vericiyle birebir aynı / identical to the transmitter
6  int16_t x;
7  int16_t y;
8  uint8_t button;
9};
10
11RF24 radio(9, 10);                        // CE, CSN pinleri / CE, CSN pins
12const uint8_t address[6] = "JOY01";       // vericiyle aynı / same as the transmitter
13JoystickData data;
14Servo servo;
15unsigned long lastPacket = 0;
16
17void setup() {
18  pinMode(4, OUTPUT);                     // LED çıkışı / LED output
19  servo.attach(3);                        // servo sinyal pini / servo signal pin
20  if (!radio.begin()) {
21    while (true) {}
22  }
23  radio.setChannel(76);
24  radio.setDataRate(RF24_250KBPS);
25  radio.setPALevel(RF24_PA_HIGH);
26  radio.setPayloadSize(sizeof(data));
27  radio.openReadingPipe(1, address);
28  radio.startListening();                 // alıcı modu / RX mode
29}
30
31void loop() {
32  if (radio.available()) {
33    radio.read(&data, sizeof(data));
34    servo.write(map(data.x, 0, 1023, 0, 180));   // X ekseni -> servo açısı / X axis -> servo angle
35    digitalWrite(4, data.button ? HIGH : LOW);   // buton LED'i yakar / button lights the LED
36    lastPacket = millis();
37  }
38  if (millis() - lastPacket > 500) {      // 0,5 sn paket yok: güvenli konum / no packet for 0.5 s: failsafe
39    servo.write(90);
40    digitalWrite(4, LOW);
41  }
42}

送信機は1秒に20パケットを送り、受信機はパケットごとにX軸を0〜180度に換算してサーボに書き込みます。受信機のフェイルセーフの部分はとても大切です。リモコンの電池が切れたり範囲外に出たりしても、最後の命令がいつまでも適用されることはなく、0.5秒パケットが来なければ、サーボは中央に戻り、LEDは消えます。ロボットカーを作るなら、同じ場所でモーターを止めましょう。サーボをArduinoの5Vピンではなく別の5 Vの電源から給電すると、サーボの電流のスパイクがnRF24L01を乱すのも防げます。Y軸をL298Nモータードライバにつなげば無線のロボットカーになります。サーボについて詳しくはサーボモーターとは?をご覧ください。

双方向の通信と複数の送信機

nRF24L01は送信と受信を同時にはできませんが、双方向の通信を簡単に実現する方法があります。ACKペイロード、つまり確認応答のパケットの中に入れて送るデータです。受信機はwriteAckPayload()で、次のACKに付けるデータ(たとえば電池の電圧やセンサーの値)を前もって用意し、write()が成功したあと、送信機はその返事をavailable()とread()で読みます。このためには、両側でenableDynamicPayloads()とenableAckPayload()を呼ぶ必要があります。複数の送信機には、受信機の6つのパイプを使います。パイプ0と1は5バイトのアドレスをまるごと持ち、パイプ2〜5はパイプ1のアドレスの残りを共有して、1バイトだけが違います。RF24ではそのバイトがアドレスの配列の最初の文字なので、「1Node」「2Node」「3Node」のようなアドレスは最初の文字だけが違います。もっと大きなネットワークには、RF24NetworkとRF24Meshのライブラリがあります。

nRF24L01をESP32につなぐ

RF24ライブラリはESP32でもそのまま動き、radio.begin()は初期設定のVSPIのバスを使います。SCK = GPIO 18、MISO = GPIO 19、MOSI = GPIO 23です。CSNにはGPIO 5、CEにはGPIO 4がよい選択です。ESP32 DevKitのボードの3.3 VのレギュレーターはUnoより強力ですが、10 µFのコンデンサは省かないでください。ESP32-C3やESP32-S3などの型番では初期設定のSPIのピンが違うので、ボードのピン配置を確認しましょう。ESP32のWi-Fiも2.4 GHzで動くので、両方を一緒に使うなら、ルーターのWi-Fiのチャンネルから十分離れたnRF24L01のチャンネルを選びましょう。下のコードは、ジョイスティックの送信機のパケットをESP32で読みます。同じ構造で、ESP32をnRF24L01のノードからのデータをインターネットに転送するゲートウェイとしても使えます。ボードが初めてなら、ESP32とは?から始めましょう。

C++ (Arduino IDE)
1#include <SPI.h>
2#include <RF24.h>
3
4struct __attribute__((packed)) JoystickData {   // packed olmasa 6 bayt olurdu / 6 bytes without packed
5  int16_t x;
6  int16_t y;
7  uint8_t button;
8};
9
10RF24 radio(4, 5);                         // CE = GPIO 4, CSN = GPIO 5 pinleri / CE = GPIO 4, CSN = GPIO 5 pins
11const uint8_t address[6] = "JOY01";
12JoystickData data;
13
14void setup() {
15  Serial.begin(115200);
16  if (!radio.begin()) {                   // varsayılan VSPI: SCK 18, MISO 19, MOSI 23 / default VSPI pins
17    Serial.println("nRF24L01 bulunamadi!");
18    while (true) delay(1000);
19  }
20  radio.setChannel(76);
21  radio.setDataRate(RF24_250KBPS);
22  radio.setPALevel(RF24_PA_HIGH);
23  radio.setPayloadSize(sizeof(data));     // packed sayesinde 5 bayt / 5 bytes thanks to packed
24  radio.openReadingPipe(1, address);
25  radio.startListening();
26}
27
28void loop() {
29  if (radio.available()) {
30    radio.read(&data, sizeof(data));
31    Serial.printf("X=%d  Y=%d  Buton=%d\n", data.x, data.y, data.button);
32  }
33}

ESP32のボード同士をつなぐだけなら、追加のモジュールがいらないESP-NOWもよい選択です。nRF24L01の利点は、Uno、Nano、Pro Miniのような安くて省電力のボードでも使えることです。

Raspberry Pi PicoとMicroPythonでnRF24L01を使う

PicoのMicroPythonでは、公式のmicropython-libのリポジトリにあるnrf24l01.pyのドライバを使います。ファイルはThonnyでPicoにコピーするか、パソコンからmpremote mip install nrf24l01でインストールできます。配線はSPI0を使い、SCK = GP2、MOSI = GP3、MISO = GP4、CSN = GP5、CE = GP6、VCC = 3V3(OUT)です。ドライバの初期設定はRF24とは違う(チャンネル46、ペイロード16バイト)ので、Arduinoと通信するために、コードでチャンネル、データレート、ペイロードのサイズをはっきり設定します。下のコードは、ジョイスティックの送信機のパケットをPicoで読み解きます。

MicroPython
1from machine import Pin, SPI
2import struct
3import utime
4from nrf24l01 import NRF24L01, POWER_2, SPEED_250K
5
6spi = SPI(0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))   # SPI0 pinleri / SPI0 pins
7csn = Pin(5, mode=Pin.OUT, value=1)                  # CSN pini GP5 / CSN pin GP5
8ce = Pin(6, mode=Pin.OUT, value=0)                   # CE pini GP6 / CE pin GP6
9
10nrf = NRF24L01(spi, csn, ce, channel=76, payload_size=5)   # Arduino ile aynı / same as the Arduino
11nrf.set_power_speed(POWER_2, SPEED_250K)             # -6 dBm güç, 250 kbps / -6 dBm power, 250 kbps
12nrf.open_rx_pipe(1, b"JOY01")                        # vericinin adresi / transmitter address
13nrf.start_listening()                                # alıcı modu / RX mode
14print("Dinleniyor... / Listening...")
15
16while True:
17    if nrf.any():                                    # paket geldi mi? / packet waiting?
18        x, y, button = struct.unpack("<hhB", nrf.recv())   # 2 + 2 + 1 bayt / 2 + 2 + 1 bytes
19        print("X:", x, "Y:", y, "Buton:", button)
20    utime.sleep_ms(5)

struct.unpack("<hhB")は、届いた5バイトを符号付きの16ビットの数2つと1バイトに変えます。先頭の"<"はリトルエンディアンのバイト順を指定し、これはAVRとESP32が使う順番です。モジュールが見つからないと、ドライバは"nRF24L01+ Hardware not responding"を出します。これは、Arduinoでbegin()がfalseを返すのに相当するMicroPythonのものです。Picoが初めてなら、先にRaspberry Pi Picoとは?を読みましょう。

radio.begin()がfalseを返す:ステップごとの直し方

シリアルモニタに「nRF24L01 bulunamadi」や、RF24の例の"radio hardware is not responding!!"(無線のハードウェアが応答しない)というメッセージが表示されたら、ArduinoはSPIでモジュールとまったく通信できていません。その場合、問題はほぼ必ず、コードではなく配線か電源にあります。まず下の診断用のスケッチを書き込みましょう。モジュールのすべての設定を読みやすい形で表示します。

C++ (Arduino IDE)
1#include <SPI.h>
2#include <RF24.h>
3#include "printf.h"                       // RF24 ile gelir / ships with RF24
4
5RF24 radio(9, 10);                        // kendi CE, CSN pinlerin / your CE, CSN pins
6
7void setup() {
8  Serial.begin(115200);
9  printf_begin();                         // printPrettyDetails için / needed for printPrettyDetails
10  bool ok = radio.begin();
11  Serial.print(F("begin(): "));
12  Serial.println(ok ? "true" : "false");
13  Serial.print(F("isChipConnected(): "));
14  Serial.println(radio.isChipConnected() ? "true" : "false");
15  radio.printPrettyDetails();             // tüm ayarları yazdır / print every setting
16}
17
18void loop() {
19}
  1. MOSIとMISOを確認する:最もよく入れ替わる2本の線です。UnoとNanoではMOSI = D11、MISO = D12、SCK = D13、Megaでは51、50、52です。
  2. CEとCSNがコードと合っているか:RF24 radio(9, 10)と書いたなら、CEをD9に、CSNをD10につなぎます。RF24のGettingStartedの例は7番と8番のピンを使うので、例をそのまま試すならそのとおりに配線しましょう。
  3. 電源は3.3 Vか:テスターでVCCとGNDの間を測り、3.0〜3.6 Vになっているはずです。モジュールを一度でも5 Vにつないだことがあるなら、壊れているかもしれません。
  4. printPrettyDetailsの出力を見る:アドレスとレジスタの値がすべて0x00か0xffなら、SPIのバスが動いていません。値がそれらしく見えるのにまだ通信できないなら、問題はCEピン、設定、電源のどれかです。
  5. 線を短くし、SPIの速度を下げる:長い線やゆるい線なら、RF24 radio(9, 10, 4000000)と書いて、SPIの速度を初期設定の10 MHzから4 MHzに下げます。
  6. モジュールを替える:ほかがすべて問題なければ、予備のモジュールを試しましょう。安いまとめ売りには、不良品が混ざっていることがあります。

データが来ない、とぎれとぎれ:よくある問題

症状考えられる原因対処法
begin()はtrueなのに受信機に何も届かないチャンネル、データレート、アドレス、ペイロードのサイズが両側で違う両方のスケッチで4つの設定を並べて比べる
UnoからESP32にデータが来ない構造体のサイズが違う(5バイトと6バイト)固定サイズの型と__attribute__((packed))を使う
write()はfalseなのに受信機はデータを受け取っている受信機のACKが送信機に届いていない。たいていは受信機の電源が原因受信機のモジュールにも10 µFのコンデンサを入れる
画面にいつも空か、でたらめなデータSPIの線がゆるいか、MISOの接続が切れている線を替え、SPIの速度を4 MHzに下げる
隣に置くと動かず、離すと動く最大出力で受信機が飽和しているsetPALevel(RF24_PA_LOW)かRF24_PA_MINを使う
触るとよく動く電源が不安定、またはシールドのないPA+LNAコンデンサ、別のレギュレーター、ホイルでのシールド
数メートルで通信が切れる2 Mbpsのデータレート、弱い電源、またはWi-Fiの干渉250 kbps、きれいな電源、74〜83のチャンネル
トラブルを調べるときは、いつも最も簡単な構成に戻りましょう。1〜2メートル離した2つのモジュール、RF24_PA_LOW、250 kbps、短い線、10 µFのコンデンサです。その構成で通信できたら、距離、出力、コードの複雑さを少しずつ上げていけば、どのステップで問題が始まるかがすぐにわかります。

nRF24L01を使ったプロジェクトのアイデア

  • ロボットカーの無線リモコン:2本のジョイスティックのリモコンで、数十メートル離れた場所からロボットカーを走らせます。
  • 庭のセンサーステーション:電池で動く送信機から、土壌水分センサーと温度のデータを家の中のディスプレイに送ります。radio.powerDown()なら、スリープ中のモジュールの消費電流は約900 nAです。電池のもちは電池寿命ツールで見積もれます。
  • 無線のドアと人感のアラーム:PIRセンサーやリードスイッチが反応したら、ハブでブザーを鳴らします。
  • 複数の部屋の温度ネットワーク:6つの部屋のセンサーの値を、6つのパイプで1台の受信機に集めます。
  • ESP32のゲートウェイ:nRF24L01のノードからのデータを、ESP32でインターネット、スマートフォン、ダッシュボードに転送します。
  • 無線のスコアボード:体育館の7セグメントのスコアボードを、ボタンのリモコンから更新します。

nRF24L01は安いのに、Bluetoothと比べて通信距離が長く、遅延が少なく、複数のデバイスにも対応します。求められるのは、きれいな3.3 Vの電源と、両側でそろった設定だけです。スマートフォンで操作したいならHC-05 Bluetoothモジュールのほうが合っているかもしれませんし、数キロメートルの通信距離ならLoRaです。選択肢の比較はArduinoの無線通信モジュールにあります。BlueGraysアプリには、2台のArduinoの通信、ジョイスティックでのサーボとLEDの制御、リモコンで操作する車などのプロジェクトが、回路図とすぐに使えるコード付きでステップごとに用意されています。

よくある質問

nRF24L01の通信距離はどのくらいですか?

PCBアンテナの標準のnRF24L01+は、250 kbpsで見通しのよい場所なら約50〜100メートル、屋内では壁によって10〜30メートル届きます。外部アンテナ付きのnRF24L01+PA+LNA版は、さえぎるもののない見通しのよい場所で800〜1100メートル届きます。この数字に届くには、きれいな3.3 Vの電源、10 µFのコンデンサ、低いデータレートが必要です。

nRF24L01は5Vで動きますか?

いいえ。VCCピンは1.9〜3.6 Vで動き、5 Vではチップが壊れることがあります。ただし、CE、CSN、SCK、MOSIの入力ピンは5 Vトレラントなので、レベル変換なしでArduino UnoのSPIのピンにつなげます。モジュールを5 Vから給電したいなら、AMS1117-3.3のレギュレーター付きのソケットアダプターを使いましょう。

「radio hardware is not responding」エラーが出るのはなぜですか?

このメッセージはradio.begin()がfalseを返したときに表示され、ArduinoがSPIでモジュールと通信できないことを意味します。最も多い原因は、MOSIとMISOの入れ替わり、コードと違うCEとCSNのピン、弱い電源か5 Vの電源、ゆるいジャンパーワイヤーです。配線を確認し、printPrettyDetails()の出力を見て、問題を絞り込みましょう。

nRF24L01とHC-05 Bluetoothはどちらを使えばいいですか?

Arduino同士で、長い通信距離、少ない遅延、複数のデバイスが必要ならnRF24L01のほうが向いています。スマートフォンから操作したいなら、HC-05のようなBluetoothモジュールかESP32を選びましょう。スマートフォンはnRF24L01のプロトコルを話せないからです。HC-05の通信距離はふつう10メートルほどですが、nRF24L01は同じくらいの値段でずっと遠くまで届きます。

1台のnRF24L01の受信機で何台の送信機を聞けますか?

受信機は6つのパイプを同時に聞けるので、6台の送信機から直接データを集められます。パイプ2〜5はパイプ1のアドレスの本体を共有し、1バイトだけが違います。もっと多くのノードには、送信機に順番に問い合わせるか、RF24NetworkとRF24Meshのライブラリでずっと大きなネットワークを作れます。

nRF24L01はスマートフォンとつながりますか?

直接はつながりません。nRF24L01は同じ2.4 GHz帯で動きますが、Wi-FiやBluetoothのプロトコルは使いません。スマートフォンで操作したいなら、nRF24L01のネットワークの中心にESP32を置き、Wi-FiかBluetoothでスマートフォンにデータを渡しましょう。こうすれば、離れたノードはnRF24L01で話し、スマートフォンはESP32と話します。

全プロジェクトを見る
2台のArduinoで一方向通信
#57中級
プレミアム

2台のArduinoで一方向通信

2台のArduinoを1本のワイヤーで通信させます。一方のボタンを押すともう一方のLEDが点灯し、もう一度押すと消灯します。送信側のボードがボタンの押下を短いメッセージで伝え、受信側のボードはそのメッセージを受け取るとLEDの点灯と消灯を切り替えます。機器同士が情報を伝える仕組みを、最もシンプルな形で確かめられます。

4.4(37)
50
2台のArduinoで双方向通信
#58中級
プレミアム

2台のArduinoで双方向通信

2台のArduinoをケーブルでつないで通信します。片方のボタンを押すともう片方のライトが点灯し、もう一度押すと消灯します。両方のボードで同じプログラムを動かすので、どちらからでもメッセージを送受信できます。ボード同士でメッセージをやり取りする仕組みを、シンプルな構成で学べます。

4.5(20)
26
ジョイスティックでLEDを制御
#59中級
プレミアム

ジョイスティックでLEDを制御

ジョイスティックのスティックをいずれかの方向へいっぱいに倒すと、その方向のLEDが点灯します。右、左、上、下にそれぞれ異なる4色のLEDがあります。スティックを離すと中央に戻り、すべてのLEDが消灯します。ロボットカーや画面上のゲームを操作するための第一歩です。

4.7(50)
67
ジョイスティックでサーボモーターを制御
#60中級
プレミアム

ジョイスティックでサーボモーターを制御

防犯カメラの向きを変えるように、ジョイスティックで2つのモーターを操作します。スティックを左右に倒すと下側のサーボモーターがゆっくり回り、上下に倒すと上側のサーボモーターが上下に傾きます。手を離すと、どちらもその位置で止まります。カメラやレーザーの台座など、2方向に動く装置の基礎を作ります。

4.7(42)
58
L298Nで作るBluetooth操作の車
#80上級
プレミアム

L298Nで作るBluetooth操作の車

スマートフォンをリモコンにして、自作のロボットカーを操作できます。ArduinoはBluetoothモジュールを通じてスマートフォンのリモコンアプリから指令を受け取り、モータードライバーで車輪を回します。前進、後退、左右や斜めへの移動ができ、速度もスマートフォンから調整できます。停止ボタンを押すと、短いブレーキ動作でモーターがすぐに止まります。

5.0(38)
65
BluetoothでスマートフォンからLEDを操作
#34初級
プレミアム

BluetoothでスマートフォンからLEDを操作

スマートフォンで部屋の照明をつけたり消したりするための、小さな練習です。Bluetoothモジュールがスマートフォンから送られた文字をArduinoに伝えます。ある文字で赤いLEDが、別の文字で緑のLEDが点灯し、同じ文字を小文字で送ると消灯します。無線操作の仕組みを、ごくシンプルな形で学べます。

4.8(68)
89
IR受信機とリモコンでLEDを点灯・消灯
#17中級
プレミアム

IR受信機とリモコンでLEDを点灯・消灯

椅子に座ったまま3つの照明を操作するように、リモコンの1、2、3のボタンで青、赤、緑のLEDを点灯させ、4、5、6のボタンで同じ順に消灯させます。電源ボタンで3つを一斉に点灯させ、消音ボタンですべて消灯させます。リモコンの信号を受け取るのは赤外線受信機です。家の機器をリモコンで操作するプロジェクトへの第一歩です。

4.7(45)
66
ESP32汎用IRリモコン(IR受信機 + リモコン + LED3個 + ブザー + OLED)
#143上級
プレミアム

ESP32汎用IRリモコン(IR受信機 + リモコン + LED3個 + ブザー + OLED)

ESP32で市販のIRリモコンの信号を読み取り、自作の機器を操作できます!このプロジェクトでは赤外線受信機(NECプロトコル、38 kHz)で20ボタンのIRリモコンの信号を受信します。各ボタンには異なる動作を割り当てています。1、2、3ボタンで3個のLEDをそれぞれオン・オフし、PWRボタンですべて消灯します。4~9の数字ボタンではOLEDに特別な表示を出します。SSD1306 OLEDには最後に押したボタンを大きく表示し、実行した動作の説明とLEDの状態もリアルタイムで表示します。未知のボタンを押すとOLEDにHEXコードを表示します。

4.5(9)
16