LPC810M021FN8 (4) -- SCT を PWM として使う (2)
LPC810 用に、毎度おなじみ DDA (Digital Differential Analyzer) アルゴリズムを使った sin / cos 波発生プログラムを作りました。
3 番ピンと 4 番ピンから PWM 波出力されるので、LPF を掛ければ音として聞くことができます。
正弦波の周波数は CPU クロックの設定が 12 MHz のままの場合、約 470 Hz、PLL によって 30 MHz まで上げた場合、約 1170 Hz となります。
ソースリストを下に示します。
/***********************************/ /* LPC810M021FN8 test */ /* sin/cos generation by DDA */ /* (Digital Differential Analyzer) */ /* */ /* 2013/12/21 Created by pcm1723 */ /***********************************/ // LPCOpen v2.01 include files #include "chip.h" #define PWM_BITS (8) #define PWM_PERIOD (1 << PWM_BITS) #define PWM_MASK (PWM_PERIOD - 1) // placeholder __attribute__((weak)) void SystemInit( void ) { } // void SystemInit() void LPC8xx_pin_setup( void ) { // disable SWD pins (fixed assign) Chip_SWM_DisableFixedPin(SWM_FIXED_SWCLK); Chip_SWM_DisableFixedPin(SWM_FIXED_SWDIO); // SCT CTOUT_0 on P0_2 (pin4 of DIP8) Chip_SWM_MovablePinAssign(SWM_CTOUT_0_O, 2); // SCT CTOUT_1 on P0_3 (pin3 of DIP8) Chip_SWM_MovablePinAssign(SWM_CTOUT_1_O, 3); } // void LPC8xx_pin_setup() // setup SCT (State Configurable Timer) to: // "Single Edge PWM mode" for PWM_mode=0 // "Double Edge PWM mode" for PWM_mode=1 // if "Double Edge PWM mode", // actual PWM period = 2*(PWM_peri - 1) // void LPC8xx_pwm_setup(LPC_SCT_T *pSCT, // "LPC_SCT" uint16_t PWM_peri, // PWM period int PWM_mode, // "0" : SE-PWM // "1" : DE-PWM int int_en // interrupt setup enable ) { int n; // initailize (bus clock ON and reset) Chip_Clock_EnablePeriphClock(SYSCTL_CLOCK_SCT); Chip_SYSCTL_PeriphReset(RESET_SCT); // set SCT config pSCT->CONFIG = ( SCT_CONFIG_16BIT_COUNTER // not UNIFY | SCT_CONFIG_CLKMODE_BUSCLK // use BUS clock ); // EVENT4 setting for counter_L LIMIT pSCT->MATCH[4].L = (PWM_peri - 1); pSCT->MATCHREL[4].L = (PWM_peri - 1); // set PWM period pSCT->EVENT[4].CTRL = ( 4 // MATCHSEL = 4 | (1 << 12) // COMBMODE = MATCH only ); pSCT->EVENT[4].STATE = 1; // enable EVENT4 at STATE=0 pSCT->LIMIT_L = (1 << 4); // EVENT4 for LIMIT // EVENT_n/CT_OUT_n setting for (n = 0; n < 4; n++) { // SETCLR_n = 0x01 (SET/CLR reversed by direction) // for "Double Edge mode" pSCT->OUTPUTDIRCTRL |= (PWM_mode << (2*n)); pSCT->EVENT[n].CTRL = ( n // MATCHSEL = n | (1 << 12) // COMBMODE = MATCH only ); pSCT->EVENT[n].STATE = 1; // enable EVENT_n at STATE=0 pSCT->OUT[n].CLR = (1 << n); // clear CT_OUT_n at EVENT_n pSCT->MATCH[n].L = (PWM_peri >> 1); pSCT->MATCHREL[n].L = (PWM_peri >> 1); // initial duty // output clear setting for "Single Edge mode" pSCT->OUT[n].SET = ((0 == PWM_mode) << 4); // set CT_OUT_n at EVENT4 // conflict resolution for "Single Edge mode" (SET_n wins) pSCT->RES |= ((0 == PWM_mode) << (2*n)); } // for (n = 0; ... if (int_en) { // interrupt enable pSCT->EVEN = (1 << 4); // enable EVENT4 interrupt NVIC_EnableIRQ(SCT_IRQn); // enable SCT interrupt } // if (int_en) // set SCT control and start timer (HALT=0, STOP=0) pSCT->CTRL_L = ( SCT_CTRL_BIDIR_L(PWM_mode) // bidirectional if "Double Edge mode" | SCT_CTRL_CLRCTR_L // clear counter | SCT_CTRL_PRE_L(1 - 1) // prescaler = 1/1 ); } // void LPC8xx_pwm_setup() void LPC8xx_periph_setup( void ) { // Double Edge PWM, no interrupt LPC8xx_pwm_setup(LPC_SCT, PWM_PERIOD, 1, 0); LPC8xx_pin_setup(); } // void LPC8xx_periph_setup() int main() { int32_t sin_dac = 0x00000000; int32_t cos_dac = 0x70000000; // initial radius const uint32_t dac_shift = 3; const uint32_t limit_ev_mask = (1 << 4); // EVENT4 (counter limit) mask // fs = F_SYSCLK / (2*(PWM_PERIOD-1)) // = 23.528 [kHz] for F_SYSCLK = 12 [MHz] // = 58.824 [kHz] for F_SYSCLK = 30 [MHz] // // sin_cos_freq = fs / (2 * pi * (2 ** dac_shift)) // = 468.1 [Hz] for F_SYSCLK = 12 [MHz] // = 1170.3 [Hz] for F_SYSCLK = 30 [MHz] LPC8xx_periph_setup(); for (;;) { // infinite loop if (limit_ev_mask & LPC_SCT->EVFLAG) { // EVENT4 occurred LPC_SCT->EVFLAG = limit_ev_mask; // clear event flag LPC_SCT->MATCHREL[0].L = (PWM_MASK & ((0x80000000 ^ cos_dac) >> (32-PWM_BITS))); LPC_SCT->MATCHREL[1].L = (PWM_MASK & ((0x80000000 ^ sin_dac) >> (32-PWM_BITS))); cos_dac -= (sin_dac >> dac_shift); sin_dac += (cos_dac >> dac_shift); // update by DDA } // if } // for } // int main()
「CMSIS」を利用するプログラムの場合、main() 関数の実行に移る前の準備をする「C ランタイム」あるいは「スタートアップ」と呼ばれる部分で、SystemInit() 関数が呼ばれてクロックなどのセットアップをする「お約束」になっています。
ここでは、特に CPU を速く動かす必要もないので、オブジェクトのサイズを小さくするために、用意されている SystemInit() 関数をリンクせず、リセット後のデフォルトである内部 12 MHz RC オシレータでそのまま動作するようにしてあります。
そのようにして作成したオブジェクトの hex ファイルを下に示します。
これを (バージョン 7 以上の)「Flash Magic」などを使って LPC810M021FN8 に書き込めば動作します。
フラッシュ上のプログラム・サイズは 688 バイトです。
:1000000000010010CB000000C1000000C300000090 :1000100000000000000000000000000000000000E0 :1000200000000000000000000000000000000000D0 :100030000000000000000000C5000000C700000034 :10004000C9000000C900000000000000C900000055 :10005000C9000000C900000000000000000000000E :10006000C9000000C9000000C9000000C90000006C :10007000C9000000C900000000000000C900000025 :100080000000000000000000000000000000000070 :100090000000000000000000000000000000000060 :1000A000C9000000C9000000C9000000C90000002C :1000B000C9000000C9000000C9000000C90000001C :1000C000FEE7FEE7FEE7FEE7FEE708B500F022F8F0 :1000D00000230A4A0A499A188A4204D20949C9588F :1000E00004331160F5E7084B084A934202D200221C :1000F00004C3F9E700F0A6F8FEE7C04600000010D0 :1001000000000010B002000000000010000100100C :10011000FEE7C0467047000008B50A4AE0235B00CE :10012000D15804200143D150D15808200143D15067 :100130006320022100F0AAF87020032100F0A6F845 :1001400008BDC04600C00040F7B5324E8025F76FAD :100150006D00314C2F430193F76767682F4E3E4087 :100160006660666835436560061C4D1EADB2FE369E :100170000024046075828426B6008553284EC92564 :10018000AD004651C8250126AD0046511025058118 :10019000F140466D6500171CAF403E434665802622 :1001A000271C760126436037B446FF00C619634614 :1001B000736001263E50271CA640A037B446FF00BE :1001C000C61963467360261C4036B6003152261CA1 :1001D0008036B6003152564256413301C351876DC5 :1001E000AE40351C3D4301348565042CD1D1019BC3 :1001F000002B07D0031CF033102119608021094B1C :100200008900196001231A40120108231A438280D1 :10021000F7BDC0460480044000800440FFFEFFFF9D :100220000410000000E100E008B580210448490006 :1002300001220023FFF788FFFFF76EFF08BDC046CD :100240000040005038B5FFF7EFFFE022D205002351 :100250000C48102405680C492542F9D00460802020 :10026000000614188020240E80000C528020000606 :100270001C188120240E80000C52D910521AD11063 :100280005B18E5E7F4400050004000500F22074B98 :1002900002400009800030B5D200C458FF259540C7 :1002A000AC439140221C0A43C25030BD00C0004004 :00000001FF
最後に、PWM 出力をカットオフ周波数 7 kHz の 3 次バタワース LC フィルタに通して WaveSpectra でスペクトラムを見た画面を下に示します。
12 MHz クロックではサンプリング周波数が約 23 kHz と低いので、FFT のサンプリング周波数を 32 kHz に落として、エイリアスの影響を少なくしています。
ひずみ率が約 0.07 %、S/N が約 48 dB と、8 ビット PWM による結果としては妥当なものとなっています。
スペクトラムのピーク位置の下が「白く」見えるのは、クロックが RC オシレータであるためにジッタが多く、スペクトラムがブロードになるためです。
外部クリスタル・オシレータをクロックに使った場合には、ジッタが少ないので、他のノイズで「黒く」塗りつぶされたようになっている部分と同様になり、白い余白は見えません。