プログラムと電子工作・楽曲演奏(2)SpeakerHATへPWM出力
M5StickC(Plusなし)には、内蔵スピーカーがないので、音を鳴らすには SpeakerHAT を外部接続します。M5StickC Plus につないでもいいです。
とりあえず、内蔵スピーカーと SpeakerHAT でどのくらい異なるのか、やってみたいと思います。Pulse Width Modulation(PWM)出力を使用して、「どんぐりころころ」と「チューリップ」を演奏します。
目標
外部接続した SpeakerHAT で音を鳴らします。
「どんぐりころころ」と「チューリップ」を演奏します。
部品・機材
使用する部品は次のとおりです。SpeakerHAT を使用します。
電子部品
M5StickC Plus 1台
SpeakerHAT 1台[例:スイッチサイエンス]
開発用機材
PC(Windows10 または 11)、開発環境 Arduino-IDE 導入ずみ
USB-A・USB-C ケーブル
開発手順
M5StickC Plus に SpeakerHAT を接続する。
PC と M5StickC Plus を USBケーブルで接続する。
Arduino-IDE でスケッチ beep2.ino を開く。
検証・コンパイルする。
M5StickC Plus に書き込む。
上ボタン(押しボタンA)を押して、演奏を開始する。
曲が流れることを確認する。
スケッチ
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)内蔵スピーカーと同じです。
参考
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
ライセンス
このページのソースコードは、複製・改変・配布が自由です。営利目的にも使用してかまいませんが、何ら責任を負いません。