気の向くままに辿るIT/ICT/IoT
IoT・電子工作

Doorbell using nRF24L01+

ホーム前へ次へ
Arduinoって?

Doorbell using nRF24L01+

Doorbell using nRF24L01+

2018/02/08
Arduino/nRF24L01+/タクトスイッチを使った無線メロディ再生

 『Doorbell using nRF24L01+』を自作してみるページ。

 回路とスケッチについては後述しますが、より具体的に言うとArduino Nano互換機 5V 16MHzとnRF24L01+無線・トランシーバモジュールを各2個、パッシブブザー他を使いました。

 が...、日本では、2.4GHzは無線LANで使われる周波数帯ですが、この機器に対しては、個別に技適を取る必要がある模様、輸入品や、まして個人での取得は、まず、無理らしい...無線LANで使ってるんだから良さそうなもんですが、グレーっぽいので要注意...。

 よくわからないまま、単価100円程度になるならと10個セット買っちゃったけど...使える国に移住するか、いつか日本でも堂々と使えるようになるのを期待するか、造りを眺めて勉強するか、飾るか、ゴミか...何れにしても、ここまで知る前に、うっかり、やってみちゃったことだけは書いておきます。

 結果から言うと無線LAN同様、壁や家具など障害をものともせず、他の部屋、キッチン、風呂場など数m余裕で通信可能でした。

 回路・スケッチについては、後述しますが、送信側のシリアルモニタ上で応答を確認、受信側のnRF24L01+が応答した時に受信側回路上のパッシブブザーがドレミの音階を再生する動画がこれです。

 送信側電源は、PCのUSBポート、受信側電源は、モバイルバッテリー、送信・受信側共にArduinoボードは、Nano互換機としました。

 送信、受信の回路概略とスケッチは、以下の通り。

#include <SPI.h>
#include "nRF24L01.h"
#include "RF24.h"
#include "printf.h"
 
// Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 9 & 10
RF24 radio(7,8);
 
byte addresses[][6] = {"1Node","2Node"};
 
 
// Set up roles to simplify testing
boolean role;                  // The main role variable, holds the current role identifier
boolean role_ping_out = 1, role_pong_back = 0;  // The two different roles.
 
#define BEAT 500 // 音の長さを指定
#define PINNO 5  // 圧電スピーカを接続したピン番号
 
void setup() {
 pinMode(PINNO,OUTPUT);
 
 
 Serial.begin(57600);
 printf_begin();
 printf("\n\rRF24/examples/GettingStarted/\n\r");
 printf("*** PRESS 'T' to begin transmitting to the other node\n\r");
 
 // Setup and configure rf radio
 radio.begin();             // Start up the radio
 radio.setAutoAck(1);          // Ensure autoACK is enabled
 radio.setRetries(15,15);        // Max delay between retries & number of retries
 radio.openWritingPipe(addresses[1]);
 radio.openReadingPipe(1,addresses[0]);
 
 radio.startListening();         // Start listening
 radio.printDetails();          // Dump the configuration of the rf unit for debugging
}
 
void loop(void){
  
 
/****************** Ping Out Role ***************************/
 if (role == role_ping_out) {
  
  radio.stopListening();                  // First, stop listening so we can talk.
  
  
  printf("Now sending \n\r");
 
  unsigned long time = micros();               // Take the time, and send it. This will block until complete
   if (!radio.write( &time, sizeof(unsigned long) )){ printf("failed.\n\r"); }
    
  radio.startListening();                  // Now, continue listening
  
  unsigned long started_waiting_at = micros();        // Set up a timeout period, get the current microseconds
  boolean timeout = false;                  // Set up a variable to indicate if a response was received or not
  
  while ( ! radio.available() ){               // While nothing is received
   if (micros() - started_waiting_at > 200000 ){      // If waited longer than 200ms, indicate timeout and exit while loop
     timeout = true;
     break;
   }   
  }
    
  if ( timeout ){                       // Describe the results
    printf("Failed, response timed out.\n\r");
  }else{
    unsigned long got_time;                 // Grab the response, compare, and send to debugging spew
    radio.read( &got_time, sizeof(unsigned long) );
 
    // Spew it
    printf("Sent %lu, Got response %lu, round-trip delay: %lu microseconds\n\r",time,got_time,micros()-got_time);
  }
 
  // Try again 1s later
  delay(1000);
 }
 
/****************** Pong Back Role ***************************/
 
 if ( role == role_pong_back )
 {
  if( radio.available()){
   unsigned long got_time;                    // Variable for the received timestamp
   while (radio.available()) {                  // While there is data ready
    radio.read( &got_time, sizeof(unsigned long) );       // Get the payload
   }
 
   radio.stopListening();                    // First, stop listening so we can talk 
   radio.write( &got_time, sizeof(unsigned long) );       // Send the final one back.   
   radio.startListening();                    // Now, resume listening so we catch the next packets.  
   printf("Sent response %lu \n\r", got_time); 
   melody(); // 受信成功時にメロディを演奏
  }
}
 
 
/****************** Change Roles via Serial Commands ***************************/
 
 if ( Serial.available() )
 {
  char c = toupper(Serial.read());
  if ( c == 'T' && role == role_pong_back )
  {
   printf("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK\n\r");
 
   role = role_ping_out;         // Become the primary transmitter (ping out)
   radio.openWritingPipe(addresses[0]);
   radio.openReadingPipe(1,addresses[1]);
 
  }
  else if ( c == 'R' && role == role_ping_out )
  {
   printf("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK\n\r");
   
    role = role_pong_back;        // Become the primary receiver (pong back)
    radio.openWritingPipe(addresses[1]);
    radio.openReadingPipe(1,addresses[0]);
  }
 }
}
 
void melody() {
  tone(PINNO,262,BEAT) ; // ド
  delay(BEAT) ;
  tone(PINNO,294,BEAT) ; // レ
  delay(BEAT) ;
  tone(PINNO,330,BEAT) ; // ミ
  delay(BEAT) ;
  tone(PINNO,349,BEAT) ; // ファ
  delay(BEAT) ;
  tone(PINNO,392,BEAT) ; // ソ
  delay(BEAT) ;
  tone(PINNO,440,BEAT) ; // ラ
  delay(BEAT) ;
  tone(PINNO,494,BEAT) ; // シ
  delay(BEAT) ;
  tone(PINNO,523,BEAT) ; // ド
  delay(3000) ;      // 3秒後に繰り返す
}

 回路は、受信側のArduinoボードのD5/GNDをそれぞれパッシブブザーのプラス/マイナスに接続する以外、送信側も受信側も全く同じ(nRF24L01+トランシーバだけの動作確認参照)。

 スケッチも共用で動作確認でも使わせて頂いたRF24ライブラリの送受信兼用サンプルスケッチである(デフォルトで受信、送信時には、シリアルモニタで[T]を、受信は[R]を入力の)GettingStartedとパッシブブザーのサンプルとして毎度使わせて頂いている圧電スピーカをつないで音を鳴らすのスケッチを組み合わせたもの。

 尤も、これだけだと433MHz RFモジュールと違って単体でもブザーを付けても少なくとも数m範囲で送受信できることがわかったに過ぎず、課題がいくつかあります。

  1. そもそもドアベルにするには、プッシュなり、タッチなりをトリガにする必要がありますが、具体案が浮かばない
  2. スリープ機能含め、省電力機能を盛り込む必要がある
  3. 正解が見えませんが、なぜか、Arduino IDEを再起動したり、USBを挿抜したりしないと通信に成功しないことがあった
  4. 冒頭の動画でもそうですが、3項に加え、なぜか、送信側のシリアルモニタでT(Transmitter/送信側)と入力+[送信]/[Enter]としてからモバイルバッテリを電源とした受信側のUSBを接続しないと通信に成功しない
  5. というか、433MHz RFモジュールはともかく、nRF24L01/nRF24L01+モジュールについては日本でも自由に使えるようにしてほしい
    技適済みで日本で使えるものもありますが、いろいろなシーンで使いたくても構成部品の1つに過ぎないモノがバカ高いとやる意味がなく、やる気が失せる(もっと安価な選択肢があれば需要はもっと多いはずで目先の利益はともかくもIoTがもっと急速に広く深く普及・浸透、新たな展開も生まれやすくなるはず)

 ちなみに有線モジュールだと用途が、かなり限定されるし、ZigBee/XBeeやESP8266でもESP-WROOM-02/ESP-WROOM-32は、日本の販売店から買う場合は、基本、技適済みのはずですが、比較的高価な上、WiFiモジュールだと無線ルータやアクセスポイントの接続数制限に影響があると思われ、そうだとするとドアベルやリモコン用途に使うのは、現実的ではない、Bluetoothも既に安いとも言い難いHC-05(/HC-06)も技適が必要となると仮に技適済みになったとしても、より高価になるであろうことを考えると尚更、コスパが見合わず、取り組む気にもなれない。

 通信できない、インターネットにつなげない、つなげるけど構成パーツの一部がコスト高でトータルすると数千円なんてことになると、ほとんどのモノは、現実的ではなくなる...から通信はしない・できない...となると機能が制約され、単なる電子工作止まりで、IoTとは呼べず、せっかくの関心、興味が削がれ、経済格差が教育格差を生んでいるなんていわれる時代に小学生からコンピュータ教育なんて謳っといて早いうちに誰でも手に入るくらい安価にしておかないと、この分野でも教育格差が広がっちゃうし、結果、思ったほど普及せず、巷にあるのは家電メーカーなどの商品のみとかいう状況だとしたら...気づいた時には、日本は、IoT超後進国になっている...なんてことも想像に難しくないんだが...なんとかならんかね?

[2018/06/20]

 ふと、Arduino and NRF24L01を参考にさせて頂き、ちょっと変更したら、一方のArduino+nRF24L01回路上のタクトボタン押下で他方のArduino+nRF24L01回路上の圧電スピーカーを鳴らすことができたのでドアベル試作機完成。

 尚、Reducing Arduino Power Consumptionによれば、かなり省電力化を図ることができる為、rocketscream/Low-Powerも使用させて頂くことにしました。

#include <LowPower.h>
 
#include <SPI.h>
#include "RF24.h"
 
#define button 3
#define confirmLed 2
#define led 4
 
RF24 NRF24L01 (7, 8);//create object called NRF24L01. specifying the CE and CSN pins to be used on the Arduino
 
byte address[] [6] = {"pipe1", "pipe2"};//set addresses of the 2 pipes for read and write
boolean buttonState = false;//used for both transmission and receive
 
void wakeUp()
{
  // Just a handler for the pin interrupt.
}
 
void setup() {
 Serial.begin(9600);
 pinMode(button, INPUT_PULLUP);
 pinMode(confirmLed, OUTPUT);//yellow LED
 pinMode(led, OUTPUT);//red LED
 
 NRF24L01.begin();
 //open the pipes to read and write from board 1
 NRF24L01.openWritingPipe(address[0]);//open writing pipe to address pipe 1
 NRF24L01.openReadingPipe(1, address[1]);//open reading pipe from address pipe 2
 
 NRF24L01.setPALevel(RF24_PA_MAX);//set RF power output to minimum, RF24_PA_MIN (change to RF24_PA_MAX if required)
 NRF24L01.setDataRate(RF24_250KBPS);//set data rate to 250kbps
 NRF24L01.setChannel(110);//set frequency to channel 110
}
 
void loop() {
// buttonState = HIGH;//reset the button state variable
 //Transmit button change to the other Arduino
 delay(10);
 NRF24L01.stopListening();
 buttonState = digitalRead(button);//test for button press on this board
// delay(100);
 if (buttonState == LOW)//button is pulled up so test for LOW
 {
  NRF24L01.write(&buttonState, sizeof(buttonState));//send LOW state to other Arduino board
  //flash the yellow LED to show progress
  digitalWrite(confirmLed, HIGH);
  delay(100);
  digitalWrite(confirmLed, LOW);
  Serial.println("buttonState:LOW");
 } else {
  buttonState = HIGH;//reset the button state variable
 }
 // Allow wake up pin to trigger interrupt on low.
 attachInterrupt(0, wakeUp, LOW);
 
 // Enter power down state with ADC and BOD module disabled.
 // Wake up when wake up pin is low.
 // LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
 LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
 
 // Disable external pin interrupt on wake up pin.
 detachInterrupt(0);
}
 
//flash the red LED five times
void flashLed()
{
 for (int i = 0; i < 5; i++)
 {
  digitalWrite(led, HIGH);
  delay(200);
  digitalWrite(led, LOW);
  delay(200);
 }
 
}

 送信側(ボタン付き回路)のラフスケッチはこれ。

 確実性を期すためには、やや長押しする必要がありますが、よしとしました。

#include <LowPower.h>
#include <SPI.h>
#include "RF24.h"
 
#define wakeUpPin 2
#define led 3
 
#define BEAT 200  // 音の長さを指定
#define PINNO 5  // 圧電スピーカを接続したピン番号
 
RF24 NRF24L01 (7, 8);//create object called NRF24L01. specifying the CE and CSN pins to be used on the Arduino
 
byte address[] [6] = {"pipe1", "pipe2"};//set addresses of the 2 pipes for read and write
 
boolean buttonState = false;//used for both transmission and receive
 
void wakeUp()
{
 // Just a handler for the pin interrupt.
}
 
void setup() {
 pinMode(wakeUpPin, INPUT_PULLUP);
 pinMode(led, OUTPUT);//red LED
 
 NRF24L01.begin();
 NRF24L01.openReadingPipe(1, address[0]);//open reading pipe from address pipe 1
 NRF24L01.openWritingPipe(address[1]);//open writing pipe to address pipe 2
 
 NRF24L01.setPALevel(RF24_PA_MAX);//set RF power output to minimum RF24_PA_MIN (change to RF24_PA_MAX if required)
 NRF24L01.setDataRate(RF24_250KBPS);//set datarate to 250kbps
 NRF24L01.setChannel(110);//set frequency to channel 110
}
 
void loop() {
 //Receive button change from the other Arduino
 delay(10);
 NRF24L01.startListening();
 if (NRF24L01.available())//do we have transmission from other Arduino board
 {
  NRF24L01.read(&buttonState, sizeof(buttonState));//update the variable with new state
  NRF24L01.stopListening();
 }
 
 if (buttonState == HIGH)//test the other Arduino's button state
 {
  digitalWrite(led, LOW);
//  digitalWrite(wakeUpPin, HIGH);
 }
 else
 {
  digitalWrite(wakeUpPin, LOW);
  digitalWrite(led, HIGH);
  delay(100);
  melody();
 }
 digitalWrite(led, LOW);
 buttonState = HIGH;//reset the button state variable
 
 // Allow wake up pin to trigger interrupt on low.
 attachInterrupt(0, wakeUp, LOW);
 
 // Enter power down state with ADC and BOD module disabled.
 // Wake up when wake up pin is low.
 // LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
 LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
 
 // Disable external pin interrupt on wake up pin.
 detachInterrupt(0);
}
 
 void melody() {
  tone(PINNO,196,BEAT); // ソ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  tone(PINNO,147,BEAT); // レ
  delay(BEAT);
  tone(PINNO,165,BEAT); // ミ
  delay(BEAT);
  tone(PINNO,175,BEAT); // ファ
  delay(BEAT);
  tone(PINNO,196,BEAT); // ソ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  delay(BEAT);
 
  tone(PINNO,220,BEAT); // ラ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,175,BEAT); // ファ
  delay(BEAT);
  tone(PINNO,196,BEAT); // ソ
  delay(BEAT);
  tone(PINNO,220,BEAT); // ラ
  delay(BEAT);
  tone(PINNO,247,BEAT); // シ
  delay(BEAT);
  tone(PINNO,33,BEAT); // ド
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  delay(BEAT);
 
  tone(PINNO,175,BEAT); // ファ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,196,BEAT); // ソ
  delay(BEAT);
  tone(PINNO,175,BEAT); // ファ
  delay(BEAT);
  tone(PINNO,165,BEAT); // ミ
  delay(BEAT);
  tone(PINNO,147,BEAT); // レ
  delay(BEAT);
 
  tone(PINNO,165,BEAT); // ミ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,175,BEAT); // ファ
  delay(BEAT);
  tone(PINNO,165,BEAT); // ミ
  delay(BEAT);
  tone(PINNO,147,BEAT); // レ
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
 
  tone(PINNO,123,BEAT); // シ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  tone(PINNO,147,BEAT); // レ
  delay(BEAT);
  tone(PINNO,165,BEAT); // ミ
  delay(BEAT);
  tone(PINNO,131,BEAT); // ド
  delay(BEAT);
  tone(PINNO,165,BEAT); // ミ
  delay(BEAT);
  delay(BEAT);
  tone(PINNO,147,BEAT); // レ
  delay(BEAT);
 }
}

 受信側(圧電ブザー付き回路)のラフスケッチはこれ。

 曲名を失念、合っているかわかりませんが、浮かんできたメロディを奏でるようにしてみました。

 音の確認には、Linux用ソフトウェアシンセサイザYoshimiを使わせて頂きました。

 ちなみに、なぜか、電源投入時、1回メロディを再生してしまいますが、よしとしました。

 一応、送信側を電池(100均のモバイルバッテリ/単3x2本)とし、待機・仮使用可能時間/日数を計測中...と思いましたが、1時間ももたないし...スケッチ間違えたか?それとも3V => 5V昇圧 => Arduino 5V => 3.3V => nRF24L01+じゃダメか...。

 あ、LowPower.powerDown()関数で第1引数を[SLEEP_FOREVER]にしたために、外部割り込みできなかったらしい...とりあえず、[SLEEP_8S]にして継続確認中。

[2018/06/21] [SLEEP_8S]にしてみたら、この回路でも短時間で電池を消耗することはなくなり、改めて検証中...。

 ただ、原因不明、タイミングは不定ながら送受信共にArduinoボードをリセットしないと機能しないことがあったので、このままだと何らかの方法でリセットの実装が必要かも。

 ん?初ウォッチドッグタイマだけど、これだとスイッチ押下時、常に有効なトリガにはならないから、任意のタイミングでリセットが...送受信ともウォッチドッグタイマかけると一方が起動してても他方は...っていうタイミングもあるよね?そしたら、そのタイミングだと機能しないか...ましてArduinoボードの内蔵時計...、仮に一方だけにしても、そういうことがあるとしたら、8秒じゃ長いっていうか、2秒でもその可能性はある上、8秒より電力抑制効果も薄れるとしたら、ウォッチドッグタイマ以外の方法の方がよいか...。

[2018/06/22] が、10時間くらいしかもたない...。

[2018/06/23] 10時頃、入力を単3x4(実際は6V弱)に変更、Arduino NanoのVin/GNDに接続...検証中...。

[2018/07/01] 22時時点で約208時間作動中...(引数を[SLEEP_8S]、入力を単3x4(実際は6V弱)、Arduino NanoのVin/GNDに接続、Nanoの3.3V => nRF24L01+で検証中)。

[2018/07/06] 朝、NanoのLEDが消灯しているのを発見、電池電圧約2V、昨夜は点いていた為、少なめに見積もって昨夜22時時点までとして約316時間、約13日と4時間作動...、ドアベルでよく使われるらしき12V 23A電池も買ってみましたが、単純計算で倍もつとしても1ヶ月弱...、それでも短いので再検討予定。

 ところで、これに限らず、2.4GHz帯を使うWifiモジュールの存在を知った時、無線LANルータのある環境でないと使えないのかと思いましたが、そうではないとわかり、より魅力を感じますが、このnRF24L01+、技適対応済みでないところが、つくづく痛い...。

 仕方ないから半ば渋々、技適済みのESP-WROOM-32/ESP-32を買った(Aliexpressで互換開発ボードを8ドル弱で買ったため国内の約半額と安価でしたが、nRF24L01+と比べると約8〜10倍、国内価格なら15倍〜18倍...)。

 というか、ESP-32は、Wifi、Bluetooth/BLEが使える上、タッチセンサやホールセンサ搭載、Arduinoボード風にも使え、メモリも比較的大容量などハイスペック過ぎ、これが8ドル程度なのは、驚愕の安さなわけですが、これはこれとして、Bluetooth、なんならBLEだけでも、もしくは、技適対応済みnRF24L01+などのWifiモジュールとか、WifiかBluetoothかBLEに機能を絞った国内で安心して使えるコスパの良いモジュール希望。

[追記:2018/07/29]

 そもそもArduinoなしでESP8266を2つ使ったWiFi玄関チャイム・呼び出しベルができました。

ホーム前へ次へ