編集履歴一覧に戻る
akira.keiのアイコン画像

akira.kei が 2026年02月10日22時53分02秒 に編集

初版

タイトルの変更

+

忘れられたUSB内蔵PIC16F1454/5をイマドキ使う(その5)

タグの変更

+

PIC

+

PIC16F1454

+

PIC16F1455

記事種類の変更

+

セットアップや使用方法

ライセンスの変更

+

(MIT) The MIT License

本文の変更

+

[<前の記事](https://elchika.com/article/a9cfcda4-12f3-4bca-9763-955f0f104858/) : 次の記事> ## 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関連とインタフェースする。 MIDIの処理については次の記事に書こう