This week I used the LUFA framework to implement a simple USB-Serial device. USB isn’t at the trivial end of the embedded system development spectrum and LUFA goes a long way to ease the pain but getting started can be difficult. Here goes the first blog post…
The starting point for my little project was a need to debug the message exchange between nodes in an XpressNet bus. XpressNet is a model railway control bus developed by Lenz Elektronik GMBH. It uses RS-485 level signals with 9-bit characters over an asynchronous serial line. One node, the controller, operates as the bus master. All other nodes operate as bus slaves and wait until polled before becoming active on the bus.
To monitor the bus the RS-485 level signal is routed through a level converter, a TI 75176, and then into an AVR ATMEGA32U4 microcontroller. The microcontroller attaches to the monitoring computer using USB. This article focuses on using the open source LUFA framework to bring XpressNet messages into the PC over a USB serial channel.
LUFA Sample Code
LUFA is an acronym for Lightweight USB Framework for AVRs and was written by Dean Camera. You install it into Atmel Studio, the Atmel IDE, using the Studio Extension Manager. Once installed the LUFA extensions become available through the ASF wizard. I’m no ASF expert but it appears to function as a large cut and paste database.
A good way to get started with LUFA is to create the USB to Serial example and read through the code. Start with File->New->Example Project…, expand the item “Four Walled Cubicle…” and select “USB to Serial Converter – AVR8 Architecture”. This creates a new project structure and copies all the required LUFA files into the selected directory. A quick note, Atmel Studio doesn’t like directory names with embedded spaces.
At this point it is possible to compile the sample code and download it to a suitable AVR device, in my case an ATMEGA32U4 breakout from Sparkfun. Am happy to report that it worked first time. Whilst I recommend reading the sample code it can be a little daunting, there is a lot of code and it can be difficult to know where to start.
The Monitor Project
I could have started with the Serial Converter example and changed it to fit my needs. However, this doesn’t really answer the question of how to add LUFA support. More importantly it doesn’t tell me what I need to do to get LUFA working with the minimal amount of code.
So step one was to create a new project using the New Project tool. In my case a C language executable targeting the ATMEGA32U4. Once created I then applied the ASF Wizard to add LUFA support. This can be a little tricky, the ASF Wizard will complain about a missing board. I found that by either cancelling the board dialog or accepting a board of ‘None’ would allow me to progress. You then need to select the FourWalledCubicle extension and then ‘Add’ the ‘LUFA USB Driver (driver)’ module. Clicking the ‘Apply’ button copies the relevant LUFA code to your project. You should have your main C source file in the project root plus the LUFA directory ‘src’. Clicking the ‘Build Solution’ button should result in compilation failure due to some undefined symbols (F_CPU and F_USB).
The LUFA documentation discusses a build system based on Make. I found I could configure the project using the standard Studio settings without resorting to a custom makefile and wonder whether Dean has done a better job of Studio integration that his documentation leads you to believe.
I got the project to compile successfully by defining the following symbols under the project properties (Toolchain->AVR/GNU C Compiler->Symbols):
From memory, items 1 & 2 were created by the ASF Wizard. The effect of USE_LUFA_CONFIG_HEADER is discussed in the next section. Items 3 & 4 must be added. The value here is the clock frequency in Hertz; the Sparkfun board comes fitted with a 16MHz crystal. The ARCH symbol isn’t strictly necessary, I found it defaults to ARCH_AVR8 somewhere in the LUFA framework. I prefer to specify these type of things upfront as it makes things a little easier to understand.
At this point the project should build cleanly.
The monitoring project is designed to operate as a USB device, over a fullspeed, i.e. 12Mb/s, connection. It logs a serial data stream so I decided to implement it as a communications device. The USB terminology is Communications Device Class or CDC device for short.
LUFA itself is designed to support multiple Atmel micro controller families and USB modes. Defining the symbol USE_LUFA_CONFIG_HEADER forces the LUFA framework to take its global settings from the configuration file src/Config/LUFAConfig.h. The sample below is abbreviated, anything not mentioned is to be assumed irrelevant and has been commented out the source file. Further information is found in the LUFA documentation under “Developing with LUFA/Summary of Compile Tokens”.
Copyright (C) Dean Camera, 2014.
dean [at] fourwalledcubicle [dot] com
* \brief LUFA Library Configuration Header File (Template)
* This is a header file which can be used to configure LUFA's
* compile time options, as an alternative to the compile time
* constants supplied through a makefile. To use this configuration
* header, copy this into your project's root directory and supply
* the \c USE_LUFA_CONFIG_HEADER token to the compiler so that it is
* defined in all compiled source files.
* For information on what each token does, refer to the LUFA
* manual section "Summary of Compile Tokens".
#if (ARCH == ARCH_AVR8)
#define USE_STATIC_OPTIONS (USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_AUTO_PLL)
#define FIXED_CONTROL_ENDPOINT_SIZE 8
#define DEVICE_STATE_AS_GPIOR 0
#define FIXED_NUM_CONFIGURATIONS 1
#elif (ARCH == ARCH_XMEGA)
... SNIP ...
#error Unsupported architecture for this LUFA configuration file.
Hardcodes the USB_Init function parameter.
|USB_DEVICE_ONLY||LUFA supports USB hosts and devices. The monitor operates as a USB device, only include that part of the framework.|
|USE_FLASH_DESCRIPTORS||Device descriptors are data structures used to control USB operation. See Part 2 for descriptor discussion. Placing the descriptors in program Flash makes them read-only but saves on RAM.|
|FIXED_CONTROL_ENDPOINT_SIZE||Controls the buffer size allocated to the control endpoint (Endpoint 0)|
|DEVICE_STATE_AS_GPIOR||Some AVR microcontrollers provide user controlled registers in their IO map. Selects GPIO register 0 to hold the USB state|
|FIXED_NUM_CONFIGURATIONS||Only one USB configuration exists|
|INTERRUPT_CONTROL_ENDPOINT||Allows the function USB_USBTask() call to be managed be the LUFA framework. User does not need to call|