前回でとりあえず動作確認まではできましたので、もうちょっと突っ込んだ使い方をしてみたいと思います。やりたいのは以下2つです。
- 2ch同時再生
- 省電力モード
1.は、「はじめに」でも述べましたとおり、これができるようになると、BGMを流しながら別の効果音を重ねて再生させることができますので、CSMのような玩具の開発がとても捗るようになります。
2.は、これができると、電源ON/OFFスイッチを必要としない、真に市販の玩具に近い玩具作りができるようになるかもしれません。
どちらも個人的に、長年やりたくてもできなかったことですので、ここでしっかり検証しておきたいと思います。
コマンドの概要
2ch同時再生の詳細に入る前に、まずコマンドの概要を確認しておきます。
UART通信でNSP2340Aに対して送信できるコマンドやプロトコルについては、”NSPPlayListEditor”をインストールした際に生成される”Document”フォルダの中にある、”NSP_UART MCU Interface_EN.pdf”に記載があります。色々ありますが、個人的に使いそうなものをいくつか抜粋すると、以下のようなコマンドがあります。英語の仕様書なので、誤訳(解釈間違い)があったら申し訳ありません。
| コマンド | コード | 説明 |
|---|---|---|
| PAUSE | 0x78 | 音声を一時停止する |
| RESUME | 0x79 | 一時停止した音声の再生を再開する |
| STOP_REPEAT | 0x7B | 現在の再生までで音声のリピート再生を終了する |
| REPEAT | 0x7C | 音声をリピート再生させる |
| PLAY_SLEEP | 0x7E | 音声の再生が終了すると、スタンバイモードに入る |
| PLAY | 0x7F | 音声を再生する |
| STOP | 0x80 | 音声を停止する |
| PWR_DOWN | 0x82 | スタンバイモードに入る |
| AUTO_SLEEP_EN | 0x83 | GUIで設定した所定時間何も起こらなければ、スタンバイモードに入る |
| AUTO_SLEEP_DIS | 0x84 | 自動でスタンバイモードに入らせない |
| PLAY_CHANNEL | 0xA0 | チャンネル指定で音声を再生する |
| STOP_CHANNEL | 0xA1 | チャンネル指定で音声を停止する |
| PAUSE_CHANNEL | 0xA2 | チャンネル指定で音声を一時停止する |
| RESUME_CHANNEL | 0xA3 | チャンネル指定で一時停止した音声の再生を再開する |
| SET_VOL | 0xF0 | 音量を設定する(0〜128) |
これだけあれば、私的には必要十分です。それぞれのコマンドの送り方については上記PDFファイルに詳細説明がありますのでここでは割愛しますが、すべてに共通しているのは、最後にチェックサムを送る必要があるということ。チェックサムについてはPDFのP.15に記載があって、「全部のコマンドを足し合わせてから反転させたもの」となっています。
2ch同時再生
ではいよいよ、2ch同時再生を試していきます。ついでに、音量設定も実装します。
上記コマンド仕様を見るに、”XXX_CHANNEL”系のコマンドを使えばチャンネル指定で操作ができそうですので、以下のようにサンプルを作ってみました。
#define PIN_SW_CH1 PIN_PA6
#define PIN_SW_CH2 PIN_PA7
#define ON LOW
#define OFF HIGH
#define AUDIO_CH1 1
#define AUDIO_CH2 2
#define AUDIO_VOLUME 48
uint8_t sw_ch1 = OFF;
uint8_t sw_ch2 = OFF;
uint8_t prev_sw_ch1 = OFF;
uint8_t prev_sw_ch2 = OFF;
uint16_t num_sound = 1;
bool is_bgm_playing = false;
uint8_t calc_checksum(const uint8_t *data, uint8_t len){
uint16_t sum = 0;
for(uint8_t i=0; i<len; i++){
sum += data[i];
}
return (uint8_t)(~sum);
}
void set_volume(uint8_t vol){
if(vol > 128){
vol = 128;
}
uint8_t cmd[3];
cmd[0] = 0xF0;
cmd[1] = vol;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void play_sound(uint8_t ch, uint16_t num_sound){
uint8_t cmd[5];
cmd[0] = 0xA0;
cmd[1] = ch;
cmd[2] = highByte(num_sound);
cmd[3] = lowByte(num_sound);
cmd[4] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[3]までの4byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void stop_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA1;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void pause_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA2;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void resume_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA3;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
/////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
Serial.begin(115200); // PB3がRX、PB2がTXになる。ボーレートはNew ProjectのSettingsで指定した値
pinMode(PIN_SW_CH1, INPUT_PULLUP);
pinMode(PIN_SW_CH2, INPUT_PULLUP);
// 起動直後のみ、通信が安定しないのでリトライ処理を入れる。5回まで
for(uint8_t i=0;i<5;i++){
delay(500);
set_volume(AUDIO_VOLUME);
if(Serial.available()){
uint8_t res = Serial.read();
if(res == 0xA5){
break; // コマンド正常受付
}else{
; // コマンド受付失敗
}
}
}
}
void loop(){
sw_ch1 = digitalRead(PIN_SW_CH1);
sw_ch2 = digitalRead(PIN_SW_CH2);
if(prev_sw_ch1 == OFF && sw_ch1 == ON){
play_sound(AUDIO_CH1, num_sound);
num_sound++;
if(num_sound >= 5){
num_sound = 1;
}
}
if(prev_sw_ch2 == OFF && sw_ch2 == ON){
if(is_bgm_playing){
stop_sound(AUDIO_CH2);
}else{
play_sound(AUDIO_CH2, 5);
}
is_bgm_playing = !is_bgm_playing;
}
prev_sw_ch1 = sw_ch1;
prev_sw_ch2 = sw_ch2;
delay(10);
}配線はこちら。
ボタンを2つに増やしまして、上の方のボタンを押すと効果音を順に再生、下の方のボタンを押すとBGMのON/OFFができます。では早速。
…完璧…ずっとやりたかったことが、いとも簡単に実現できてしまいました。すごいぞNSP2340A。これができるだけでもDFPlayerから乗り換える価値が十分あります。
省電力モード
続いて省電力モードを探っていきますが、その前に、今時点のNSP2340A単体での消費電流を計測しておきます。
まず、音声再生なしの待機状態。4.91mA。
DFPlayerに比べると遥かに少ないですが、それでもコイン電池やボタン電池だと苦しくなってくる値ですね。続いて、音声再生中。数値が暴れるので動画で確認します。
うーん、箇所によっては余裕で数十mAいくので、流石にコイン電池やボタン電池では厳しいかもしれません。
ちなみに、これは音量設定を64(←ちょうど真ん中)にしているので、やや極端に、10まで下げてみるとこうなります。
うーん、これぐらいならボタン電池とかでもいけそうですが、さすがに音量的に厳しい。自分の工作だと、48ぐらいは欲しいところです。この場合はこちら。
うーん、ギリいけるかいけないか。。。
およその消費電流はわかりましたので、ここから省電力モード(スタンバイモード)の利用方法を確認していきます。先のコマンド表によれば、スタンバイモードに入る方法は3種類ありそうです。
- “PLAY_SLEEP”コマンドを使う
- “PWR_DOWN”コマンドを使う
- “AUTO_SLEEP_EN”コマンドを使う
“PLAY_SLEEP”は一見便利そうですが、曲を再生しない限りはSLEEPに移行できないので、意外と不便かもしれません。”PWR_DOWN”はわかりやすいですが、毎回明示的にSLEEPさせるのも結構面倒です。
ということで、”AUTO_SLEEP_EN”を使用したいところですが、説明書きには”GUIで設定した時間が経過するとスタンバイモードに入る”とあります。GUI。おそらくですが、NSP2340Aのプロジェクトを作成したときに出てきた画面
ここのことじゃないかなと。ということで、Windows PCに戻って”NSPPlayListEditor”再度開き、先日作成したプロジェクトを開きます。
“Profect Settings”を開きます。
“CPU Mode”の”Auto Sleep”にチェックを入れます。”Reset Status”は、”Enable”にしていると、特にプログラムで明示的に”AUTO_SLEEP_EN”コマンドを送らなくても、通電時に勝手にオートスリープが有効になるようです。”Disable”だと、明示的なコマンド発行が必要。今回は”Enable”にしてみます。”Wait Time to Sleep”は、スリープに入るまでの時間で、最初20秒にしようとしたら、「0.5秒?か10秒しかダメ」みたいなメッセージが出てきましたので、デフォルト値の10秒にしています。
それから、このタイミングで”UART Mode”の”Baud rate”も、9600に落としています(前の例では115200)。これは、省電力化のためにマイコン側の動作周波数を落としても通信を安定させるためです。
ということで、コンフィグの修正が完了したら、”Rebuild Project”で再度プロジェクトをビルドし、前回と同様の手順でNSP2340Aに書き込みます。
以下、この後まとめて電池駆動の検証をするつもりなので、マイコン側の方のプログラムを少し書き換えます。前の検証に倣ってATtiny1616を出来る限り省電力状態にしています(←1MHz動作とこれに伴うSerial通信のボーレートの変更、不要な部分の電力カット、不使用ピンのプルアップ)。
#define PIN_SW_CH1 PIN_PA6
#define PIN_SW_CH2 PIN_PA7
#define ON LOW
#define OFF HIGH
#define AUDIO_CH1 1
#define AUDIO_CH2 2
#define AUDIO_VOLUME 48
uint8_t sw_ch1 = OFF;
uint8_t sw_ch2 = OFF;
uint8_t prev_sw_ch1 = OFF;
uint8_t prev_sw_ch2 = OFF;
uint16_t num_sound = 1;
bool is_bgm_playing = false;
uint8_t calc_checksum(const uint8_t *data, uint8_t len){
uint16_t sum = 0;
for(uint8_t i=0; i<len; i++){
sum += data[i];
}
return (uint8_t)(~sum);
}
void set_volume(uint8_t vol){
if(vol > 128){
vol = 128;
}
uint8_t cmd[3];
cmd[0] = 0xF0;
cmd[1] = vol;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void set_auto_sleep(){
uint8_t cmd[2];
cmd[0] = 0x83;
cmd[1] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void play_sound(uint8_t ch, uint16_t num_sound){
uint8_t cmd[5];
cmd[0] = 0xA0;
cmd[1] = ch;
cmd[2] = highByte(num_sound);
cmd[3] = lowByte(num_sound);
cmd[4] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[3]までの4byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void stop_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA1;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void pause_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA2;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
void resume_sound(uint8_t ch){
uint8_t cmd[3];
cmd[0] = 0xA3;
cmd[1] = ch;
cmd[2] = calc_checksum(cmd, sizeof(cmd)-1); // cmd[0]~cmd[1]までの2byteのチェックサムを計算
Serial.write(cmd, sizeof(cmd));
}
/////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
// 省電力化のため、不要な部分を停止。動作周波数はArduino IDEの方のClock指定で1MHzに落とす
ADC0.CTRLA &= ~ADC_ENABLE_bm; // ADC(アナログデジタル変換)を停止
SPI0.CTRLA &= ~SPI_ENABLE_bm; // SPI停止
TWI0.MCTRLA = 0; // I2C マスター停止
TWI0.SCTRLA = 0; // I2C スレーブ停止
Serial.begin(9600); // PB3がRX、PB2がTXになる。ボーレートはNew ProjectのSettingsで指定した値。
pinMode(PIN_SW_CH1, INPUT_PULLUP);
pinMode(PIN_SW_CH2, INPUT_PULLUP);
// 省電力化のため、未使用ピンはすべてプルアップする
pinMode(PIN_PA1, INPUT_PULLUP);
pinMode(PIN_PA2, INPUT_PULLUP);
pinMode(PIN_PA3, INPUT_PULLUP);
pinMode(PIN_PA4, INPUT_PULLUP);
pinMode(PIN_PA5, INPUT_PULLUP);
pinMode(PIN_PB0, INPUT_PULLUP);
pinMode(PIN_PB1, INPUT_PULLUP);
pinMode(PIN_PB4, INPUT_PULLUP);
pinMode(PIN_PB5, INPUT_PULLUP);
pinMode(PIN_PC0, INPUT_PULLUP);
pinMode(PIN_PC1, INPUT_PULLUP);
pinMode(PIN_PC2, INPUT_PULLUP);
pinMode(PIN_PC3, INPUT_PULLUP);
// 起動直後のみ、通信が安定しないのでリトライ処理を入れる。5回まで
for(uint8_t i=0;i<5;i++){
delay(500);
set_volume(AUDIO_VOLUME);
if(Serial.available()){
uint8_t res = Serial.read();
if(res == 0xA5){
break; // コマンド正常受付
}else{
; // コマンド受付失敗
}
}
}
// NSP2030Aのオートスリープを有効化。NSP2030AのConfig Settingsで、"Reset Status"を"Enable"にしている場合は不要
//set_auto_sleep();
}
void loop(){
sw_ch1 = digitalRead(PIN_SW_CH1);
sw_ch2 = digitalRead(PIN_SW_CH2);
if(prev_sw_ch1 == OFF && sw_ch1 == ON){
play_sound(AUDIO_CH1, num_sound);
num_sound++;
if(num_sound >= 5){
num_sound = 1;
}
}
if(prev_sw_ch2 == OFF && sw_ch2 == ON){
if(is_bgm_playing){
stop_sound(AUDIO_CH2);
}else{
play_sound(AUDIO_CH2, 5);
}
is_bgm_playing = !is_bgm_playing;
}
prev_sw_ch1 = sw_ch1;
prev_sw_ch2 = sw_ch2;
delay(10);
}これで動作させてみます。想定どおりであれば、起動して10秒ほどで、勝手に消費電流が落ちるはず。
計測できないレベルで落ちますね。意図通り。そして、最後のボタン操作から10秒程度で、再びスタンバイモードに入っています。
が、みなさんお気づきだったでしょうか。スタンバイモードに入ったあとの最初の音声再生操作で、電流値を見るにスタンバイモードから復帰はしていましたが、音声は再生されませんでした。本来なら「ジョーカー!」となるはず。
これはなかなか悩ましい仕様です。オートスリープを使うと、復帰時の一発目の再生命令が飛ばされてしまう。シンプルにこれを避けるなら、スリープに入るまでの時間を長めにとるのが良いと思いますが、現状はMax10秒しか設定できなさそう。となると、やや面倒かもしれませんが、マイコン側で自分で時間を計測して”PWR_DOWN”コマンドを使う、などの対応も、場合によっては必要かもしれません。このあたりは、ユースケースに依りそうですね。
だいぶ長くなってしまったので、最後に「電池駆動できるか」チェックをして終わろうと思います。マイコンと組み合わせてのスタンバイ・スリープモード時の検証はまた別の機会にするとして、今回は通常動作ができるかどうかだけ確認したいと思います。
まずは単四電池 x 2本。
いけました。これまで3.0Vで音声再生を安定駆動できた試しがなかったので、これだけでも大きな一歩です。
続いて単五電池 x 2本。
これも問題なし。容量が小さい単四電池みたいなものなので、当然と言えば当然。
そして、ボタン電池(LR44) x 3個。これで動けば悲願の(?)ボタン電池で音声再生が実現できますが。。。
なんと動きました!これは嬉しい。。。しかもこれだけ4.5Vなせいか、心なしかボリュームもちょっと大きい気がします。
最後、コイン電池(CR2032) x 1個。これは流石にきつい気がしますが。。。
なんと、これも動きました。すばらしい。。。
ということで、だいぶ長くなってしまいましたが、まず、2ch同時再生は可能でした。さらに、ATtiny1616との組み合わせで、ボタン電池やコイン電池でも動作することが確認できました。とはいえ、まだたかだか数十秒しか動かしていませんし、実際にはさらに複雑なこともしますし、なによりマイコンとNSP2340Aの省電力モードをうまく使いこなさないとあっという間に電池切れにはなると思うので、あと一歩踏み込んだ検証は必要な気がします。
が、とりあえずNSP2340A単独での評価はここで一旦区切り。。。のつもりでしたが、もうちょっとだけ続きます。おまけ。
コメント