前回の続きです。EP2 の挙動はともかく、今回は割り込み関数にて EP0 の挙動を設定します。
データ転送のためにレジスタをいじる必要がありますが、他のマイコンに比べてかなり簡単です。前回と同様のソースコードはこちら。↓
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] == 34
の if
文では、HID ディスクリプタの送信をしています。
HID ディスクリプタには他と違って bLength
フィールドがありませんから、そういう場合は EP0BUF
レジスタ(正確には FIFO)にデータを書き込み、データ長を指定する必要があります。今回は 50bytes long ですので、EP0BCH:EP0BCL == 50
としています。
データのアラインメント
SUDPTRx
は 1bit 目が無効です。つまるところディスクリプタのアドレスは2byteのアラインメントに揃わなければならないということを意味します。例えば 0x11
を書き込んだとしたら、SUDPTRH:SUDPTRL
は 0x10
を参照してしまいますからね。これは駄目ということです。
使っている 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 レポートを送る文を書けばいいだけですからね。