この記事はNefry Advent Calendar 2017の24日目の記事です。
昨日はtikchikuさんの「部屋の電気が付いたらLINEメッセージを送ってくれる仕組み(の後編)」でした。
tikchikuさんのIoTのファーストステップがNefryBTによって良いスタートになってよかったです!そして、センサーのつないだ後の試行錯誤こそ肝要。それが、Grove+NefryBTで素直にたどり着けるというのはよいですよね。
今回は、ESP32ベースで出来ているNefryBTでGroveスピーカーからメロディを鳴らすメモをお伝えします。
昨年もメロディを鳴らした
- PCキーボードで弾くと遠隔のlittleBits Arduinoモジュールからスピーカーが演奏されるMilkcocoa連携を試す – 1ft-seabass.jp.MEMO
- littleBits Arduinoモジュールからシンセスピーカーモジュールで音を鳴らす基礎のメモ – 1ft-seabass.jp.MEMO
ちょうど、昨年のいまごろ、音の鳴らし方の基礎を学びました。
ということで、NefryBTで今年も鳴らしてみようと思い立ちました。ESP32で鳴らすのはじめてなんですよね。
まずつないでみる
このようにPWMで音階が鳴らせるGROVE スピーカーをD2につないで早速プログラムを書いていきます。
tone関数を使ってみる
いつも参考にしている圧電スピーカでメロディを鳴らすのナレッジを使わさせていただきます。
一部抜粋しますが、tonesは以前使ったドレミ音階を入れている配列で、1秒ごとに鳴らす音を変えます。
void loop() { tone(0,5); delay(1000); tone(tonePin,tones[1]); delay(1000); tone(tonePin,tones[2]); delay(1000); tone(tonePin,tones[3]); delay(1000); tone(tonePin,tones[4]); delay(1000); }
と、プログラムを書いてみたものの、以下のコンパイルエラー。
NefryBT_Tone:16: error: ‘tone’ was not declared in this scope
tone(0,5);exit status 1
‘tone’ was not declared in this scope
どうも、tone関数が存在していない模様。
たしかに、純正のArduinoファミリーであればtone関数が揃っているのかもしれませんが、ESP32由来であるNefry32では、揃ってないこともありえます。
どうやって鳴らすんだ
えー、じゃあ、どうやって鳴らすんだろ???と、途方にくれていたところ「そうか、ESP32由来ならば他のESP32デバイスで鳴らしたナレッジを探せばいいか!」と、探してみたところ、発見したのがこちら。
ESP32のLEDC Driver機能で利用できるPWMを使って、ピエゾスピーカーに出力する信号の周波数を変えます。
まさに、こちらです。ありがたさしかないです。
参考に以下のようなコードを書きました。
#include <Nefry.h> #define SOUNDER 15 #define LEDC_CHANNEL_2 2 #define LEDC_TIMER_13_BIT 13 #define LEDC_BASE_FREQ 5000 // 音階配列 int tones[12] = {262,294,330,349,392,440,494,523,587,659,698,784}; // 対象ピン int tonePIN = D2; void setup() { Serial.begin(115200); } void loop() { delay(1000); tone(tonePIN,tones[1]); delay(1000); tone(tonePIN,tones[2]); delay(1000); } void noTone(int pin) { ledcWriteTone(LEDC_CHANNEL_2, 0.0) ; } void tone(int pin, int freq) { ledcSetup(LEDC_CHANNEL_2, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT) ; ledcAttachPin(pin, LEDC_CHANNEL_2) ; ledcWriteTone(LEDC_CHANNEL_2, freq) ; }
ledcSetup、ledcAttachPin、ledcWriteToneのあたりが先ほどの記事を参考にして音を鳴らす部分です。
実際に動かしてみましょう。
無事にledcWriteToneで交互にレとミの音がなりました!
しっかり音を鳴らす
つづいて、メロディを鳴らしていきます。以下のledcWriteTone周辺コードを見て関数の性質をチェックしてみました。
arduino-esp32/esp32-hal-ledc.h at master · espressif/arduino-esp32
結果、ド・レ・ミ・ファ・ソ・ラ・シ・ドするなら、ledcWriteToneよりledcWriteNoteのほうが音階の定数があり使い勝手がよさそうと読み取れました。
また、こちらの記事で、ESP-WROOM-32 ledcWriteNoteさらに突っ込んだ使い方も把握できました。音階表もあってステキ。
これを反映しつつ、
- NefryBTでDataStoreを使ったIoTデモ成功率を高めるネットワーク設定施策のメモ – Qiita
- Node-RED MQTTブローカー経由でNefryBT+フルカラーテープLEDを動かすメモ – 1ft-seabass.jp.MEMO
のMQTTを使って、外部からNode-REDでデータ melody で音階のやり取りが出来るようにしたプログラムがこちらです。
#include <Nefry.h> #include <WiFiClient.h> #include <PubSubClient.h> #include <ArduinoJson.h> IPAddress endpoint; const int port = 1883; String deviceNameStr,pubTopicStr,subTopicStr,ip1_str,ip2_str,ip3_str,ip4_str; const char *pubTopic; const char *subTopic; const char *deviceName; int ip1,ip2,ip3,ip4; WiFiClient httpsClient; PubSubClient mqttClient(httpsClient); #define LEDC_CHANNEL_0 0 #define LEDC_TIMER_13_BIT 13 #define LEDC_BASE_FREQ 5000 // ledcWriteNote 対応 ////////////////////////////////////////////// const int NOTE_NONE = NOTE_MAX; // ド・レ・ミ・ファ・ソ・ラ・シ int melody[] = { NOTE_C, NOTE_D, NOTE_E, NOTE_F, NOTE_G, NOTE_A, NOTE_B }; // オクターブ const int baseOctaves = 4; // 低い数字だと鳴りにくい模様なので4 // ledcWriteNote 対応おわり ////////////////////////////////////////////// // 対象ピン int tonePIN = D2; void setup() { //// NefryBT設定画面まわり //////////////////////////////////////// // NefryBTから値入力の場所を取得する Nefry.setStoreTitle("IP1",0); // IP 右から1桁目 Nefry.setStoreTitle("IP2",1); // IP 右から2桁目 Nefry.setStoreTitle("IP3",2); // IP 右から3桁目 Nefry.setStoreTitle("IP4",3); // IP 右から4桁目 Nefry.setStoreTitle("pubTopic",4); Nefry.setStoreTitle("subTopic",5); Nefry.setStoreTitle("deviceName",6); // IPアドレス:変換前に変数に入れておく ip1_str = Nefry.getStoreStr(0); ip2_str = Nefry.getStoreStr(1); ip3_str = Nefry.getStoreStr(2); ip4_str = Nefry.getStoreStr(3); // IPアドレス:IPを数字に変換 ip1 = ip1_str.toInt(); ip2 = ip2_str.toInt(); ip3 = ip3_str.toInt(); ip4 = ip4_str.toInt(); // NefryBT入力画面 pubTopicStr = Nefry.getStoreStr(4); subTopicStr = Nefry.getStoreStr(5); deviceNameStr = Nefry.getStoreStr(6); pubTopic = pubTopicStr.c_str(); subTopic = subTopicStr.c_str(); deviceName = deviceNameStr.c_str(); // IPAddress型に収納。配列っぽく入れる。 endpoint[0] = ip1; endpoint[1] = ip2; endpoint[2] = ip3; endpoint[3] = ip4; //// 以下通常処理 //////////////////////////////////////// Serial.begin(115200); mqttClient.setServer(endpoint, port); mqttClient.setCallback(mqttCallback); pinMode(tonePIN,OUTPUT); // ledcSetup 初期設定 ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT) ; ledcAttachPin(tonePIN, LEDC_CHANNEL_0) ; connectMQTT(); } void connectMQTT() { Serial.println("connectMQTT"); Serial.println(deviceName); while (!mqttClient.connected()) { Serial.print("."); if (mqttClient.connect(deviceName)) { Serial.println("Connected."); int qos = 0; mqttClient.subscribe(subTopic, qos); Serial.println("Subscribed."); } else { Serial.print("Failed. Error state="); Serial.print(mqttClient.state()); // Wait 5 seconds before retrying delay(5000); } } } char pubMessage[128]; void mqttCallback (char* topic, byte* payload, unsigned int length) { String str = ""; Serial.print("Received. topic="); Serial.println(topic); for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); str += (char)payload[i]; } Serial.print("\n"); StaticJsonBuffer<200> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(str); // パースが成功したか確認。できなきゃ終了 if (!root.success()) { Serial.println("parseObject() failed"); return; } // 音階がJSONで連絡される int melody = root["melody"]; Serial.print("melody = "); Serial.println(melody); if( melody == 0 ){ noTone(); } else { noTone(); // 少し無音を入れて連続したときに分かるように Nefry.ndelay(100); tone(tonePIN,melody); } Nefry.ndelay(200); // キビキビ動くように短めに } void mqttLoop() { if (!mqttClient.connected()) { connectMQTT(); } mqttClient.loop(); } void loop() { mqttLoop(); } void noTone() { ledcWriteTone(LEDC_CHANNEL_0, 0.0); } void tone(int pin, int note) { ledcWriteNote(LEDC_CHANNEL_0,(note_t)melody[note-1],baseOctaves); }
LEDC_CHANNEL_2をLEDC_CHANNEL_0にし、noTone 無音のコードはそのままで、tone内部はnoteで音階を受け取ってからledcWriteNoteに渡すようにしています。音階を司る?定数 note_t のあたりも注目です。
今回はド・レ・ミ・ファ・ソ・ラ・シのみなので、Node-RED側では、
- 0 = 無音
- 1~7 = ド・レ・ミ・ファ・ソ・ラ・シ
としています。
実際に1~7を順々に動かしてみます。
うまく動きました!
音階もちゃんとド・レ・ミ・ファ・ソ・ラ・シで合っています。ちょうど、オクターブ 4 が、自分にも馴染みの深いオクターブ域だったようです。
まとめ
ということで、NefryBTでGroveスピーカーからメロディを鳴らすメモをお伝えしました。
では最後にクリスマスソング、ジングルベルをNode-REDで鳴らしてみましょうか。
全部のフローを載せてしまうと、とても画面に収まらないので、「ジングルベール、ジングルベール・・・」なイントロのキャプチャです。
うまく鳴りましたね!
こちらが出来ると、単純な音よりもメロディでより細かな情報を伝える音ができます。
それではよき NefryBT & Grove Life を!