NefryBTでGroveスピーカーからメロディを鳴らすメモ

この記事はNefry Advent Calendar 2017の24日目の記事です。

昨日はtikchikuさんの「部屋の電気が付いたらLINEメッセージを送ってくれる仕組み(の後編)」でした。
tikchikuさんのIoTのファーストステップがNefryBTによって良いスタートになってよかったです!そして、センサーのつないだ後の試行錯誤こそ肝要。それが、Grove+NefryBTで素直にたどり着けるというのはよいですよね。

今回は、ESP32ベースで出来ているNefryBTでGroveスピーカーからメロディを鳴らすメモをお伝えします。

昨年もメロディを鳴らした

ちょうど、昨年のいまごろ、音の鳴らし方の基礎を学びました。

ということで、NefryBTで今年も鳴らしてみようと思い立ちました。ESP32で鳴らすのはじめてなんですよね。

まずつないでみる

image

このように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: 音の出力 | マイクロファン ラボ

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さらに突っ込んだ使い方も把握できました。音階表もあってステキ。

これを反映しつつ、

の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 のあたりも注目です。

image

今回はド・レ・ミ・ファ・ソ・ラ・シのみなので、Node-RED側では、

  • 0 = 無音
  • 1~7 = ド・レ・ミ・ファ・ソ・ラ・シ

としています。

実際に1~7を順々に動かしてみます。

うまく動きました!

音階もちゃんとド・レ・ミ・ファ・ソ・ラ・シで合っています。ちょうど、オクターブ 4 が、自分にも馴染みの深いオクターブ域だったようです。

まとめ

ということで、NefryBTでGroveスピーカーからメロディを鳴らすメモをお伝えしました。

では最後にクリスマスソング、ジングルベルをNode-REDで鳴らしてみましょうか。

image

全部のフローを載せてしまうと、とても画面に収まらないので、「ジングルベール、ジングルベール・・・」なイントロのキャプチャです。

うまく鳴りましたね!

こちらが出来ると、単純な音よりもメロディでより細かな情報を伝える音ができます。

それではよき NefryBT & Grove Life を!