We are three Juniors in Cornell University's College of Engineering. Ben "For Some Reason I Like Medicine-Tasting Energy Drinks Now" Hutton and Devrin "The Assembler" Talen are Electrical and Computer Engineering majors. Chris "Interrupt-less" Leary is an ECE and Computer Science double major.
The prominence of microcontroller-based embedded design and a steadily increasing base of USB peripherals provided the impetus for this project. Previous implementations of USB software solutions have seen a great deal of success on more powerful microcontrollers[1], and it was hypothesized that a scaled down version of such an implementation would be possible using Atmel's 8-bit ISA. A fully functional Low Speed USB Host Controller was created for the Atmel Mega32 according to ANSI C99, with the exception of the carefully assembled Serial Interface Engine, which was written in Atmel's 8-bit assembly language. From this code base, USB communication was established, and an (ANSI C99) API was created for peripheral utilization. These results demonstrate the propensity for any Low Speed device's nearly seamless use in the AVR programming environment. This project was also notably successful in achieving goals of hardware simplicity, unobtrusiveness when used as a software library, complete originality of code, and utilization and production of solely open source software.
1. Jungo Software extended its USB software stack implementation to include Atmel's ARM7 and ARM9 microcontrollers, which use a 32-bit ISA. This implementation runs at Full Speed (12Mb/s).
A software implemented USB 2.0 Host Controller was created for the Atmel Mega32 microcontroller.
This project has yielded a library that can be included from any program written for the Mega32. By using this library, a user can implement USB functionality with their application with minimal fuss. Several compiler options are included for various levels of debugging and verbosity.
Documentation supplied by the USB Implementers Forum's website and various summary articles allowed for the development and implementation of the USB protocol stack for the Atmel Mega32. We broke down the protocal into layers of abstraction, implementing one on top of the other. We designed the software such that any layer (coupled with those beneath it) is able to handle USB communication.
The goal of our project was to create a software library for any Atmel AVR series microprocessor. The software library is implemented as an interface to a USB port that accepts "transactions." These transactions are passed to the USB Interface, which handles converting them to valid bus traffic. This traffic is then sent out via the Serial Interface Engine (SIE). The SIE also handles reading traffic back from a device, and reports this to the Interface. The Interface modifies the transaction passed to it, and this is returned back to the client program. In this manner, our library abstracts the process of sending and receiving USB data.
We built a Low Speed USB Transceiver according to USB spec. We used an Atmel Mega32 microcontroller plugged into an STK500 development board. Using our program, one of the ports on the Mega32 is dedicated to USB functionality, and is hooked up to the transceiver. Of this port, 5 pins are used:
Pin0 | Tx- |
Pin1 | Tx+ |
Pin2 | Output Enable |
Pin6 | Rx- |
Pin7 | Rx+ |
The goal of the transceiver hardware is to combine the Rx and Tx lines into one bi-directional set of lines: D+ and D-, which operate using differential encoding.
It is possible to connect the Rx pins directly to D+ and D- as they aren't trying to change the value on the lines - just read them. The Tx pins must be connected through SN74126N tri-state buffers so that they can be disconnected from D+ and D- when they aren't transmitting - otherwise they'll try to drive the line to their own values. The tri-state buffer essentially inserts an open circuit, and are controlled (active low) by an output enable pin - Pin2.
The USB spec requires both the D+ and D- lines be pulled to ground through 15 kOhm resistors right before they enter the USB cable. Also included in the cable are Vbus (5V) and Gnd lines, which are connected to the STK500's port's Vtg and Gnd pins, respectively.
Our hardware design is meant to be easily replicable in lab (following the wiring and block diagrams in the Schematics Appendix) - it is composed of parts acquired entirely from the lab, with the exception of the USB port - so that our code could be potentially used in future projects and or laboratory assignments.
The driver is the client program that uses the USB library. This is the layer that creates transactions to send to the USB Interface.
The USB Interface is the bridge between the client program (driver) and the Serial Interface Engine (SIE). It is the Interface that turns the data expressed in the packets of a given transaction into USB traffic that the SIE can understand. In its operation, it accomplishes several large tasks:
First, the USB Interface manages transactions that are passed to it. To make using USB as seamless as possible, the interface will modify the transaction passed to it and return this to the client. This allows both the client and the interface to keep track of several transactions at a time. After the interface has used the SIE to communicate with the USB device, it will store the results of the transfer into the transaction that it was processing. This saves on memory usage, as only a minimum number of transactions need to be created to accomplish communication. This also gives rise to the way in which transactions are passed to the interface. The interface maintains a FIFO stack of pending transactions. New transactions are pushed onto the stack. When the client wants a given transaction to be run, it pops the oldest transaction off the bottom of the stack. This is useful for queueing a complicated series of transactions (such as receiving a device descriptor). The client creates the transactions beforehand, pushes them into the interface, then pops them off as it is ready to process them.
The second major design aspect of the USB Interface is that it communicates with the SIE and readies all data passed to it for USB traffic. This involves three major steps:
The lowest level of software is the Serial Interface Engine (SIE). This level is implemented in assembly code due to timing constraints. Creating and maintaining a USB low-speed link requires being able to transmit and receive data at 1.5 Mb/s. By using a 15 MHz crystal (rather than the usual 16 MHz), the SIE achieves this by transmitting and receiving once every 10 cycles.
The SIE sends and receives transactions. A transaction is comprised of three packets: a token packet, a data packet, and a handshake packet. Depending on the type of transaction, these will be transmitted/received by either the USB host or the function (device). The SIE must make decisions on whether to transmit or receive the data in the space of a few cycles before it must react to data, or send it. Once the SIE has finished the transaction, it stores all the results back into a single location for the Host Controller to handle.
The SIE was broken up into macros that were units of functionality. Several parts of the SIE were similar, such as sending a sync field, or the process of sending bits of the token packet. To improve code readability and maintainability, we created macros to do these parts. This idea is recursed, and several macros use other macros within themselves. Overall, the SIE is a piece of tightly optimized assembly code that bit blasts buffers efficiently and receives (and stores) data quickly as well.
Our USB Host Controller successfully established communication with a low-speed mouse. Our library is easy to include in any existing program, and requires minimal setup. Being USB, however, and implementing the full specifications for low-speed devices means that significant amounts of time can be spent processing the USB data, and the program must concede control to the USB framework when trying to communicate to a device. At other times, however, keeping USB communication active, however, requires no upkeep. This makes our USB library an ideal fit for any light- to middle-weight program on an AVR microprocessor that requires USB communication.
When we set out to do this project, we both foresaw the difficulty that implementing USB entailed as well as envisioned the potential utility of as USB Host Controller driver for the Atmel Mega32. Implementing the HC consumed all of our available time, and while we were able to get a USB mouse working as a demonstration, we did not have time to implement anything else. In addition to mice, USB keyboards could be easily made to work with the Mega32. Also, flash drives and card readers could be added to increase storage, to allow the Mega32 to operate on larger sets of data. To do this, some sort of storage driver (such as FAT32) would also need to be written.
We began a rather thorough code re-write a few days before the project was due that we were unable to complete on time. Were we to do this project over again, or to continue working on it, we would continue with the direction we had been going, implementing the Open Host Controller Interface. Also, one key hardware lesson we learned is to not rely on whiteboards for high-frequency signals - capacitance within the board made device response very flaky.
We set out to implement a subsection of a very large, very well-defined standard, from the onset knowing that we could never comprehensively implement it, yet attempting to remain faithful to its details. We fall short of USB2.0 in several significant ways. First, we only support low-speed devices, which restricts us to only Control and Interrupt transfers. Second, we only support one device at a time.
Safety was very easy to maintain within our design. We have very little actual hardware, so very little to risk injuring ourselves with. All currents and voltages are very low. We don't have an RF transmitter, so we won't interfere with other projects or devices. Our code is meant to be usable by other people, potentially others in this class, as a framework to utilize USB devices in Mega32 projects.
Measured | Spec | |
Pulse Width: | 660 ns | 666ns |
D+ Rise Time: | 30 ns | 75ns-300ns |
D+ Fall Time: | 18 ns | 75ns-300ns |
D+ 1 Average Value: | 3.06 V | >2.8V |
D+ 0 Average Value: | -0.031 V | <0.3V |
D- Rise Time: | 40 ns | 75ns-300ns |
D- Fall Time: | 26 ns | 75ns-300ns |
D- 1 Average Value: | 3.15 V | >2.8V |
D- 0 Average Value: | 0.125 V | <0.3V |
Incremental changes to software were run through regression testing, set up to ensure that previously working functionality did not break as new code was added. As the code was a protocol stack with layers of abstraction, each layer could be tested and verified independently.
Getting a mouse to work with our USB driver is not difficult, and example code has been provided to demonstrate. A sequence of Standard Requests and resets must be issued to the mouse to get it into a state where it will begin giving output on its IN Endpoint 1.
Reproduced below is the IEEE code of ethics, a document that serves to guide the ethical conduct and decision-making of electrical engineers during the course of their design and fabrication work. It thus serves as the ethical guideline by which projects in ECE476 are conducted.
We, the members of the IEEE, in recognition of the importance of our technologies in affecting the quality of life throughout the world, and in accepting a personal obligation to our profession, its members and the communities we serve, do hereby commit ourselves to the highest ethical and professional conduct and agree:
Approved by the IEEE Board of Directors February 2006
Almost all of our project was in software. For what hardware fabrication was necessary, all specified precautions were taken. Specifically with regards to soldering, proper eye protection was worn at all times and irons were kept at reasonable temperatures.
There were no conflicts of interest present in our project, in any form. We are not beholden to any outside group or company in any way, nor did we encounter or deal with bribery. We did not encounter any situations where we noticed we were treating anyone unfairly, nor did we harm or injure anyone or anything by our actions.
This project was undertaken knowing that it would stretch our skills and abilities to their limits, forcing us to improve our technical compentence in the area of hardware/software interfacing, object-oriented c programming, assembly programming, protocol stack design and implementation, specification reading and comprehension, and project management. This project, though submitted for a grade and licensed for future use, was mainly undertaken for ourselves - we are the ones who benefit from the time and effort put into learning these things and improving our technical competencies in these areas. Working in a group of three, each of us having different strengths and specialties allowed us to learn from each other, assessing and critiquing each other's technical work and offering improvements.
All code is written by us with a very few exceptions where noted. As we were implementing a specification (USB2.0), a significant amount of our design came from its technical documentation.
Almost all of our code (>99%) was written by us. We borrowed CRC5 code from http://www.lvr.com/files/usb_crc.c and adapted it, using instructions in www.usb.org/developers/whitepapers/crcdes.pdf.
The USB2.0 Specification is provided at http://www.usb.org/developers/docs/ with the following disclaimer:
An adopter's agreement is available at http://www.usb.org/developers/docs/adopters.pdf, used to enter into a crosslicensing agreement with other USB implementers. We did NOT fill out this agreement.
We aren't using any transmitter, and we aren't using any code or specification that requires licensing. For us to legally use the USB logo on any of our material, we would need to either pay a $2000 fee or join the USB Implementers Forum. We haven't, so we aren't using the logo.
We are licensing our code under the GPL (see License section). You may feel free to use it, according to the terms and conditions of the license.
The USB Protocol is a well-defined stacked protocol consisting of several layers of abstraction and data encapsulation. USB sends data in transactions composed of several packets each.
At the lowest layer, a Transceiver and Serial Interface Engine translate the bits in a packet into the proper format to send on the electrical bus, using NRZI Encoding, Bit Stuffing, and Differential Encoding.
There are several different transaction types and several different packet types. The Host Controller handles packet, transaction, and transfer formation. There are four type of transfers, though our low-speed USB implementation only handles the first two:
Each transfer is executed using transactions composed of several common packet types:
Each packet is composed of several common field types. Some typical packet fields are:
Every USB device is composed of one or more "USB Functions", which contain one or more "Endpoints", which are connected to client software through one of two types of "Pipes":
The four transfer types (Control, Interrupt, Isochronous, and Bulk) form the foundation for data movement across the USB. Our low-speed USB implementation, as specced, implementes Control and Interrupt transfers.
Control Transfers are used to configure devices, exchanging setup data between the device and the host controller. They consist of three stages - Setup, Data, and Status.
Interrupt Transfers are similar to microcontroller interrupts in that they happen at non-regular intervals - they are a function of the needs of the device. They differ, though, in that the devices can't actually "interrupt" the Host Controller - these transfers must be polled to be read. They have a guaranteed latency, they are uni-directional (using a stream pipe), they incorporate error detection (and will be retried at the next poll if an error is found), and, in our low-speed implementation, can have a maximum data payload of 8 bytes. They consist of a single stage, either IN or OUT.
USB Devices use what are called descriptors to convey a variety of information to the Host. There are several different types of descriptors, arranged hierarchically. The common types are as follows:
Control Transfers are used to implement various types of requests, getting and setting device configuration data. For each of them, the data payload (in the Data stage of the transfer) varies, conveying the request type and various values associated with the request. Request types can be broken down into three categories, by which Descriptor they relate to: Device Requests, Interface Requests, and Endpoint Requests.
Device Requests
Interface Requests
Endpoint Requests
STK500 w/ Mega32 | $15 |
6" Solder Board | $2.50 |
USB Header | $0.86 |
2x DIP Socket | $1 |
15MHz Crystal | $0.94 |
DM74126N Hex Tri-state Buffer | Scavanged in lab |
2x 15kOhm Resistors | Scavanged in lab |
Total | $20.30 |
This code is released under the GNU Public License.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA