ムライボックス (2) --- PSoC5LP 富豪版
1 本の MIDI ケーブル上を流れる MIDI 16 チャネルを 1 チャネルずつそれぞれ別のポートに分配する場合を考えると、出力ポートは 16 チャネル分が必要となり、これが実用上の必要十分なポート数の上限となります。
ムライボックスの実現方法には大きく分けて次のふたつが考えられます。
- 富豪版
- エコノミー版
「エコノミー版」については、次回以降の記事で触れます。 今回は「富豪版」について説明します。
「富豪版」というのは、出力ポートが 16 個必要なら、1 ポート出力のムライボックスを 16 個パラに接続して動かすか、あるいは 1 個のシステムの中に UART を 16 個使用して 16 ポート出力を実現することを指します。
「富豪」というよりも、出力ポートひとつ当たり UART 出力をひとつ用意する、単純明快、直接的な「普通版」とも言えますが、リソースを大量に使うので「富豪版」と呼ぶことにします。
これまでに使ったことのあるマイコンで内蔵 UART 数が多かったものをあげると、ルネサス SH2A では 8 チャネル、ルネサス RX62N では 6 チャネルが内蔵されていました。
それ以外の一般的なマイコンでは、内蔵 UART の数は多くても 2 〜 3 個であり、1 チップで多数の出力を得るのは難しいです。
Cypress PSoC5LP ではコンフィギュアラブルな UDB (Universal Digital Block) コンポーネントを 24 個内蔵しており、多数の UART を実現できます。
実際に構成してみると、下の回路図のように 11 ポート出力 (1 送受両用 UART + 10 送信専用 UART) が得られました。
送受両用 UART はデータパス・セル 3 個、送信専用 UART はデータパス・セル 2 個を消費するようで、24 UDB に対して UART 出力 11 本 (データパス・セル全 24 個中 23 個使用) となり、16 本には届きませんでした。
内蔵固定ファンクションによるコンポーネントと違って、UDB 上にインプリメントされたコンポーネントは、データパス・セル/ステータス・レジスタ/コントロール・レジスタ等は配置配線アルゴリズムに基づいて割り付けられるため、アドレス空間上に規則的に配置される保証はありません。
そのため、各インスタンスそれぞれに専用の API (Application Program Interface) 関数が生成されます。
たとえば、UART 出力の API 関数は、インスタンス UART_1, UART_2, ... , UART_11 に対して、
- UART_1_WriteTxData()
- UART_2_WriteTxData()
... - UART_11_WriteTxData()
という、11 個の独立な API 関数が生成されます。
ムライボックス・アプリケーションとしては、出力ポート番号をインデックスとして、規則的に扱いたいので、次のように「関数ポインタの配列」を定義して利用しています。
// // Number of MIDI-OUT ports // #define N_PORTS (11) // // table of UART_x_WriteTxData() function pointer in ROM // void (* const write_func_p[N_PORTS])(uint8_t) = { UART_1_WriteTxData, UART_2_WriteTxData, UART_3_WriteTxData, UART_4_WriteTxData, UART_5_WriteTxData, UART_6_WriteTxData, UART_7_WriteTxData, UART_8_WriteTxData, UART_9_WriteTxData, UART_10_WriteTxData, UART_11_WriteTxData }; // void (*write_func_p[])
必要な出力ポートの数だけ UART を用意する「富豪版」のハード上では、各出力ポートごとに自由な処理が行なえる「富豪版」のソフトウェアを動作させることができますが、ここでは、「エコノミー版」のソフトと同じ制約のあるソフトウェアを作成しています。
メイン関数の部分を下に示します。
// "running" mask pattern volatile uint16_t r_mask = 0x0000; int main(void) { uint8_t c; uint8_t midi_ch = 0; uint8_t i; uint16_t bm = 0; start_all_UART(); CyGlobalIntEnable; /* Enable global interrupts. */ for (;;) { // infinite loop // is there any received byte(s) in MIDI-IN UART Rx FIFO ? if (UART_1_RX_STS_FIFO_NOTEMPTY & UART_1_ReadRxStatus()) { c = UART_1_ReadRxData(); // get a byte from MIDI-IN midi_ch = (0x0f & c); // extract MIDI ch / system msg type bm = r_mask; // use "running" port mask if (0x80 & c) { // status byte if (0xf0 > c) { // ch mode message [0x80..0xef] midi_ch = (0x0f & c); // extract MIDI ch bm = mask_tab[midi_ch]; // get port mask pattern r_mask = bm; // set "running" port mask pattern c = ((c & 0xf0) | remap_tab[midi_ch]); // remap MIDI channel } else { // system message [0xf0..0xff] bm = sysmsg_tab[midi_ch]; // escaping for system realtime if (0xf8 > c) { // system common msg [0xf0..0xf7] r_mask = bm; // set "running" port mask pattern } // if (0xf8 > c) { ... } // if (0xf0 > c) {} else { ... } else { // data byte // no processing required for data byte } // if (0x80 & c) { ... for (i = 0; i < N_PORTS; i++) { if (0x01 & bm) { // port active (*write_func_p[i])(c); // port output } // if bm >>= 1; } // for (i = 0; ... } // if } // while } // int main()
「富豪版」のハードに対応しているのは、MIDI バイトを各ポートに分配する
for (i = 0; i < N_PORTS; i++) { if (0x01 & bm) { // port active (*write_func_p[i])(c); // port output } // if bm >>= 1; } // for (i = 0; ...
の部分だけで、他のロジックは「エコノミー版」と同様なので、ここでの説明は省略します。