SSブログ

macOSからPi Picoを使う - その28 [Pi Pico]

ここんとこPi PicoばっかりいじってるせいでCしか書かなくなってる(C++はずっと昔十数年前のことだけど、何度か挑戦したあと挫けた。結局C++は僕には複雑で大きすぎた、ということのようである。Swiftもその気配が忍び寄っている。C++に比べるとObjective-Cは小さくて簡単だった)。継続的に書いてないと他の言語を忘れてしまう。せっかくなんとなくわかりかけてきたSwiftもなんだかもやもやしてきた。つい先日工場に行ったときに昔書いたObjective-Cのコードに手を加えようとしてうろが来た。あれだけ何年も書いてきたObjective-Cでさえ、あれ?なんだっけ?なんてことになってる。ほんの2、3ヶ月のことなのになんということだ。

というわけでlibusbのおさらい。当然これもCで書いてあるんだよなぁ....

話を続ける前に、前回か、あるいは前々回書かないといけないことを忘れていた。そっちを先に片付ける。

デバイスをオープンしただけでは転送ができない。activeなconfigurationの中のどのinterfaceを使うか、を宣言しないといけなかった。
int libusb_claim_interface(libusb_device_handle *dev_handle,
                           int                  interface_number);
この手続きは代替interfaceを持つconfigurationがあるので必要になっているようだけど、interfaceがひとつしかなくてもこれを呼ばないと転送でエラーになる。従って転送モードごとにinterfaceが分かれているようなconfigurationの場合(例えばbulk転送はinterface0で、interrupt転送はinterface1で、など。あまりそんなことをする必然性はないかもしれないけど)、libusbではendpoint番号が独立であるにもかかわらず、この関数を呼んでinterfaceを切り替える必要がある、ということである。

この関数はUSB経由でPi PicoをBOOTSELモードにリセットするところではちゃんと書いていた。ところがうっかりこれを忘れていてデバイスはオープンできたのに転送は何度やっても失敗するので悩んでしまった。これに気がつくまで半日かかった。バカみたい。

ちなみにこの関数呼び出しは信号としての出入りはない、とドキュメントに書いてある。つまり純粋にlibusb内部の問題だということである。

17.4.3  非同期転送

前回の3つの関数は転送が終わるまで返ってこない。転送のタイミングはホスト(正確にはホスト側のUSBコントローラ)が制御するので、デバイス側でそれを知る手段はない。この時間が惜しいアプリのために非同期転送が用意されている。これはUSBの規格とは関係なく、libusbからのアプリケーション側への配慮である。ただしlibusbの思想にまつわる問題、つまりなんというか、ライブラリとしてなるべく軽量であるべし、という要求から非同期転送に特有の難しさが存在する。その話はあとで。

非同期転送は少しだけ面倒でまず転送用の構造体を用意する。
struct libusb_transfer {
    libusb_device_handle        *dev_handle;
    uint8_t                     flags;
    unsigned char               endpoint;
    unsigned char               type;
    unsigned int                timeout;
    enum libusb_transfer_status status;
    int                         length;
    int                         actual_length;
    libusb_transfer_cb_fn       callback;
    void                        *user_data;
    unsigned char               *buffer;
    int                         num_iso_packets;
    struct libusb_iso_packet_descriptor
                                iso_packet_desc[ZERO_SIZED_ARRAY];
};
転送モードに関わらず同じ構造体を使うけど、モードによって無意味なフィールドとかもある。

非同期転送の手順として
  1. Allocation 構造体の領域確保
  2. Filling 中身を埋める
  3. Submission 転送指示
  4. Completion handling 終わったかどうかの確認
  5. Deallocation 領域解放
となっている。この手順はUSBのプロトコルに依存した特殊なものではなくて、非同期通信のまったく一般的な手順である。

転送が終わるとコールバック関数
typedef void(* libusb_transfer_cb_fn)(struct libusb_transfer *transfer);
が呼ばれる。これは構造体のcallbackメンバとして埋め込んでおく。転送が正常に終わったかどうかは構造体のstatusメンバを見る。この中身はenumで


LIBUSB_TRANSFER_COMPLETED 正常終了
LIBUSB_TRANSFER_ERROR なんらかのエラーが起きた
LIBUSB_TRANSFER_TIMED_OUT タイムアウト
LIBUSB_TRANSFER_CANCELLED 転送がキャンセルされた
LIBUSB_TRANSFER_STALL エンドポイントがストールした
LIBUSB_TRANSFER_NO_DEVICE 指定されたデバイスがない、あるいはケーブルが抜けた
LIBUSB_TRANSFER_OVERFLOW デバイスから送られてきたデータのほうが多い


だそうである。

転送用のデータはbufferを使う。lengthはこの確保された長さを指定する。actual_lengthは転送後に実際に転送された長さが入る。user_dataはいわゆるRefCon、つまりこのAPIを使う側が必要なデータを保持させるコンテクストデータである。

bufferは構造体の確保と開放と同時に処理されるけど、user_dataは単にポインタを確保しているだけなので、もしこれがmalloc()なんかで取られた場合は自分でfree()しないといけない。

17.4.4  Allocation

libusb_transfer構造体の確保と解放には
struct libusb_transfer* libusb_alloc_transfer(int iso_packets);
void    libusb_free_transfer(struct libusb_transfer *transfer);
と言う関数が用意されている。cntrol、bulk、interupt transferをするときにはlibusb_alloc_transfer()の引数を0にする。isochronous transferではpacket descriptorの数を渡す。

この構造体は複数の転送に使い回せるらしい。つまり転送が終わるたびに解放する必要はない、となっている。

ちょっと区切りが悪いけど、長くなってしまうので残りの非同期転送の話は次回に。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。