今日は工作をちょっとお休みして、MSP430 でのプログラミング入門。この頃はこうしたマイクロコンピュータでも C によるプログラミング環境が整っているから、アセンブラでゴリゴリ記述しなくて済む。もちろん IO や割り込み制御についての知識は必要であるが、Windows などで API を憶えるようなものだ。トランジスタ技術誌のサンプルプログラムにちょっと手を入れて電子オルゴールもどきを書いてみた。
これは DAC0 から簡単なメロディを出力する簡単なサンプルプログラム。 DAC サンプルの DAC_OSC_A と WDT サンプルの WDT_interval_A を組み合わせ、250ms 毎の WDT 割込みで Timer_A0 割込み周期を変更することにより出力音階を変化させる。LED の点滅は元のプログラムの名残り。なお、DAC0 にオーディオアンプを接続する場合には、DC 成分が含まれているから必ずコンデンサを挿んでカットすること。レベル調整も忘れずに。
//****************************************************************************** // MSP430F4270 デモ・ソフト // // DAC0から正弦波を出力 // 出力タイミングはTimer_A0割込みを使う //****************************************************************************** #include "msp430x42x0.h" #include "math.h" int i; unsigned int w0[64]; float const pi = 3.141592654; // 音階表 : 実際には 219=0xdb 辺りが A=440Hz // const とすることで Flash 領域に割り当てる。 const unsigned int notes[24] = { // C D E F G A B 0x1ac, 0x194, 0x17d, 0x168, 0x153, 0x140, 0x12e, 0x11d, 0x10d, 0x0fe, 0x0f0, 0x0e2, 0x0d6, 0x0ca, 0x0be, 0x0b4, 0x0aa, 0x0a0, 0x097, 0x08f, 0x087, 0x07f, 0x078, 0x071, }; // シーケンスデータ : 音階表のオフセット。-1 は無音 #define NOTES 8 const int seq[NOTES] = { 0, 4, 7, 12, 7, 4, 0, -1 }; int cnt; void main(void) { WDTCTL = WDTPW + WDTHOLD; // WDTの停止 SCFQCTL = 94 - 1; // MCLK = 94 * ACLK * 2 = 6.16MHz, D = 2 FLL_CTL0 = DCOPLUS + XCAP18PF; // DCOPLUS = 1, 水晶容量負荷は18pF SCFI0 = FLLD_2 + FN_2; // D = 2, fDCOCLK = 2.2-17Mhz DAC12_0CTL = DAC12OPS + DAC12SREF_0 + DAC12LSEL_0 + DAC12AMP_7 + DAC12IR; // // DAC出力を外部端子に出力, 基準電圧はAVcc, DAC12_0DATを書込むと即DAC出力 // DAC出力の最大値は基準電圧, アンプは高速(高電流)モード for (i=0; i < 64; i++) { w0[i] = 2000. * sin( 2. * pi * i / 64. ) + 2047.; // } // 波形データの計算。中点が2047で, 振幅は2000, 分割数は64 CCR0 = 0; // 192; // 周波数設定 0: 停止 CCTL0 = CCIE; // CCR0 割込み許可 TACTL = TASSEL_2 + MC_1; // カウント源はSMCLK, UPカウントモード(TARはCCR0までカウント) cnt = 0; WDTCTL = WDT_ADLY_250; // WDTの設定, インターバルモード, 250ms // WDTPW(パスワード)+WDTTMSEL(inter. Timerモード) +WDTCNTCL(カウンタ・リセット)+WDTSSEL(ACLK) P1OUT &= ~0x01; // P1.0の出力を0に設定(入力設定なのでLEDは点灯しない) IE1 |= WDTIE; // WDT Timer割込みを許可 _BIS_SR(LPM0_bits + GIE); // LPM0に入る, 周辺モジュール割込み許可 } // Timer A0 割込みサービスルーチン #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { DAC12_0DAT = w0[i]; // DAデータをDAC12に設定 i = (i + 1) & 0x3f; // DAデータを0~63で循環 } // WDT 割込みサービスルーチン #pragma vector=WDT_VECTOR __interrupt void WDT (void) { if (cnt >= NOTES) cnt = 0; // 繰り返し int n = seq[cnt]; if (n < 0) { // 無音 CCR0 = 0; DAC12_0DAT = 2047; } else { CCR0 = notes[n]; } i = 0; cnt++; P1DIR ^= 0x01; // P1.0の入出方向を反転(LEDが点滅する) }
メロディのシーケンスデータに音の長さなどの要素を加えれば、もっとそれらしくなるだろう。手抜きプログラムでゴメン。もっときちんとした方法は マイコンを使った簡易DDS [プロパック] などを参照されたし。WaveTable電子オルゴール [ELM] のように書けるのが(遠い)目標。
過去の MSP430 関連記事。
[2007.01.21]
あまりの手抜きで申し訳ないので、もう少しだけ真面目に。上記の DDS 例のブレゼンハムによって周波数を変える方式で書き直してみた。 「間引き」をすることで高い周波数に対応できる。
#include "msp430x42x0.h" #include "math.h" int i; unsigned int w0[64]; // wavetable float const pi = 3.141592654; int cnt; int freq; // 音階表 const unsigned int notes[36] = { // C D E F G A B 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 493, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, }; // シーケンスデータ : 音階表のオフセット。-1 は無音 #define NOTES 8 const int seq[NOTES] = { 0, 4, 7, 12, 7, 4, 0, -1 }; int st = 0; #define BASEFREQ 1000 // 基本周波数 void main(void) { WDTCTL = WDTPW + WDTHOLD; // WDTの停止 SCFQCTL = 94 - 1; // MCLK = 94 * ACLK * 2 = 6.16MHz, D = 2 FLL_CTL0 = DCOPLUS + XCAP18PF; // DCOPLUS = 1, 水晶容量負荷は18pF SCFI0 = FLLD_2 + FN_2; // D = 2, fDCOCLK = 2.2-17Mhz DAC12_0CTL = DAC12OPS + DAC12SREF_0 + DAC12LSEL_0 + DAC12AMP_7 + DAC12IR; // // DAC出力を外部端子に出力, 基準電圧はAVcc, DAC12_0DATを書込むと即DAC出力 // DAC出力の最大値は基準電圧, アンプは高速(高電流)モード for (i=0; i < 64; i++) { w0[i] = 2000. * sin( 2. * pi * i / 64. ) + 2047.; // } // 波形データの計算。中点が2047で, 振幅は2000, 分割数は64 CCR0 = 96; // 192; // 基本周波数設定 1KHz: freq = 0; CCTL0 = CCIE; // CCR0 割込み許可 TACTL = TASSEL_2 + MC_1; // カウント源はSMCLK, UPカウントモード(TARはCCR0までカウント) WDTCTL = WDT_ADLY_250; // WDTの設定, インターバルモード, 250ms // WDTPW(パスワード)+WDTTMSEL(inter. Timerモード) +WDTCNTCL(カウンタ・リセット)+WDTSSEL(ACLK) P1OUT &= ~0x01; // P1.0の出力を0に設定(入力設定なのでLEDは点灯しない) IE1 |= WDTIE; // WDT Timer割込みを許可 _BIS_SR(LPM0_bits + GIE); // LPM0に入る, 周辺モジュール割込み許可 } // Timer A0 割込みサービスルーチン #pragma vector=TIMERA0_VECTOR __interrupt void Timer_A (void) { DAC12_0DAT = w0[i]; // DAデータをDAC12に設定 cnt += freq; while (cnt >= BASEFREQ) { cnt -= BASEFREQ; i = (i + 1) & 0x3f; // DAデータを0~63で循環 } } // WDT 割込みサービスルーチン #pragma vector=WDT_VECTOR __interrupt void WDT (void) { if (st >= NOTES) st = 0; // 繰り返し int n = seq[st]; if (n < 0) { // 無音 freq = 0; } else { freq = notes[n]; } i = 0; st++; P1DIR ^= 0x01; // P1.0の入出方向を反転(LEDが点滅する) }
複数同時発音や複雑な音色、エンベロープなどは次の課題。マザーボードに実装されている圧電サウンダを使えるよう DAC から PWM 変調出力に、タイマ割込みによるメロディシーケンスをメインループにすることなども予定している。以下のサンプルは石の種類が違うからそのままでは使えない。なお、ある程度まともな音を出すのなら圧電サウンダではなく、圧電スピーカーのほうが良いが、このマザーボード上に実装するには径が大きい。
このサンプルでは解説の冒頭にある通り、MSP430F149 で Timer_B を PWM 用にし、Timer_A による割り込みで DCO 操作をしている。MSP430F4270 では Timer_B が存在しないから、Timer_A を PWM に、DCO 操作には WDT か Basic Timer1 を使うことになる。MSP430 のユーザーズ・ガイドは日本語版 PDF ファイルが Texas Instruments 社のページから無料で入手できる(ユーザー登録が必要)。[2007.01.26]
二番目の例で DAC の代わりに PWM 出力を用いるように変更してみたのが次のコード。その他、細かいところがちょこちょこ違うのはあれこれ試行錯誤した名残り。PWM 周波数はなるべく高くしないとノイズっぽい音になる。デューティー比の分解能での PWM 周波数は 32.768KHz でローパスフィルタも入っていないから、まぁ止むを得ないか。圧電サウンダを使うとあまり音質どうのは関係ないし、まともな音にならないが、スピーカーにしてもやはりノイズっぽい音になる。やはりローパスフィルタを入れるか、PWM ではなく DAC を使ったほうが良い結果が得られそうだ。(2006.01.29 追記。BASICTIMER 割り込みルーチンの記述が間違っていました。その他、割り込み周期が予定と違っているような。)
#include "msp430x42x0.h" const unsigned char Sine_Tab[] = { 128, 103, 79, 57, 37, 22, 10, 2, 1, 2, 10, 22, 37, 57, 79, 103, 128, 153, 177, 199, 219, 234, 246, 255, 255, 254, 246, 234, 219, 199, 177, 153, }; unsigned char i; // Sine_Tab offset const unsigned int freq[] = { 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 493, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, }; #define NOTES 8 const int seq[NOTES] = { 0, 4, 7, 12, 7, 4, 0, -1 }; int st = 0; int cnt; int fq; #define BASEFREQ 1000 void main( void ) { WDTCTL = WDTPW + WDTHOLD; // stop WDT SCFQCTL = 128 - 1; // 8.39MHz FLL_CTL0 = DCOPLUS + XCAP18PF; SCFI0 = FLLD_2 + FN_2; fq = 0; cnt = 0; P1DIR |= 0x08; // P1.3 as output P1SEL |= 0x08; // select TA2 // Timer_A で PWM TACTL = TASSEL_2 | TACLR; // SMCLK clocks TA CCTL2 = OUTMOD_7; CCR0 = 255; // put 255 in CCR0 i = 0; CCR2 = Sine_Tab[cnt]; // Load first sample value into CCR1 TACTL |= MC_1; // start with up-mode // DCO のために 32KHz 周期での割り込み BTCTL = BT_MDLY_0_25; // 0.032ms IE2 |= BTIE; // Enable interrupt by Basic Timer // メロディ用の WDT による .25sec 割り込み st = 0; WDTCTL = WDT_ADLY_250; IE1 = WDTIE; P1OUT &= ~0x01; _BIS_SR(LPM0_bits + GIE); // LPM0 } #pragma vector=BASICTIMER_VECTOR __interrupt void BASICTIMER (void) { #if 0 i++; i &= 0x1f; CCR2 = Sine_Tab[i]; #else CCR2 = Sine_Tab[i]; cnt += fq; while (cnt >= BASEFREQ) { cnt -= BASEFREQ; i = (i + 1) & 0x1f; } #endif } #pragma vector=WDT_VECTOR __interrupt void WDT(void) { if (st >= NOTES) st = 0; cnt = 0; int n = seq[st]; if (n < 0) { fq = 0; } else { fq = freq[n]; } i = 0; st++; P1DIR ^= 0x01; }
PWM 方式でもうひとあがき。エンベロープを付けてみる。複数同時発音やシーケンサについてはまたいずれ。細かい数値はいい加減で、コードの整理もしていないのは相変わらずでゴメン。
#include "msp430x42x0.h" const signed char Sine_Tab[] = { 0, -25, -49, -71, -91, -106, -118, -126, -127, -126, -118, -106, -91, -71, -49, -25, 0, 25, 49, 71, 91, 106, 118, 127, 127, 126, 118, 106, 91, 71, 49, 25, }; unsigned char i; // sin_tab offset const unsigned char envelope[] = { 255,250,245,240,235,231,226,222,217,213,209,205,201,197,193,189, 185,182,178,174,171,168,164,161,158,155,152,149,146,143,140,137, 134,132,129,127,124,122,119,117,115,112,110,108,106,104,102,100, 98, 96, 94, 92, 90, 88, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 71, 69, 68, 67, 65, 64, 63, 62, 60, 59, 58, 57, 56, 55, 54, 53, 51, 50, 49, 48, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 35, 34, 33, 32, 31, 30, 29, 28, 26, 25, 24, 23, 22, 21, 20, 19, 17, 16, 15, 14, 13, 12, 11, 10, 8, 7, 6, 5, 4, 3, 2, 1, }; unsigned char ei; const unsigned int freq[] = { // C D E F G A B 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 493, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047,1109,1175,1245,1319,1397,1480,1568,1661,1760,1865,1976 }; #define NOTES 9 const int seq[NOTES] = { 12, 14, 16, 17, 19, 21, 23, 24, -1 }; // const int seq[NOTES] = { 0, 2, 4, 5, 7, 9, 11, 12, -1 }; int st; int cnt; int fq; unsigned char step; #define BASEFREQ 1000 void main( void ) { WDTCTL = WDTPW + WDTHOLD; // stop WDT SCFQCTL = 128 - 1; FLL_CTL0 = DCOPLUS + XCAP18PF; SCFI0 = FLLD_2 + FN_2; fq = 0; cnt = 0; P1DIR |= 0x08; // P1.3 as output P1SEL |= 0x08; // select TA2 TACTL = TASSEL_2 | TACLR; // SMCLK clocks TA CCTL2 = OUTMOD_7; CCR0 = 255; // put 255 in CCR0 i = 0; CCR2 = Sine_Tab[cnt]; // Load first sample value into CCR1 TACTL |= MC_1; // start with up-mode // BTCTL = BT_ADLY_0_032; // 0.064ms BTCTL = BT_MDLY_0_25; // 0.032ms IE2 |= BTIE; // Enable interrupt by Basic Timer P1OUT &= ~0x01; step = 0; st = 0; WDTCTL = WDT_ADLY_1000; IE1 = WDTIE; _BIS_SR(LPM0_bits + GIE); // LPM0 } #pragma vector=BASICTIMER_VECTOR __interrupt void BASICTIMER (void) { int n; n = Sine_Tab[i]; // サンプル値 (-128 ... 127) n = (int)n * envelope[ei] / 255.0 + 128; // エンベロープを適用 (0..255) CCR2 = n; // Sine_Tab[i] + 128; cnt += fq; while (cnt >= BASEFREQ) { cnt -= BASEFREQ; i = (i + 1) & 0x1f; } step++; if (step > 63 & ei < 127) { step = 0; ei++; } } #pragma vector=WDT_VECTOR __interrupt void WDT(void) { if (st >= NOTES) st = 0; cnt = 0; int n = seq[st]; if (n < 0) { fq = 0; } else { fq = freq[n]; } i = 0; st++; ei = 0; P1DIR ^= 0x01; }
[2007.01.22]
後田さんや hamayan さんのページで紹介していただきました。もう少しまともなプログラムにすべく努力します。
[2007.01.29]
良く理解していないまま、いくつかプログラムを書きましたが、やはりどうもヘンです。もっと勉強します。
[2007.02.26]
後田敏さんからトラックバックをいただきました。ありがとうございます。続編もどーぞ。
篁����MSP430-13
トランジスタ技術誌の2007年1月号付録のMSP430F4270マイコン基板(MSP430-CQ)をユニバーサル基板に組み立てた。13回目でもうレポートは終わっても良いかな。
今回は、プリンタポートのMSP-FET43...
おそらく携帯電話等からは投稿できません。日本語文字列を含まないコメントやトラックバック、および当サイトへの言及を含まないトラックバックは御遠慮いただいております。また、90日以上経過した記事へのコメントはできません。