USBデバイスをつくってみたい(2) HIDデバイスコントロール転送編

More than 5 years have passed since last update.

(続きその3→ http://qiita.com/takeru@github/items/23f41616db9367ea26b5)


ここまでのあらすじ


  • http://qiita.com/takeru@github/items/c591323acf24e58ed6c0

  • AVRにUSBケーブル直結でUSBデバイスが作れるV-USB

  • AVR-CDCでシリアル読み書きデバイスはできたがイマイチ

  • HIDに準じたものを作るのが良いっぽい

  • とりあえず、LEDがついたりきえたりするHIDデバイスを作ってみる。


調査

キーワードがわかるとどんどん情報がみつかってくる。


ハードウエア

AVR-CDCとおなじ。

http://qiita.com/takeru@github/items/c591323acf24e58ed6c0

http://www.recursion.jp/avrcdc/cdc-ioj.html

クリスタルだけ20MHzなので、Makefileの設定を変えています。


ソフトウエア


main.c

#include <string.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "usbdrv.h"

uchar recv_buf[8];
uchar counter = 0;

/*
typedef struct usbRequest{
uchar bmRequestType;
uchar bRequest;
usbWord_t wValue;
usbWord_t wIndex;
usbWord_t wLength;
}usbRequest_t;
*/

PROGMEM const char usbHidReportDescriptor[USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH] = {
0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)

0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)

0x95, 0x08, // REPORT_COUNT (8)
0x09, 0x00, // USAGE (Undefined)
0xb2, 0x02, 0x01, // FEATURE (Data,Var,Abs,Buf)

0xc0 // END_COLLECTION
};

/* You need to implement this function ONLY if you provide USB descriptors at
* runtime (which is an expert feature). It is very similar to
* usbFunctionSetup() above, but it is called only to request USB descriptor
* data. See the documentation of usbFunctionSetup() above for more info.
*/

/*
usbMsgLen_t usbFunctionDescriptor(struct usbRequest_t *rq)
{
}
*/

/* This function is called when the driver receives a SETUP transaction from
* the host which is not answered by the driver itself (in practice: class and
* vendor requests). All control transfers start with a SETUP transaction where
* the host communicates the parameters of the following (optional) data
* transfer. The SETUP data is available in the 'data' parameter which can
* (and should) be casted to 'usbRequest_t *' for a more user-friendly access
* to parameters.
*
* If the SETUP indicates a control-in transfer, you should provide the
* requested data to the driver. There are two ways to transfer this data:
* (1) Set the global pointer 'usbMsgPtr' to the base of the static RAM data
* block and return the length of the data in 'usbFunctionSetup()'. The driver
* will handle the rest. Or (2) return USB_NO_MSG in 'usbFunctionSetup()'. The
* driver will then call 'usbFunctionRead()' when data is needed. See the
* documentation for usbFunctionRead() for details.
*
* If the SETUP indicates a control-out transfer, the only way to receive the
* data from the host is through the 'usbFunctionWrite()' call. If you
* implement this function, you must return USB_NO_MSG in 'usbFunctionSetup()'
* to indicate that 'usbFunctionWrite()' should be used. See the documentation
* of this function for more information. If you just want to ignore the data
* sent by the host, return 0 in 'usbFunctionSetup()'.
*
* Note that calls to the functions usbFunctionRead() and usbFunctionWrite()
* are only done if enabled by the configuration in usbconfig.h.
*/

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
usbRequest_t *rq = (usbRequest_t*)data;

if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){
if(rq->bRequest == USBRQ_HID_SET_REPORT){
// host to device
return USB_NO_MSG; // => usbFunctionWrite
}
if(rq->bRequest == USBRQ_HID_GET_REPORT){
// device to host
return USB_NO_MSG; // => usbFunctionRead
}
}

return 0;
}

/* This function is called by the driver to provide a control transfer's
* payload data (control-out). It is called in chunks of up to 8 bytes. The
* total count provided in the current control transfer can be obtained from
* the 'length' property in the setup data. If an error occurred during
* processing, return 0xff (== -1). The driver will answer the entire transfer
* with a STALL token in this case. If you have received the entire payload
* successfully, return 1. If you expect more data, return 0. If you don't
* know whether the host will send more data (you should know, the total is
* provided in the usbFunctionSetup() call!), return 1.
* NOTE: If you return 0xff for STALL, 'usbFunctionWrite()' may still be called
* for the remaining data. You must continue to return 0xff for STALL in these
* calls.
* In order to get usbFunctionWrite() called, define USB_CFG_IMPLEMENT_FN_WRITE
* to 1 in usbconfig.h and return 0xff in usbFunctionSetup()..
*/

uchar usbFunctionWrite(uchar *data, uchar len)
{
// host to device
if(len==8){
memcpy(recv_buf, data, len);
return 1;
}else{
return -1; // STALL
}
}

/* This function is called by the driver to ask the application for a control
* transfer's payload data (control-in). It is called in chunks of up to 8
* bytes each. You should copy the data to the location given by 'data' and
* return the actual number of bytes copied. If you return less than requested,
* the control-in transfer is terminated. If you return 0xff, the driver aborts
* the transfer with a STALL token.
* In order to get usbFunctionRead() called, define USB_CFG_IMPLEMENT_FN_READ
* to 1 in usbconfig.h and return 0xff in usbFunctionSetup()..
*/

uchar usbFunctionRead(uchar *data, uchar len)
{
// device to host
if(len==8){
//memset(data, 0, len);
memcpy(data, recv_buf, len);
data[7] = counter++;
return len;
}else{
return -1; // STALL
}
}

/* This function is called by the driver when data is received on an interrupt-
* or bulk-out endpoint. The endpoint number can be found in the global
* variable usbRxToken. You must define USB_CFG_IMPLEMENT_FN_WRITEOUT to 1 in
* usbconfig.h to get this function called.
*/

/*
void usbFunctionWriteOut(uchar *data, uchar len)
{
usbDisableAllRequests();
// ...
usbEnableAllRequests();
}
*/

static void hardwareInit(void)
{
uchar i;
USB_CFG_IOPORT = (uchar)~((1<<USB_CFG_DMINUS_BIT)|(1<<USB_CFG_DPLUS_BIT));

usbDeviceDisconnect();
for(i=0; i<10; i++){
wdt_reset();
_delay_ms(10);
}
usbDeviceConnect();
}

int main(void)
{
wdt_enable(WDTO_1S);
hardwareInit();
usbInit();

sei();
for(;;){
wdt_reset();
usbPoll();
}
return 0;
}


hidled% AVR_PATH=/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin; PATH=$AVR_PATH:$PATH

hidled% make
avr-gcc -I"." -I"./v-usb/usbdrv" -mmcu=atmega88p -Wall -gdwarf-2 -DF_CPU=20000000UL -Os -fsigned-char -MD -MP -MT main.o -MF dep/main.o.d -c main.c
avr-gcc -I"." -I"./v-usb/usbdrv" -mmcu=atmega88p -mmcu=atmega88p -Wall -gdwarf-2 -DF_CPU=20000000UL -Os -fsigned-char -MD -MP -MT usbdrvasm.o -MF dep/usbdrvasm.o.d -x assembler-with-cpp -Wa,-gdwarf2 -c v-usb/usbdrv/usbdrvasm.S
avr-gcc -I"." -I"./v-usb/usbdrv" -mmcu=atmega88p -Wall -gdwarf-2 -DF_CPU=20000000UL -Os -fsigned-char -MD -MP -MT oddebug.o -MF dep/oddebug.o.d -c v-usb/usbdrv/oddebug.c
avr-gcc -I"." -I"./v-usb/usbdrv" -mmcu=atmega88p -Wall -gdwarf-2 -DF_CPU=20000000UL -Os -fsigned-char -MD -MP -MT usbdrv.o -MF dep/usbdrv.o.d -c v-usb/usbdrv/usbdrv.c
avr-gcc -mmcu=atmega88p main.o usbdrvasm.o oddebug.o usbdrv.o -o hidled.elf
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature hidled.elf hidled.hex
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex hidled.elf hidled.eep || exit 0
avr-objdump -h -S hidled.elf > hidled.lss

hidled% avrdude -C /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/etc/avrdude.conf -P /dev/tty.usbserial-A9005bvI -c avrisp -p m88p -b 19200 -U flash:w:hidled.hex

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.06s

avrdude: Device signature = 0x1e930f
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "hidled.hex"
avrdude: input file hidled.hex auto detected as Intel Hex
avrdude: writing flash (1582 bytes):

Writing | ################################################## | 100% 2.78s

avrdude: 1582 bytes of flash written
avrdude: verifying flash memory against hidled.hex:
avrdude: load data flash data from input file hidled.hex:
avrdude: input file hidled.hex auto detected as Intel Hex
avrdude: input file hidled.hex contains 1582 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 1.82s

avrdude: verifying ...
avrdude: 1582 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done. Thank you.


hidled_host.rb

require "libusb"

include LIBUSB

USBRQ_HID_GET_REPORT = 0x01
USBRQ_HID_GET_IDLE = 0x02
USBRQ_HID_GET_PROTOCOL = 0x03
USBRQ_HID_SET_REPORT = 0x09
USBRQ_HID_SET_IDLE = 0x0a
USBRQ_HID_SET_PROTOCOL = 0x0b

usb = LIBUSB::Context.new

devices = usb.devices(:idVendor => 0x16c0, :idProduct => 0x05dc)
puts "devices = #{devices.inspect}"
exit if devices.size==0

device = devices.first
device.open_interface(0) do |handle|
16.times do |n|
# send to device
data = [n+0,n+1,n+2,n+3,n+4,n+5,n+6,n+7]

sent_byte_size = handle.control_transfer(
:bmRequestType => ENDPOINT_OUT|REQUEST_TYPE_CLASS|RECIPIENT_ENDPOINT,
:bRequest => USBRQ_HID_SET_REPORT,
:wValue => 0x0000,
:wIndex => 0x0000,
:dataOut => data.pack("C*")
)
puts "sent_byte_size = #{sent_byte_size}"

# receive from device
received_bytes = handle.control_transfer(
:bmRequestType => ENDPOINT_IN|REQUEST_TYPE_CLASS|RECIPIENT_ENDPOINT,
:bRequest => USBRQ_HID_GET_REPORT,
:wValue => 0x0000,
:wIndex => 0x0000,
:dataIn => 8
)
puts "received_bytes = #{received_bytes.unpack('C*').inspect}"
end
end


hidled% ruby hidled_host.rb

devices = [#<LIBUSB::Device 26/7 16c0:05dc ssktkr.com HID-LED ? (Vendor specific (00,00))>]
sent_byte_size = 8
received_bytes = [0, 1, 2, 3, 4, 5, 6, 0]
sent_byte_size = 8
received_bytes = [1, 2, 3, 4, 5, 6, 7, 1]
sent_byte_size = 8
received_bytes = [2, 3, 4, 5, 6, 7, 8, 2]
sent_byte_size = 8
received_bytes = [3, 4, 5, 6, 7, 8, 9, 3]
sent_byte_size = 8
received_bytes = [4, 5, 6, 7, 8, 9, 10, 4]
sent_byte_size = 8
received_bytes = [5, 6, 7, 8, 9, 10, 11, 5]
sent_byte_size = 8
received_bytes = [6, 7, 8, 9, 10, 11, 12, 6]
sent_byte_size = 8
received_bytes = [7, 8, 9, 10, 11, 12, 13, 7]
sent_byte_size = 8
received_bytes = [8, 9, 10, 11, 12, 13, 14, 8]
sent_byte_size = 8
received_bytes = [9, 10, 11, 12, 13, 14, 15, 9]
sent_byte_size = 8
received_bytes = [10, 11, 12, 13, 14, 15, 16, 10]
sent_byte_size = 8
received_bytes = [11, 12, 13, 14, 15, 16, 17, 11]
sent_byte_size = 8
received_bytes = [12, 13, 14, 15, 16, 17, 18, 12]
sent_byte_size = 8
received_bytes = [13, 14, 15, 16, 17, 18, 19, 13]
sent_byte_size = 8
received_bytes = [14, 15, 16, 17, 18, 19, 20, 14]
sent_byte_size = 8
received_bytes = [15, 16, 17, 18, 19, 20, 21, 15]


まとめ


  • HIDクラスのデバイスをつくってRubyで操作できるようになった。

  • ハブを通しても直接接続でもちゃんと動く

  • デスクリプタは後々詳しいことを調べるかもしれない。

  • インタラプト転送はまだやっていない。

  • TODO


    • 受信データによってLEDを点けたり消したりする

    • シリアルデバッグできるようにする

    • インタラプト転送



スクリーンショット 2014-05-18 21.28.59.png