USB is a ubiquitous protocol that enables a nearly endless variety of peripheral devices to be attached using the same connectors and communication protocol. However, this openness makes it difficult to implement on microcontrollers due to the higher overhead compared to something like I2C or SPI.
Microcontrollers contains specialized blocks of hardware called peripherals - peripherals are designed to offload tasks from the CPU, improve system performance, and/or implement functions that are not normally possible (ex: Analog-to-Digital Conversion). The AVR® DU family of microcontrollers are 8-bit microcontrollers with a USB Full Speed (12 Mbps) transceiver on-board. peripheral handles a lot of the USB protocol, but there is still a need for software to manage the USB bus. As part of MPLAB® Code Configurator (MCC), a free GUI based configuration tool for Microchip microcontrollers, there is a USB Stack Library which handles almost all of the tasks required to implement a USB device.
Out of the box, the library supports Communication Device Class (CDC),Human Interface Devices (HID) or a custom vendor class. An example of a CDC class device is a USB-UART bridge, which allows serial communication with a microcontroller from a PC. These devices can be found as a standalone Application Specific Integrated Circuit (ASIC) or as part of another device, like the programmer on the Curiosity Nano Evaluation Boards.
HID class devices are devices to allow humans to interact with a computer – examples of these devices include keyboards, mice and joysticks. The library supports the keyboard and mouse protocols in USB to allow the microcontroller to send keypresses or mouse movement data to the computer. In this example, the microcontroller sends key macros (CTRL + C, CTRL + V, CTRL + Z, CTRL + X, ALT + F4) and multi-key sequences (spelling out “AVR DU”) to demonstrate the use of this library.
AssemblyTo build this keypad, an AVR64DU32 Curiosity Nano Evaluation Kit (EV59F82A) is inserted into a Curiosity Nano Base for Click boards™ (AC164162). Note – when inserting the Curiosity Nano into the socket, make sure the programmer USB is facing outwards (see image below). The VUSB jumper should be connected, since the Curiosity Nano Base forces the Curiosity Nano to run at 3.3V, rather than the 5V required for the internal regulator.
Then, put a 2x2 Click (MIKROE-2152) from MikroElektronica into Slot 1. This board provides 4 extra buttons of input. Note: the 2x2 Click debounce time is in 100s of ms, so button tapping is not supported on these buttons.
As an optional extra, an external button or switch can be connected to AN2 (PD4). If the switch is NC (Normally Closed), rather than NO (Normally Open), set the macro `EXTERNAL_BUTTON_NC`. This is not set by default. In this setup, a large E-Stop button is used for this external input. Since the key handling state machine prevents duplicate inputs from being registered, latching buttons are compatible with this application.
ProgrammingTo program without downloading MPLAB® X IDE, go to Github → Releases and download the zip file containing hex files. Then, drag and drop the hex file (free or pro*) onto the Curiosity Nano from File Explorer. The board will program the MCU without the need for MPLAB X IDE and XC8 Compiler. *Pro is pre-compiled using the high-level optimizations in the compiler for smaller, faster code. Functionality is the same.
1. Download a copy of the source code from Github.
2. Unzip the files.
3. Plugin the Curiosity Nano on the Programmer Side.
4. Open MPLAB X IDE.
5. Go to File → Open Project… and select the folder ending in.X (avr64du32-keypad-mplab-mcc.X).
6. Press the program button in the top toolbar (arrow into Microcontroller, see image below).
7. When prompted, select the Curiosity Nano from the available tools list.
In total, there are 6 buttons (5 required, 1 optional) in this example, plus an LED. The LED follows the state of CAPS LOCK of the system.
- SW0 on the Curiosity Nano – print “AVR DU”
- Button 1 on 2x2 Click – CTRL + C
- Button 2 – CTRL + V
- Button 3 – CTRL + Z
- Button 4 – CTRL + X
- External Button (optional) – ALT + F4
For a detailed overview of USB 2.0, please see the “Introduction to USB 2.0” Microchip University course (free, login required). The USB 2.0 specification is available for free online as well.
Key HandlingKey presses are handled in a simple state machine. The state machine is required because physical buttons electrically bounce internally when pressed or released. Additionally, this state machine ensures each button press is registered only once, rather than a continuous stream of inputs. The state machine is called every 5 ms. This is done for two reasons. The first is that button bounces are usually a few milliseconds long, so by polling them relatively slowly, the bounces become invisible to the microcontroller. Secondly, USB 2.0 runs on a 1 ms cycle, which limits how fast a key press can be reported.
The state machine starts in the NOT_PRESSED state. When a button is detected as “pushed”, the appropriate key commands are loaded into a KeyReport structure and a flag is set to indicate the report should be sent. The state machine transitions to a PRESSED state.
In the PRESSED state, the report is cleared and the transmit flag is set again. By removing the key down events in the report, the USB host interprets this as the key having been released. The state machine transitions to a HELD_WAIT state.
In HELD_WAIT, the state machine waits for all of the keys to be released. Once this occurs, the state machine resets to the NOT_PRESSED state.
LED Report ProcessingKeyboards receive information about flags like CAPS LOCK either through an OUT endpoint or through a report on the CONTROL endpoint, if no OUT endpoint exists. By default, the library does not initialize an OUT endpoint (although the Interface configuration can be modified in a few clicks), so this application will get data through the reports sent to CONTROL.
The reports are handled in main.c by the handleUSBReport function.
//Bitmask of USB Report
#define USB_NUM_LOCK_bm (0b1 << 0)
#define USB_CAPS_LOCK_bm (0b1 << 1)
#define USB_SCROLL_LOCK_bm (0b1 << 2)
#define USB_COMPOSE_bm (0b1 << 3)
#define USB_KANA_bm (0b1 << 4)
//Handle USB Reports
void handleUSBReport(uint16_t report)
{
printf("%x\r\n", report);
//If there is no output endpoint, CAPS LOCK/SCROLL LOCK/NUM LOCK/COMPOSE/KANA are reported here
//[7:5] Constants
//[4] Kana
//[3] Compose
//[2] Scroll Lock
//[1] Caps Lock
//[0] Num Lock
LED0_SetLow();
uint8_t keyMap = report & 0xFF;
if (keyMap & USB_NUM_LOCK_bm)
{
//Num Lock
}
if (keyMap & USB_CAPS_LOCK_bm)
{
//Caps Lock
LED0_SetHigh();
}
if (keyMap & USB_SCROLL_LOCK_bm)
{
//Scroll Lock
}
if (keyMap & USB_COMPOSE_bm)
{
//Compose
}
if (keyMap & USB_KANA_bm)
{
//Kana
}
}
ConclusionWhile USB is a commonly used communication protocol, it is complex to develop for. The AVR DU family softens this barrier to entry with its on-chip USB peripheral hardware and the USB Stack Library in MCC. A copy of the USB 2.0 specification is available online.
Comments
Please log in or sign up to comment.