見出し画像

プログラムと電子工作・楽曲演奏(2)SpeakerHATへPWM出力

M5StickC(Plusなし)には、内蔵スピーカーがないので、音を鳴らすには SpeakerHAT を外部接続します。M5StickC Plus につないでもいいです。

とりあえず、内蔵スピーカーと SpeakerHAT でどのくらい異なるのか、やってみたいと思います。Pulse Width Modulation(PWM)出力を使用して、「どんぐりころころ」と「チューリップ」を演奏します。


目標

  • 外部接続した SpeakerHAT で音を鳴らします。

  • 「どんぐりころころ」と「チューリップ」を演奏します。

部品・機材

使用する部品は次のとおりです。SpeakerHAT を使用します。

電子部品

開発用機材

  • PC(Windows10 または 11)、開発環境 Arduino-IDE 導入ずみ

  • USB-A・USB-C ケーブル

開発手順

  1. M5StickC Plus に SpeakerHAT を接続する。

  2. PC と M5StickC Plus を USBケーブルで接続する。

  3. Arduino-IDE でスケッチ beep2.ino を開く。

  4. 検証・コンパイルする。

  5. M5StickC Plus に書き込む。

  6. 上ボタン(押しボタンA)を押して、演奏を開始する。

  7. 曲が流れることを確認する。

スケッチ

beep2.ino

#include <M5StickCPlus.h>

#define SPEAKER_PIN GPIO_NUM_26  /*Speaker HAT ピン*/
#define TONE_PIN_CHANNEL 0       /*チャネル*/
#define DUTY_BITNUM 13           /*デューティビット数*/

#define C3 130.813  /*ド*/
#define D3 146.832  /*レ*/
#define E3 164.814  /*ミ*/
#define F3 174.614  /*ファ*/
#define G3 195.998  /*ソ*/
#define A3 220.000  /*ラ*/
#define B3 246.942  /*シ*/

#define C4 261.626  /*ド*/
#define D4 293.665  /*レ*/
#define E4 329.628  /*ミ*/
#define F4 349.228  /*ファ*/
#define G4 391.995  /*ソ*/
#define A4 440.000  /*ラ*/
#define B4 493.883  /*シ*/

#define C5 523.251  /*ド*/
#define D5 587.330  /*レ*/
#define E5 659.255  /*ミ*/
#define F5 698.456  /*ファ*/
#define G5 783.991  /*ソ*/
#define A5 880.000  /*ラ*/
#define B5 987.767  /*シ*/

#define C6 1046.502 /*ド*/

#define SS 0.0      /*休符*/

#define T8  200  /*8分休符*/
#define T4  400  /*4分休符*/
#define T2  800  /*2分休符*/
#define T15 1200 /*2.5分休符*/
#define T1  1600 /*全休符*/

typedef struct {
    float freq;
    uint16_t period;
} tone_t;

const tone_t donguri[] = {
    {G4, T4}, {E4, T8}, {E4, T8}, {F4, T8}, {E4, T8}, {D4, T8}, {C4, T8},
    {G4, T4}, {E4, T8}, {E4, T8}, {D4, T4}, {SS, T4},
    {E4, T8}, {E4, T8}, {G4, T8}, {G4, T8}, {A4, T8}, {A4, T8}, {SS, T8}, {A4, T8},
    {C5, T4}, {E4, T8}, {E4, T8}, {G4, T4}, {SS, T4},
    {G4, T8}, {G4, T8}, {E4, T8}, {E4, T8}, {F4, T8}, {E4, T8}, {D4, T8}, {C4, T8},
    {G4, T4}, {E4, T8}, {E4, T8}, {D4, T4}, {SS, T4},
    {G4, T4}, {E4, T4}, {A4, T4}, {G4, T8}, {G4, T8},
    {A4, T8}, {A4, T8}, {B4, T8}, {B4, T8}, {C5, T4}, {SS, T4}
};

const tone_t tulip[] = {
    {C4, T4}, {D4, T4}, {E4, T2},
    {C4, T4}, {D4, T4}, {E4, T2},
    {G4, T4}, {E4, T4}, {D4, T4}, {C4, T4},
    {D4, T4}, {E4, T4}, {D4, T2},
    {C4, T4}, {D4, T4}, {E4, T2},
    {C4, T4}, {D4, T4}, {E4, T2},
    {G4, T4}, {E4, T4}, {D4, T4}, {C4, T4},
    {D4, T4}, {E4, T4}, {C4, T2},
    {G4, T4}, {G4, T4}, {E4, T4}, {G4, T4},
    {A4, T4}, {A4, T4}, {G4, T2},
    {E4, T4}, {E4, T4}, {D4, T4}, {D4, T4},
    {C4, T15}, {SS, T4}
};

//------------------------------------------------------------------------------
//  setup()
void setup() {
    //  電源ON時に 1回だけ実行する処理をここに書く。
    M5.begin();               /*M5を初期化する*/
    M5.Axp.ScreenBreath(20);  /*画面の輝度を少し下げる*/
    M5.Lcd.setTextSize(2);    /*文字サイズはちょっと小さめ*/
    M5.Lcd.setRotation(3);    /*上スイッチが左になる向き*/
    M5.Lcd.println("beep2");

    Serial.begin(115200);   /*デバッグ用のシリアル通信を初期化する*/

    M5.Lcd.println("BtnA to play");
    beep_begin();
}
//------------------------------------------------------------------------------
//  loop()
void loop() {
    //  自動的に繰り返し実行する処理をここに書く。
    M5.update();

    if (M5.BtnA.wasPressed()) {
        //  「どんぐりころころ」を演奏する。
        M5.Lcd.println("donguri");
        playSound(donguri, sizeof(donguri)/sizeof(donguri[0]));
        delay(1000);

        //  「チューリップ」を演奏する。
        M5.Lcd.println("tulip");
        playSound(tulip, sizeof(tulip)/sizeof(tulip[0]));
    }
    delay(1);
}
//------------------------------------------------------------------------------
//  playSound
//  楽曲データを演奏する。
//  ■  演奏が終了するまでこの関数はブロックされる。
//      in: const uint16_t* tone 音程データ
//          const uint32_t length 音程データ数
void playSound(const tone_t* tone, const uint32_t length)
{
    Serial.println("*** playSound()");
    for (int i = 0; i < length; i++) {
        Serial.printf("  freq: %f, period: %d\n", tone[i].freq, tone[i].period);

        //  指定した周波数の音を出す。
        if (0 < tone[i].freq) {
            beep_tone(tone[i].freq);  /*set duty*/
        }
        else {
            beep_mute();
        }
        //  指定した時間待つ。
        delayMicroseconds((uint32_t)(tone[i].period) * 1000);
    }
    beep_mute();
}
//------------------------------------------------------------------------------
//  使用準備、setup()内で 1回実行する。
void beep_begin()
{
    ledcSetup(TONE_PIN_CHANNEL, 0, DUTY_BITNUM);
    ledcAttachPin(SPEAKER_PIN, TONE_PIN_CHANNEL);
}
//------------------------------------------------------------------------------
//  指定された周波数を鳴らす。
//  音を止めるには beep_mute() を実行する必要がある。
//      in: uint16_t frequency 周波数(Hz)
void beep_tone(uint16_t frequency)
{
    ledcWriteTone(TONE_PIN_CHANNEL, frequency);
}
//------------------------------------------------------------------------------
//  音を休止する。
void beep_mute()
{
    ledcWriteTone(TONE_PIN_CHANNEL, 0);
    digitalWrite(SPEAKER_PIN, 0);
}

beep_begin()、beep_tone()、beep_mute() 関数は、楽曲演奏(その1)内蔵スピーカーの M5.Beep.begin()、M5.Beep.tone()、M5.Beep.mute() 関数とほぼ同じです。

内蔵スピーカーが G2ピンに接続されているのに対して、SpeakerHAT のスピーカーは G26ピンに接続されているところが異なるだけです。なので、スケッチ 3行めの #define SPEAKER_PIN GPIO_NUM_26 を GPIO_NUM_2 に変えれば内蔵スピーカーが鳴るはずです。

音符に対応する周波数と継続時間を求めて、周波数は beep_tone() で鳴らし、継続時間は delayMicroseconds() で計っています。休符は beep_mute() で音を止めます。

結果

上ボタン(押しボタンA)を押すと、曲が流れます。内蔵スピーカーよりも音量が大きく、音質もいいようです。

このスケッチでは、演奏中は他の処理ができないので、あまり実用性がないのは、楽曲演奏(その1)内蔵スピーカーと同じです。

写真1 beep2.ino の実行結果

参考

  • SpeakerHAT の回路図は、スイッチサイエンスの Webサイトに掲載されています。

  • 参考にした関数 M5.Beep.begin()、M5.Beep.tone() や M5.Beep.mute() は、次のファイルで定義されています。
    C:\Users\<ユーザ>\Documents\Arduino\libraries\M5StickCPlus\src\utility\Speaker.h
    C:\Users\<ユーザ>\Documents\Arduino\libraries\M5StickCPlus\src\utility\Speaker.cpp

ライセンス

このページのソースコードは、複製・改変・配布が自由です。営利目的にも使用してかまいませんが、何ら責任を負いません。


この記事が気に入ったらサポートをしてみませんか?