SNSへはこちら

STM32でUSBをベアメタル(3) - 取り敢えずDESCRIPTORまで来たぞ

前回から進みました。まず、SET_ADDRESS がうまく通信終了しないことについてですが、あっさりと解決。でもやっぱりハマりましたな。

SET_ADDRESS の解決

勘の良い方、その手のプロには分かったでしょうが、結論を言うと ステータスステージが完了する前にアドレスをセットしてはならないというのが原因でした。

説明します。前回のコードでは usb_ep0_handle_setup 内にて以下のような記述をしていました。

static void usb_ep0_handle_setup(void) {
    if( current_setup_packet.bRequest == SET_ADDRESS ) {
        USB->DADDR |= current_setup_packet.wValue & 0x7F;
        usb_ep0_send(0, 0);
    }
}

おわかりでしょうか。usb_ep0_send の前に、SETUP パケットとして受信したアドレス値をセットしています。これがいけなかった
宛もなく、途方に暮れて 某マイクロソフトの USB 解説記事を読んでいたところ、気づきました。

「あぁ、そりゃステータスステージにもアドレスって付くよなあ...」
ん??このアドレスってフレーム終了まで変更しちゃダメなのでは??

ということでドンピシャ。STATUS_IN が終わった後に与えられたデバイスアドレスをセットしたところ、次のステップに進めました。やったあああああ

RESET
** SETUP
bmResuestType: 0
bRequest: 5
wLength: 0x0
wValue: 0x3b
** SETUP
bmResuestType: 128
bRequest: 6
wLength: 0x8
wValue: 0x100

今度は bRequest == 6 です!! GET_DESCRIPTOR を受信できていますね!やりました。大進捗。

実はこれまでは SET_ADDRESS を受け取ることすら出来ずに挫折していたんです。一度通信が出来てしまうと(比較的)トントン拍子ですね(多分)。今回こそは実装が成功しそうだ。

GET_DESCRIPTORのための実装(途中)

ということですので、早速それ用の実装をしましょう。

デバイスディスクリプタの用意

まず送るべきディスクリプタである、デバイスディスクリプタを作成しておきます。ちなみに値は適当です。USB CDC 向けに値を打ち込みましたが、多分コンフィグ数やら何やらが正しくないのでは、と思います。ともかく転送さえできればいいので、今回は雑で許して。

最初に構造体を定義します。単なる配列でもいいかなと思ったんですが、意味がわかりにくかったり、バイトスワップが面倒くさかったりとあるので、取り敢えず構造体です。ちなみに USB 通信ではリトルエンディアン、そして STM32(というか ARM マイコン)もリトルエンディアンなので、都合がいいということです。

typedef struct __attribute__((packed)) {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint16_t bcdUSB;
    uint8_t bDeviceClass;
    uint8_t bDeviceSubClass;
    uint8_t bDeviceProtocol;
    uint8_t bMaxPacketSize0;
    uint16_t idVendor;
    uint16_t idProduct;
    uint16_t bcdDevice;
    uint8_t iManufacturer;
    uint8_t iProduct;
    uint8_t iSerialNumber;
    uint8_t bNumConfigurations;
} USBDeviceDescriptor;
enum {
    DEVICE_DESCRIPTOR_LENGTH = 18,
    DEVICE_DESCRIPTOR_TYPE = 1
};

この __attribute__((packed)) は各メンバの隙間を埋めろという指示です。これによって、このディスクリプタ構造体を送る時に (void *) でキャストしてしまえばちゃんと先頭から値を送信できるというわけです。
そして、値を定義します。ひとまずメインメモリ上に確保。

// For USB CDC
USBDeviceDescriptor dev_desc = {
        .bLength = DEVICE_DESCRIPTOR_LENGTH,
        .bDescriptorType = DEVICE_DESCRIPTOR_TYPE,
        .bcdUSB = 0x200, // USB2.0
        .bDeviceClass = 2, // Communication
        .bDeviceSubClass = 0, // tekitou
        .bDeviceProtocol = 0,
        .bMaxPacketSize0 = 64,
        .idVendor = 0xdead,
        .idProduct = 0xbeef,
        .bcdDevice = 0x01,
        .iManufacturer = 1,
        .iProduct = 2,
        .iSerialNumber = 3,
        .bNumConfigurations = 1
};

tekitou とかあからさまに書いてありますねwすみません。送信時にはこれを PMA にコピーしてやればいいということになりますね。

ディスクリプタ送出のための記述

前回確か SET_ADDRESS の記述をした usb_ep0_handle_setup 関数ですが、記事冒頭で記述位置を変えたため、もぬけの殻となっています。ここにディスクリプタ送出のための記述をします。

static void usb_ep0_handle_setup(void) {
    if( g_current_setup_packet.bRequest == GET_DESCRIPTOR ) {
        set_descriptor(g_current_setup_packet.wValue);
        usb_ep0_send(next_xfer.xfer_ptr, next_xfer.xfer_len);
        iprintf("DESC!(len = %d)\n", g_packet_buffer[0].count_tx);
    }
}

set_descriptor の中身はこちら(関数名が紛らわしいですね...)。

static void set_descriptor(uint16_t wValue) {
    const uint8_t desc_type = wValue >> 8;
    switch( desc_type ) {
    case DEVICE: // Device Descriptor
        if( pma_offsets.device_desc == 0 ) {
            pma_offsets.device_desc = alloc(sizeof(USBDeviceDescriptor));
            pma_in(pma_offsets.device_desc,
                    &dev_desc,
                    sizeof(USBDeviceDescriptor)
            );
        }
        next_xfer.xfer_ptr = pma_offsets.device_desc;
        next_xfer.xfer_len = g_current_setup_packet.wLength;
        break;
    case CONFIGURATION:
        break;
    case STRING:
        break;
    case INTERFACE:
        break;
    case ENDPOINT:
        break;
    default:
        break;
    }
}

現状 DEVICE のみの実装です。もちろん追記はしていきますよ。大文字の DEVICE 等は enum でマクロ的に定義した定数です。

有限状態機械(FSM)の導入

SETUP, IN, OUT... と状態が目まぐるしく変わるので、導入します。
そのために enum を定義。

typedef enum  {
    ST_RESET, SETUP, IN, OUT, STATUS_IN, STATUS_OUT, ST_ERROR
} EP0_fsm;
static volatile EP0_fsm ep0_fsm;

そして、次のステートへ遷移する関数を定義します。

EP0_fsm next_state_ep0(void) {
    volatile USBSetupPacket *p = &g_current_setup_packet;
    switch(ep0_fsm) {
    case SETUP:
        if( p->bmRequestType & 0x80 ) {
            return (p->wLength != 0) ? IN : STATUS_IN;
        }else{
            return (p->wLength != 0) ? OUT : STATUS_IN;
        }
        break;
    case IN:
        return STATUS_OUT;
        break;
    case OUT:
        return STATUS_IN;
        break;
    case STATUS_IN:
        break;
    case STATUS_OUT:
        break;
    default:
        break;
    }
    return ST_ERROR;
}

ひとまず bmRequestTypewLength のみに依存する感じです。多分これ以外に影響を受ける要素はあると思うので、実装を進めていくうちに改変していくかも知れません。

実行!

今回の変更を取り入れた stm32f042_usb_no3.c はこちらです。書き込んで...ポチッとな。

RESET

RESET
** SETUP
bmResuestType: 0
bRequest: 5
wLength: 0x0
wValue: 0x12
Ready for STATUS_IN...
** STATUS IN
Ready for SETUP...
** SETUP
bmResuestType: 128
bRequest: 6
wLength: 0x8
wValue: 0x100
DESC!(len = 8)
** IN
Ready for STATUS_OUT...
** STATUS OUT
Ready for SETUP...
** SETUP
bmResuestType: 128
bRequest: 6
wLength: 0x12
wValue: 0x100
DESC!(len = 18)
** IN
Ready for STATUS_OUT...
** STATUS OUT
Ready for SETUP...
** SETUP
bmResuestType: 128
bRequest: 6
wLength: 0x12
wValue: 0x100
DESC!(len = 18)
** IN
Ready for STATUS_OUT...


RESET
** SETUP
bmResuestType: 0
bRequest: 5
wLength: 0x0
wValue: 0x14
Ready for STATUS_IN...
** STATUS IN
Ready for SETUP...
** SETUP
bmResuestType: 128
bRequest: 6
wLength: 0x8
wValue: 0x100
DESC!(len = 8)
** IN
Ready for STATUS_OUT...
** STATUS OUT
Ready for SETUP...
** SETUP
bmResuestType: 128
bRequest: 6
wLength: 0x8
wValue: 0x100
DESC!(len = 8)
** IN
Ready for STATUS_OUT...

若干通信が進んでいますが、むむ、何やら奇妙です。

状況をまとめる

現状は以下の感じになってるっぽいです。

  • 2つめの SETUP パケットが来ていることから、SET_ADDRESS によるアドレス割当は成功している
  • bRequest == 6 ということなので、GET_DESCRIPTOR が来ている
    • 1回目は 8Bytes 分を要求、2回目は 18Bytes 文を要求している
    • どうやら正常な挙動らしい。
  • しかし、どうやら失敗しているトランザクションがある。
    • ログ前半では GET_DESCRPITORwLength == 18 で同じパケットが再送されているから、多分ここが上手く行っていない
    • 一方でログ後半ではリセットがかかる。今度は wLength == 8 で固まっている状態。
    • わけがわからないが、とにかくディスクリプタ送出で上手く行っていないことだけは分かる。

ということで、次回に向けてはこの状況を改善したいと思います。