mbed USB クラスを使用してコントロールリクエスト経由でデータを送信
USB のコントロールリクエストはベンダー独自のコマンドを送信できます。
さらに、セットアップステージに続いてデータを送信または受信できます。データのパケットサイズは通信速度ごとに規定されており、low-speed 8 bytes、full-speed 64 bytes です。セットアップステージは次のような固定データフィールドで構成されています。
Field | Size | 説明 |
---|---|---|
bmRequestType | 1 | データの方向、種類、受け取り相手などを指定 |
bRequest | 1 | リクエストコマンド |
wValue | 2 | 値 |
wIndex | 2 | インデックス |
wLength | 2 | データバイト長 |
ベンダーリクエストでは wValue と wIndex を自由に利用できます。送信したいデータが 4 バイト以下であれば wValue と wIndex に入れてしまったほうが楽です。それよりも長いデータを受信したいときには、サブコマンドの指定などに利用すると便利かもしれません。
ホスト側クライアント
ホストからベンダーリクエストを送信するプログラムは libusb - Python で作成しました。
4 バイトのキーコードをデータとして送信、または受信します。リクエストコマンドは以下の二つです。
- Get_Keycodes: 0x10、4 バイトデータをホストが受信
- Set_Keycodes: 0x11、4 バイトデータをホストが送信
import usb1 import libusb1 import platform import struct class USBControl: """ USB user driver """ MANUFACTURER_NAME = "" PRODUCT_NAME_PREFIX = "" VENDOR_ID = 0x1235 PRODUCT_ID = 0x0050 INTERFACE = 0 HOST_TO_DEVICE = 0 << 7 DEVICE_TO_HOST = 1 << 7 TYPE_VENDOR = libusb1.LIBUSB_TYPE_VENDOR REC_INTERFACE = libusb1.LIBUSB_RECIPIENT_INTERFACE USB_VENDOR_GET_KEYCODES = 0x10 USB_VENDOR_SET_KEYCODES = 0x11 USB_GET_KEYCODES_REQUEST_TYPE = DEVICE_TO_HOST | TYPE_VENDOR | REC_INTERFACE USB_SET_KEYCODES_REQUEST_TYPE = HOST_TO_DEVICE | TYPE_VENDOR | REC_INTERFACE def __init__(self): self.handle = None self.claimed = False def __enter__(self): self.open() return self def __exit__(self, a, b, c): self.close() def open(self): self.ctx = usb1.USBContext() dev = self.ctx.getByVendorIDAndProductID(self.VENDOR_ID, self.PRODUCT_ID) if dev is None: raise Exception("No device found") self.handle = dev.open() if platform.system() == "Linux" and self.handle.kernelDriverActive(self.INTERFACE): self.handle.detachKernelDriver(self.INTERFACE) # check manufacturer and product name """ if dev.getManufacturer() != self.MANUFACTURER_NAME or \ not dev.getProduct().startswith(self.PRODUCT_NAME_PREFIX): # illegal device, close device self.close() raise Exception("Manufacturer or product not match") """ self.handle.claimInterface(self.INTERFACE) self.claimed = True def close(self): if self.handle: if self.claimed: self.handle.releaseInterface(self.INTERFACE) if platform.system() == "Linux" and not self.handle.kernelDriverActive(self.INTERFACE): self.handle.attachKernelDriver(self.INTERFACE) self.handle.close() self.handle = None self.claimed = False self.ctx.exit() def get_keycodes(self): if self.handle is None: raise Exception("Device not opened") data = self.handle.controlRead( self.USB_GET_KEYCODES_REQUEST_TYPE, self.USB_VENDOR_GET_KEYCODES, 0, 0, self.KEYCODES_LENGTH, timeout=2000) return struct.unpack("<BBBB", data) def set_keycodes(self, keycodes): if self.handle is None: raise Exception("Device not opened") return self.handle.controlWrite( self.USB_SET_KEYCODES_REQUEST_TYPE, # bmRequestType self.USB_VENDOR_SET_KEYCODES, # bRequest 0, # wValue 0, # wIndex struct.pack("<BBBB", keycodes), # data timeout=2000) def main(): keycodes = [1, 2, 3, 4] try: with USBControl() as b: # set keycode passed as command line arguments try: b.set_keycodes(keycodes) except Exception as e: print(e) # read current keycodes data = b.get_keycodes() print(data) except Exception as e: print(e) if __name__ == "__main__": main()
プログラムは (1, 2, 3, 4) の 4 バイトデータとして送信します。受信したデバイスは変数に保存しておき、次の送信リクエストにそのデータを返送します。これで正しく送受信できたかどうか確認できます。
mbed 側
mbed のライブラリにある USBDevice クラスを継承するクラスでは、USBCallback_request メソッドがクラスリクエストとベンダーリクエストの処理を行う起点となります。データを受信したい場合、USBCallback_requestCompleted メソッドも必要です。
#define USB_VENDOR_GET_KEYCODES 0x10 #define USB_VENDOR_SET_KEYCODES 0x11 static uint8_t codes[] = {0x10, 0x20, 0x30, 0x40}; bool USBBootKeyboard::USBCallback_request() { bool success = false; CONTROL_TRANSFER *transfer = getTransferPtr(); switch (transfer->setup.bRequest) { case USB_VENDOR_GET_KEYCODES: transfer->remaining = 4; transfer->ptr = (uint8_t *)&codes; transfer->direction = DEVICE_TO_HOST; success = true; break; case USB_VENDOR_SET_KEYCODES: if (transfer->setup.wLength == 4) { // check length //transfer->ptr = (uint8_t *)&codes; // not usable? transfer->remaining = transfer->setup.wLength; transfer->direction = HOST_TO_DEVICE; transfer->notify = true; success = true; } break; default: break; } return success; }
Get_Keycodes コマンドでは transfer ポインタの ptr にデータを保持する変数を指定、データバイト数を remainig に設定します。direction を DEVICE_TO_HOST にし、メソッドから true を返します。これでデータがホストに送信できます。
Set_Keycodes コマンドでは wLength で送信データバイト数を確認、transfer ポインタの remaining メンバに受信するデータバイト数を指定します。direction を HOST_TO_DEVICE に、notify を true に変更します。そして USBCallback_request メソッドから true を返します。このようにすると USBCallback_requestCompleted メソッドが後で呼び出されます。
void USBBootKeyboard::USBCallback_requestCompleted(uint8_t *buf, uint32_t length) { CONTROL_TRANSFER *transfer = getTransferPtr(); switch (transfer->setup.bmRequestType.Type) { case VENDOR_TYPE: if (transfer->setup.bRequest == USB_VENDOR_SET_KEYCODES) { if (length == 4) { // copy data memcpy(&codes, buf, length); } } break; default: break; } }
USBCallback_requestCompleted メソッドでもまだ transfer ポインタへアクセスできるので、リクエストの種類に応じて分岐できます。
受信データをここで処理します。