NefryBTでGroveスピーカーからメロディを鳴らすメモ
この記事は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 を!