/* ----------------------------------------------------------------------------
* ATMEL Microcontroller Software Support
* ----------------------------------------------------------------------------
* Copyright (c) 2008, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ----------------------------------------------------------------------------
*/
//------------------------------------------------------------------------------
/// \dir
///
/// !!!Purpose
///
/// This directory provides definitions, structs and functions for a USB HID
/// %device - USB HID Transfer driver, to implement an USB HID compatible
/// %device for customized data transmitting.
///
/// !!!Contents
///
/// There are three things for the implement of the USB HID Transfer driver:
/// - Implement the USB HID driver structs and functions for the %device,
/// to initialize, to handle HID-specific requests and dispach
/// standard requests in USBD callbacks, to read/write through assigned USB
/// endpoints,
/// - Create the HID Transfer device's descriptors that should be passed to
/// the USBDDriver instance on initialization, so that the host can
/// recognize the %device as a USB Transfer %device.
/// - Implement methods to read/write data through interrupt endpoints, so that
/// host and device can exchange data.
///
/// For more information about what a particular group contains, please refer to
/// "USB HID Transfer".
//------------------------------------------------------------------------------
/**
\page "USB HID Transfer"
This page describes how to use the "AT91 USB device framework" to produce a USB
HID Transfer driver, which appears as a USB HID complient device on host.
Details about the USB and the HID class can be found in the }USB specification
2.0} and the }HID specification 1.11}, respectively.
!!!References
- "AT91 USB device framework"
- "USB Device Enumeration"
-
Universal Serial Bus Revision 2.0 specification
(.zip file format, size 9.80 MB)
-
Device Class Definition for HID 1.11
-
HID Usage Tables 1.12
!!!HID Basic
See "USB HID Basic".
!!!Architecture
See "USB Device Framework Architecture".
!!!Descriptors
...
!!Device Descriptor
The Device descriptor of an HID %device is very basic, since the HID class
code is only specified at the Interface level. Thus, it only contains
standard values, as shown below:
\code
static const USBDeviceDescriptor deviceDescriptor = {
sizeof(USBDeviceDescriptor),
USBGenericDescriptor_DEVICE,
USBDeviceDescriptor_USB2_00,
HIDDeviceDescriptor_CLASS,
HIDDeviceDescriptor_SUBCLASS,
HIDDeviceDescriptor_PROTOCOL,
BOARD_USB_ENDPOINTS_MAXPACKETSIZE(0),
HIDDKeyboardDriverDescriptors_VENDORID,
HIDDKeyboardDriverDescriptors_PRODUCTID,
HIDDKeyboardDriverDescriptors_RELEASE,
1, // Index of manufacturer description
2, // Index of product description
3, // Index of serial number description
1 // One possible configuration
};
\endcode
Note that the Vendor ID is a special value attributed by the USB-IF
organization. The product ID can be chosen freely by the vendor.
!!Configuration Descriptor
Since one interface is required by the HID specification, this must be
specified in the Configuration descriptor. There is no other value of
interest to put here.
\code
// Configuration descriptor
{
sizeof(USBConfigurationDescriptor),
USBGenericDescriptor_CONFIGURATION,
sizeof(HIDDKeyboardDriverConfigurationDescriptors),
1, // One interface in this configuration
1, // This is configuration #1
0, // No associated string descriptor
BOARD_USB_BMATTRIBUTES,
USBConfigurationDescriptor_POWER(100)
},
\endcode
When the Configuration descriptor is requested by the host (by using the
GET_DESCRIPTOR command), the %device must also sent all the related
descriptors, i.e. Interface, Endpoint and Class-Specific descriptors. It is
convenient to create a single structure to hold all this data, for sending
everything in one chunk. In the example software, a
HIDDKeyboardDriverConfigurationDescriptors structure has been declared for
that.
!!HID Class Interface Descriptor
Since a keyboard %device needs to transmit as well as receive data, two
Interrupt (IN & OUT) endpoints are needed. This must be indicated in the
Interface descriptor. Conversely to the mouse example, the Boot protocol is
not implemented here, since there are more constraints on a keyboard %device.
\code
// Interface descriptor
{
sizeof(USBInterfaceDescriptor),
USBGenericDescriptor_INTERFACE,
0, // This is interface #0
0, // This is alternate setting #0
2, // Two endpoints used
HIDInterfaceDescriptor_CLASS,
HIDInterfaceDescriptor_SUBCLASS_NONE,
HIDInterfaceDescriptor_PROTOCOL_NONE,
0 // No associated string descriptor
},
\endcode
!!HID Descriptor
While a HID keyboard produces two different reports, one Input and one Output,
only one Report descriptor can be used to describe them. Since having Physical
descriptors is also useless for a keyboard, there will only be one HID class
descriptor specified here.
For a keyboard, the }bCountryCode} field can be used to specify the language
of the key caps. As this is optional, it is simply set to 00h in the example:
\code
// HID descriptor
{
sizeof(HIDDescriptor),
HIDGenericDescriptor_HID,
HIDDescriptor_HID1_11,
0, // Device is not localized, no country code
1, // One HID-specific descriptor (apart from this one)
HIDGenericDescriptor_REPORT,
HIDDKeyboardDriverDescriptors_REPORTSIZE
},
\endcode
!!Report Descriptor
Two current reports are defined in the Report descriptor. The first one is
used to notify the host of which keys are pressed, with both modifier keys
(alt, ctrl, etc.) and alphanumeric keys. The second report is necessary for
the host to send the LED (num lock, caps lock, etc.) states.
The Report descriptor starts with the global %device functionality, described
with a #Usage Page# and a #Usage# items:
\code
const unsigned char hiddReportDescriptor[] = {
HIDReport_GLOBAL_USAGEPAGE + 2, 0xFF, 0xFF, // Vendor-defined
HIDReport_LOCAL_USAGE + 1, 0xFF, // Vendor-defined
\endcode
An Application collection is then defined to group the reports together:
\code
HIDReport_COLLECTION + 1, HIDReport_COLLECTION_APPLICATION,
\endcode
The first report to be defined is the input report, all data in the buffer
is vendor defined:
\code
// Input report: Vendor-defined
HIDReport_LOCAL_USAGE + 1, 0xFF, // Vendor-defined usage
HIDReport_GLOBAL_REPORTCOUNT + 1, HIDDTransferDriver_REPORTSIZE,
HIDReport_GLOBAL_REPORTSIZE + 1, 8,
HIDReport_GLOBAL_LOGICALMINIMUM + 1, (unsigned char) -128,
HIDReport_GLOBAL_LOGICALMAXIMUM + 1, (unsigned char) 127,
HIDReport_INPUT + 1, 0, // No Modifiers
\endcode
The output report is then defined, data is for the user to decode:
\code
// Output report: vendor-defined
HIDReport_LOCAL_USAGE + 1, 0xFF, // Vendor-defined usage
HIDReport_GLOBAL_REPORTCOUNT + 1, HIDDTransferDriver_REPORTSIZE,
HIDReport_GLOBAL_REPORTSIZE + 1, 8,
HIDReport_GLOBAL_LOGICALMINIMUM + 1, (unsigned char) -128,
HIDReport_GLOBAL_LOGICALMAXIMUM + 1, (unsigned char) 127,
HIDReport_OUTPUT + 1, 0, // No Modifiers
\endcode
The last item, }End Collection}, is necessary to close the previously opened
}Application Collection}.
\code
HIDReport_ENDCOLLECTION
};
\endcode
The input report and output report are all user defined. We define the first
byte as bit map of push buttons and LEDs, remaining bytes as data.
!!Physical Descriptor
A Physical descriptor is useless for a general transfer %device, so none is
defined in this example.
!!Endpoint Descriptor
Following the Interface and HID-specific descriptors, the two necessary
endpoints are defined.
\code
// Interrupt IN endpoint descriptor
{
sizeof(USBEndpointDescriptor),
USBGenericDescriptor_ENDPOINT,
USBEndpointDescriptor_ADDRESS(
USBEndpointDescriptor_IN,
HIDDKeyboardDriverDescriptors_INTERRUPTIN),
USBEndpointDescriptor_INTERRUPT,
sizeof(HIDDKeyboardInputReport),
HIDDKeyboardDriverDescriptors_INTERRUPTIN_POLLING
},
// Interrupt OUT endpoint descriptor
{
sizeof(USBEndpointDescriptor),
USBGenericDescriptor_ENDPOINT,
USBEndpointDescriptor_ADDRESS(
USBEndpointDescriptor_OUT,
HIDDKeyboardDriverDescriptors_INTERRUPTOUT),
USBEndpointDescriptor_INTERRUPT,
sizeof(HIDDKeyboardOutputReport),
HIDDKeyboardDriverDescriptors_INTERRUPTIN_POLLING
}
\endcode
!!String Descriptors
Please refer to "Usage: USBD VID, PID & Strings".
!!!Class-specific requests
A driver request handler should first differentiate between class-specific and
standard requests using the corresponding bits in the }bmRequestType} field.
In most cases, standard requests can be immediately forwarded to the standard
request handler method; class-specific methods must be decoded and treated by
the custom handler.
!!GetDescriptor
Three values have been added by the HID specification for the #GET_DESCRIPTOR#
request. The high byte of the }wValue} field contains the type of the
requested descriptor; in addition to the standard types, the #HID
specification# adds the #HID descriptor# (21h), the #Report descriptor#
(22h) and the #Physical descriptor# (23h) types.
There is no particular action to perform besides sending the descriptor. This
can be done by using the USBD_Write method, after the requested descriptor has
been identified:
\code
switch (USBGenericRequest_GetRequest(request)) {
case USBGenericRequest_GETDESCRIPTOR:
// Check if this is a HID descriptor,
// otherwise forward it to
// the standard driver
if (!HIDDKeyboardDriver_GetDescriptor(
USBGetDescriptorRequest_GetDescriptorType(request),
USBGenericRequest_GetLength(request))) {
USBDDriver_RequestHandler(&(hiddKeyboardDriver.usbdDriver),
request);
}
break;
default:
USBDDriver_RequestHandler(&(hiddKeyboardDriver.usbdDriver),
request);
}
\endcode
A slight complexity of the GET_DESCRIPTOR and SET_DESCRIPTOR requests is that
those are standard requests, but the standard request handler
(USBDDriver_RequestHandler) must not always be called to treat them (since
they may refer to HID descriptors). The solution is to first identify
GET/SET_DESCRIPTOR requests, treat the HID-specific cases and, finally,
forward any other request to the standard handler.
In this case, a GET_DESCRIPTOR request for the Physical descriptor is first
forwarded to the standard handler, and STALLed there because it is not
recognized. This is done because the %device does not have any Physical
descriptors, and thus, does not need to handle the associated request.
!!SetDescriptor
This request is optional and is never issued by most hosts. It is not
implemented in this example.
!!GetReport
Since the HID keyboard defines two different reports, the Report Type value
specified by this request (upper byte of the }wValue} field) must be examined
to decide which report to send. If the type value is 01h, then the Input
report must be returned; if it is 02h, the Output report is requested:
\code
case HIDGenericRequest_GETREPORT:
//-------------------------------
type = HIDReportRequest_GetReportType(request);
length = USBGenericRequest_GetLength(request);
switch (type) {
case HIDReportRequest_INPUT:
// Adjust size and send report
if (length > sizeof(HIDDKeyboardInputReport)) {
length = sizeof(HIDDKeyboardInputReport);
}
USBD_Write(0, // Endpoint #0
&(hiddKeyboardDriver.inputReport),
length,
0, // No callback
0);
break;
case HIDReportRequest_OUTPUT:
// Adjust size and send report
if (length > sizeof(HIDDKeyboardOutputReport)) {
length = sizeof(HIDDKeyboardOutputReport);
}
USBD_Write(0, // Endpoint #0
&(hiddKeyboardDriver.outputReport),
length,
0, // No callback
0);
break;
default:
USBD_Stall(0);
}
break;
\endcode
!!SetReport
For an HID keyboard, the #SET_REPORT# command can be sent by the host to
change the state of the LEDs. Normally, the dedicated Interrupt OUT endpoint
will be used for this; but in some cases, using the default Control endpoint
can save some bandwidth on the host side.
Note that the SET_REPORT request can be directed at the Input report of the
keyboard; in this case, it can be safely discarded, according to the HID
specification. Normally, most host drivers only target the Output report. The
Report Type value is stored in the upper byte of the }wValue} field.
The length of the data phase to follow is stored in the }wLength} field of the
request. It should be equal to the total length of the Output report. If it is
different, the report status must still be updated with the received data as
best as possible.
When the reception of the new data is completed, some processing must be done
to enable/disable the corresponding LEDs. This is done in the callback
function passed as an argument to USBD_Read:
\code
case HIDGenericRequest_SETREPORT:
//-------------------------------
type = HIDReportRequest_GetReportType(request);
length = USBGenericRequest_GetLength(request);
switch(type) {
case HIDReportRequest_INPUT:
// SET_REPORT requests on input reports are ignored
USBD_Stall(0);
break;
case HIDReportRequest_OUTPUT:
// Check report length
if (length != sizeof(HIDDKeyboardOutputReport)) {
USBD_Stall(0);
}
else {
USBD_Read(0, // Endpoint #0
&(hiddKeyboardDriver.outputReport),
length,
(TransferCallback) HIDDKeyboardDriver_ReportReceived,
0); // No argument to the callback function
}
break;
default:
USBD_Stall(0);
}
break;
\endcode
!!SetIdle
In this case study, the #SET_IDLE# request is used to set a delay before a key
is repeated. This is common behavior on keyboard devices. Usually, this delay
is set to about 500 ms by the host.
The only action here is to store the new Idle rate. The management of this
setting must be done in the main function, since Interrupt IN reports are sent
from there.
In practice, it is not necessary to perform any action, apart from sending a
zero-length packet to acknowledge it. The main application however has to make
sure that only new reports are sent by the %device.
\code
case HIDGenericRequest_SETIDLE:
//-----------------------------
hiddKeyboardDriver.inputReportIdleRate =
HIDIdleRequest_GetIdleRate(request);
USBD_Write(0, 0, 0, 0, 0);
break;
\endcode
!!GetIdle
The only necessary operation for this request is to send the previously saved
Idle rate. This is done by calling the USBD_Write method with the one-byte
variable as its parameter:
\code
case HIDGenericRequest_GETIDLE:
//-----------------------------
USBD_Write(0, &(hiddKeyboardDriver.inputReportIdleRate), 1, 0, 0);
break;
\endcode
!!GetProtocol, SetProtocol
This HID keyboard example does not support the Boot protocol, so there is no
need to implement the SET_PROTOCOL and GET_PROTOCOL requests. This means they
can be safely STALLed when received.
!!!Main Application
Like the mouse example, the main program must perform two different
operations. First, it has to monitor the physical inputs used as keys. In the
example software, the buttons present on the evaluation boards are used to
produce several modifier and alphanumeric keys.
Also, the main program is in charge of sending reports as they are modified,
taking into account the Idle rate specified by the host. Idle rate management
can be carried out by firing/resetting a timer once a new report is sent; if
the timer expires, this means the Input report has not changed since.
According to the HID specification, a single instance of the report must be
sent in this case.
Finally, the HID specification also defines that if too many keys are pressed
at the same time, the %device should report an }ErrorRollOver} usage value
(01h) in every byte of the key array. This has to be handled by the main
application as well.
*/