ストロボチューナーとは
- 旧来のストロボチューナー
- 入力信号と同期したストロボ光で、回転する白黒パターンが書かれた円盤を照らす事で、入力信号の周波数を合わせるもの。
- 狭義のストロボチューナー。
- 現在のストロボチューナー
- 周波数測定の原理は別の物を用い、その表示器としてストロボチューナー風を用いるタイプ(バーチャルストロボ式と書かれることも)
- 周波数測定と表示は同一の原理を用いる。ただし、その原理は、円盤をストロボで照らす機械・光学のハイブリッド式ではなく、アナログ回路やディジタル回路による電子式。(トゥルーストロボ式と書かれることも)
今回製作するチューナーの仕様
- かずさんのAVR式ストロボチューナーを参考にしました。これをベース用にアレンジ。
- トゥルーストロボ式
- 入力波形を2値化した信号と、欲する周波数(たとえばA=55 Hz)で1周するLEDアレイとを電気的に結合することで、ハードウェア的に論理式をたたき出して、そのままLEDに表示する。
- LED8灯式
- 入力は電気信号(元はエレクトレットコンデンサマイクだったのだが・・・)
- 入力音階検出機能は無し。
- スイッチで指定された周波数の波形を出力し続けるだけの簡単な構造
- 後に、周波数を検知して自動的に適した周波数を出力できるクロマチックチューナーにする予定あり。
- 発振回路は汎用の水晶振動子。精度が低いので、ソフトウェアで校正する。
- 分解能1cent
ブロック図
- 概略ですが・・・
音声部分回路図
- 元々入力がECMとして設計されたので定数が最適で無いかも
- ただし、ベースからの直接入力をしてみた感じでは丁度良いか、もしくはゲインが不足気味かも。
- ゲインをもう少し大きくするか、ヒステリシスを大きめにとったヒステリシスコンパレータを入れればだいぶ見やすくなる?
- ヒステリシスが位相でどういう変化を与えてしまうのか不明なため、あまり大きい物を一気に入れたくもない。
- 撥弦楽器の場合、入力は減衰していくので、でかいヒステリシスが入った物では表示ががらっと変わってしまいそうな懸念もある。もっとゲインを大きくして、ヒステリシスをうっすらかける程度でいい?
- 2段目は元々ヒステリシスコンパレータだったのだが、色々上手くいかなくてフィードバックを取っ払ってしまったため、今ではただのコンパレータ
- アンプのゲインを変えると、点灯部分と消灯部分の比が変わる。また、高いゲインだと倍音成分が全て圧縮して天井へと消えてしまうので、見やすい矩形波になりやすい?
- 入力ゲインは、基板上に可変抵抗を設けることなく、接続したミキサー出力である程度変えればいいかなーとも。
- フィルタのカットオフ周波数が色々おかしい?確かベースの4弦解放から1弦24fの範囲で設計した気がする。忘れた・・・。
- ・・・というわけでかなり模索中。ここだけでもPSoCにしようかなとは思う。
- 回路図注意点
全体回路図
- 準備中
- 2313に水晶振動子を接続
- PortB7:0にLEDのAを接続、LEDのKはコモンにしてOPAの出力へ接続
マイコン部分
使用MPU
- (AVR)ATtyny2313A-20PU
クロック
- 水晶振動子10MHz
- 内部分周8
機能割り付け
- PORTB7:0
- LEDを接続
- INT0
- 弦番号切り替え(4→3→2→1→4)
- スリープ復帰
- Timer0
- 2分間タイマー(スリープ移行用)
- Timer1
- コンペアマッチングによるCTC周期割り込み(LED点滅波形生成)
動作
- 詳しくはソースで。色々忘れたので。概要を以下に。
- 電源投入と共に始動
- Timer1のコンペアマッチにより、LEDのLSBを1bitだけ点灯→4弦の周期の8分割、つまり8倍の周波数で右シフト動作
- SWが押されたら、弦番号表示としてLEDを6つ光らせてから、コンペアレジスタの値を3弦の周波数のものへ切り替え
- 弦番号は高弦側へ向かってループ
- SWが120秒間押されないとスリープ
- スリープ中にSWが押されると復帰
プログラムソース
/* f_osc=外付け水晶振動子10MHz、システムクロックプリスケール8=1.25MHz Processor=ATtiny2313 Device Signature =
FUSE: Low: 11111111 ||||++++-- CKSEL[3:0] 外部クリスタル発振子8~MHz ||++-- SUT[1:0] 起動時間:外部クリスタル、低速立ち上がり電源(注意:2313でクリスタルでTimer出力を使いつつBODを使うと4.5V以上でリセットがかかるバグがあるらしい) |+-- CKOUT (0:PD2にシステムクロックを出力) +-- CKDIV8 クロック分周初期値 (1:1/1, 0:1/8) High:11-11111 Ext: -------1 */
/*************** include ***************/ #include<avr/io.h> #include<avr/interrupt.h> #include <avr/sleep.h>
/*************** define ***************/ #define SW PD2
#define BV(bit) (1<<(bit)) #define cbi(addr,bit) addr&=~_BV(bit) #define sbi(addr,bit) addr|=_BV(bit) #define outp(data,addr) addr=(data) #define inp(addr) (addr) #define outb(addr,data) addr=(data) #define inb(addr) (addr) #define PRG_RDB(addr) (pgm_read_byte(addr))
#define MAX_str 4 //弦数 void IO_init(void); void wait(unsigned int time); // 0.1ms @ 5MHz void SW_scan_init(void); void wink(uint8_t gen); void CTC_init(void); void Timer0_init(void);
volatile uint8_t sel=3; //初期で4弦選択 volatile uint8_t lob=0; //LED点灯位置LedOnBit volatile uint16_t SleepCounter=0; //電源消し忘れ対策用カウンター
//タイマトップ値テーブル //システムクロック1.25MHz(10MHz/8)、タイマ分周N=1の場合TOP=(Fclk/2Focr)-1 ////データシートのCTC動作の式は、トグル動作を行いそれをピンに直に出す設定であるため、 //トグルにより周波数が1/2になっている。割り込み間隔は普通にカウンタがトップに達するまでの時間なので //1/2は付かない。 //ただしLED8個で1周期なので、上記の値は8倍の周波数で計算する volatile uint16_t f_table[MAX_str] = { 1593, //1弦(97.9989Hz) 2127, //2弦(73.4162Hz) 2840, //3弦(55.0000Hz) 3791 //4弦(41.2034Hz) };
volatile uint8_t FlashLEDTable[MAX_str] = { 0b11000000, //1弦(97.9989Hz) 0b11110000, //2弦(73.4162Hz) 0b11111100, //3弦(55.0000Hz) 0b11111111 //4弦(41.2034Hz) };
int main(void) { //システムクロック分周 CLKPR=_BV(CLKPCE); //分周比変更可能許可、残りは必ず0 CLKPR=0b0011; //分周比8、必ずCLKPCE(MSB)を折ること。上で許可してから4クロック以内
IO_init(); SW_scan_init(); CTC_init(); Timer0_init();
PORTB=1; //初期値代入 OCR1A=f_table[sel]; //周波数初期化 sei();
set_sleep_mode(SLEEP_MODE_IDLE); //アイドルモード(CPU以外はクロック走行) while(1) { sleep_mode(); //スリープへ }
return 0; }
void IO_init(void) { DDRB= 0xFF; //全出力 PORTB=0; //0出力
PORTD=0b1111111; //(全入力:初期値)、全プルアップ }
ISR (INT0_vect) //INT0ピン変化割り込み(弦切り替えSW押下) { wait(200); //チャッタリング拿捕20ms
if(bit_is_clear(PIND,SW)){ if(sel!=0){ sel--; //細い弦へ } else sel=3; //配列のインデックスを3(4弦)に戻す
OCR1A=f_table[sel]; //周波数更新 wink(sel); //配列インデックスを表示関数へ渡し、弦番号表示 }
TCCR1B|=_BV(WGM12)|_BV(CS10); //パワーダウンからの復帰時はT1のカウンタが停止しているのでリスタート PORTB=1; //パワーダウンから復帰時はPORTB=0なので、ビットシフトの種を強制生成 set_sleep_mode(SLEEP_MODE_IDLE); //パワーダウンからの復帰のため、アイドルモードに設定 }
ISR(TIMER1_COMPA_vect) //CTC周期割り込み { if(PORTB!=_BV(7)){ PORTB<<=1; } else PORTB=1; }
ISR(TIMER0_OVF_vect) //Timer0オーバーフロー割り込み(1/1.25M*255*1024*574=120s後に処理) { SleepCounter++; if(SleepCounter==574){ TCCR1B&=0b11111000; //Timer1クロック停止 PORTB=0; //LED全消灯 set_sleep_mode(SLEEP_MODE_PWR_DOWN); //パワーダウンモード(INT0,1ピン変化のみで復帰)main()のスリープ命令で発行
SleepCounter=0; //リセット } }
void SW_scan_init(void) { MCUCR &= ~(_BV(ISC01)|_BV(ISC00)); //INT0ピン=Lで外部割り込み(エッジ検出はパワーダウンから復帰できないのでNG) GIMSK =_BV(INT0); //INT0割り込み許可 }
void CTC_init(void) { TCCR1B|=_BV(WGM12)|_BV(CS10);//CTC(TOP=OCR1A)モード、1分周カウンタスタート TIMSK|=_BV(OCIE1A); //タイマー1Aコンペア割り込み許可
return; }
void Timer0_init(void) { TCCR0B|=_BV(CS02)|_BV(CS00); //フリーランモード、CLK_IOの1024分周でカウントスタート TIMSK|=_BV(TOIE0); //タイマー0オーバーフロー割り込み許可
return; }
void wait(unsigned int time) // 0.1ms @ 1.25MHz { unsigned char lpcnt; __asm__ __volatile__("\n" "CPU_wait_entry:\n\t" "ldi %0,31\n" //@1.25MHz "CPU_wait_lp:\n\t" "nop\n\t" "dec %0\n\t" "brne CPU_wait_lp\n\t" "sbiw %1,1\n\t" "brne CPU_wait_entry\n\t" :"=&a"(lpcnt) :"w"(time) ); return; }
void wink(uint8_t gen) { PORTB=FlashLEDTable[gen]; wait(5000); return; }
クロック源について
- 記述中ですが内容のメモ
- 結論的に内蔵RC発振は使えなかった。キャリブレーションレジスタで校正をかけても非常に不安定で再現性に乏しかった。
- 水晶振動子とセラロックで迷った。複数ソースを眺めたところ、セラロックと水晶は、発振周波数だけみれば1桁オーダーしか変わらないという噂もあったが、温度特性だか電圧特性に問題があるため、セラロックでチューナーを作ることは博打かなーと感じたため、結局水晶にした。
- 水晶は数回の校正で高精度がだせることが確認できた。
校正
校正方法
writing
校正用ファイル
とりあえず置いときます。後で書きます。
校正用エクセルファイル