2007年06月08日

Server-Client化と通信プロトコル

いままで作ってきたプログラムは、サウンドジェネレータの動作確認のためのものだった。これをもとに、AVR で I2Cスレーブとして動くプログラムと、それをコントロールするプログラムを作っていくことにする。もちろん、実機だけで動くものではなく、Linux/Windows で評価できるものにしていくのが前提。
AVR側 は、I2CデバイスドライバのAPI でコマンドパケットを受け取り処理する構造にして、コントローラ側は、コマンドパケットを生成して送信する構造にすることになる。そして ... (評価環境では)その間をつなぐものが必要。
このつなぐところを、どうするか...というと TCP/IP による通信にしてしまうのが、実は最も簡単で便利。便利というのは、Socket API は、Linux , Windows で同じように使えるのでコードを1つ作ればよいというのが理由。コントローラ側/デバイス側の構造を変に変えなくて良いという理由もある。

コントローラ側の処理の流れ

メインループは次のように キーが入力されるとそれに対応した処理になるようにしている。もはや AVR-CDC とは関係ないのだが、getchar() に相当する関数が、いままでの経緯で usbcdc_getc() になっている。( ちなみに、プログラムを2つに分けた利点がもう1つあある。前のコードは、MinGW の環境で問題があったが、このコードなら Linux と同じように動く。)

for (;;) {
c = usbcdc_getc();
change_note(c);
}


change_note() はなにするかというと、キーに応じて関数を call するだけのもの。

static void change_note(uint8_t c) {
const uint8_t note_base = 36+24; // C1
static int oct;
switch(c) {
case 'z': oct --; break;
case 'x': oct ++; break;
case 'q': exit(0);
case 'w': set_param(PARAM_VCO_WAVE, WAVE_SIN); break;
case 'e': set_param(PARAM_VCO_WAVE, WAVE_TRI); break;
case 'r': set_param(PARAM_VCO_WAVE, WAVE_SAW); break;
case 'a': note_on(note_base + 12*oct +0, 127); break;
case 's': note_on(note_base + 12*oct +2, 127); break;
case 'd': note_on(note_base + 12*oct +4, 127); break;
case 'f': note_on(note_base + 12*oct +5, 127); break;
case 'g': note_on(note_base + 12*oct +7, 127); break;
case 'h': note_on(note_base + 12*oct +9, 127); break;
case 'j': note_on(note_base + 12*oct +11, 127); break;
case 'k': note_on(note_base + 12*oct +12, 127); break;
case 'l': note_on(note_base + 12*oct +14, 127); break;
}
}


で、たとえば note_on() は何をするかというと、パケットを作って、i2c_send() で 送信する。

static void note_on(uint8_t note, uint8_t verosity) {
ext_buf[0] = CMD_NOTEON;
ext_buf[1] = note;
ext_buf[2] = verosity;
ext_buf[3] = 0;
i2c_send(0x20, 4);
}


i2c_send は、以前作った I2Cホストドライバの API で、これにあわせて、TCP/IP で通信するモジュールを作る。この API にあわせて USB910A の I2C 機能を利用するモジュールを作れば、そのまま実機テストできるし、AVR のコントローラを作った場合でも 利用できるのだ。

ちなみに、コマンドは MIDI もどきにする。CMD_NOTEON は、0x90 で、ローカルな機能には、MIDI に決して MAP されない 0x00 〜 0x7f を使う。

デバイス側(AVR 側)の処理の流れ


for (;;) {
if (can_sndout()) {
generate_snd();
continue;
}
if (i2cdev_can_getc(1)) {
command_proc();
i2cdev_flush();
}
}


メインループはこんなもの。バッファーが FULL でなければサウンドを生成して詰め込み、I2C経由でコマンドがくればその処理をする。

さて、このコードは バッファーが FULL になった状態でコマンドがこなければ無限に回ってしまう。AVR のときは、それでよいのだが、Linux/Windows のときは、それではまずい。CPU を離すしくみを入れる必要があり、それを入れるとすれば、i2cdev_can_getc() 以外にない。
実際どうしているかというと、select() を使って通信がないときも、0.05 秒だけ待ち合わせるようにしている。逆にここで止まるので、音が途切れないだけの量は、バッファーに溜めておかなければならない。


static void command_proc() {
uint8_t cmd,p1,p2,p3;
cmd = i2cdev_getc() & 0xf0;
p1 = i2cdev_getc();
p2 = i2cdev_getc();
p3 = i2cdev_getc();
if (cmd == CMD_NOTEON) {
note_on(p1,p2);
} else if (cmd == CMD_NOTEOFF) {
note_off();
} else if (cmd == CMD_SETPARAM) {
switch(p1) {
case PARAM_LFO_FREQ:
lfo.wave_inc = ((int16_t)p3 << 8)|p2;
break;
case PARAM_LFO_INTENSITY:
lfo.intensity = p2; break;
case PARAM_VCO_WAVE:
vco.wave_type = p2; break;
        :
:
}
}
}


さて、コマンド処理はこんなかんじ。いまのところ 4 バイト固定のパケットが来るとして処理している。あと、内部に持っているパラメータは、CMD_SETPARAMで全部設定可能なようにしている。他のコマンド/機能はまだ実装していないが、簡単に追加していけそうだ。

AVR 側の処理とサイズ

ずいぶん回り道した感があるが、実は AVR 固有の機能で作らないといけないのは、PWM で 出力する部分だけ。サンプリングレートで割り込みが起きるようにして、PWM のレジスタにデータをセットするだけのものを作れば、Linux/Windows 版と同等になる。

いきなり動作するとは思えないが、一応作ってみてサイズを見てみた。

text data bss dec hex filename
2032 0 89 2121 849 i2csnd.elf


なんと..まだ 2KB 。これなら機能を充実させていっても ATtiny45 に入りそうだ。...ちなみに これなら ATtiny2313で何か作れるのでは?と考えては決していけない。最小限度必要なものを埋めていって 2KB ... スタート地点ですでに 2KB なのだから、なにかが作れたとしてもコードを縮小することばかりに追われてしまう。

プログラムについて

ここまでのプログラムを i2csnd-0.1.tar.gzにおいておく。今度は大丈夫だと思うので、どんな音が出るか興味がある人は動かしてみてほしい。

linux でビルドする場合は、cd lin; make。 MinGW+MSYS の場合は、cd lin; make -f win.mk 。
動かし方は、Window を2つ用意して、1つで i2csnd を実行し、もうひとつで、sndtest を実行する。i2csnd は、MSYS コンソールでも動作するが、sndtest は、コマンドプロンプトでしか動作しない。
posted by すz at 18:56| Comment(0) | TrackBack(0) | I2CSND
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/30760396
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック