Help us understand the problem. What is going on with this article?

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away