経緯とか
組み込みなんてやったこと無いのに業務で必要になったのでやってみた備忘録。
概ね libusb関係のメモと、バルク転送サンプル の二番煎じです。
具体的なコードも載せますが、どちらかというと作業フロー的な話をメインに。
[環境]
- OS: debian 9
- libusb: libusb-1.0-0
- gcc6(64bit版)
- 言語: C
僕の場合は購入した機器の公式のlibusbサポートがlibusb0.1(Windows用)で、Linux環境でlinusb1.0-0に合わせるのに難儀しました。Raspbianではなくdebianを使ったのはRaspberryPi以外の選択肢もあり得たからです。Raspbianでも適切に設定すると動作すると思います。
USBIOを初めて触るような人を想定して書いています。
用語
- HID: Human Interface Device
- 一般的にはマウスとかキーボードとか
- 今回使用したKm2NetさんのUSB-IO2.0はHIDデバイスとして認識されました。
- EP: EndPoint
- エンドポイント、要はアクセス先です。
- EP IN がUSB→マシン方向の信号つまり読み込み・受信側
- EP OUT がマシン→USB方向の信号つまり書き込み・送信側
USB-IO周りの構造
╭──────────────────────────────────────╮
┌──────┐ │ device ┌─────┐ ┌───────────┐ │
│ Port ├──┐ │ ┌─┤ EP0 ├──┤ control │ │
└──────┘ │ │ ┌────────┐ │ └─────┘ │┌─────────┐│ │
├────┤ addr ├─┤ ┌─────┐ ││ ││ │
│ │ └────────┘ ├─┤ EP1 ├──┼┤interface││ │
│ │ │ └─────┘ ││ #0 ││ │
│ │ │ ┌─────┐ │├─────────┤│ │
│ │ ├─┤ EP2 ├──┼┤ ││ │
│ │ │ └─────┘ ││interface││ │
│ │ │ ┌─────┐ ││ #1 ││ │
│ │ └─┤ EP3 ├──┼┤ ││ │
│ │ └─────┘ │└─────────┘│ │
│ │ └───────────┘ │
│ ╰──────────────────────────────────────╯
:
ref) https://unix.stackexchange.com/questions/292933/how-can-i-write-raw-data-to-a-usb-device (ここの人はUSBからの出力信号をcat
で読み込もうとして「そんなに単純じゃないよ」と言われています)
USBデバイスにはエンドポイントが複数定義されていて、この図でいうinterfaceまで正しく辿り着けなければならず、そのためにはいくつかの調査が必要です。
調査の基本的な流れ
0. PortとAddrを取得する
この部分はUSBデバイスのVENDOR_IDとPRODUCT_IDが分かっていれば、libusbが上手くやってくれるので必要ありません。
1. USBデバイスのVENDOR_IDとPRODUCT_IDを取得する。
これは該当デバイスの仕様が分かっていれば問題ありません。
調べる場合はUSBを抜き差しした直後に下記コマンドのそれっぽい出力からidVendorとidProductを確認します。
具体的にはこんな感じです。
sudo dmesg|grep USB
[69958.155195] usb 1-4: USB disconnect, device number 6
[69961.010124] usb 1-4: new full-speed USB device number 12 using xhci_hcd
[69961.403436] usb 1-4: New USB device found, idVendor=1352, idProduct=0121
[69961.403439] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[69961.403440] usb 1-4: Product: USB-IO2.0
[69961.417534] hid-generic 0003:1352:0121.0015: hiddev0,hidraw10: USB HID v1.11 Device [Km2Net Inc. USB-IO2.0] on usb-0000:01:00.0-4/input0
2. USBデバイスのEPを取得する。
調べる場合はUSBを差している状態で下記コマンドの出力から先のidVendorとidProductの欄を確認します。
USBデバイス多いと出力が長くなるのでファイルにリダイレクトして検索をおすすめします。
sudo lsusb -v > out.txt
必要部分だけ抜き出した生の出力
Bus 001 Device 006: ID 1352:0121
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x1352
idProduct 0x0121
bcdDevice 0.01
iManufacturer 1 Km2Net Inc.
iProduct 2 USB-IO2.0
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 41
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xc0
Self Powered
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 29
Report Descriptor: (length is 29)
Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
(null)
Item(Local ): Usage, data= [ 0x01 ] 1
(null)
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
(null)
Item(Local ): Usage Maximum, data= [ 0x40 ] 64
(null)
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x40 ] 64
Item(Main ): Input, data= [ 0x00 ] 0
Data Array Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
(null)
Item(Local ): Usage Maximum, data= [ 0x40 ] 64
(null)
Item(Main ): Output, data= [ 0x00 ] 0
Data Array Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Device Status: 0x0001
Self Powered
─ Device Descriptor
├─ Configurtion Descriptor
│ ├─ bConfigurationValue: 1 (*1)
│ └─ Interface Descriptor
│ ├─ Endpoint Descriptor
│ │ ├─ bLength: 7 (*2)
│ │ ├─ bEndpointAddress: 0x81 EP 1 IN (*3)
│ │ └─ bmAttributes
│ │ └─ Transfer Type: Interrupt (*4)
│ ├─ Endpoint Descriptor
│ │ ├─ bLength: 7 (*5)
│ │ ├─ bEndpointAddress: 0x01 EP 1 OUT (*6)
│ │ └─ bmAttributes
│ │ └─ Transfer Type: Interrupt (*7)
これでこのデバイスは
- 入力: 0x81 EP 1に size7で(※後ほど訂正) intterrupt transfer (*2-4)
- 出力: 0x01 EP 1に size7で(※後ほど訂正) intterrupt transfer (*5-7)
すれば良いと分かります。
3. 作法を調べる
実際にUSBデバイスからの信号を受信するには、製造元が定めた形式でリクエストを送ってやる必要があります。製造元の仕様を調べましょう。データ受信前にデータ送信が必要となる仕様が全てのUSB共通の規格かは分かりませんが、少なくとも今回はそうでしたし、Webリクエストとかも基本的に規格に沿った挨拶から始まるので、そういうものなのかも知れません。
仕様: http://km2net.com/usb-fsio/command.shtml
先程「bLength 7だからsize7で」と書きましたが、仕様では64のようです。仕様に合わせることにします(64未満ではオーバーフローしました)。
デジタル入出力には0バイト目に0x20を送信せよとあるので、そのようにします。
コード
#include <libusb-1.0/libusb.h>
#include <stdlib.h> // for exit
#include <stdio.h> // for printf
#define VENDOR_ID 0x1352
#define PRODUCT_ID 0x0121
#define EP 0x1
#define DEVICE_CONFIGURATION 1
int main()
{
libusb_device **list = NULL;
ssize_t number_of_devices = 0;
if(libusb_init(NULL) != 0) // libusb初期化
{
fprintf(stderr, "%s:%d: unable to initialize libusb\n", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
number_of_devices = libusb_get_device_list(NULL, &list); // デバイス取得
if(number_of_devices < 0)
{
fprintf(stderr, "%s:%d: unable to get the list of USB devices\n", __FILE__, __LINE__);
libusb_exit(NULL);
exit(EXIT_FAILURE);
}
printf("number_of_devices = %d\n", number_of_devices);
int index = 0;
for(index = 0; index < number_of_devices; ++index)
{
libusb_device *device = list[index];
struct libusb_device_descriptor desc = {0};
if(libusb_get_device_descriptor(device, &desc) != 0)
{
fprintf(stderr, "%s:%d: unable to get the USB device descriptor\n", __FILE__, __LINE__);
libusb_free_device_list(list, 1);
libusb_exit(NULL);
exit(EXIT_FAILURE);
}
printf("%d/%d: idVendor: %04x, idProduct: %04x\n", index + 1, number_of_devices, desc.idVendor, desc.idProduct);
// idVendorとidProductでデバイス指定
if(desc.idVendor == VENDOR_ID && desc.idProduct == PRODUCT_ID)
{
printf("USB device found!\n");
printf("Opening the USB device...\n");
libusb_device_handle *handle = NULL;
// デバイスオープン
if(libusb_open(device, &handle) != 0)
{
fprintf(stderr, "%s:%d: unable to open the USB device\n", __FILE__, __LINE__);
libusb_free_device_list(list, 1);
libusb_exit(NULL);
exit(EXIT_FAILURE);
}
printf("Done!\n");
// カーネルドライバを良い感じに
if(libusb_kernel_driver_active(handle, 0) != 0)
{
printf("Kernel driver is active!\n");
printf("Detaching the kernel driver...\n");
if(libusb_detach_kernel_driver(handle, 0) != 0)
{
fprintf(stderr, "%s:%d: unable to detach the kernel driver from the interface of the USB device\n", __FILE__, __LINE__);
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(NULL);
exit(EXIT_FAILURE);
}
printf("Done!\n");
}
// デバイスの使用権を宣言
printf("Claiming the interface...\n");
if(libusb_claim_interface(handle, 0) != 0)
{
fprintf(stderr, "%s:%d: unable to claim the interface of the USB device\n", __FILE__, __LINE__);
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(NULL);
exit(EXIT_FAILURE);
}
printf("Done!\n");
unsigned char data[64] = {0x20}; // 0x20, 0x00, 0x00, ...というデータになる。
int actual_length;
int r;
printf("Gonna Reading data...\n");
while(1)
{
// 受信のリクエストを送る
r = libusb_interrupt_transfer(handle, 0x01|EP, data, sizeof(data), &actual_length, 0);
// データの受信
r = libusb_interrupt_transfer(handle, 0x81|EP, data, sizeof(data), &actual_length, 0);
if(r == 0) {
printf("IN: %d: ", actual_length);
for (int _i=0; _i<actual_length; ++_i) {
printf("%02X ", data[_i]);
}
printf("\n");
}
}
// デバイス開放など
printf("Releasing the interface...\n");
if(libusb_release_interface(handle, 0) != 0)
{
fprintf(stderr, "%s:%d: unable to detach the kernel driver from the interface of the USB device\n", __FILE__, __LINE__);
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(NULL);
exit(EXIT_FAILURE);
}
printf("Done!\n");
printf("Closing the USB device...\n");
libusb_close(handle);
printf("Done!\n");
}
}
libusb_free_device_list(list, 1);
libusb_exit(NULL);
exit(EXIT_SUCCESS);
}
環境
sudo apt install -y libusb-1.0-0-dev
コンパイルは好きなコンパイラで。。
gcc -lusb-1.0 read.cc
実行はsuで。。
sudo ./a.out
正直
よく分かってないです。
今回は個人的な目的は達せたので良かったのですが
「動いたからこれが正しそう」以上の何物でもないです。
ネット上の情報は2010年前後を境に激減していてLinux関連の情報も少ないです。
詳しい人いれば教えてください(´・ω・`)