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 オシレータであるためにジッタが多く、スペクトラムがブロードになるためです。
外部クリスタル・オシレータをクロックに使った場合には、ジッタが少ないので、他のノイズで「黒く」塗りつぶされたようになっている部分と同様になり、白い余白は見えません。