PSoC5LP Prototyping Kit (30) --- USB MIDI (2)

標準ライブラリとして用意されている「USBMIDI」コンポーネントでは、下の図のように、USBFS コンポーネントと、UART、入出力ピン、インバータの回路がセットになったものがデフォルトとして定義されています。
「USB_MIDI」サンプル・プロジェクトは、これに UART をもうひとつ加え、2 系統の (レガシー) MIDI 入出力を持つ USB-MIDI コンバータとして機能するプログラムになっています。

USB を機能させるには、クロック関係を

  • USB クロックとして 48 MHz を供給
  • ILO (Internal Low-speed Oscillator) は 100 kHz に設定

とする必要があります。
外部水晶振動子 / クリスタル・オシレータおよび PLL を利用して、高精度の 48 MHz を供給することもできますが、

  • IMO (Internal Main Oscillator) を 24 MHz に設定
  • IMOx2 で 48 MHz を発生

させることにより、外付けの素子なしに USB を機能させることができます。
IMO の周波数は、裸では ±3 % 程度の精度しかありませんが、USB ホスト側の信号を基準にして IMO の周波数の調整を行い、周波数精度を ± 0.25 % 程度にまで向上させて USB 規格内におさめる機能があります。
USB の SOF (Start Of Frame) 信号と、IMO (を分周したもの) をオシロスコープで観測すると、両者の位相関係はぴったりと「ロック」しているわけではなく、「流れて」いるのが分かります。
デザイン・ワイド・リソース・マネジャーのクロック・エディタを開くと、必要な設定と異なっている設定の部分に赤色でエラーがあることが表示されるので、それらを解消するように設定していくと正しい設定が得られるようになっています。
上の図の USBMIDI コンポーネントを 1 系統の (レガシー) MIDI 入出力を持つ USB-MIDI コンバータとして機能させるための「main.c」は、最も簡単には下のようになります。

#include <project.h>

void USB_callbackLocalMidiEvent(uint8_t cable, 
                                uint8_t *midiMsg)
{
} // void USB_callbackLocalMidiEvent();

int main()
{
  CyGlobalIntEnable;
  . . . . . < 他の初期化 > . . . . 
  USB_Start(0, USB_DWR_VDDD_OPERATION);
  do {} while (0 == USB_GetConfiguration()); 
  USB_MIDI_EP_Init();
  for(;;) {
    USB_MIDI_IN_Service();
    USB_MIDI_OUT_EP_Service();

    . . . . . < 他の処理 > . . . . 

  } // for (;;) { ...
} // int main()

USBMIDI コンポーネントインスタンス名を「USB」としたので、USBMIDI コンポーネント関係の関数名には「USB_」というプリフィクスが付きます。
USB_callbackLocalMidiEvent() 関数は、ホストから OUT エンドポイントに到着した MIDI パケットを読み出す際に呼び出されるコールバック関数で、上のように中身が空っぽでも、関数自体は定義しておかなければなりません。
MIDI_Start() 関数で USBMIDI コンポーネントの初期化を行います。
ホストからのエニュメレーションが進んで、SET_CONFIGURATION リクエストを受けると USB_GetConfiguration() 関数の戻り値がゼロ以外になって、コンフィギュレーションされたことを示します。
コンフィギュレーション完了まで do {} while (); ループで待機してから、USB MIDI 関係のエンドポイントのイネーブルおよび UART の初期化のための USB_MIDI_EP_Init() 関数を呼び出します。
SET_CONFIGURATION で全エンドポイントが初期化されるので、コンフィギュレーション完了を待たずに先に  USB_MIDI_EP_Init() を実行してしまうと、設定した効果がなくなってしまいます。
メインの無限ループ中では、繰り返し USB_MIDI_IN_Service() 関数と USB_MIDI_OUT_EP_Service() 関数を呼び出し、エンドポイントおよび UART に関する処理を行います。
上のプログラムは USB-MIDI コンバータとして機能する「最も簡単」なもので、サスペンド等のサポートもなく、ターゲット・プログラムを実行開始後エニュメレーションされて接続が完了し、その後ずっとそのままという場合にだけ機能します。
フラッシュ書き込みのために KitProg 側の USB 接続をずっとキープしていて、ターゲットには常時通電され、ターゲット・プログラムが動作したままデバイスからホストへの USB ケーブルを接続したり、切り離したりという状況では、2 回目以降の接続では機能しなくなります。
それを解消するために、メインの無限ループの中で (再) コンフィギュレーションされたかどうかを判別し、必要に応じて USB_MIDI_EP_Init() 関数を呼び出すようにしたのが下のリストです。

#include <project.h>

void USB_callbackLocalMidiEvent(uint8_t cable, 
                                uint8_t *midiMsg)
{
} // void USB_callbackLocalMidiEvent();

int main()
{
  CyGlobalIntEnable;
  . . . . . < 他の初期化 > . . . . 
  USB_Start(0, USB_DWR_VDDD_OPERATION);
  for(;;) {
    if (0 != USB_IsConfigurationChanged()) {
      if (0 != USB_GetConfiguration()) {
        USB_MIDI_EP_Init();
      } // if
    } // if
    USB_MIDI_IN_Service();
    USB_MIDI_OUT_EP_Service();

    . . . . . < 他の処理 > . . . . 

  } // for (;;) { ...
} // int main()

「USB_MIDI」サンプル・プログラムは上のような構造になっていて、さらに、サスペンドへの対応、ボタン・スイッチを押すと MIDI ノート・オン / オフの発生、ユニバーサル・システム・エクスクルーシブの「Identify Request」への応答などの機能を持っています。
次回は、UART でレガシー MIDI ハードウェア接続する必要がなく、ソフト的なデータの受け渡しだけで済む場合の設定や、ソフトウェア API について述べます。