Вы находитесь на странице: 1из 6

LUFA LIBRARY

1 Lightweight USB Framework for AVRs (LUFA)


2 December 8, 2009
Lately Ive been killing off course requirements by taking project based
courses. Happily, Bart Massey offered a Linux device driver course that gave me an
opportunity to get my hands dirty in the embedded world again. The project was to
make an AVR based USB gadget do something anything. Most prior experience with
Cs was with Rabbits a fitting name seeing as the useful and higher level libraries
seemed to multiply like rabbits. Unfortunately, the AVRs didnt have any apparent high
level library for USB; many of the examples used commented byte arrays as USB
descriptors. While understandable, this feels harder to debug, ugly, and just generally
wrong. Enter LUFA.
LUFA
LUFA is a high-level USB library for AVRs generously released under MIT. It has
considerable documentation, but no real guide stating what the bare-minimum functions
are for any new/custom gadget this is my attempt at providing such a guide. After
you verify your AVR is supported by LUFA go ahead and download LUFA to an
includes directory for your project. All code here is made possible by LUFA and its
included examples Ill only claim to being the original author of any and all bugs.
My Device
Before going any further you should have an understanding of what your device will do,
how many and what type of USB endpoints it will use (BULK, INTERRUPT,
CONTROL, ISOCRONOUS), and what driver will support it. If you are making
something that will use a generic driver, such as a HID, then you probably can stop here
as there is plenty of code for you to use this post is aimed at people making custom
devices.
Im using a Teensy 2.0 with three bulk interfaces (two OUT one IN). One OUT
provides commands to the gadget, which will change its state. Another OUT will send
jobs to the gadget and the final IN will read the results of those jobs. This requires a
custom driver which I and several other folks developed for course work.
The Makefile
Its probably safe for you to rip-off one of the makefiles from an example in
LUFA/Demo/Devices/LowLevel/. Noitice several of the defines change the API,
defines I used for this post/project include:
-DUSB_DEVICE_ONLY # Saves space if you only need gadget-side
code
-DNO_STREAM_CALLBACKS # Changes the API to exclude any callbacks on
streams
-DINTERRUPT_CONTROL_ENDPOINT # So you don't have to call USB_USBTask()
manually on a regular basis
-DUSE_FLASH_DESCRIPTORS # Save RAM, leave these in the Flash
-DUSE_STATIC_OPTIONS="(USB_DEVICE_OPT_FULLSPEED | USB_OPT_AUTO_PLL)"
EDIT: All the makefiles Ive seen are broken, they dont properly track dependencies,
so either fix that or be sure to make clean prior to each build.
Enumeration
When your gadget is plugged in its going to have to enumerate its interfaces by sending
descriptors to the host. Start by declaring a global USB_Descriptor_Device_t along
with Language, Manufacturer and Product structures:
typedef struct {
USB_Descriptor_Configuration_Header_t Config;
USB_Descriptor_Interface_t Interface;
USB_Descriptor_Endpoint_t DataInEndpoint;
USB_Descriptor_Endpoint_t DataOutEndpoint;
USB_Descriptor_Endpoint_t CommandEndpoint;
} USB_Descriptor_Configuration_t;

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_BUSPOWERED,

.MaxPowerConsumption = USB_CONFIG_POWER_MA(100)
},

.Interface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type
= DTYPE_Interface},

.InterfaceNumber = 0,
.AlternateSetting = 0,
.TotalEndpoints = 3,

.Class = 0xff,
.SubClass = 0xaa,
.Protocol = 0x0,

.InterfaceStrIndex = NO_DESCRIPTOR
},

.DataInEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type
= DTYPE_Endpoint},

.EndpointAddress = (ENDPOINT_DESCRIPTOR_DIR_IN | BULK_IN_EPNUM),
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC |
ENDPOINT_USAGE_DATA),
.EndpointSize = BULK_IN_EPSIZE,
.PollingIntervalMS = 0x00
},

.DataOutEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type
= DTYPE_Endpoint},

.EndpointAddress = (ENDPOINT_DESCRIPTOR_DIR_OUT | BULK_OUT_EPNUM),
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC |
ENDPOINT_USAGE_DATA),
.EndpointSize = BULK_OUT_EPSIZE,
.PollingIntervalMS = 0x00
},

.CommandEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type
= DTYPE_Endpoint},

.EndpointAddress = (ENDPOINT_DESCRIPTOR_DIR_OUT | COMMAND_EPNUM),
.Attributes = (EP_TYPE_BULK | ENDPOINT_ATTR_NO_SYNC |
ENDPOINT_USAGE_DATA),
.EndpointSize = COMMAND_EPSIZE,
.PollingIntervalMS = 0x00
}

};

So now you see I have a device with a single interface and three bulk endpoints (two
OUT and one IN). Remember, in USB terminology OUT means from the HOST to the
GADGET while IN means from the GADGET to the HOST. While there is a direction
to these channels, USB 1.0 and 2.0 BULK communications start with the host sending a
message to the device (i.e. IN endpoints are polled) and the device should always
respond. Now that we have descriptors lets be sure to send them when the host tries to
enumerate the device.
uint16_t CALLBACK_USB_GetDescriptor
(const uint16_t wValue,
const uint8_t wIndex,
void **const DescriptorAddress)
{
const uint8_t DescriptorType = (wValue >> 8);
const uint8_t DescriptorNumber = (wValue & 0xFF);

void* Address = NULL;
uint16_t Size = NO_DESCRIPTOR;

switch (DescriptorType) {
case DTYPE_Device:
Address = (void*)&DeviceDescriptor;
Size = sizeof(USB_Descriptor_Device_t);
break;
case DTYPE_Configuration:
Address = (void*)&ConfigurationDescriptor;
Size = sizeof(USB_Descriptor_Configuration_t);
break;
case DTYPE_String:
switch (DescriptorNumber) {
case 0x00:
Address = (void*)&LanguageString;
Size = pgm_read_byte(&LanguageString.Header.Size);
break;
case 0x01:
Address = (void*)&ManufacturerString;
Size = pgm_read_byte(&ManufacturerString.Header.Size);
break;
case 0x02:
Address = (void*)&ProductString;
Size = pgm_read_byte(&ProductString.Header.Size);
break;
}
break;
}

*DescriptorAddress = Address;
return Size;
}

With this, and calling USB_Init() when your device starts up, you should have a gadget
that is properly enumerated by the matching driver. Once a configuration is selected we
need to configure the endpoints, but after that we can finally send and receive data!
void EVENT_USB_Device_ConfigurationChanged(void)
{
int i,succ=1;

succ &= Endpoint_ConfigureEndpoint(BULK_IN_EPNUM, EP_TYPE_BULK,
ENDPOINT_DIR_IN,BULK_IN_EPSIZE, ENDPOINT_BANK_SINGLE);

succ &= Endpoint_ConfigureEndpoint(BULK_OUT_EPNUM, EP_TYPE_BULK,
ENDPOINT_DIR_OUT,BULK_OUT_EPSIZE, ENDPOINT_BANK_SINGLE);

succ &=
Endpoint_ConfigureEndpoint(COMMAND_EPNUM,EP_TYPE_BULK,ENDPOINT_DIR_OUT,
COMMAND_EPSIZE,ENDPOINT_BANK_SINGLE);
LED_CONFIG;
while(!succ) {
LED_ON;
_delay_ms(1000);
LED_OFF;
_delay_ms(1000);
}
for(i = 0; i < 5; i ++) {
LED_ON;
_delay_ms(50);
LED_OFF;
_delay_ms(50);
}
}
Now to handle any data on the endpoints, youll probably want to tight loop around
code that checks each endpoint, reads data, and performs some action:
if (USB_DeviceState != DEVICE_STATE_Configured)
return;

Endpoint_SelectEndpoint(BULK_IN_EPNUM);
if (Endpoint_IsConfigured() && Endpoint_IsINReady() &&
Endpoint_IsReadWriteAllowed()) {
do_something(state, data, &len);
err = Endpoint_Write_Stream_LE((void *)data, len);
// FIXME handle err
Endpoint_ClearIN();
}

Endpoint_SelectEndpoint(BULK_OUT_EPNUM);
if (Endpoint_IsConfigured() && Endpoint_IsOUTReceived() &&
Endpoint_IsReadWriteAllowed()) {
err = Endpoint_Read_Stream_LE(data, len);
do_other_thing(data, len);
Endpoint_ClearOUT();
}

Endpoint_SelectEndpoint(COMMAND_EPNUM);
if (Endpoint_IsConfigured() && Endpoint_IsOUTReceived() &&
Endpoint_IsReadWriteAllowed()) {
usb_cmd_t cmd;
err = Endpoint_Read_Stream_LE(&cmd, sizeof(usb_cmd_t));
update_state(cmd);
Endpoint_ResetFIFO(BULK_IN_EPNUM);
Endpoint_ResetFIFO(BULK_OUT_EPNUM);
Endpoint_ClearOUT();
}And thats the whole deal. Some things to take note of:
1) I dont know why I needed Endpoint_ResetFIFO() my results lagged by one
message without them but I feel certain this is not the correct way to fix the issue.
Perhaps I need to reset the endpoints some time after configuration and before use, but
Im not sure which callback to use for that, if thats the case.
2) C needs phantom types. Many of these functions arent intended for use with
CONTROL endpoints. See the LUFA documentation for which functions to use if you
have non-BULK endpoints.
3) Be sure to number your ENDPOINTS starting at 1 (the *_EPNUM values). Ive seen
unusual behavior when numbering started higher not sure if that was against standards
or not.
Conclusion
At a minimum, you must declare a USB_Descriptor_Device_t variable, three
USB_Descriptor_String_t variables (Language, Manufacturer, Product), a custom
USB_Descriptor_Configuration_t structure and variable. Also declare a function
named CALLBACK_USB_GetDescriptor and one called
EVENT_USB_Device_ConfigurationChanged(void) which calls
Endpoint_ConfigureEndpoint(*_EPNUM, EP_TYPE_{BULK, INT, CONTROL,
ISOC}, ENDPOINT_DIR_{IN,OUT}, *_EPSIZE,
ENDPOINT_BANK_{DOUBLE,SINGLE}); for each endpoint in the configuration.
Finally use Endpoint_SelectEndpoint(*_EPNUM) followed by
Endpoint_Is{INReady,OutReceived}() && Endpoint_IsReadWriteAllowed() to select
an endpoint and check for data. Finish all this by reading your stream
Endpoint_Read_Stream_LE(buf, len) and issue a call to
Endpoint_Clear{IN,OUT}() when done.

Вам также может понравиться