SNSへはこちら

簡単USBマイコン EZ-USB FX2LP(6) - USBマウスを作ってみる(その2)

前回の続きです。EP2 の挙動はともかく、今回は割り込み関数にて EP0 の挙動を設定します。
データ転送のためにレジスタをいじる必要がありますが、他のマイコンに比べてかなり簡単です。前回と同様のソースコードはこちら。↓

GitHub Gist: instantly share code, notes, and snippets.

USB RESET割り込み

ホストから EZ-USB に対して「リセットよろ」と言われた時の処理です。実は基本的にやることはありません

USB の規格に則りますと、各種エンドポイントの初期化はこの USB リセットのタイミングで行うべきなのですが、EZ-USB では必要ないようです(事前に設定を行ってしまって OK なようです)。
というのも、EZ-USB がこのリセットに伴って各種レジスタ値のリセットを行ってくれます。

割り込み関数の記述は以下。ぶっちゃけリセット割り込みは不要ですが、一応書いておいたという程度です。

    if( USBIRQ & (1 << 4)  ) { // RESET
        USBIRQ = 1 << 4;
    }

USBIRQ では Read -> OR -> Write をしてはならないとユーザーマニュアルに書いてあります。これは対応するビットに対してソフトウェアが1を書き込んだ場合、そのビットはクリアされる(=クリアされた割り込み要因は、次にリクエストがあるまで割り込みが起こらなくなる)からです。なのでビットクリアは単に対応するビットに1を書き込むだけとしましょう。

SETUP Data Available割り込み

セットアップステージが終了した時の割り込みです。この時点でデータステージ or ステータスステージに入っています。この割り込みが入ったタイミングで色々と書き込み/読み出しの処理をします。

ここで驚いたのが、SET_ADDRESS では割り込みが発生しないことです。これはすなわち、デバイスアドレスの設定は USB 側が何の設定なしにも自動で行ってくれるということを意味します。そして自動でステータスステージに移行し、トランザクションを終了してくれるということです。とっても親切ですね。
では実際にどの処理を記述すればよいかですが、今回の例の場合は以下でした。

  • GET_DESCRIPTOR
  • GET_CONFIGURATION
  • SET_CONFIGURATION

いずれもステータスステージは全く気にせず、送受信するデータを指定するだけです。簡単!!

なお USB CDC を実装する場合は他の処理も記述せねばなりません(SET_LINE_CODING とか)。

この割り込みリクエストに関しては、以下の記述が作法のようです。

    if( USBIRQ & (1 << 0) ) { // SETUP
        ... // 何かしらの処理
        EP0CS |= 1 << 7;
        USBIRQ = 1 << 0;
    }

USBIRQ の対応するビットをクリアすることはもちろん、この割り込みでは EP0 の NAK ハンドシェイクを解除します。
割り込み時点では、デフォルトで NAK を返す仕様になっているらしく、ソフトウェアがデータステージに備えた処理を終了したらこれを解除する必要があるとのことです。このコードのように、解除する場合は EP0CS の 7bit 目をセットします。

それでは、以下で具体的な記述を見ていきましょう。

GET_DESCRIPTOR

多分一番厄介です。設定が面倒くさいと言う訳ではなく、データのアラインメントがあります。
その前に、具体的な転送法を示しておきましょう。以下コード中に現れる大文字のトークンは定数として定義してあります。

        if( SETUPDAT[1] == GET_DESCRIPTOR ) {
            if( SETUPDAT[3] == DEVICE ) {
                SUDPTRH = (unsigned int)device_desc >> 8;
                SUDPTRL = (unsigned int)device_desc & 0xFF;
            }
            else if( SETUPDAT[3] == CONFIGURATION ) {
                SUDPTRH = (unsigned int)config_desc >> 8;
                SUDPTRL = (unsigned int)config_desc & 0xFF;
            }
            else if( SETUPDAT[3] == STRING ) {
                SUDPTRH = (unsigned int)string_desc[SETUPDAT[2]] >> 8;
                SUDPTRL = (unsigned int)string_desc[SETUPDAT[2]] & 0xFF;
            }
            else if( SETUPDAT[3] == DEVICE_QUALIFIER ) {
                SUDPTRH = (unsigned int)qualifier_desc >> 8;
                SUDPTRL = (unsigned int)qualifier_desc & 0xFF;
            }
            else if( SETUPDAT[3] == OTHER_SPEED_CONFIGURATION ) {
                SUDPTRH = (unsigned int)config_desc >> 8;
                SUDPTRL = (unsigned int)config_desc & 0xFF;
            }
            else if( SETUPDAT[3] == 34 ) {
                for(int i = 0; i < 50; i++) {
                    EP0BUF[i] = ReportDescriptorForMouse[i];
                }
                EP0BCH = 0;
                EP0BCL = 50;
            }
        }

基本的にディスクリプタは SUDPTRx でメモリアドレスを指定すれば転送できます。何bytes送れば良いのかは該当ディスクリプタの bLength フィールドを見て USB 側が勝手に決めてくれるので、これまた親切この上ありません。
実装するデバイスは USB HS なので、デバイスクオリファイア ディスクリプタアザースピードコンフィギュレーション ディスクリプタが必要になってきます。また、SETUPDAT[3] == 34if 文では、HID ディスクリプタの送信をしています。
HID ディスクリプタには他と違って bLength フィールドがありませんから、そういう場合は EP0BUF レジスタ(正確には FIFO)にデータを書き込み、データ長を指定する必要があります。今回は 50bytes long ですので、EP0BCH:EP0BCL == 50 としています。

データのアラインメント

SUDPTRx1bit 目が無効です。つまるところディスクリプタのアドレスは2byteのアラインメントに揃わなければならないということを意味します。例えば 0x11 を書き込んだとしたら、SUDPTRH:SUDPTRL0x10 を参照してしまいますからね。これは駄目ということです。

使っている SDCC コンパイラはアラインメント指定のディレクティブが無いようなので、現状の揃え方が分かりません。グローバル領域で const で置いた変数はプログラムの後方に配置されるようなので、ビルド後にもしディスクリプタのアドレスが奇数だったら、プログラムのどこかしらに1箇所 nop(): を記述して無理やりアラインメントを合わせています
地味に辛いので、これは今後工夫したいところです。おそらくアセンブリする必要があるでしょう。

あっ、そうそう。ディスクリプタを公開するの忘れていました。こちらで記載しておきます。

const unsigned char device_desc[] = {
    18, // bLength
    1, // bDescriptorType
    0x00,
    0x02, // bcdUSB
    0x00, // bDeviceClass
    0x00, // bDeviceSubClass
    0x00, // bDeviceProtocol
    64, // bMaxPacketSize0
    0x45,
    0x45, // idVendor
    0x19,
    0x19, // idProduct
    0x00, // bcdDevice
    0x01,
    0, // iManufacturer
    1, // iProduct
    0, // iSerialNumber
    1, // bNumConfigurations
};
const unsigned char config_desc[] = {
    // configuration
    9, // bLength
    2, // bDescriptorType
    9 + 9 + 9 + 7, // wTotalLength
    0,
    1, // bNumInterface
    1, // bConfigurationValue
    0, // iConfiguration
    0x80, // bmAttributes
    0x25, // bMaxPower
    // interface
    9, // bLength
    4, // bDescriptorType
    0, // bInterfaceNumber
    0, // bAlternateSetting
    1, // bNumEndpoints
    0x03, // bInterfaceClass
    0x01, // bInterfaceSubClass
    0x02, // bInterfaceProtocol
    0, // iInterface

    // HID Class Descriptor
    0x09, // bLength
    0x21, // bDescriptorType
    0x10, // bcdHID
    0x01, // L> 1.10
    0, // bCountryCode
    1, // bNumDescriptors
    0x22, // bDescriptorType
    50, // wDescriptorLength(The length of Report Desc)
    0x00,

    // Endpoint Descriptor
    7,
    5,
    0x82, // .bEndpointAddress(IN, EP2)
    0x03, // Interrupt Transfer
    64, // .wMacPacketSize
    0,
    0x1 // bInterval
};
const unsigned char string_desc0[] = {
    4, // bLength
    3, // bDescriptorType
    0x09,
    0x04 // wLangID
};
const unsigned char string_desc1[] = {
    8,
    3,
    'U', 0, 'n', 0, 'k', 0,
};
const unsigned char ReportDescriptorForMouse[50] = {
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,                    // USAGE (Mouse)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x75, 0x05,                    //     REPORT_SIZE (5)
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x06,                    //     INPUT (Data,Var,Rel)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION
};
const unsigned char qualifier_desc[] = {
    10, // bLength
    6, // bDescriptorType
    0x00,
    0x02, // bcdUSB
    0x00, // bDeviceClass
    0x00, // bDeviceSubClass
    0x00, // bDeviceProtocol
    64, // bMaxPacketSize0
    1, // bNumConfigurations
    0x00
};
const unsigned char other_config_desc[] = {
    // configuration
    9, // bLength
    2, // bDescriptorType
    0,
    9 + 9, // wTotalLength
    1, // bNumInterface
    2, // bConfigurationValue
    0, // iConfiguration
    0x80, // bmAttributes
    0x25, // bMaxPower
    // interface
    9, // bLength
    4, // bDescriptorType
    0, // bInterfaceNumber
    0, // bAlternateSetting
    0, // bNumEndpoints
    0xFF, // bInterfaceClass
    0xFF, // bInterfaceSubClass
    0xFF, // bInterfaceProtocol
    0, // iInterface
};

SET/GET CONFIGURATION

こちらはテキトーです。現在のコンフィギュレーションがなにかということを主張できればいいんですよ。というかこの辺は USB 規格でよく理解できていません。ひとまずメインのコンフィギュレーション値 1 に一致させればいいのでは。

まあ確かに USB 機器としてそれはどうなのかという点はあると思いますが、趣味の範囲なので大目に見てください...

以上!

ということで、これだけで EP0 の通信は出来てしまいます。PIC18F であったデータトグルだとか、STM32 であった USB PMA へのコピーだとかは全く不要。
唯一面倒なのはディスクリプタのアラインメントですかね。この Intel 8051 コアはアラインメントの概念が事実上なく、SDCC はこの辺を指定するディレクティブが存在しないため、この特性との相性は悪い気がします。おそらく内部回路の簡略化のためなのでしょう。仕方がないっちゃ仕方がないですね。ここに関してはアセンブリをいじる機会を与えてくれたことに感謝しますか(?)。

次回は main 関数に戻って、EP2 に関するポーリング処理の中身を記述していきます。ここまで来たら楽チンです。普通に HID レポートを送る文を書けばいいだけですからね。