ColdFire MCF52233 基板 (9) -- のこぎり波ピーピー

インターフェース誌の Web サイトで、GCC & GDB 環境での「LEDチカチカ」のサンプル・プログラム「cf_led」が公開されています。
この環境をそっくりそのまま利用して、LED 点滅の代わりに、可聴周波数の音(のこぎり波)を発生させる「のこぎり波ピーピー」プログラムを作ってみました。
正確に言えば、固定周波数の音が鳴り続けるだけなので、「ピーピー」ではなく「のこぎり波ピー」と呼ぶべきプログラムとなります。
これは単なるサンプルではなく、「FM音源プログラム」を新しいマイコンに移植する際の第一段階と言えるもので、毎回、最初に、これに類したプログラムを作っています。
「FM音源プログラム」のハードウェア依存の部分の割合は、感覚的には 10 〜 20 % 程度と言えると思います。
音を出すためには、最低限、

  • サンプリング・レートでの割り込み機能
  • DA コンバータ機能

があれば、内蔵デモ曲の演奏ができます。
実際には、16 bit シリアル DAC の接続に LRCK が必要ですから、fs 割り込みを発生させるタイマでの LRCK 生成の方法についても考慮しておきます。
DA 機能としては 8 ビット PWM の PWM1 と PWM0 を連結して 9 ビット PWM として使っています。
PWM の繰り返し周波数は 30 MHz / 513 = 58.5 kHz です。
出力は 30 番ピン (*IRQ1/SYNCA/PWM1/PNQ[1]) で、たとえば、次のようなパッシブ LC LPF (3 次バタワース) を接続します。

LPF は一次 RC フィルタでもアクティブフィルタでも構いませんが、パッシブ LC フィルタは 3 次でも部品数は5個だけなので、基板に組まずに部品の足同士をハンダ付けしただけの空中配線状態でもテスト用には十分実用になります。
GCC & GDB, サンプルプログラムのパッケージをインターフェース誌の Web サイトからダウンロードして、解凍およびインストールをしておきます。 (Cygwin をインストールしていない場合は Cygwin も)
インストール方法については、パッケージ中の README ファイルに書いてあります。
経験がある方なら README ファイルの説明で分かると思いますが、読んでも分からない方はインターフェース誌 10 月号の記事を待ったほうが良いと思われます。
必要なソフトウェアをインストールした状態で、「cf_led.c」プログラムのコンパイル / 実行がうまく行くことを確かめておきます。
「cf_led.c」ファイルをリネームして、たとえば「cf_led_old.c」とします。
下のプログラムをコピー&ペーストして、あらたに「cf_led.c」ファイルを作ります。

/**********************************/
/* 16 kHz sampling rate interrupt */
/* and LRCK generation            */  
/* and 9-bit D/A using PWM        */
/*                                */
/* derived from sample "cf_led.c" */
/*                                */
/* pin (6): LRCK    (16 kHz)      */
/* pin(30): PWM out (440 Hz saw)  */ 
/**********************************/

#include "m5223x.h"

#define F_SAW (440.0)
#define FS    (16000)

typedef volatile unsigned long  vuint32;
typedef volatile unsigned short vuint16;
typedef volatile unsigned char  vuint8;

#define Hz2i(f) (((f)*65536.0*65536.0)/(double)FS+0.5)

#define MCF_GPIO_PORTNQ_PWM1 (3 << (1 << 1))
#define MCF_GPIO_DDR_B1      (1 << 1)

#define MCF_GPIO_PTCPAR PTCPAR
#define MCF_GPIO_DDRTC  DDRTC
#define MCF_GPIO_PORTTC PORTTC

// PWM module definitions 

#define MCF_PWM_BASE     (IPSBAR + 0x001B0000)
#define MCF_PWM_PWME     (*((vuint8  *)(MCF_PWM_BASE+0x00)))
#define MCF_PWM_PWMPOL   (*((vuint8  *)(MCF_PWM_BASE+0x01)))
#define MCF_PWM_PWMCTL   (*((vuint8  *)(MCF_PWM_BASE+0x05)))

#define MCF_PWM_PWMPER0  (*((vuint8  *)(MCF_PWM_BASE+0x14)))
#define MCF_PWM_PWMPER1  (*((vuint8  *)(MCF_PWM_BASE+0x15)))

#define MCF_PWM_PWMPER01 (*((vuint16 *)(MCF_PWM_BASE+0x14)))

#define MCF_PWM_PWMDTY0  (*((vuint8  *)(MCF_PWM_BASE+0x1C)))
#define MCF_PWM_PWMDTY1  (*((vuint8  *)(MCF_PWM_BASE+0x1D)))

#define MCF_PWM_PWMDTY01 (*((vuint16 *)(MCF_PWM_BASE+0x1C)))

// DMA Timer module definitions 

#define MCF_DTIM0_BASE   (IPSBAR + 0x0400)
#define MCF_DTIM0_DTMR   (*((vuint16 *)(MCF_DTIM0_BASE+0x00)))
#define MCF_DTIM0_DTXMR  (*((vuint8  *)(MCF_DTIM0_BASE+0x02)))
#define MCF_DTIM0_DTER   (*((vuint8  *)(MCF_DTIM0_BASE+0x03)))
#define MCF_DTIM0_DTRR   (*((vuint32 *)(MCF_DTIM0_BASE+0x04)))
#define MCF_DTIM0_DTCR   (*((vuint32 *)(MCF_DTIM0_BASE+0x08)))
#define MCF_DTIM0_DTCN   (*((vuint32 *)(MCF_DTIM0_BASE+0x0C)))

extern unsigned long bios_call(unsigned long func, 
                               unsigned long arg0, 
                               unsigned long arg1,  
                               unsigned long arg2,
                               unsigned long arg3); 

int int_vcs(int vecno, void* p) {
  return (int)bios_call(8, vecno, (unsigned long)p, 0, 0);
}

vuint8 lrck, t1_tick;

vuint32 phacc, phinc;

void __attribute__((interrupt_handler)) 
dtim0_handler() {
  MCF_DTIM0_DTER &= 0x03; // clear REF, CAP flag
  if (lrck ) { // left channel timing
    t1_tick = 1;
    MCF_PWM_PWMDTY01 = ((0x1ff & (phacc >> (32-9)))+1);
  }
  lrck ^= 0x01;
} // void dtim0_handler()

void mcf_port_setup( void )
{
// setup for PWM01 (16-bit concatenate)

  MCF_GPIO_PNQPAR  = MCF_GPIO_PORTNQ_PWM1;
  MCF_GPIO_DDRNQ   = MCF_GPIO_DDR_B1;

  MCF_PWM_PWMCTL  |= 0x10;  // concat PWM0 & PWM1 for 16-bit op
  MCF_PWM_PWMPER01 = 513-1; // set PWM01 period = 513 (9-bit PWM)
  MCF_PWM_PWMDTY01 = 256;   // set PWM01 duty
  MCF_PWM_PWMPOL  |= 0x02;  // flip PWM1 polarity
  MCF_PWM_PWME    |= 0x02;  // enable PWM1

// setup for DMA Timer 0

  lrck = 0; // LRCK flag
  int_vcs(64+19, dtim0_handler); // setup interrupt vector
  MCF_GPIO_PTCPAR  = 0x02; // select DTOUT0
  MCF_GPIO_DDRTC   = 0x01; // select output

  MCF_DTIM0_DTMR   = 0x003A; // sys_clk/1, toggle out, intr, reload
  MCF_DTIM0_DTRR   = 1875-1; // 60 MHz / 1875 / 2 = 16 kHz
  (*(unsigned char*)(MCF_INTC_ICR(MCF_INTC0, 19))) 
    =  (((5) << 3) | (5));
  (*(unsigned long*)(MCF_INTC_IMRL(MCF_INTC0)))
    &= ~(1 << (19));
  MCF_DTIM0_DTMR  |= 0x0001; // timer start
} // void mcf_port_setup()

int main() {
  phacc = 0; // clear phase acc
  phinc = Hz2i(F_SAW); // set phase increment
  mcf_port_setup();
  while (1) {
    if (t1_tick) {    // sampling timing 
      t1_tick = 0;    // clear flag
      phacc += phinc; // update phase acc
    } // if (t1_tick) ...
  } // while (1)
  return (0);
}

つまり、「cf_led.c」プログラムの中身だけを、上の内容に置き換えます。
あとは元と同様にコンパイル / 実行させます。
上のプログラムでは、440 Hz の「A」の音を出力していますが、最初の部分の

#define F_SAW (440.0)

の「440.0」を欲しい周波数に書き換えてコンパイルすると、その周波数の音が出力されます。
サイン波テーブルなしで、フェーズ・アキュムレータの上位ビットをそのまま出力しているので、波形としては、のこぎり波になります。