Photo of Dr. S. Salewski

Homepage of Stefan Salewski

Free USB firmware for Micro-Controller

Nearly every modern PC is equipped with USB sockets today, which are used for the connection of peripheral devices. The USB also offers itself for the connection of various microcontroller circuits, from simple experimentation boards to professional devices for measuring-, control- and regulation tasks. Compared to the traditional serial RS-232 (UART) port and the parallel printer port, the USB is characterized by simpler wiring, higher data transmission rate and integrated power supply. Contrary to the mentioned conventional interfaces, for the USB firmly defined protocol- and transmission-modes are defined, which are to be kept strictly.

Starting first with the principles of USB, you might be deterred by it's complexity. (The free accessible USB specification, available at USB Implementers Forum, has 650 pages.) This leads often to the fact that either outdated interfaces are used further, or that the complexity of the USB is covered by the use of blown up software libraries. Such libraries (or example applications) are offered e.g. by the manufacturers of USB chips or together with experimentation boards -- often however only as binary files (DLL) for a specific operating system, or with a very strict copyright license, which restricts the distribution of the software. In this case releasing of the developed software together with all source code under the GPL or other free software licenses is usually not permitted, so that further development of the software or adaption to different controllers by other developers is practically impossible. Unfortunately for some commercially distributed devices the concrete realization of the firmware-interface is not indicated also, so that these devices can only be used with vendor supplied device drivers on specific operating systems.

Hence for some time different projects exists, which try to develop free, open source USB firmware (sometimes called USB device stack) for various micro-controllers. For instance the usbn2mc-project of Mr. B. Sauter, which uses the USB-Bridge-Chip USBN9604 of National-Semiconductor in order to extend arbitrary micro-controllers with USB functionality. For this project already many interesting applications in alliance with Atmels AVR controllers are available. Also very interesting is the concept of the Porus-Project, which employs a script driven code generator together with application specific configuration files to generate firmware code mostly automatically.

With the LUFA-project (formerly MyUSB) of Dean Camera now one more free USB firmware for AVR controllers with USB interface (AT90USB...) is available. That one is more elaborated than my own firmware, i.e. his firmware supports HID protocoll, the smaller AT90USB162 devices and has support for Windows OS. Unfortunally that project did not exist when I wrote my own firmware in the year 2006. (Just using that one would have saved me a lot of work.) I do not think that you should prefer the LUFA software always, but at least you should visit the project page. (I have not found enough time myself to study that project, and I guess that I will prefer my own firmware further.) Here is the project page of Dean Camera:

Free (GPL) USB-Firmware for Atmel's AT90USB Micro-Controller

The controller AT90USB is available in version 1287 since summer 2006 in larger quantities -- further exists the subtypes 1286 (without host functionality) and subtypes 646 and 647 with halved program memory (FLASH) sizes. So far I have used only type 1287 in device mode, however the firmware should be compatible with the other types. (Since spring 2007 two smaller devices are available too: AT90USB82 and AT90USB162. I have not investigated these devices until now -- my firmware may need modifications to support these parts.)

The AT90USB1287 device is part of the AVR family and can be considered as an ATmega128 extended with USB functionality, thus a 8-bit processor with 128 kByte program memory (Flash), 8 kByte main memory (RAM) and up to 16 MHz clock frequency. Its architecture allows executing of most instructions in only one clock cycle, so that it is fast enough for many applications. The AT90USB supports USB Low- and Full-Speed (no High-Speed) with data transfer rates up to 1 MByte/s, which is enough for most applications. The AT90USB has, like other AVR devices, many additional functionality integrated on the chip -- this includes USART, 10-Bit-ADC, different Timers and a Comparator. The chip can be programmed up to 100,000 times via ISP (In-System-Programmer) -- alternatively programming can occur over the USB port using the factory supplied USB-Bootloader. With the compiler avr-gcc, the programming software avr-dude and the tool dfu-programmer for firmware upload via USB and many other tools all necessary software is available for GNU/Linux in free, open-sourced shape. (For Windows similar tools, are available, supported by a programming environment called AVR-Studio made available free of charge by Atmel.

The AT90USB1287 is only available in TQFP- or QFN-packages, which makes it nearly impossible to use this device in the hobby area together with breadboards or stripboards. But soldering devices in TQFP case is not too difficult, if you have a good soldering iron and a suitable printed circuit board on-hand.

People with little experience with electronics and soldering can get access to this controller by using an experimentation board called STK525 or a very small board called USBKEY, both available from Atmel.

I have made my own board for the AT90USB -- schematics and board layout is available at

Structure of the USB-Firmware

Firmware and example application are programmed in C language completely. Compared to assembly language this improves the clarity and makes modifications or extensions of the code or a transfer to other controller families easier. The code generated by avr-gcc has high quality, so that porting to assembly language may be useful only for time critical code segments. (These code segments may be the writing or reading of the USB buffer (FIFO).) (If you compile the example application with deactivated debugging code, the generated code will occupy only 4 kByte of program memory of the AT90USB.) The complete enumeration process and processing of USB standard device requests is supported by the firmware. Class specific requests, the transition to sleep modes with associated re-awaking and the awaking of the host trough the device is not implemented yet. Host functionality (USB OTG) is still not supported.

The PC example application is written in C too, communication with the device is managed by using functions of the library LibUSB. Tests of the firmware were accomplished so far exclusively in connection with Linux (Gentoo for AMD64) -- since the firmware is programmed according to the USB standard, communication with the PC should succeed also if it is running another operating system or if another programming language is used. (So far I always compiled the firmware with release 3.4.6 or 4.1.2 of avr-gcc; it may be possible that small modifications in the code are necessary for other releases of avr-gcc.)

Since driver programming under Microsoft operating systems is not easy and often is not possible without employment of additional software packages, the HID device class is often used there for communication with USB devices. With the employment of this device class no special device drivers are necessary, however communication may be restricted to USB Low-Speed. At the present time special device classes like HID are not supported by the firmware, but extension with this functionality is possible. However there exists a special version of the LibUSB library for Microsoft operating systems, which can be used as an alternative to the HID device class.

Overview of the files

In the following the source text files of the firmware and of the example application are listed in alphabetical order:

com_def.h      Constants used by the Device- and Host-Software commonly  
daq_dev.c      Timer-controlled data recording using the intern ADC (Port ADC0)
daq_dev.h
defines.h      Project-specific constants
macros.h       A few useful macros
ringbuffer.c   Simple ring buffer (FIFO) for buffering measurement data 
ringbuffer.h
SUDAP.c        PC application: Shell-Program takes command line parameter and outputs measurement data
SUDD.c         Application on device side: A signal is digitized and send to the PC
usart_debug.c  Output of status information over serial port
usart_debug.h	
usart_drv.c    Functions for the output of text or hexadecimal numbers over the serial port
usart_drv.h
usb_api.c      Application specific parts of the USB protocol
usb_api.h      
usb_drv.c      Elementary, controller specific USB-functions
usb_drv.h      Macros for setting and reading USB-registers
usb_isr.c      USB-specific interrupt-service-routines
usb_requests.c USB (standard) requests, enumeration
usb_requests.h
usb_spec.c     USB data structures (Descriptors) and constants
usb_spec.h	

The total size of all source text is approx. 130 kByte. Thus this firmware is still quite small and clear. The files SUDAP.c and SUDD.c forms together with daq_dev.c and daq_dev.h the example application. The files usb_api.c und usb_api.h contains the application specific USB parts, usually these must be modified for different applications.

The files are available in the as single files or in compressed form In the case that someone wants to print all source texts (approx. 60 pages) a preformated generated with enscript or a preformated generated with pdflatex and the listings-packet are available. If you have enscript or pdflatex installed on your computer, you may adapt the script file gen_postscript.txt or the latex file gen_pdf.tex to generate a printout adapted to your personal taste.

Principles of USB

By its conception the USB is more versatile and efficient, but at the same time more complicated than the traditional serial or parallel computer interfaces. The USB uses multiple transmission modes, partially with automatic error correction, and multiple data channels (end points) with configurable buffers, and the use of multiple dividers (hubs) allows to connect up to 127 USB devices to a single PC. So if you wants to develop and build your own USB devices, you will need some basic knowledge about the principles of the USB. Now there may exist a few people who only wants to switch or query some electrical signal lines with his USB device, e.g. for controlling a model railroad from the PC. This could be done in principle by two functions GetPort() and SetPort() -- beside the names of the function and their parameters no further knowledge would be necessary. Indeed, a similar function for the control of an output port is available in my example application. But this is only one of many possible applications, and it is not possible to define special functions or to give concrete examples for all potential situations. However if you have some basic knowledge about USB, it is not difficult to use my USB firmware for building of applications customized to your needs. For a successful use of the firmware you should know at least the meaning of the basic USB terms like Host, Device, Enumeration, Descriptor, Configuration, Interface and Endpoint.

A quite understandable introduction is given in the two first chapters (approx. 100 pages) of the German book Perhaps you have the option to lend this book somewhere for a few days. Freely accessible, but very brief representations of the USB principles can be found at The whole USB-Specification (650 pages) is available as a PDF document at where you can get many more information concerning USB also. If you prefer English books, you may choose instead of the book of H.J. Kelm, which is only available in German language.

Of course you should download the datasheet of the AT90USB controller from the Atmel Homepage and read chapter 21 (USB Controller) and chapter 22 (USB Device Operating modes), in particular the sections which describe reading of data from the USB FIFO memory, writing data into this FIFO, and the corresponding interrupt sources.

The Example Application

The example application is a simple program for the recording of an (analog) electrical signal; additional port B of the AT90USB devices can be set to a specific voltage level (high or low). (So that a light emitting diode (LED) connected (over a series resistor) to one of the 8 pins of port B, can be switched on and off under program control.) To keep the example program short and clear, only one channel (ADC0) of the analog to digital converter (ADC) of the AT90USB is used, together with the internal 2.56 V voltage reference. (Therefore voltages to be measured should be in the range from 0 to 2.56 V.)

The example application essentially consists of the files SUDAP.c (Simple USB Data-Acquisition Program), SUDD.c (Simple USB Data-acquisition Device) and daq_dev.c. SUDAP.c is the PC program used in a shell- or terminal-window controlled by command line parameters. It accepts some command line parameters, starts data recording and writes the result of the measurement to the shell window (use > to redirect output to a file). At the firmware side SUDD.c is in principle the main program, because it contains the main-function. Actually this function has currently no real work to do, it only toggles pin A0 rapidly, causing flashing of a connected LED. The actual data recording occurs periodically using a timer interrupt -- the corresponding function is contained in the file daq_dev.c. If you have a suitable board with a AT90USB at your hands, you can (and should) try this application now. A 16 MHz-quartz is recommended, otherwise you have to adjust the content of file defines.h and you are not able to use the maximum data recording speed. (If you have any problems with the USB connection, then you should check the Fuse-Bytes of the AT90USB1287 device, in particular the "Fuse Low Byte" (Table 6-3, 6-4 and 29-5 in datasheet) which is relevant for clock generation. The default setting of this byte was "01011110" (binary) resp. "5E" (hexadecimal) for my chips. With these settings my device worked well, but this bit combination is not defined in datasheet. So I reprogrammed this byte to "01111111" resp. "7F" for my 16 MHz-quartz. For the first tests of your hardware you should use a not too long USB cable.) Per default the firmware sends a couple of textual diagnostic output over the serial port -- in the case that your board is equipped with a level converter (MAX232) which is connected to a serial port of your PC. To see the diagnostic output you may open a further shell window and start a terminal program like minicom (or a similar one) with parameters 8N1 and 9600 Baud.

Now you should connect your AT90USB board to your PC using a suitable USB cable.

Change to the directory containing all source files and type in the following instructions:

make
make dfu
gcc -l usb -o sudap SUDAP.c

The second line uses the tool dfu-programmer to transfer the firmware via USB to the chip and starts the application. Now some diagnostic messages may appear in the (minicon) terminal window and a LED attached to port A0 starts blinking indicating ready status. If the command make dfu gives an error message instead, the tool dfu-programmer is not installed properly, or the USB bootloader of AT90USB device is not in active state (you may need root/superuser privileges to use dfu-programmer?) In the later case you should inform yourself how to activate the bootloader of your AT90USB board. (For the STK525, USBKEY or my own board pin E2 (HWB) must be tied to ground till end of reset, or maybe till firmware upload is completed.) Of course you can also use any other programming tool to transfer the firmware on the chip. Attention: Programming in this way via ISP will overwrite the factory supplied USB bootloader!)

Now USB communication between the console program sudap and the firmware in the AT90USB chip should work: Enter the following instructions in the shell window successively:

./sudap --help
./sudap -p 11111111
./sudap -t 500us -s 100

The second line will switch all 8 outputs of port B to high (5 V), the third line will read 100 voltage levels from pin ADC0 with a time interval of 500 us and prints the measured values in the console window. The output is unscaled, depending on the input voltage you will get numbers from 0 to 1023 corresponding to 10 bit resolution of the ADC. (For testing you should apply an voltage signal to pin ADC0, i.e. the adjustable output of a potentiometer.)

Note that this application shall demonstrate the USB communication, not the function of the ADC and the timer. I have not investigated the function of the timer and the ADC or the quality of the measured data carefully, and besides this the accuracy of the ADC is not very good when using the highest possible sampling rate (20 us period).

If someone wants to use this example application for real data acquisition, then proper function should be proved carefully. Those people may want to extend this simple program, i.e. by allowing the use of various reference voltages or by using multiple channels of the ADC.

In this application the measured data consist of single values in equidistant time intervals, which have to be send over USB to the host PC in real time, or buffering inside the USB device is necessary. In order to bridge any latencies of the USB connection (up to a few milliseconds on my computer), measuring data is buffered in a ring buffer in the controller. When the smallest available sampling interval of 20 us is used, the controller is nearly busy by administrating this buffer.

When recording large data sets at high sampling period, then it can come to bottlenecks in the data transmission -- in this case the remaining data is marked as invalid by using the value 65535 and an error report is written to the console. Whether this error occurs depends on your PC -- with my PC it arose only occasionally when displaying data in the console window, but never when redirecting data to a file. (A higher data transmission rate at less processor load can be achieved in principle, if data can be read in larger chunks from a large buffer (or written to the buffer), for example if external RAM, an external ADC with its own buffer memory or a FPGA is connected to the chip.)

The internal operation of the USB firmware is explained more deeply in the next section.

Usage of the firmware and adaption to other applications

The USB communication can be divided into three fundamental phases: The Enumeration, in which the device communicates its characteristics to the host, the selection of a configuration by the host, and finally the transmission of the real data (payload) between host and device.

Depending on which device is attached to the USB, these three phases differ -- accordingly the USB firmware must be adapted for the special type of device.

In the enumeration phase Descriptors (chunks of data) are transfered from the device to the host, in order to inform the host about the properties of the attached device. So these descriptors have to be made available by the device. It may seem to be obvious to create these descriptors in the RAM of the processor. The problem is, that multiple types of descriptors exists, which are correlated. These internal dependency can in the simplest form be presented by the order in which the descriptors reside in the memory of the controller, or in the form of linked lists. Also conceivable would be the use of multidimensional arrays, controlling the access to single descriptors via a set of indexes. But due to the structure and the variable number of descriptors usually some array positions would be unused. This implies unnecessary memory consumption, so that this solution will not be used in practice. Direct stacking of descriptors in the processor memory, with access to it by well known, firm addresses, is compact but inflexible, unclear and thus error-prone. For simplification one can generate the structures controlled by a script before storing it in RAM -- this is what the Porus project does.

The administration of the descriptors by concatenated lists has the advantage, that structures can be build dynamically by function calls. With the statement ep3 = NewEndpoint(config1, interface2, altsetting1, ...); for instance a further endpoint for the second interface of the first configuration could be generated. A similar method is used by the usbn2mc-project of Mr. B. Sauter. This solution appears conceptional as very flexible and user friendly. Unfavorable however is the low transparency and the overhead caused by the function calls, the bunch of pointers necessary for the concatenation, and the necessary employment of dynamic memory management (malloc()). Additional the used functions needs a lot of parameters to cover all possible situations. Thus the ease of use is related again.

For my firmware I have decided to use a method, which addressed the descriptors by indexes. In order to use as few memory as possible, however no arrays are used for storing the descriptors, but each descriptor is made available by functions, which are called with suitable parameters. This method works quite well. It requires however modifications of these functions to customize the firmware for other applications. If the programmer has some basic knowledge about USB and the meaning of the descriptors, then these modifications are not difficult.

Customizing of the functions for the supply of the descriptors

According to the USB specification a device descriptor must be present for each USB device. Further for each device at least one configuration descriptor must exist, to which again at least one interface descriptor belongs, whereby in principle each interface may exists several times with different "Alternate Settings". Assigned to each interface are then one or multiple endpoints, which forms the actual data channels. Simple applications like the example program have only one single configuration with only one interface, to which a few endpoints are assigned.

These definitions are established in the files usb_api.h and usb_api.c:

#define USB_NumConfigurations 1
const uint8_t USB_Interfaces[USB_MaxConfigurations] = {1, 0, 0, 0};
const uint8_t USB_MaxPower_2mA[USB_MaxConfigurations] = {50, 0, 0, 0};
const uint8_t USB_AltSettings[USB_MaxConfigurations][USB_MaxInterfaces] =
  {{1, 0, 0, 0}, // number of alt. settings of interfaces of first configuration 
   {0, 0, 0, 0}, // number of alt. settings of interfaces of second configuration 
   {0, 0, 0, 0},
   {0, 0, 0, 0}};
const uint8_t USB_Endpoints[USB_MaxConfigurations][USB_MaxInterfaces] =
  {{3, 0, 0, 0}, // number of endpoints of interfaces of first configuration 
   {0, 0, 0, 0}, // number of endpoints of interfaces of second configuration 
   {0, 0, 0, 0},
   {0, 0, 0, 0}};

USB_NumConfigurations defines the number of configurations of the device, the field USB_Interfaces[USB_MaxConfigurations] indicates, how many interfaces belongs to each configuration. The two-dimensional matrices USB_AltSettings[USB_MaxConfigurations][USB_MaxInterfaces] and USB_Endpoints[USB_MaxConfigurations][USB_MaxInterfaces] indicate how many "Alternate Settings" and endpoints belongs to each tuple (Configuration, Interface). Additional the field USB_MaxPower_2mA[USB_MaxConfigurations] exists, which indicates the power consumption of each individual configuration. Intended are up to four different configurations (USB_MaxConfigurations) each with up to four interfaces (USB_MaxInterfaces) with a unlimited number of "Alternate Settings" each. But this can be adapted also. With the AT90USB up to six data endpoints can be used.

For the supply of the mentioned descriptors the following functions are provided in the files usb_api.h und usb_api.c:

void UsbGetDeviceDescriptor(USB_DeviceDescriptor *d);
bool UsbGetConfigurationDescriptor(USB_ConfigurationDescriptor *c, uint8_t confIndex);
bool UsbGetInterfaceDescriptor(USB_InterfaceDescriptor *i, uint8_t confIndex,
  uint8_t intIndex, uint8_t altSetting);
bool UsbGetEndpointDescriptor(USB_EndpointDescriptor *e, uint8_t confIndex,
  uint8_t intIndex, uint8_t altSetting, uint8_t endIndex);
void UsbGetStringDescriptor(char s[], uint8_t index);

After a view into the source code their principle of operation becomes clear immediately: If the queried descriptor does not exists for the specific device, false is returned, otherwise the descriptor is filled with associated data.

Adaption of the functions for the selection of configuration and interface

If the host has requested all descriptors, it can activate a specific configuration and possible select an interface with a specific "Alternate Setting".

For this process the file usb_api.c contains these functions:

bool UsbDevSetConfiguration(uint8_t c);
bool UsbDevSetInterface(uint8_t conf, uint8_t inf, uint8_t as);

These two functions are build up relatively simple also and can be easily adapted accordingly. UsbDevSetInterface() configures the endpoints supported by the firmware or returns false if the "Alternate Setting" of the interface does not exists for the selected configuration. UsbDevSetConfiguration() calls UsbDevSetInterface() for all interfaces of the configuration with the default value as == 0. The configuration of the endpoints is performed thereby with the function

bool UsbDevEP_Setup(uint8_t num, uint8_t type, uint16_t size, uint8_t banks, uint8_t dir);

from file usb_drv.c. The first parameter num designates the number of the endpoint which is to be configured, type can take the values UsbEP_TypeIso, UsbEP_TypeBulk or UsbEP_TypeInterrupt. For size powers of two in the range from 8 to 64 byte are allowed, for type UsbEP_TypeIso up to 512 byte. The parameter banks determines if one or two (dual bank, ping-pong-mode) buffer memories are used. For dir the values UsbEP_DirOut or UsbEP_DirIn can be used. Note that according to the AT90USB data sheet for all used FIFO memory maximally 832 bytes are available altogether.

Thus in principle the enumeration and configuration of the USB device is finished, now real data (payload) can be transfered.

Transfer of real data (payload) to or from endpoints

The AT90USB supports up to 6 data endpoints, over which payload can be transferred from the host to the device (out endpoint) or from the device to the host (in endpoint). In principle the data can be written to the FIFO buffer or read from the buffer by the firmware either continuously or interrupt-controlled. For the interrupt driven communication the desired interrupt source must be selected or enabled. Before data can be written to the FIFO, it must be usually first examined if the FIFO is ready and whether sufficient space for more data is available. If the FIFO is filled (full), it must be released and sent explicitly. For out endpoints (firmware reads data from the FIFO) it is similar: It has to be examined whether and how many data are available from the FIFO, then data is read out and finally a flag is set indicating that all data is read and that the host may fill the FIFO with new data.

How one best handles the filling or reading of the FIFO buffer depends on the concrete application, and I did not try out all possibilities yet. Therefore for the conclusion of this section I will only describe the three methods used in my example application. It is then your task to find, with the help of the AT90USB data sheet (in particular section 22.14, 22.15 and 22.18) and some experiments, an optimal communication process for your application.

In my application measuring data are written continuously (timer-controlled) to the FIFO memory of an in-endpoint. This process has this form:

ISR(TIMER0_COMPA_vect)
{
  UsbDevSelectEndpoint(2);
  if (UsbDevTransmitterReady())
  {
    switch (UsbDevGetByteCountLow())
    {
      default:
        w = RB_Read();
        UsbDevWriteByte(LSB(w));
        UsbDevWriteByte(MSB(w));
        break;
      case 64:
        UsbDevClearTransmitterReady();
        UsbDevSendInData();
    }
  }
}

First the appropriate endpoint must be selected, then it is queried whether it is ready to accept new data (it is possible that the content of this FIFO is just sent to the host, in this case we must wait). Then the fill level is examined. If it is smaller than 64 (FIFO size of this endpoint), then data bytes can be written using UsbDevWriteByte(). Otherwise it is full and must be sent with UsbDevSendInData(), whereby according to the data sheet UsbDevClearTransmitterReady() must be called earlier. By the way these functions are only macros, which are defined in the file usb_drv.h and set a single bit in a register.

I should mention briefly how the data recording is started. For this task a vendor request is used, one could have used however a data endpoint as well: The PC program executes the function call

usb_control_msg(handle, USB_VendorRequestCode, UC_ADC_Read,
  (int) timeres, (int) samples, NULL, 0, TimeOut);

(function from LibUSB library) with the special identification tag USB_VendorRequestCode, where at this the function

void UsbDevProcessVendorRequest(USB_DeviceRequest *req)

is called from the firmware. Thereby the 8 byte large device request is used to send a command, the number of data to read and the time resolution.

The two other endpoints are read out and filled interrupt-controlled. So first the appropriate interrupt sources must be activated -- this happens directly during the configuration of the endpoints in the function UsbDevSetInterface():

if (UsbDevEP_Setup(1, UsbEP_TypeBulk, EP1_FIFO_Size, 1, UsbEP_DirIn))
  UsbDevEnableNAK_IN_Int(); // trigger interrupt when host got a NAK as a result of a read request
else
  Debug("Setup of EP1 failed!");
if (UsbDevEP_Setup(2, UsbEP_TypeBulk, EP2_FIFO_Size, 2, UsbEP_DirIn));
else
  Debug("Setup of EP2 failed!");
if (UsbDevEP_Setup(3, UsbEP_TypeBulk, EP3_FIFO_Size, 1, UsbEP_DirOut))
  UsbDevEnableReceivedOUT_DATA_Int(); // trigger interrupt when out data is available
else
  Debug("Setup of EP3 failed!");

Endpoint 1 is used for status inquiry of the device. The call of function UsbDevEnableNAK_IN_Int(); ensures for the fact, that each time when the host tries in vain to read from the empty FIFO an interrupt is triggered, so that the firmware can write a current status byte to the FIFO, which is available for the host in the next read attempt. Over endpoint 3 a single byte can be send by the host to the device, which is used by the firmware to control port B. UsbDevEnableReceivedOUT_DATA_Int() ensures for the fact that an interrupt is generated if data is written to the FIFO of this endpoint by the host.

In order to be able to react properly to endpoint interrupts, a few macros of the form

#define UsbDevEP1IntAction() UsbDevFillEP1FIFO() 

are defined in the file usb_api.h, which are called from the function ISR(USB_COM_vect) of file usb_isr.c. The definition of these functions which can be called takes place in the file usb_api.c:

void
UsbDevFillEP1FIFO(void)
{
  if UsbDevTransmitterReady()
  {
    UsbDevClearTransmitterReady();
    UsbDevClearNAK_ResponseInBit();
    UsbDevWriteByte(DAQ_Result);
    UsbDevSendInData();
  }
}

void
UsbDevReadEP3FIFO(void)
{
  if (UsbDevHasReceivedOUT_Data())
  {
    UsbDevClearHasReceivedOUT_Data();
    if (UsbDevReadAllowed())
    {
      DDRB = 0xFF;
      PORTB = UsbDevReadByte();
      UsbDevClearFifoControllBit(); // maybe we should use an alias for this macro
    }
  }
}

The function UsbDevFillEP1FIFO() checks first with the function UsbDevTransmitterReady() if the FIFO memory is ready to accept data, the it clears the FIFO flag with function UsbDevClearTransmitterReady() and with UsbDevClearNAK_ResponseInBit() the corresponding interrupt flag, before a status byte is written to the FIFO and the FIFO is released.

The function UsbDevReadEP3FIFO(void) checks first if data are available for reading and then clears the corresponding flag. With UsbDevReadAllowed() is checked additional if at least one byte is available in the FIFO for reading. Thereon this byte is read and written in the register of port B. By the function call UsbDevClearFifoControllBit() the end of the read operation is declared, and the FIFO is released for a filling by the host once again.