<前の記事 : 次の記事>
MLAのMIDI Exampleをよく見ると
前の記事でも書いた通り、MLAのMIDI ExampleはUSBコネクタを挿せばMIDIデバイスとして認識され、それだけでは何のデータも送受信しない。メインルーチンは以下のように構成されており、「USB以外を初期化」、「USBを初期化」、「USBの接続処理」した後に無限ループ内で「USBデバイスとしてのタスク」と「アプリケーションとしてのタスク」を順に実行し続けると言う、極めて普通でわかりやすい。
void main(void) {
SYSTEM_Initialize(SYSTEM_STATE_USB_START);
USBDeviceInit();
USBDeviceAttach();
while(1) {
USBDeviceTasks();
APP_DeviceAudioMIDITasks();
}
}
ちなみにUSBDeviceTasks()はポーリングなら無限ループ内で、割り込みなら割り込みルーチン内で呼ばれることになる。将来的に割り込みはシリアル通信で使いたいし、元々のサンプルもポーリングになっているので、USB向けの割り込みは使用しないことにする。
MIDIを取り扱うルーチンは無限ループ内のAPP_DeviceAudioMIDITasks()である。その構成は以下のとおり。
void APP_DeviceAudioMIDITasks() {
if( (USBGetDeviceState() < CONFIGURED_STATE) ||
(USBIsDeviceSuspended() == true)) {
return;
}
if(!USBHandleBusy(USBRxHandle)) {
//INSERT MIDI PROCESSING CODE HERE
//Get ready for next packet (this will overwrite the old data)
USBRxHandle = USBRxOnePacket(USB_DEVICE_AUDIO_MIDI_ENDPOINT,(uint8_t*)&ReceivedDataBuffer,64);
}
if(BUTTON_IsPressed(BUTTON_DEVICE_AUDIO_MIDI) == true) { /* ここでスイッチの処理*/}
}
デカデカと「//INSERT MIDI PROCESSING CODE HERE」とあるように受信ルーチンは自分で書けよwってことで、このサンプルでは受信しても何もしない。受信の実態はUSBRxOnePacket()で、送信はUSBTxOnePacket()である。スイッチはリセットスイッチを流用しており、リセット無効にしてからスイッチ入力端子としているが、押すと適当なノートオンパケットを送信し、離すとノートオフパケットを送信する。受信側は64バイトのバッファにデータが入り、送信時にはバッファは使っていない。
#pragma config
名前が気に入らないがsystem.c内にコンフィギュレーションビットの定義が書いてある。大切なのはsystem.h内で「#define USE_INTERNAL_OSC」を有効にすることだ。Microchipの評価ボードは外部発振らしいので、ここは最初は無効になっている。
#if defined (USE_INTERNAL_OSC)
// CONFIG1
#pragma config FOSC = INTOSC
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config CP = OFF
#pragma config BOREN = ON
#pragma config CLKOUTEN = OFF
#pragma config IESO = OFF
#pragma config FCMEN = OFF
この中で重要なのはFOSC=INTOSC、MCLRE=OFF(リセットボタンを入力スイッチとして使う)くらいか。USBに接続しっぱなしだからクロックは固定なのでIESOとかFCMENはOFFでいいし、電源関係の監視(PWRTE、BOREN)も不要だろう。
USARTを使う
USB-MIDI変換なので、「USBからのMIDIパケットをシリアルポートに順次流していく処理」と、「シリアルから来たバイト列をMIDIパケットに変換してUSB送信する処理」を考える。とにかくシリアルポートを初期化しないといけないが、最初からMIDI速度である31250にするとターミナル接続が面倒な場合があるので、Debug中は普通のボーレート(9600や115200)にできるようにしておいた。
void USART_init(unsigned long fosc, unsigned long baud) {
TRISCbits.TRISC4=0; // TX
TRISCbits.TRISC5=1; // RX
BAUDCONbits.BRG16 = 1;
RCSTAbits.SPEN = 1;
RCSTAbits.CREN = 1;
TXSTAbits.TXEN = 1;
TXSTAbits.BRGH = 1;
SPBRG = (uint16_t)((fosc/4UL/baud) -1UL);
PIE1bits.RCIE=1;
バッファなしでも動く?
USBからのデータは受信バッファに入っているので、有効なパケットを1つずつ読み出し、バイト列にしてシリアルに流す、と言う部分はNoteOn/Off程度なら一見ちゃんと動いた。だがある程度まとまった8バイト程度のMIDIパケットや10バイト程度のシステムエクスクルーシブを送ってみると動作しない。結局、シリアルは送受信バッファ、USBは送信バッファを持つようにした。詰まりやすいシリアル送信バッファには1024しかないRAMの半分を当てた。
#define TXRING_BUFFER_SIZE 512
#define TXRING_BUFFER_MASK (TXRING_BUFFER_SIZE-1)
typedef volatile struct {
uint8_t buf[TXRING_BUFFER_SIZE];
uint8_t rp,wp;
} txRingQue_t, *txRingQue_p;
シリアル送信はputch(c)でバッファに積み、割り込みで実際に送信する構成である。割り込みルーチンはUSB用にsystem.c内に定義があるので、これを削除する必要がある。実際にはuart.hとuart.c内でシリアル関連の処理は完結させ、putchとgetchだけでMIDI関連とインタフェースする。
肝心の本体は
受信データがあればパケットを一つずつ取り出して「usb_midi_to_uart(c)」に送り、受信データがあれば「uart_midi_to_usb(c)」に送る、と言う単純な受け渡ししかやってないが、これらの中身が大変だった。
void APP_DeviceAudioMIDITasks() {
if( (USBGetDeviceState() < CONFIGURED_STATE) || (USBIsDeviceSuspended() == true)) return;
if(!USBHandleBusy(USBRxHandle)) {
uint8_t length = USBHandleGetLength(USBRxHandle);
uint8_t *p = RXdataBuffer;
for(uint8_t i = 0; i < length; i += 4u) {
USB_AUDIO_MIDI_EVENT_PACKET c;
c.v[0] = p[i];
c.v[1] = p[i+1u];
c.v[2] = p[i+2u];
c.v[3] = p[i+3u];
usb_midi_to_uart(c);
}
USBRxHandle = USBRxOnePacket(USB_DEVICE_AUDIO_MIDI_ENDPOINT, (uint8_t *)&RXdataBuffer, 64);
}
uint8_t c;
while(getch(&c)) {
P_USB_AUDIO_MIDI_EVENT_PACKET p=uart_midi_to_usb(c);
if(p!=NULL) set_usbTxQue(&usbtxq,*p);
}
usb_send_from_tx_queue();
}
残りは次の記事で。
投稿者の人気記事

-
akira.kei
さんが
前の火曜日の22:53
に
編集
をしました。
(メッセージ: 初版)
-
akira.kei
さんが
前の火曜日の23:00
に
編集
をしました。
-
akira.kei
さんが
前の水曜日の17:49
に
編集
をしました。
ログインしてコメントを投稿する