Sat 02/03, 2007

MSP430 プログラミング入門(2) [Electronics ]

引き続き、MSP430 プログラミングの学習。良く判らないまま適当に書いたプログラムをあちこちいじったりして少しずつ理解を進めている…つもりなのだけれど、これがなかなか難しい。オルゴールプログラムはその後4声に発展させてみたが、いくつか難関が残っている。

  • 32KHz サンプリングで発音する場合、8MHz 動作では 256 サイクル以内に処理をおこなう必要があるが、これは非常に厳しい。
  • サンプリング周波数を 16KHz に下げて 512 サイクルでなんとか間に合わせよう。
  • それでもエンベロープを適用するまでの余裕はない。となると、音の継続時間(duration)をシーケンスデータに含めなければならない。

こうしてみると改めて、ChaN さん(ELM)WaveTable 電子オルゴールは凄いと思う。

それはさておき、MSP430 基板関連情報の Wiki を始めました。今のところ、主に hamayan さんに書いていただいてます。そちらもよろしく。

[2007.02.05]
プログラミングが進まないので中途のままコードを公開。DAC0 から正弦波によるメロディを出力するサンプル。試行錯誤の残骸やコメントの嘘もそのまま。エンベロープが使えなくなったため、デュレーション固定で誤魔化している。ツッコミどころ満載。

#include "msp430x42x0.h"

// wave table - 64 point
#define BASEFREQ 512 // 32KHz 割り込みの場合
#define WAVTBLMASK 0x3f
const signed char wav_tbl[] = {
     0,  11,  23,  34,  45,  55,  65,  74,  82,  89,  95, 100, 104, 107, 109, 110,
   110, 109, 107, 104, 100,  95,  90,  83,  76,  68,  59,  50,  41,  31,  21,  10,
     0, -10, -21, -31, -41, -50, -59, -68, -76, -83, -90, -95,-100,-104,-107,-109,
  -110,-110,-109,-107,-104,-100, -95, -89, -82, -74, -65, -55, -45, -34, -23, -11,
};

// envelope table - 256 points - 役に立っていない
const unsigned char env_tbl[] = {
  255, 252, 250, 247, 245, 243, 240, 238, 235, 233, 231, 228, 226, 224, 222, 219,
  217, 215, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 191, 189, 187,
  185, 183, 182, 180, 178, 176, 174, 173, 171, 169, 168, 166, 164, 163, 161, 159,
  158, 156, 155, 153, 152, 150, 149, 147, 146, 144, 143, 141, 140, 139, 137, 136,
  134, 133, 132, 130, 129, 128, 127, 125, 124, 123, 122, 120, 119, 118, 117, 116,
  115, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100,  99,
   98,  97,  96,  95,  94,  93,  92,  91,  90,  89,  88,  87,  87,  86,  85,  84,
   83,  82,  82,  81,  80,  79,  78,  78,  77,  76,  75,  75,  74,  73,  72,  72,
   71,  70,  69,  69,  68,  67,  67,  66,  65,  65,  64,  64,  63,  62,  62,  61,
   60,  60,  59,  59,  58,  57,  57,  56,  56,  55,  55,  54,  54,  53,  53,  52,
   51,  51,  50,  50,  49,  49,  48,  48,  48,  47,  47,  46,  46,  45,  45,  44,
   44,  43,  43,  43,  42,  42,  41,  40,  40,  39,  39,  38,  38,  37,  37,  36,
   35,  35,  34,  34,  33,  33,  32,  31,  31,  30,  30,  29,  29,  28,  28,  27,
   26,  26,  25,  25,  24,  24,  23,  22,  22,  21,  21,  20,  20,  19,  19,  18,
   17,  17,  16,  16,  15,  15,  14,  13,  13,  12,  12,  11,  11,  10,  10,   9,
    8,   8,   7,   7,   6,   6,   5,   4,   4,   3,   3,   2,   2,   1,   1,   0,
};

// note-freq table - 48 points
const unsigned int frq_tbl[] = {
//   A         B    C         D         E    F         G     
   220, 233, 247, 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,2093,2217,2349,2489,2637,2794,2960,3136,3322,
  3520,3729,3951,4186,4435,4699,4978,5274
};

enum PITCH_NAME {
    A2=0,B2,H2,C2,Cis2,D2,Dis2,E2,F2,Fis2,G2,Gis2,
    A3,B3,H3,C3,Cis3,D3,Dis3,E3,F3,Fis3,G3,Gis3,
    A4,B4,H4,C4,Cis4,D4,Dis4,E4,F4,Fis4,G4,Gis4,
    A5,B5,H5,C5,Cis5,D5,Dis5,E5,F5,Fis5,G5,Gis5,
    A6,B6,H6,C6,Cis6,D6,Dis6,E6
};

typedef struct _Note {
  signed int    frq; // frequency - 0: none
  signed int     bh; // for bresenham
  unsigned char  wi; // wav_tbl offset
  unsigned char env; // env_tbl offset
} Note;

#define DUPLEX 4
#define DUPMASK 0x03

#define EON 0x80  // End of Notes flag
#define EOS 0xff  // End of Score

#include "menuet.h" // sequence data

Note notes[DUPLEX]; // note data
unsigned char *melp;
unsigned int tick;
unsigned int tick_next;
unsigned char tickn;
unsigned char nidx; // index to next note data: 0..3

void init_seq(void);
int get_mel(void);

int main(void)
{
  WDTCTL = WDTPW + WDTHOLD;               // WDTの停止
  SCFQCTL = 128 - 1;                      // MCLK = 128 * ACLK * 2 = 8.39MHz, D = 2
  FLL_CTL0 = DCOPLUS + XCAP18PF;          // DCOPLUS = 1, 水晶容量負荷は18pF
  SCFI0 = FLLD_2 + FN_2;                  // D = 2, fDCOCLK =   2.2-17Mhz 

  // DAC 設定
  DAC12_0CTL = DAC12OPS + DAC12SREF_0 + DAC12LSEL_0 + DAC12AMP_7 + DAC12IR; // 
      // DAC出力を外部端子に出力, 基準電圧はAVcc, DAC12_0DATを書込むと即DAC出力
      // DAC出力の最大値は基準電圧, アンプは高速(高電流)モード

  init_seq();
  get_mel();

  // Timer A 16KHz
  CCR0 = 512; // 256 で 32KHz
  CCTL0 = CCIE;
  TACTL = TASSEL_2 + MC_1; // SMCLK, up-mode
  
  _BIS_SR(LPM0_bits + GIE);               // LPM0に入る, 周辺モジュール割込み許可
  return 0;
}

void init_seq(void)
{
  // init note table
  for (nidx = 0; nidx < DUPLEX; nidx++) {
    Note *n = ¬es[nidx];
    n->frq = 0; // none
    n->bh = 0;
    n->wi = 0;
    n->env = 0;
  }
  nidx = 0;

  melp = (unsigned char*)&mel_tbl[0];
  tick = 0;
  tick_next = *melp++;
  tick_next += (*melp++ << 8);
  tickn = 0;
}

int get_mel(void)
{
  unsigned char eon;
  unsigned char note;
  Note *np;

  if (tick != tick_next) 
    return 0;

  nidx = 0;
  while (1) {
    if (*melp == EOS)
      init_seq();

    eon = *melp & EON;
    note = *melp & ~EON;
    melp++;
    
    np = &(notes[nidx++]);
    nidx &= DUPMASK;
    np->frq = frq_tbl[note];
    np->bh = 0;
    np->wi = 0;
    np->env = 0;

    if (eon)
      break;
  }

  tick_next = *melp++;
  tick_next += (*melp++ << 8);
  return 0;
}

// Basic Timer  割込みサービスルーチン
#pragma vector=TIMERA0_VECTOR
__interrupt void BasicTimer (void)
{
  signed int n4;
  signed int n;
  int i;

  n4 = 0;
  for (i = 0; i < DUPLEX; i++) {
    if (notes[i].frq == 0)
      continue;
    if (notes[i].env == 255) {
      notes[i].frq = 0; // 消音
      continue;
    }

    n = wav_tbl[notes[i].wi];
    // envelope を適用すると処理が間に合わない。
    // n = n * env_tbl[notes[i].env] >> 8; // 255.0;
    n4 += n;
    
    notes[i].bh += notes[i].frq;
    while (notes[i].bh >= BASEFREQ) {
      notes[i].bh -= BASEFREQ;
      (notes[i].wi)++;
      notes[i].wi &= WAVTBLMASK; // 64 point
    }
  }

  n4 += 2048;
  if (n4 > 4095) n4 = 4095;
  else if (n4 < 0) n4 = 0;
  
  DAC12_0DAT = n4;

  tickn++;
  // tickn &= 0x7f;
  if (!(tickn & 0x3f)) {
    for (int i = 0; i < DUPLEX; i++) {
      if (notes[i].frq)
        notes[i].env++;
    }
    tickn &= 0x7f;
    if (!tickn) {
      tick++;
      get_mel();
    }
  }
}
こちらはシーケンスデータ(menuet.h)。
// Bach Menuet
const unsigned char mel_tbl[] = {
	0, 0, B5, G2|EON, 
	60, 0, A5|EON, 
	120, 0, G4|EON, 
	180, 0, A5, F2|EON, 
	240, 0, D4|EON, 
	44, 1, D4|EON, 
	104, 1, G4, Dis2|EON, 
	164, 1, G3|EON, 
	194, 1, A4|EON, 
	224, 1, B4|EON, 
	254, 1, C4|EON, 
	28, 2, D2, D4|EON, 
	//
        // 途中省略
        //
	128, 22, EOS, 
};
上記ふたつのソースコードをまとめたものは sample.zip として置きました。 ついでに Intel-Standard 形式の実行ファイル sound.hex も。

[2007.02.07] セルフ・ツッコミ。

  • 楽曲シーケンスの処理も波形生成割込みルーチン内でおこなうのは効率が悪いかな。
  • エンベロープの適用が間に合わないなら、あらかじめ音量を変えた波形データを用意すればいいんじゃないの。64サンプルを128レベル分で4KB。MSP430F4270は32KBのフラッシュメモリがあるから十分に収まる。← 力技。
  • char(8bit)よりもint(16bit)データを使ったほうが処理サイクル数を減らせるのかな。

そろそろこのネタは切り上げて、計測アプリケーションのほうを試そうか知らん。

Posted by masato at 11:18 PM
このエントリーのトラックバックURL: http://bird.dip.jp/cgi-bin/mt/mt-tb.cgi/1300
コメントする

おそらく携帯電話等からは投稿できません。日本語文字列を含まないコメントやトラックバック、および当サイトへの言及を含まないトラックバックは御遠慮いただいております。また、90日以上経過した記事へのコメントはできません。










名前、アドレスを登録しますか?