動機
自作キーボードを作っているが、キー入力だけでは物足りなくなってきた。
Stream Deckや、Orbital2のように、windows側のアプリケーションでデバイスの操作を受取り、より柔軟なマクロを実行できる左手デバイスを開発したい。
その第一歩として、WindowsとUSBデバイス間で8bitのデータをリアルタイムに送受信することを目指す。USBシリアル通信は余りかっこよくないので使用しない。
使用するデバイス
- デバイス: Pro Micro Type-C版
- マイコン: Atmega 32U4
自作キーボードによく使用されているPro Microを使用することにした。Atmega 32U4はUSB専用のハードウェア回路が入っている。また、qmk firmwareを用いてNative USBデバイスとして認識されるキーボードを作成し動作した実績があるからである。
USBについての調査
- USBの仕様はすべて公開されており、USB2.0の仕様は上記のページで閲覧が可能
- USB2.0までに、通信速度が3種類ある
- ロースピード(1.5 Mbps)
- フルスピード(12 Mbps)
- ハイスピード(480 Mbps)
- HIDデバイスは、通信頻度が低く、1度の通信のデータ量も小さいため、ロースピードでさえ十分な速度である
UBSデバイスクラスについて
- USBは、実際のパケットによる通信プロトコルの上位にUSBクラスというものがあり、それぞれのクラスに固有のプロトコルが定義されているらしい
- キーボードや、マウスは HID (Human Interface Device)のクラスが使用されている。そのクラスではHostからのポーリングを用いた、(ほぼ)リアルタイムな割り込みをHostに通知できるようだ
- また、HIDクラスは、キーボードやマウス以外にも独自のデータ構造を定義して、通知できる事がわかった
- 今回はこれを使用する
HIDクラスについて
- HIDは、Report Descriptorを使用して、デバイスの性質や、通信に使用するデータ構造を定義する
- そして、Reportを使用して、データを送受信する
開発環境・ライブラリ
Atmega 32U4でUSBを使用するには、いくつかの方法があるようだ
- USBドライバハードウェアを、レジスタを直接書き換えることで使用する
- Lightweight USB Framework for AVRsライブラリを使用して、レジスタ操作を抽象化する
- V-USBを使用して、ハードウェアを使用しないで、ソフトウェアでエミュレートしてUSB通信を行う
1は、USBのイベント処理をすべて自分で書くのは難しいし、時間がかかる
3は、せっかく付属しているUSBハードウェアを使用しないのはもったいない
よって、今回は2のLUFAを使用することにした
今回は、以下の開発環境を使用する
-
Microchip Studio
- Atmel Studioがいつの間にかMicrochip Studioに変わっていた
- Lightweight USB Framework for AVRs
実装するデバイスの仕様
- 今回は8bitのデータを、送受信できるデバイスを作成する
- ボタンが押されたらデバイスからホストに0x01を送信する
- ホストからデバイスに0x00を受信したらLEDを消灯、0x01を受信したらLEDを点灯する
ハードウェア
以下のようにUSBステータス用のLEDと、Host(PC)からコントロールするLED、ホスト(PC)に信号を送るトリガーとなるタクトスイッチを配置する
プロジェクトのセットアップ
こちらの記事を参考に、プロジェクトの雛形を作成する
一から自分で定義するのは、細かい設定やLUFAライブラリのプロジェクトへの追加が面倒なのでおすすめしない
- Generic HID Device Demo (Classic Driver APIs) - AVR8 Architectureを選ぶ
- Licenseに同意する
- プロジェクトのプロパティを開き
- デバイスを32U4に変更
- ASF WizardでBoardをUserに設定しApply
組み込みソフトウェアコード
ボードのユーティリティコード
まずは、今回使うボードのためのLED、ボタンの設定を書く。今回は、既存のボードのコードを軽く書き換えた。
Pro Microの回路図と、ブレッドボードの配線を見比べながら、使用するポートを書き換える。
#ifndef __BOARD_MICRO_H__
#define __BOARD_MICRO_H__
/* Includes: */
#include "../LUFA/LUFA/Common/Common.h"
#include "./LEDs.h"
/* Enable C linkage for C++ Compilers: */
#if defined(__cplusplus)
extern "C" {
#endif
/* Public Interface - May be used in end-application: */
/* Macros: */
/** Indicates the board has hardware LEDs mounted. */
#define BOARD_HAS_LEDS
/* Disable C linkage for C++ Compilers: */
#if defined(__cplusplus)
}
#endif
#endif
#ifndef __LEDS_MICRO_H__
#define __LEDS_MICRO_H__
/* Includes: */
#include "../LUFA/LUFA/Common/Common.h"
/* Enable C linkage for C++ Compilers: */
#if defined(__cplusplus)
extern "C" {
#endif
/* Private Interface - For use in library only: */
#if !defined(__DOXYGEN__)
/* Macros: */
#define LEDS_PORTB_LEDS (0)
#define LEDS_PORTD_LEDS (LEDS_LED1 | LEDS_LED2 | LEDS_LED3 | LEDS_LED5)
#define LEDS_PORTC_LEDS (LEDS_LED4)
#endif
/* Public Interface - May be used in end-application: */
/* Macros: */
/** LED mask for the first LED on the board. */
// PD1
#define LEDS_LED1 (1 << 1)
/** LED mask for the second LED on the board. */
// PD0
#define LEDS_LED2 (1 << 0)
/** LED mask for the third LED on the board. */
// PD4
#define LEDS_LED3 (1 << 4)
/** LED mask for the third LED on the board. */
// PC6
#define LEDS_LED4 (1 << 6)
/** LED mask for the third LED on the board. */
// PD7
#define LEDS_LED5 (1 << 7)
/** LED mask for all the LEDs on the board. */
#define LEDS_ALL_LEDS (LEDS_LED1 | LEDS_LED2 | LEDS_LED3 | LEDS_LED4 | LEDS_LED5)
/** LED mask for none of the board LEDs. */
#define LEDS_NO_LEDS 0
/* Inline Functions: */
#if !defined(__DOXYGEN__)
static inline void LEDs_Init(void)
{
DDRB |= LEDS_PORTB_LEDS;
PORTB &= ~LEDS_PORTB_LEDS;
DDRD |= LEDS_PORTD_LEDS;
PORTD &= ~LEDS_PORTD_LEDS;
DDRC |= LEDS_PORTC_LEDS;
PORTC &= ~LEDS_PORTC_LEDS;
}
static inline void LEDs_Disable(void)
{
DDRB &= ~LEDS_PORTB_LEDS;
PORTB &= ~LEDS_PORTB_LEDS;
DDRD &= ~LEDS_PORTD_LEDS;
PORTD &= ~LEDS_PORTD_LEDS;
DDRC &= ~LEDS_PORTC_LEDS;
PORTC &= ~LEDS_PORTC_LEDS;
}
static inline void LEDs_TurnOnLEDs(const uint8_t LEDMask)
{
PORTB |= (LEDMask & LEDS_PORTB_LEDS);
PORTD |= (LEDMask & LEDS_PORTD_LEDS);
PORTC |= (LEDMask & LEDS_PORTC_LEDS);
}
static inline void LEDs_TurnOffLEDs(const uint8_t LEDMask)
{
PORTB &= ~(LEDMask & LEDS_PORTB_LEDS);
PORTD &= ~(LEDMask & LEDS_PORTD_LEDS);
PORTC &= ~(LEDMask & LEDS_PORTC_LEDS);
}
static inline void LEDs_SetAllLEDs(const uint8_t LEDMask)
{
PORTB = ((PORTB & ~LEDS_PORTB_LEDS) | (LEDMask & LEDS_PORTB_LEDS));
PORTD = ((PORTD & ~LEDS_PORTD_LEDS) | (LEDMask & LEDS_PORTD_LEDS));
PORTC = ((PORTC & ~LEDS_PORTC_LEDS) | (LEDMask & LEDS_PORTC_LEDS));
}
static inline void LEDs_ChangeLEDs(const uint8_t LEDMask,
const uint8_t ActiveMask)
{
PORTB = ((PORTB & ~(LEDMask & LEDS_PORTB_LEDS)) | (ActiveMask & LEDS_PORTB_LEDS));
PORTD = ((PORTD & ~(LEDMask & LEDS_PORTD_LEDS)) | (ActiveMask & LEDS_PORTD_LEDS));
PORTC = ((PORTC & ~(LEDMask & LEDS_PORTC_LEDS)) | (ActiveMask & LEDS_PORTC_LEDS));
}
static inline void LEDs_ToggleLEDs(const uint8_t LEDMask)
{
PORTB ^= (LEDMask & LEDS_PORTB_LEDS);
PORTD ^= (LEDMask & LEDS_PORTD_LEDS);
PORTC ^= (LEDMask & LEDS_PORTC_LEDS);
}
static inline uint8_t LEDs_GetLEDs(void) ATTR_WARN_UNUSED_RESULT;
static inline uint8_t LEDs_GetLEDs(void)
{
return ((PORTB & LEDS_PORTB_LEDS) | (PORTD & LEDS_PORTD_LEDS) | (PORTC & LEDS_PORTC_LEDS));
}
#endif
/* Disable C linkage for C++ Compilers: */
#if defined(__cplusplus)
}
#endif
#endif
#ifndef __BUTTONS_MICROPENDOUS_H__
#define __BUTTONS_MICROPENDOUS_H__
/* Includes: */
#include "../LUFA/LUFA/Common/Common.h"
/* Enable C linkage for C++ Compilers: */
#if defined(__cplusplus)
extern "C" {
#endif
/* Private Interface - For use in library only: */
#if !defined(__DOXYGEN__)
// PB4
#define _BOARD_BUTTON1_MASK (1 << 4)
#define _BOARD_BUTTON_PORTLETTER B
#define _BOARD_BUTTON_PORT CONCAT_EXPANDED(PORT, _BOARD_BUTTON_PORTLETTER)
#define _BOARD_BUTTON_PIN CONCAT_EXPANDED(PIN, _BOARD_BUTTON_PORTLETTER)
#define _BOARD_BUTTON_DDR CONCAT_EXPANDED(DDR, _BOARD_BUTTON_PORTLETTER)
#endif
/* Public Interface - May be used in end-application: */
/* Macros: */
/** Button mask for the first button on the board. */
#define BUTTONS_BUTTON1 _BOARD_BUTTON1_MASK
/* Inline Functions: */
#if !defined(__DOXYGEN__)
static inline void Buttons_Init(void)
{
_BOARD_BUTTON_DDR &= ~BUTTONS_BUTTON1;
_BOARD_BUTTON_PORT |= BUTTONS_BUTTON1;
}
static inline void Buttons_Disable(void)
{
_BOARD_BUTTON_DDR &= ~BUTTONS_BUTTON1;
_BOARD_BUTTON_PORT &= ~BUTTONS_BUTTON1;
}
static inline uint8_t Buttons_GetStatus(void) ATTR_WARN_UNUSED_RESULT;
static inline uint8_t Buttons_GetStatus(void)
{
return ((_BOARD_BUTTON_PIN & BUTTONS_BUTTON1) ^ BUTTONS_BUTTON1);
}
#endif
/* Disable C linkage for C++ Compilers: */
#if defined(__cplusplus)
}
#endif
#endif
続いてUSBの処理を少し改変する
主にCALLBACK_HID_Device_CreateHIDReportとCALLBACK_HID_Device_ProcessHIDReportを修正した
#ifndef _GENERICHID_H_
#define _GENERICHID_H_
/* Includes: */
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <string.h>
#include "Descriptors.h"
#include "Config/AppConfig.h"
#include <LUFA/Drivers/Board/LEDs.h>
#include <LUFA/Drivers/Board/BUTTONs.h>
#include <LUFA/Drivers/USB/USB.h>
#include <LUFA/Platform/Platform.h>
/* Macros: */
/** LED mask for the library LED driver, to indicate that the USB interface is not ready. */
#define LEDMASK_USB_NOTREADY LEDS_LED1
/** LED mask for the library LED driver, to indicate that the USB interface is enumerating. */
#define LEDMASK_USB_ENUMERATING (LEDS_LED2 | LEDS_LED3)
/** LED mask for the library LED driver, to indicate that the USB interface is ready. */
#define LEDMASK_USB_READY (LEDS_LED2 | LEDS_LED4)
/** LED mask for the library LED driver, to indicate that an error has occurred in the USB interface. */
#define LEDMASK_USB_ERROR (LEDS_LED1 | LEDS_LED3)
/* Function Prototypes: */
void SetupHardware(void);
void EVENT_USB_Device_Connect(void);
void EVENT_USB_Device_Disconnect(void);
void EVENT_USB_Device_ConfigurationChanged(void);
void EVENT_USB_Device_ControlRequest(void);
void EVENT_USB_Device_StartOfFrame(void);
bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
uint8_t* const ReportID,
const uint8_t ReportType,
void* ReportData,
uint16_t* const ReportSize);
void CALLBACK_HID_Device_ProcessHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
const uint8_t ReportID,
const uint8_t ReportType,
const void* ReportData,
const uint16_t ReportSize);
#endif
#include "GenericHID.h"
/** Buffer to hold the previously generated HID report, for comparison purposes inside the HID class driver. */
static uint8_t PrevHIDReportBuffer[GENERIC_REPORT_SIZE];
/** LUFA HID Class driver interface configuration and state information. This structure is
* passed to all HID Class driver functions, so that multiple instances of the same class
* within a device can be differentiated from one another.
*/
USB_ClassInfo_HID_Device_t Generic_HID_Interface =
{
.Config =
{
.InterfaceNumber = INTERFACE_ID_GenericHID,
.ReportINEndpoint =
{
.Address = GENERIC_IN_EPADDR,
.Size = GENERIC_EPSIZE,
.Banks = 1,
},
.PrevReportINBuffer = PrevHIDReportBuffer,
.PrevReportINBufferSize = sizeof(PrevHIDReportBuffer),
},
};
/** Main program entry point. This routine contains the overall program flow, including initial
* setup of all components and the main program loop.
*/
int main(void)
{
SetupHardware();
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
GlobalInterruptEnable();
for (;;)
{
HID_Device_USBTask(&Generic_HID_Interface);
USB_USBTask();
}
}
/** Configures the board hardware and chip peripherals for the demo's functionality. */
void SetupHardware(void)
{
#if (ARCH == ARCH_AVR8)
/* Disable watchdog if enabled by bootloader/fuses */
MCUSR &= ~(1 << WDRF);
wdt_disable();
/* Disable clock division */
clock_prescale_set(clock_div_1);
#elif (ARCH == ARCH_XMEGA)
/* Start the PLL to multiply the 2MHz RC oscillator to 32MHz and switch the CPU core to run from it */
XMEGACLK_StartPLL(CLOCK_SRC_INT_RC2MHZ, 2000000, F_CPU);
XMEGACLK_SetCPUClockSource(CLOCK_SRC_PLL);
/* Start the 32MHz internal RC oscillator and start the DFLL to increase it to 48MHz using the USB SOF as a reference */
XMEGACLK_StartInternalOscillator(CLOCK_SRC_INT_RC32MHZ);
XMEGACLK_StartDFLL(CLOCK_SRC_INT_RC32MHZ, DFLL_REF_INT_USBSOF, F_USB);
PMIC.CTRL = PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm;
#endif
/* Hardware Initialization */
LEDs_Init();
Buttons_Init();
USB_Init();
}
/** Event handler for the library USB Connection event. */
void EVENT_USB_Device_Connect(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_ENUMERATING);
}
/** Event handler for the library USB Disconnection event. */
void EVENT_USB_Device_Disconnect(void)
{
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
}
/** Event handler for the library USB Configuration Changed event. */
void EVENT_USB_Device_ConfigurationChanged(void)
{
bool ConfigSuccess = true;
ConfigSuccess &= HID_Device_ConfigureEndpoints(&Generic_HID_Interface);
USB_Device_EnableSOFEvents();
LEDs_SetAllLEDs(ConfigSuccess ? LEDMASK_USB_READY : LEDMASK_USB_ERROR);
}
/** Event handler for the library USB Control Request reception event. */
void EVENT_USB_Device_ControlRequest(void)
{
HID_Device_ProcessControlRequest(&Generic_HID_Interface);
}
/** Event handler for the USB device Start Of Frame event. */
void EVENT_USB_Device_StartOfFrame(void)
{
HID_Device_MillisecondElapsed(&Generic_HID_Interface);
}
/** HID class driver callback function for the creation of HID reports to the host.
*
* \param[in] HIDInterfaceInfo Pointer to the HID class interface configuration structure being referenced
* \param[in,out] ReportID Report ID requested by the host if non-zero, otherwise callback should set to the generated report ID
* \param[in] ReportType Type of the report to create, either HID_REPORT_ITEM_In or HID_REPORT_ITEM_Feature
* \param[out] ReportData Pointer to a buffer where the created report should be stored
* \param[out] ReportSize Number of bytes written in the report (or zero if no report is to be sent)
*
* \return Boolean \c true to force the sending of the report, \c false to let the library determine if it needs to be sent
*/
bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
uint8_t* const ReportID,
const uint8_t ReportType,
void* ReportData,
uint16_t* const ReportSize)
{
uint8_t* Data = (uint8_t*)ReportData;
Data[0] = !!Buttons_GetStatus(); // 0 or 1にするために、二重否定
*ReportSize = GENERIC_REPORT_SIZE; // SIZEは常に1byte
return false;
}
/** HID class driver callback function for the processing of HID reports from the host.
*
* \param[in] HIDInterfaceInfo Pointer to the HID class interface configuration structure being referenced
* \param[in] ReportID Report ID of the received report from the host
* \param[in] ReportType The type of report that the host has sent, either HID_REPORT_ITEM_Out or HID_REPORT_ITEM_Feature
* \param[in] ReportData Pointer to a buffer where the received report has been stored
* \param[in] ReportSize Size in bytes of the received HID report
*/
void CALLBACK_HID_Device_ProcessHIDReport(USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,
const uint8_t ReportID,
const uint8_t ReportType,
const void* ReportData,
const uint16_t ReportSize)
{
uint8_t* Data = (uint8_t*)ReportData;
if (Data[0]) { // 送られてきたデータが0ならばLEDを消す、それ以外ならLEDを点ける
LEDs_TurnOnLEDs(LEDS_LED5);
} else {
LEDs_TurnOffLEDs(LEDS_LED5);
}
}
続いてDescriptorの定義を編集する
Vender ID、Device IDを主に編集した
#ifndef _DESCRIPTORS_H_
#define _DESCRIPTORS_H_
/* Includes: */
#include <avr/pgmspace.h>
#include <LUFA/Drivers/USB/USB.h>
#include "Config/AppConfig.h"
/* Type Defines: */
/** Type define for the device configuration descriptor structure. This must be defined in the
* application code, as the configuration descriptor contains several sub-descriptors which
* vary between devices, and which describe the device's usage to the host.
*/
typedef struct
{
USB_Descriptor_Configuration_Header_t Config;
// Generic HID Interface
USB_Descriptor_Interface_t HID_Interface;
USB_HID_Descriptor_HID_t HID_GenericHID;
USB_Descriptor_Endpoint_t HID_ReportINEndpoint;
} USB_Descriptor_Configuration_t;
/** Enum for the device interface descriptor IDs within the device. Each interface descriptor
* should have a unique ID index associated with it, which can be used to refer to the
* interface from other descriptors.
*/
enum InterfaceDescriptors_t
{
INTERFACE_ID_GenericHID = 0, /**< GenericHID interface descriptor ID */
};
/** Enum for the device string descriptor IDs within the device. Each string descriptor should
* have a unique ID index associated with it, which can be used to refer to the string from
* other descriptors.
*/
enum StringDescriptors_t
{
STRING_ID_Language = 0, /**< Supported Languages string descriptor ID (must be zero) */
STRING_ID_Manufacturer = 1, /**< Manufacturer string ID */
STRING_ID_Product = 2, /**< Product string ID */
};
/* Macros: */
/** Endpoint address of the Generic HID reporting IN endpoint. */
#define GENERIC_IN_EPADDR (ENDPOINT_DIR_IN | 1)
/** Size in bytes of the Generic HID reporting endpoint. */
#define GENERIC_EPSIZE 8
/* Function Prototypes: */
uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue,
const uint16_t wIndex,
const void** const DescriptorAddress)
ATTR_WARN_UNUSED_RESULT ATTR_NON_NULL_PTR_ARG(3);
#endif
#include "Descriptors.h"
/** HID class report descriptor. This is a special descriptor constructed with values from the
* USBIF HID class specification to describe the reports and capabilities of the HID device. This
* descriptor is parsed by the host and its contents used to determine what data (and in what encoding)
* the device will send, and what it may be sent back from the host. Refer to the HID specification for
* more details on HID report descriptors.
*/
const USB_Descriptor_HIDReport_Datatype_t PROGMEM GenericReport[] =
{
/* Use the HID class driver's standard Vendor HID report.
* Vendor Usage Page: 0
* Vendor Collection Usage: 1
* Vendor Report IN Usage: 2
* Vendor Report OUT Usage: 3
* Vendor Report Size: GENERIC_REPORT_SIZE
*/
HID_DESCRIPTOR_VENDOR(0x00, 0x01, 0x02, 0x03, GENERIC_REPORT_SIZE)
};
/** Device descriptor structure. This descriptor, located in FLASH memory, describes the overall
* device characteristics, including the supported USB version, control endpoint size and the
* number of device configurations. The descriptor is read out by the USB host when the enumeration
* process begins.
*/
const USB_Descriptor_Device_t PROGMEM DeviceDescriptor =
{
.Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device},
.USBSpecification = VERSION_BCD(1,1,0),
.Class = USB_CSCP_NoDeviceClass,
.SubClass = USB_CSCP_NoDeviceSubclass,
.Protocol = USB_CSCP_NoDeviceProtocol,
.Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE,
.VendorID = 0xF055,
.ProductID = 0x1234,
.ReleaseNumber = VERSION_BCD(0,0,1),
.ManufacturerStrIndex = STRING_ID_Manufacturer,
.ProductStrIndex = STRING_ID_Product,
.SerialNumStrIndex = NO_DESCRIPTOR,
.NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS
};
/** Configuration descriptor structure. This descriptor, located in FLASH memory, describes the usage
* of the device in one of its supported configurations, including information about any device interfaces
* and endpoints. The descriptor is read out by the USB host during the enumeration process when selecting
* a configuration so that the host may correctly communicate with the USB device.
*/
const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor =
{
.Config =
{
.Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration},
.TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t),
.TotalInterfaces = 1,
.ConfigurationNumber = 1,
.ConfigurationStrIndex = NO_DESCRIPTOR,
.ConfigAttributes = (USB_CONFIG_ATTR_RESERVED | USB_CONFIG_ATTR_SELFPOWERED),
.MaxPowerConsumption = USB_CONFIG_POWER_MA(100)
},
.HID_Interface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_GenericHID,
.AlternateSetting = 0x00,
.TotalEndpoints = 1,
.Class = HID_CSCP_HIDClass,
.SubClass = HID_CSCP_NonBootSubclass,
.Protocol = HID_CSCP_NonBootProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.HID_GenericHID =
{
.Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID},
.HIDSpec = VERSION_BCD(1,1,1),
.CountryCode = 0x00,
.TotalReportDescriptors = 1,
.HIDReportType = HID_DTYPE_Report,
.HIDReportLength = sizeof(GenericReport)
},
.HID_ReportINEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},
.EndpointAddress = GENERIC_IN_EPADDR,
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = GENERIC_EPSIZE,
.PollingIntervalMS = 0x05
},
};
/** Language descriptor structure. This descriptor, located in FLASH memory, is returned when the host requests
* the string descriptor with index 0 (the first index). It is actually an array of 16-bit integers, which indicate
* via the language ID table available at USB.org what languages the device supports for its string descriptors.
*/
const USB_Descriptor_String_t PROGMEM LanguageString = USB_STRING_DESCRIPTOR_ARRAY(LANGUAGE_ID_ENG);
/** Manufacturer descriptor string. This is a Unicode string containing the manufacturer's details in human readable
* form, and is read out upon request by the host when the appropriate string ID is requested, listed in the Device
* Descriptor.
*/
const USB_Descriptor_String_t PROGMEM ManufacturerString = USB_STRING_DESCRIPTOR(L"Dean Camera");
/** Product descriptor string. This is a Unicode string containing the product's details in human readable form,
* and is read out upon request by the host when the appropriate string ID is requested, listed in the Device
* Descriptor.
*/
const USB_Descriptor_String_t PROGMEM ProductString = USB_STRING_DESCRIPTOR(L"LUFA Generic HID Demo");
/** This function is called by the library when in device mode, and must be overridden (see library "USB Descriptors"
* documentation) by the application code so that the address and size of a requested descriptor can be given
* to the USB library. When the device receives a Get Descriptor request on the control endpoint, this function
* is called so that the descriptor details can be passed back and the appropriate descriptor sent back to the
* USB host.
*/
uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue,
const uint16_t wIndex,
const void** const DescriptorAddress)
{
const uint8_t DescriptorType = (wValue >> 8);
const uint8_t DescriptorNumber = (wValue & 0xFF);
const void* Address = NULL;
uint16_t Size = NO_DESCRIPTOR;
switch (DescriptorType)
{
case DTYPE_Device:
Address = &DeviceDescriptor;
Size = sizeof(USB_Descriptor_Device_t);
break;
case DTYPE_Configuration:
Address = &ConfigurationDescriptor;
Size = sizeof(USB_Descriptor_Configuration_t);
break;
case DTYPE_String:
switch (DescriptorNumber)
{
case STRING_ID_Language:
Address = &LanguageString;
Size = pgm_read_byte(&LanguageString.Header.Size);
break;
case STRING_ID_Manufacturer:
Address = &ManufacturerString;
Size = pgm_read_byte(&ManufacturerString.Header.Size);
break;
case STRING_ID_Product:
Address = &ProductString;
Size = pgm_read_byte(&ProductString.Header.Size);
break;
}
break;
case HID_DTYPE_HID:
Address = &ConfigurationDescriptor.HID_GenericHID;
Size = sizeof(USB_HID_Descriptor_HID_t);
break;
case HID_DTYPE_Report:
Address = &GenericReport;
Size = sizeof(GenericReport);
break;
}
*DescriptorAddress = Address;
return Size;
}
少し解説
以下の部分が、HIDデバイスのデスクリプタであり、デバイスが扱うHID Reportの意味や構造を定義している部分。
const USB_Descriptor_HIDReport_Datatype_t PROGMEM GenericReport[] =
{
/* Use the HID class driver's standard Vendor HID report.
* Vendor Usage Page: 0
* Vendor Collection Usage: 1
* Vendor Report IN Usage: 2
* Vendor Report OUT Usage: 3
* Vendor Report Size: GENERIC_REPORT_SIZE
*/
HID_DESCRIPTOR_VENDOR(0x00, 0x01, 0x02, 0x03, GENERIC_REPORT_SIZE)
};
HID_DESCRIPTOR_VENDORのマクロは以下のように定義されている
#define HID_DESCRIPTOR_VENDOR(VendorPageNum, CollectionUsage, DataINUsage, DataOUTUsage, NumBytes) \
HID_RI_USAGE_PAGE(16, (0xFF00 | VendorPageNum)), \
HID_RI_USAGE(8, CollectionUsage), \
HID_RI_COLLECTION(8, 0x01), \
HID_RI_USAGE(8, DataINUsage), \
HID_RI_LOGICAL_MINIMUM(8, 0x00), \
HID_RI_LOGICAL_MAXIMUM(8, 0xFF), \
HID_RI_REPORT_SIZE(8, 0x08), \
HID_RI_REPORT_COUNT(8, NumBytes), \
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), \
HID_RI_USAGE(8, DataOUTUsage), \
HID_RI_LOGICAL_MINIMUM(8, 0x00), \
HID_RI_LOGICAL_MAXIMUM(8, 0xFF), \
HID_RI_REPORT_SIZE(8, 0x08), \
HID_RI_REPORT_COUNT(8, NumBytes), \
HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE), \
HID_RI_END_COLLECTION(0)
- HID_RI_USAGE_PAGE(16, (0xFF00 | VendorPageNum)): Usage PageがVendorPageNum。ベンダー定義の場合、0xFFxxの形であれば何でもいい
- HID_RI_USAGE(8, CollectionUsage): UsageがCollectionUsage。ベンダー定義の場合、なんでもいい
- HID_RI_COLLECTION(8, 0x01): いくつかのプロパティをまとめるためのもの。まとめる基準を示すいくつかのプロパティがある。0x01はアプリケーションによる区分を意味する
- HID_RI_USAGE(8, DataINUsage): UsageがDataINUsage。ベンダー定義の場合、なんでもいい
- HID_RI_LOGICAL_MINIMUM(8, 0x00): 値の下限
- HID_RI_LOGICAL_MAXIMUM(8, 0xFF): 値の上限
- HID_RI_REPORT_SIZE(8, 0x08): 8bitのデータが
- HID_RI_REPORT_COUNT(8, NumBytes): num bytes個分
- HID_RI_INPUT(8, ): 入力に使用する構造であることを表す
- HID_IOF_DATA: HID_IOF_DATAは、書込み可能であることを意味する。書き込み不能はHID_IOF_CONSTANT
- HID_IOF_VARIABLE: HID_IOF_VARIABLEは、単一のデータであることを表す。反対はHID_IOF_ARRAYで、配列を意味する
- HID_IOF_ABSOLUTE: HID_IOF_ABSOLUTEは、絶対値であることを表す。反対は、HID_IOF_RELATIVEで、前回との差分であることを表す
- ...
- HID_RI_END_COLLECTION(0): コレクションを閉じる
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
0x09, 0x01, // Usage (Collection Usage 0x01)
0xA1, 0x01, // Collection (Application)
0x09, 0x02, // Usage (Data IN Usage 0x02)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x81, 0x06, // Input (Data, Variable, Absolute)
0x09, 0x03, // Usage (Data OUT Usage 0x03)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xFF, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x91, 0x0E, // Output (Data, Variable, Absolute, Non-Volatile)
0xC0 // End Collection
多分こんな感じに展開される。
詳しくはHIDのSpecを読もう。
最後に、1 byteのみを送受信するようにした
#ifndef _APP_CONFIG_H_
#define _APP_CONFIG_H_
#define GENERIC_REPORT_SIZE 1
#endif
ビルド
F7でビルドできる
以下のように、0 failedであればビルドは成功しているだろう。
Build succeeded.
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
書き込み
書き込みは、DFUモードを用いて行うことにした。遊舎工房のPro Microはresetを素早く2回行うとDFUモードに入る仕様のようだ。素早く、2回RSTと隣のGNDを接触させるとCOMポートデバイスとして認識される。10秒ほどするとDFUモードを抜けてしまうので、素早く書き込みを行わなければならない。
AVRにDFUモードで書き込むには、arduinoなどでお馴染みのavrdudeを使用する。
scoopを使うなり、インストーラを使うなりしてavrdudeをインストールする
scoopを使う場合は以下でインストールできる
scoop install avrdude
Microchip Studioでは、外部の書き込みコマンドを登録することが出来る
Title: AVRDUDE # 自分がわかれば何でもいい
Command: C:\Users\{UserName}\scoop\shims\avrdude.exe
Arguments: -p atmega32u4 -c avr109 -U flash:w:"$(TargetDir)$(TargetName).hex":i -P COM9 # 自分のCOMポートの値に設定する
Initial directory: C:\Users\{UserName}\scoop\shims\ # なんでもいい
以下の値を設定する。インストーラでインストールした場合は、適宜コマンドのパスを書き換える。(パスが通っていれば where.exe avrdude
などで探せるだろう。
COMポートの番号は、デバイスマネージャで確認すると良いだろう。
準備ができたら、RSTを2度GNDと接触させて、登録したAVRDUDEを実行する。
Erase,Write,Verifyが行われ以下のように出たら書き込みは成功だ。
avrdude: AVR device initialized and ready to accept instructions
avrdude: device signature = 0x1e9587 (probably m32u4)
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: processing -U flash:w:C:\Users\{UserName}\Documents\Atmel Studio\7.0\MyFirstHIDDevice\MyFirstHIDDevice\Debug\MyFirstHIDDevice.hex:i
avrdude: reading input file C:\Users\{UserName}\Documents\Atmel Studio\7.0\MyFirstHIDDevice\MyFirstHIDDevice\Debug\MyFirstHIDDevice.hex for flash
with 4898 bytes in 1 section within [0, 0x1321]
using 39 pages and 94 pad bytes
avrdude: writing 4898 bytes flash ...
Writing | ################################################## | 100% 0.36s
avrdude: 4898 bytes of flash written
avrdude: verifying flash memory against C:\Users\{UserName}\Documents\Atmel Studio\7.0\MyFirstHIDDevice\MyFirstHIDDevice\Debug\MyFirstHIDDevice.hex
Reading | ################################################## | 100% 0.03s
avrdude: 4898 bytes of flash verified
avrdude done. Thank you.
テスト、デバッグ
デバイスの認識の確認
USB Device Tree Viewerを使って、接続されているUSBの一覧を取得し、作成したデバイスを探す。
VID, PIDが一致するデバイスを見つけることができました
Host側受信テスト
WiresharkとUSBPcapをインストールします。
つながっているUSBツリーに該当するPcapのチャンネルを気合で探します。
ボタンを押すと1を、放すと0を返すデバイスが発見できました。
Host側送信テスト
libusb-win32をインストール
Zadigを使用して、ドライバを入れ替える
適当にスクリプトを書き、USBを介してLチカ出来ることを確かめます。
import usb.core
import usb.util
import usb.backend.libusb1
import time
def list_usb_devices():
backend = usb.backend.libusb1.get_backend()
devices = usb.core.find(find_all=True, backend=backend)
if devices is None:
print("USBデバイスが見つかりません")
return
for device in devices:
print(f"デバイス: {device}")
print(f" ベンダーID: 0x{device.idVendor:04x}")
print(f" プロダクトID: 0x{device.idProduct:04x}")
try:
manufacturer = usb.util.get_string(device, device.iManufacturer)
print(f" メーカー: {manufacturer}")
except usb.core.USBError:
print(" メーカー: 取得できません")
try:
product = usb.util.get_string(device, device.iProduct)
print(f" プロダクト: {product}")
except usb.core.USBError:
print(" プロダクト: 取得できません")
try:
serial_number = usb.util.get_string(device, device.iSerialNumber)
print(f" シリアルナンバー: {serial_number}")
except usb.core.USBError:
print(" シリアルナンバー: 取得できません")
print("")
def send_control_data(device, data):
bmRequestType = 0x21 # ホストからデバイスへのクラスリクエスト
bRequest = 0x09 # HID Set_Report リクエスト
wValue = 0x0200 # Report Type and Report ID (0x02 for output report, 0x00 for Report ID)
wIndex = 0 # インターフェース番号
device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, bytearray(data))
print(f"送信: {data}")
def main():
backend = usb.backend.libusb1.get_backend()
VID = 0xF055 # デバイスのベンダーID
PID = 0x1234 # デバイスのプロダクトID
dev = usb.core.find(idVendor=VID, idProduct=PID, backend=backend)
if dev is None:
raise ValueError("デバイスが見つかりません")
dev.set_configuration()
try:
while True:
send_control_data(dev, [1])
time.sleep(1)
send_control_data(dev, [0])
time.sleep(1)
except KeyboardInterrupt:
print("終了")
usb.util.dispose_resources(dev)
if __name__ == "__main__":
list_usb_devices()
main()
Vender IDの話
USBのVender IDの取得は、かなり大変らしい。今回は、個人的なテストのため、0xF055を使用させていただきました。
次回予告
Windowsのドライバを書いて、アプリケーションでInterruptを受け取れるようにします。