何か問題や疑問点が積んできたので、今回は色々見直してみることにします。
前回の状況だと、GET_DESCRIPTOR を受けた送信で上手く行かないという問題がありましたが、「とりあえず動けばいいじゃない」というスタンスを一度捨てて、現状の把握に全力を注ごうと思います。もはやスルーできるような状況じゃなくなってきた。
どこが分からんのかまとめると、以下の2つに大別できます。
- パケットバッファ書き込み時に奇妙なキャストが必要
- 元々
g_packet_buffer[0].addr_tx
はuint16_t
で宣言していますが、直接書き込むことは出来ずに*(uint16_t *)&g_packet_buffer[0].addr_tx
としないと値が変になる - というか値が反映されない
- 元々
- そもそも各トランザクションが上手く行っているのか
- エンドポイントレジスタの値どうなってる?
モヤモヤしっぱなしではできることも出来ないので、状況を可視化します。
パケットバッファ書き込み時に奇妙なキャストが必要問題
これに関しては C 言語のソースを見てもよくわかりません。関数内に雑多な記述があるし。ということで、別途関数を用意して、その挙動を逆アセンブルすることで解析することにしました。
使用ソースとコマンド
構造体 PacketBuffer
を真似て、以下の構造体で検証です。
#include <stdint.h>
typedef struct __attribute__((packed)){
uint16_t a;
uint16_t b;
uint16_t c;
uint16_t d;
} St;
volatile uint32_t addr = 0x00114514;
volatile static St *st;
void f(void) {
st = addr;
st[0].a = 10;
st[0].b = 20;
}
以下のように状況を揃えました。
- とあるアドレスに構造体
St
は配置されると考える volatile
も揃えたpacked
属性もお忘れなく
さて、コンパイル/逆アセンブルしてみます。コマンドは以下。
$ arm-none-eabi-gcc -mthumb -mcpu=cortex-m0 -c -Os a.c
$ arm-none-eabi-objdump -d a.o
結果
逆アセンブル結果です。
08000220 <f>:
8000220: 4b06 ldr r3, [pc, #24] ; (800023c <f+0x1c>)
8000222: 210a movs r1, #10
8000224: 681b ldr r3, [r3, #0]
8000226: 781a ldrb r2, [r3, #0]
8000228: 2200 movs r2, #0
800022a: 7019 strb r1, [r3, #0]
800022c: 7859 ldrb r1, [r3, #1]
800022e: 705a strb r2, [r3, #1]
8000230: 7899 ldrb r1, [r3, #2]
8000232: 2114 movs r1, #20
8000234: 7099 strb r1, [r3, #2]
8000236: 78d9 ldrb r1, [r3, #3]
8000238: 70da strb r2, [r3, #3]
800023a: 4770 bx lr
800023c: 20000000 .word 0x20000000
何がなんやら、という方も多いと思いますが、よく見てください。命令の末尾に b
と付いているものが多いですよね??つまり、このソースでは 8bit アクセスをしているアセンブリコードが生成されます。
ところで、ユーザーマニュアルによると、どうやら PMA はバイトアクセスとハーフワードアクセスができるらしい。
上のコードはバイトアクセスですね。コードを見る限り、ちゃんと命令は構成されています。一応以下で挙動を説明します。
0: r3 <= 0x2000 0000 # addrのアドレス (&addr)
2: r1 <= 10
4: r3 <= *(uint32_t *)r3 # r3は*(uint32_t *)&addr, つまり変数addrの値 == 構造体のベースアドレス
6: r2 <= *(uint8_t *)r3 # 構造体の最初の要素にバイトアクセスし、値を退避
8: r2 <= 0 # このプログラムでは r2 == 0 で固定
a: *(uint8_t *)r3 <= r1 # 構造体の最初の要素low byteにバイトアクセスし、10を書き込む
c: r1 <= *((uint8_t *)r3 + 1) # 構造体の最初の要素high byteにバイトアクセスし、値を退避
e: *((uint8_t *)r3 + 1) <= r2 # 構造体の最初の要素high byteにバイトアクセスし、0を書き込む
~~~ この時点で構造体内のデータは {10, 00, xx, xx} (xxは不定)
0: r1 <= *((uint8_t *)r3 + 2) # 構造体の2番目の要素high byteにバイトアクセスし、値を退避
2: r1 <= 20
4: *((uint8_t *)r3 + 2) <= r1 # 構造体の2番目の要素high byteにバイトアクセスし、20を書き込む
6: r1 <= *((uint8_t *)r3 + 3) # 構造体の2番目の要素low byteにバイトアクセスし、値を退避
8: *((uint8_t *)r3 + 3) <= r2 # 構造体の最初の要素high byteにバイトアクセスし、0を書き込む
a: return;
~~~ ここまでで構造体内のデータは {10, 00, 20, 00}
この冗長な挙動は、packed
属性に依るものです。この属性は構造体内の各メンバを詰めて配置するというものであり、つまりある意味で変数のアラインメントを無視するように指示するものになります。よって無理やり 16bit 変数を構成するために、アラインメントの不整合が起きぬよう8bitアクセスのみで命令が構成されるというわけです。ちなみにですが、この挙動は addr
を const
とすればなくなってハーフワードアクセスで動作するようになりました。アドレスが定数になり、またそのアドレス値が2の倍数になったので、16bit 変数のアラインメントに整合します。なのでこのような考慮は不要になったのでしょうね。
ではここで、__attribute__((packed))
を外してみます。すると、以下のようにコードが短くなりました。命令の末尾に h
がついていますね。コレがハーフワードアクセス(16bit)です。
08000220 <f>:
8000220: 220a movs r2, #10
8000222: 4b03 ldr r3, [pc, #12] ; (8000230 <f+0x10>)
8000224: 681b ldr r3, [r3, #0]
8000226: 801a strh r2, [r3, #0]
8000228: 1892 adds r2, r2, r2
800022a: 805a strh r2, [r3, #2]
800022c: 4770 bx lr
800022e: 46c0 nop ; (mov r8, r8)
8000230: 20000000 .word 0x20000000
それでは USB のコードの構造体、PacketBuffer
の packed
も同様に外してみます。すると上と同じように、ハーフワードアクセスになりました。
すると、、、なんと、変な記述をしなくても送受信に成功しました。
そもそも各トランザクションが上手く行っているのかの確認
このペリフェラルは PMA オーバーフロー等のメモリ関連や通信フレーム関連のエラーは捕捉してくれますが、STALL 等のトランザクション単位でのエラー発生時は状況がよくわかりません。なんだそれ。なので逐一エンドポイントレジスタを監視する感じで状況を把握します。
以下にその結果を示しましょう。
SET_ADDRESS時
タイミング | EP0R (hex) | DTOG_RX | DTOG_TX | STATUS_RX | STATUS_TX |
---|---|---|---|---|---|
SETUP待機 | 3200 | 0 | 0 | VALID | DISABLED |
SETUP受信 | 6260 | 1 | 1 | NAK | NAK |
ステータス送信準備 | 6270 | 1 | 1 | NAK | VALID |
ユーザーマニュアルには SETUP 受信時には {DTOG_RX, DTOG_TX} = {0, 1}
になるよって書かれているのに、異なっている。でもまあ普通に考えて、両方とも 1 のほうが都合いいよね。
なお、STATUS 値はマニュアルどおり、どちらも NAK になっていて良き良き。
GET_DESCRIPTOR (8Bytes)時
タイミング | EP0R (hex) | DTOG_RX | DTOG_TX | STATUS_RX | STATUS_TX |
---|---|---|---|---|---|
SETUP待機 | fa60 | 1 | 1 | VALID | NAK |
SETUP受信 | 7260 | 1 | 1 | VALID | NAK |
送信準備 | 7270 | 1 | 1 | VALID | VALID |
...あれ?STATUS_OUT
の受信どこ行った??
何らかの理由で、ここで止まっているのかも...
GET_DESCRIPTOR (16Bytes)時
タイミング | EP0R (hex) | DTOG_RX | DTOG_TX | STATUS_RX | STATUS_TX |
---|---|---|---|---|---|
SETUP待機 | fa60 | 1 | 1 | VALID | NAK |
SETUP受信 | 7260 | 1 | 1 | VALID | NAK |
SEND受信 | 7270 | 1 | 1 | VALID | VALID |
う〜ん。。。上と同じ状況。。。
つまり...?
以上を鑑みて、何かしらの通信が欠損していることが考えられます。その後も貧相な print デバッグで色々と見てみましたが、どうやら SETUP パケットの受信に失敗するっぽい事が分かりました。もう何もかもわからん。
ここまでのソースコードはこちら。
〆
なんとかします。