音声メトロノーム その1.

ギター合奏団の練習では、テンポをキープするため先生かリーダ格の人が声で 1・2・3・・・とリズムを刻みます。
普通は、メトロノームと言う道具を使いますが練習の時はかなりゆっくりの速度で演奏するので機械音のカチ・カチでは、何拍目なのかが分からなくなります。
そこで、音声で拍数を知らせてくれるメトロノームを制作しました。
もちろん、Arduino UNO とそのスケッチを利用しています。

完成品の外観を図1.に示します。

図1.音声メトロノームの外観

図1.は、左側がArduino UNOとシールドで作成したコントローラー部で右側は、スピーカーボックス(iPhoneの外箱を利用して作りました)です。

コントローラ部は、3枚のプリント板で構成されています。分解した写真を図2.に示します。左側が Arduino UNO(互換ボードで廉価な、「びんぼーでいいの」)、中央が自作のシールドで音声合成部(AquesTalk pico ATP3012F6-PU)とD級アンプ回路部が入っています。右側は既成品のArduino用LCDシールド(Sunhyato製AS-E401)です。

図2.コントローラー部を分解した外観

「びんぼーでいいの」は、秋葉原にあるショップaitendo製の超安いArduino UNOと考えて下さい。但し、USB-シリアル変換用のデバイスが本家のArduinoUNOと異なることでパソコンと接続する場合に専用のドライバーを使う必要があります。

 

自作部のシールドは、今回は回路図を載せておきます。動作についての説明は別の機会にしたいと思います。

図3. 自作シールド内部とArduinoUNOとの接続部の回路図

主要な部品
1.音声合成LSI  ATP3012F  シリアル(I2C)ラインで受信したローマ字表記のテキストを音声に変換してくれます。
2.XTL 16MHz  セラミック振動子
3.AE-TPA2006D1  超小型のD級アンプです(秋月通商)
4.2SA1015Y  (たまたま手元に、あったトランジスタです。他のものでも代用可能)

音声メトロノーム その2.

音声メトロノーム用のスケッチです。

#include <AquesTalk.h>
#include <Wire.h> // I2CライブラリAquesTalkライブラリ内部で使用するので定義必要
#include <LiquidCrystal.h>

LiquidCrystal lcd(12,11,5,4,3,2); // LCD の変数を生成

const int ledPin = 13; // LED のピン番号
const int BL = 9; // LCD Back Light 制御用ピン番号
const int SW1 = 6; // Switch 1のピン番号
const int SW2 = 7; // Switch 2のピン番号
const int ARST = 8; // 追加基板上のリセットSW

long dt_1mini; // 1分当たりのミリ秒
AquesTalk atp; // インスタンス定義 変数名は任意
int Beat_s; // 拍子
int Max_Beat; // 最大拍子数
int Tempo_s ; // 速度
int Max_Tempo ; // 最速
int Min_Tempo ; // 最低速
int Interval_ms; // 時間間隔
int cur_beat; // 何拍目
int Led_St; // Ledの状態
int Ura_B ; // 裏拍子を刻むかを制御するフラグ

// ****** 以上 変数宣言 ******

void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT); // LED端子を出力に
pinMode(BL, OUTPUT); // BL制御端子を出力に
pinMode(SW1, INPUT); // SW1端子を入力に
pinMode(SW2, INPUT); // SW2端子を入力に
pinMode(ARST, INPUT); // ARST端子を入力に
dt_1mini = 60000; // 60000ms
Beat_s = 2 ; // 2拍子
Max_Beat =8 ; // 8拍子まで
Tempo_s = 75; // 速度75
Max_Tempo = 100; // 最速 100
Min_Tempo = 35; // 最低速 35
cur_beat =1;
Led_St = HIGH; // 点灯状態

lcd.begin(16,2) ;
digitalWrite(BL,HIGH); // Back Lightを点灯
lcd.print(“Beat = “) ;
lcd.print(Beat_s,DEC) ;
lcd.setCursor(0,1) ;
lcd.print(“Tempo = “) ;
lcd.print(Tempo_s,DEC) ;
lcd.setCursor(6,0) ;

// 拍子の設定変更
lcd.cursor() ;
do {
if(digitalRead(SW1)==LOW){
delay(100) ;

if(digitalRead(SW1)==LOW){
Beat_s = Beat_s +1 ;
if(Beat_s > Max_Beat){
Beat_s = 2 ; // 拍子を初期値に戻す
}
lcd.setCursor(0,0) ; // 1行1列目にカーソルセット
lcd.print(“Beat = “) ; //
lcd.print(Beat_s,DEC) ;
lcd.setCursor(6,0) ;
}
}

} while ( digitalRead(ARST)==HIGH) ; // ARST SWが押されたら終了

lcd.setCursor(7,1) ;
delay(200) ;

// テンポの設定変更
do{
if(digitalRead(SW1) != digitalRead(SW2)) {
delay(100) ;

if(digitalRead(SW1) == LOW){
if(Tempo_s < Max_Tempo){
Tempo_s = Tempo_s +1 ;
}
}

if(digitalRead(SW2) == LOW){
if(Tempo_s > Min_Tempo){
Tempo_s = Tempo_s -1 ;
}
}

lcd.setCursor(0,1) ;
lcd.print(“Tempo = “) ;
lcd.print(Tempo_s,DEC) ;
lcd.print(” “) ;
lcd.setCursor(7,1) ;
}

} while (digitalRead(ARST)) ; // ARST SWが押されたら終了
lcd.noCursor() ;

Interval_ms = dt_1mini / (Tempo_s * 2) ; // 時間計算
Serial.println(“Interval_ms:”) ; // パソコンへ送信
Serial.println(Interval_ms) ; // パソコンへ送信
Serial.println(“=== Start ===”) ; // パソコンへ送信

digitalWrite(BL,LOW); // Back Lightを消灯

//  イントロのBeep 音
atp.Synthe(“#K”) ; //発声
delay(Interval_ms * 2) ;

for(int i=1; i < Beat_s ; i++) {
atp.Synthe(“#J”); //発声
delay(Interval_ms * 2) ;
}
}

void loop() {
// 初期化処理の実行
if(digitalRead(ARST) == LOW){
cur_beat = 1;

if(digitalRead(SW1) == LOW){
Ura_B = HIGH ;
}
//  イントロのBeep 音
atp.Synthe(“#K”); //発声
delay(Interval_ms * 2);

for(int i=1; i < Beat_s ; i++) {
atp.Synthe(“#J”); //発声
delay(Interval_ms * 2);
}
}

// 何拍子目かの判定
if(cur_beat > Beat_s){

cur_beat = 1; // 現拍子が最大を越えたら初期セット
}

// 拍子を刻む毎にLEDの状態を反転させる処理
digitalWrite(ledPin,Led_St);
Led_St = !Led_St;

switch(cur_beat){
case 1:
atp.Synthe(“ichi”); //発声
break;
case 2:
atp.Synthe(“ni”); //発声
break;
case 3:
atp.Synthe(“san”); //発声
break;
case 4:
atp.Synthe(“yon”); //発声
break;
case 5:
atp.Synthe(“go”); //発声
break;
case 6:
atp.Synthe(“roku”); //発声
break;
case 7:
atp.Synthe(“nana”); //発声
break;
case 8:
atp.Synthe(“hati”); //発声
break;
default:
break;
}
delay(Interval_ms);
// 裏拍子を刻む処理

if(Ura_B == HIGH) {
atp.Synthe(“#J”); // 裏拍子を発声
}

delay(Interval_ms);

Serial.println(cur_beat) ; // パソコンへ送信
cur_beat++ ;
}

Multi-function Shield (シールド)その1.

<マルチ・ファンクション・シールド>
秋葉原を散歩中にパーツ店で見つけました。4桁の7-セグLED、電子ブザーと押し釦スイッチが実装されたArduino 用のシールド(拡張用基板)です(たしか、数百円で購入)。
しばらく放置していましたが、ラーメンタイマーを作成しました(3分間のダウンカウントタイマー)。

スケッチ(プログラム)の作成時に気づいた問題
● 4桁の7-セグLEDの最上位桁の表示がでない
1)ネットで検索して知りましたが、実はこのシールドの実装上の設計ミス(?)と思われる点がありました。
シールドは、Arduino UNOボードにスタックする様に重ねて実装します。
まずは、図1.のようにシールドとArduino UNOは別々の状態です。

図1. 左:Multi-function-Shiled          右:Arduino UNO

次に、シールドをArduino UNO の上に載せて(スタック)拡張用コネクタで接続します。図2.のような状態になります。

図2. シールドとArduino UNO をスタックした状態

この状態にすると、電気的な問題(信号線がグランドに短絡する)が発生します。(図3. 参照)

図3. 電気的な短絡箇所の説明

この問題の回避策は、短絡を起こしているリード線を短くカットし更にUSBコネクタケースを絶縁テープで覆う方法が簡単で有効です。

2) 最上位桁の表示が出ない原因は、別にありました。
しかしながら、電気的な考察をすると7-セグLEDの7番ピンとグランドの短絡では、最上位桁が表示しない現象には結び付きません。(7番ピンがグランドと短絡すると、7-セグのB部のLEDが全桁点灯状態になると思われます。LEDをダイナミック点灯させるためA~G、DPを駆動する信号線が4桁共通になっています。アノードのラインをDG.1~DG.4でサイクリックにアクティブ化するタイミングで切り替えています。)
7-セグLEDの内部回路を図4.に、ピンレイアウトを図5. に示します。

図4.7-セグLEDの内部回路

図5.7-セグLEDのピンレイアウト

この資料から推察すると、最上位桁(DG.1)のコモンアノード(12番ピン)がHight Activeにならない状態が原因と思われます。
そこで更なる調査として、シールドの回路図をネットで検索してダウンロードしました。どうも U2のIC(74HC595)の15番ピン(QA)と接続されているようです(図6.参照)。テスターで導通を確認すると、案の定このパターン(回路図中 ✖ 記号で示した箇所)が断線していることが分かりました。

図6. シールド回路図の断線箇所

黄色の線で接続してハード的な問題が解決できました。(図7. 断線箇所の補修)
(この断線は、製品の製造不良ですね!)

図7.断線箇所の補修

Multi-function Shield (シールド)その2.

3分間ラーメンタイマーのスケッチです。
サンプルスケッチの「Countdown_timer」を、ちょっとだけ改造しました。

IMG_1568

タイマースタート前

IMG_1569.JPG

ダウンカウント中

IMG_1571

タイムアップ       (End表示)

#include <TimerOne.h>
#include <Wire.h>
#include <MultiFuncShield.h>

enum CountDownModeValues
{ COUNTING_STOPPED,
COUNTING
};
byte countDownMode = COUNTING_STOPPED;
byte tenths = 0;
char seconds = 0;
char minutes = 3;
boolean time_over = false;

void setup() {
// put your setup code here, to run once:
Timer1.initialize();
MFS.initialize(&Timer1); // initialize multi-function shield library
MFS.write(minutes*100 + seconds);

Serial.begin(9600);
Serial.write(“Program Started!”);
}

void loop() {
// put your main code here, to run repeatedly:
byte btn = MFS.getButton();
switch (countDownMode)
{
case COUNTING_STOPPED:
if (btn == BUTTON_1_SHORT_RELEASE || btn == BUTTON_1_LONG_PRESSED)
{
// start the timer
time_over = false;
countDownMode = COUNTING;

tenths = 0;
seconds = 0;
minutes = 3;
MFS.write(minutes*100 + seconds);
MFS.blinkDisplay(DIGIT_ALL, OFF);

}
break;

case COUNTING:
if (btn == BUTTON_1_SHORT_RELEASE || btn == BUTTON_1_LONG_RELEASE)
{
// stop the timer
countDownMode = COUNTING_STOPPED;
MFS.blinkDisplay(DIGIT_ALL, OFF);
}
else
{
// continue counting down
tenths++;
if (tenths == 10)
{
tenths = 0;
seconds–;

if (seconds < 0 && minutes > 0)
{
seconds = 59;
minutes–;
}

if (minutes == 0 && seconds == 0)
{
// timer has reached 0, so sound the alarm
MFS.beep(5, 5, 4, 3, 50); // beep 4 times, 50 milliseconds on / 50 off loop 3 times
countDownMode = COUNTING_STOPPED;
time_over = true;

}

if (time_over){
MFS.write(“End”);
MFS.blinkDisplay(DIGIT_ALL, ON);
}
else
{
MFS.write(minutes*100 + seconds);
MFS.blinkDisplay(DIGIT_ALL, OFF);
}
}
delay(100);
}
break;
}
}