Devices from Silicon Laboratories are not widely popular in amateur circles, they are far from such flagships like Atmel. However, they have both quite accessible to the mere mortal microcontrollers of the main lines in the TQFP package, and the USB ToolStick starter kits (which was recently
mentioned in the Habré ). I myself began my acquaintance with microprocessor technology, working with the Silbses, and quite successfully.
In this article, I will tell you how to connect a computer with a PC using a USB interface, and how Silabs tried to make it easy for the developer.
As a test, we will use the C8051F320DK board, with a microcontroller, respectively, of the F32x series, which supports USB hardware, and Keil's uVision4 development environment.
Before you start digging towards USB communication, you need to decide on some basic aspects of the protocol: what place does the device occupy in the topology (host or slave device) and what character will the information transmitted via the interface.
The USB architecture allows four basic types of data transfer:
- Control transfers are used to configure devices during their connection and to control devices during operation. The protocol provides guaranteed data delivery.
- Bulk data transfers are bulk transfers without any obligation to delay delivery and transfer speeds. Array transfers can occupy the entire bus bandwidth that is free from other types of transfers. The priority of these programs is the lowest, they can be suspended with a large load of the bus. Delivery guaranteed - with a random error is repeated. Array transfers are appropriate for exchanging data with printers, scanners, storage devices, and so on.
- Interrupt transfers are short transfers that are spontaneous in nature and must be serviced no slower than the device requires.
The service time limit is set in the range of 10-255 ms for
low, 1-255 ms for full speed, 125 ÎĽs can be ordered at high speed. In case of random exchange errors, a repetition is performed. Interrupts are used, for example, when entering characters from the keyboard or to send a message about moving the mouse. - Isochronous transfers ( isochronous transfers ) - continuous transmission in real time, occupying a pre-agreed part of the bandwidth of the bus with a guaranteed delivery delay time. They allow to organize at full speed a channel with a band of 1.023 MB / s (or two of 0.5 MB / s each), occupying 70% of the available band (the rest can be filled with less capacious channels). At high speed, the endpoint can receive a channel up to 24 MB / s (192 Mbit / s). If an error is detected, the isochronous data is not repeated - invalid packets are ignored. Isochronous transfers are needed for streaming devices: video cameras, digital audio devices (USB speakers, microphone), devices for playing and recording audio and video data (CD and DVD).
In the case of connecting the MC to the computer, the controller will obviously be a slave device.
')
Creating a USB compatible HID device such as a joystick
The most common and simply implemented type of USB device is HID (Human Interface Devices). The type of transmission used for such devices is interrupts. Typical representatives of this class are USB keyboards, mice, joysticks, monitor setting panels, barcode readers, card readers, and the like.
The advantages of HID devices are:
- ease of implementation;
- compact code;
- Windows support (no additional drivers needed).
So, we realize the simplest
joystick manipulator . For example, we will need a gas handle with two (or more) buttons for combat fur (!), Which we collect in the garage. On the demoboard C8051F320DK there is one variable resistor and 2 buttons - for a minimum it is enough.
Silabovtsy provide an example of microcontroller firmware, which emulates a USB mouse with a HID interface. This example is enough for the eyes for a quick and painless implementation of most human interaction interfaces. As a result, in the example taken as a basis, it is necessary to recycle:
- HID device handle configuration;
- data transfer procedures;
- HID device name descriptor.
Starting with a device descriptor
We need the handle in the following form:
code const hid_report_descriptor HIDREPORTDESC =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x02, // USAGE_PAGE (Simulation Controls)
0x09, 0xbb, // USAGE (Throttle)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x02, // USAGE_MAXIMUM (Button 2)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x55, 0x00, // UNIT_EXPONENT (0)
0x65, 0x00, // UNIT (None)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0 // END_COLLECTION
}
Now we will analyze in detail what is what. The most important part in describing a future device is data types. It is necessary to describe the
Simulation Controls section (simulation of the control body), in which there is just a
Throttle (gas knob), for this we indicate:
- the range of values ​​in which Throttle will be LOGICAL_MINIMUM (0) and LOGICAL_MAXIMUM (255),
- set the size of this range (one byte) - REPORT_SIZE (8) and
- The number of controls of this type is REPORT_COUNT (1).
Buttons with a similar story (USAGE_PAGE (
Button )):
- value range - LOGICAL_MINIMUM (0) and LOGICAL_MAXIMUM (1);
- range size (one bit) - REPORT_SIZE (1);
- the number of buttons is more than one, so here it is already necessary to use a field of byte length, which means REPORT_COUNT (8);
All this is necessary for the operating system, now it will know how to handle the 2 bytes it receives from the controller, using the descriptor as the key to decryption.
Yes, and also, there are such lines in .h, right before the hid_report_descriptor declaration:
#define HID_REPORT_DESCRIPTOR_SIZE 0x002C
#define HID_REPORT_DESCRIPTOR_SIZE_LE 0x2C00 //LITTLE ENDIAN
Here it is important that the size of the descriptor is set after the creation of the descriptor itself, and it must be specified so that the controller is recognized by the computer.
To simplify the task of writing a descriptor, you can use the program on the
www.usb.org (
HID Descriptor Tool ). Included with the program are examples of the configurations of some HID-devices, which can be adjusted for your task or create your own HID-device.
This is the end of the description of the joystick and you need to prepare the data for transfer to the PC.
Data transfer procedures
We find in the example the following code:
void IN_Report(void){
IN_PACKET[0] = VECTOR;
IN_PACKET[1] = BUTTONS;
// point IN_BUFFER pointer to data packet and set
// IN_BUFFER length to transmit correct report size
IN_BUFFER.Ptr = IN_PACKET;
IN_BUFFER.Length = 2;
}
In this procedure, we are compiling a sending packet, which afterwards through a cunning pointer (in fact, it is just a structure from the pointer and its length) and is transmitted by our device. The main thing is to make a package carefully, which the comment hints at, and then we will do everything with it without our participation.
Now I’ll tell you how and from where we take the VECTOR and BUTTONS variables (both, by the way, are of type unsigned char byte size).
The global variable VECTOR is assigned values ​​from the ADC when an interrupt occurs:
void ADC_Conver_ISR(void) interrupt 10
{
AD0INT = 0;
//
if( VECTOR != ADC0H)
LED = 1;
else
LED = 0;
VECTOR = ADC0H;
}
The BUTTONS global variable likewise changes the value depending on the button presses. Buttons are interrogated on interruption from the timer. Adjust the timer according to your personal preferences.
void Timer2_ISR (void) interrupt 5
{
P2 &= ~Led_2;
if ((P2 & Sw1)==0) // #1
{
// pressed
BUTTONS = BUTTONS | (1<<0);
LED2 = 1;
}
else
{
// not pressed
BUTTONS = BUTTONS & 0xFE;
}
if ((P2 & Sw2)==0) // #2
{
// pressed
BUTTONS = BUTTONS | (1<<1);
LED2 = 1;
}
else
{
// not pressed
BUTTONS = BUTTONS & 0xFD;
}
TF2H = 0; // Timer2
}
HID device name descriptor
Finally, we can adjust the string data so that the device has the name we want (in my example, “JOYSTICK-HABR”).
We are looking for a string descriptor
String2Desc , rewritten
#define STR2LEN sizeof ("JOYSTICK-HABR") * 2
code const unsigned char String2Desc [STR2LEN] =
{
STR2LEN, 0x03,
'J', 0,
'O', 0,
'Y', 0,
'S', 0,
'T', 0,
'I', 0,
'C', 0,
'K', 0,
'-', 0,
'H', 0,
'A', 0,
'B', 0,
'R', 0,
};
HID Device Identification
After compiling the project and programming the microcontroller, you can connect the device to the USB port. The host determines that the device belongs to the HID class and transfers control of the device to the appropriate driver.

Now in Windows, go to Control Panel-> Game Devices and see our passenger there. We look at the properties and check the functionality.

Low transmission speed is the main limitation of the HID-version of the device. The maximum possible data transfer rate for such an exchange is 64 kbps. This figure compared to 12 Mbit / s of full-speed USB-bus looks like a minus of HID-technology in the matter of choosing a specific USB-implementation. However, for many communication tasks of the specified speed, the HID-architecture, as a specialized tool, occupies a worthy place among the ways of organizing data exchange.
Generally speaking, HID devices are easy to implement on almost any MK with USB support. As a rule, one working example from the developers is enough, correcting which you can get any required functionality.
Creating a complete USB device using Silabs USBXpress
But here comes the moment when you need to use your own protocol for working with the device on the MC. In this case, I would like to transfer a lot of data at high speed, and do it all with the help of my laptop, in which there is a lot of USB and not a single COM, and even your device should be no more than a matchbox, and sculpt on a USB-UART board on a chip FT232RL there is no possibility.
It was then that the guys from Silabs decided to make life easier for everyone and show “the way to the future”, without hard breaking their teeth about writing their own firewood and firmware.
The USBXpress Development Kit is a complete solution for the MK and host (PC), providing simple operation with the USB protocol using the high-level API for both parties. No special knowledge is required of either the USB protocol itself or the writing of drivers. That is how silibovtsy write in their guide.

Speaking of
Programmer's Guid : occupying only 30 pages, it is extremely simple and easy to understand. I personally don’t like examples, often there are very crooked places, programs for PCs are generally better not to watch, they are extremely unreadable.
USBXpress DK is provided for the C8051F32x, C8051F34x microcontrollers and the CP210x (USB-to-UART Bridge Controller). The USBXpress library includes a lower level library, USB drivers for PCs and a DLL for developing applications at the top level. And, of course, a set of documentation and examples.
The library provides data transfer only in the BULK mode. When using all the functions of the library, their implementation will take only 3 KB of flash memory of the microcontroller.
Firmware
Let us analyze one more or less simple and clear example, similar in functionality to the previous example in HID. In the PC application we will not climb, with it everything will be crystal clear after we finish the firmware for the MK.
So, the essence of the example TestPanel: we take from the microcontroller the readings of the ADC (Potentiometer) and the built-in thermometer (
Temperature ), as well as by pressing the buttons (
Switch1State and
Switch2State ), and we can flash LEDs (
Led1 and
Led2 ).
Now the required steps and subtle points that we will consider:
- Writing USB descriptor;
- Initialization of the device and USB on board;
- Processing of incoming data and the formation of the outgoing packet;
- Interrupt handling
But first, when creating a project, do not forget to include the USB_API.h
header file and the
USBX_F320_1.lib library
into it .
Writing USB descriptor
Unlike HID with its cunningly formalized structure, everything is simple
code const UINT USB_VID = 0x10C4;
code const UINT USB_PID = 0xEA61;
code const BYTE USB_MfrStr[] = {0x1A,0x03,'S',0,'i',0,'l',0,'a,0,'b,0,'s,0};
code const BYTE USB_ProductStr[] = {0x10,0x03,'U',0,'S',0,'B',0,'X',0,'_',0,'A',0,'P',0};
code const BYTE USB_SerialStr[] = {0x0A,0x03,'H',0,'A',0,'B',0,'R',0};
code const BYTE USB_MaxPower = 15;
code const BYTE USB_PwAttributes = 0x80;
code const UINT USB_bcdDevice = 0x0100;
With VID, PID and names, I think everything is clear, plus you can also set the maximum current with the MaxPower parameter (max current = _MaxPower * 2), PwAttributes — the parameter responsible for the remote wake-up of the host, and bcdDevice — the device release number.
Nuance of device initialization and USB on board
Now we will begin the main function itself, in which the MC will tirelessly perform the reception and transmission of data.
void main(void)
{
PCA0MD &= ~0x40; // Disable Watchdog timer
USB_Clock_Start(); // Init USB clock *before* calling USB_Init
USB_Init(USB_VID,USB_PID,USB_MfrStr,USB_ProductStr,USB_SerialStr,USB_MaxPower,USB_PwAttributes,USB_bcdDevice);
Initialize();
USB_Int_Enable();
...
Here, as the comment requires, first of all it is necessary to initialize the clock generator for USB before its initialization, only then carry out the rest of the starting operations for MC - Initialize (); - which configures ports, timer and ADC; then enable USB interrupts.
Processing of incoming data and the formation of the outgoing packet
That got to the most important thing
//... main
while (1)
{
if (Out_Packet[0] == 1) Led1 = 1;
else Led1 = 0;
if (Out_Packet[1] == 1) Led2 = 1;
else Led2 = 0;
In_Packet[0] = Switch1State;
In_Packet[1] = Switch2State;
In_Packet[2] = Potentiometer;
In_Packet[3] = Temperature;
}
// main
}
Out_Packet - packet received from the host;
In_Packet - package sent to the host;
The essence is clear, MK constantly updates the sent packet and reads the status of the received.
Interrupt handling
Now in 2 words about where we get the values ​​in the package being sent. As in the example with HID, the state of the buttons is obtained by interrupts from the timer, and the values ​​of the ADC and thermometer - by interrupts from the ADC.
Here is one subtle point - during the initialization of the ADC, we adjust it so that the conversion of values ​​takes place on the overflow of the timer (the same one that we use for the buttons), and the very same interrupt from the ADC occurs upon the completion of the conversion. And here, in addition to receiving the values ​​of the converter, we call the API function at the end of the procedure.
Block_Write (In_Packet, 8)
which sends the collected data to the computer.
Receiving commands from the computer occurs in the procedure for handling interrupts from USB:
void USB_API_TEST_ISR(void) interrupt 16
{
BYTE INTVAL = Get_Interrupt_Source();
if (INTVAL & RX_COMPLETE)
{
Block_Read(Out_Packet, 8);
}
if (INTVAL & DEV_SUSPEND)
{
Suspend_Device();
}
if (INTVAL & DEV_CONFIGURED)
{
Initialize();
}
}
This moment is detailed in the Programmer's Guid. The bottom line is that the Get_Interrupt_Source () API function is called, which returns the code for causing the interrupt API. Next, the code is analyzed and the necessary action is performed.
Programs on PC
I will not analyze the program for the computer. Silabovtsy provided examples in Visual Basic and C, but, even without looking at the source code, you shouldn’t connect the library in the development environment you use and read a couple of pages about the functions.
Therefore, I will use the already compiled program from the example.
So, we compile the project for MK, sew up, install universal drivers for USBXpress and connect a debugging board. The system will detect the new device and install drivers for it.
We'll see after the installation what is happening in the device manager of Windows:

Now run the program:

We see that she correctly found the device.

That's all, now you can poke buttons here, blink diodes, warm MK with your hands, see how the temperature grows.
Conclusion
In general, the creation of USB devices using USBXpress libraries turned out to be faster and more transparent than using the HID architecture. And the speed will be definitely higher. The thinnest place is that the library is closed, and it is impossible to know how reliable this solution is, besides only BULK data transfer mode is available.
Used and useful sources:
- Guk M., PC hardware interfaces. Encyclopedia. - SPb .: Peter, 2002. - 528 p.
- Kurilin A.I. Silicon Labs microcontrollers with USB interface. Magazine "Electronic Components" â„–5, 2007
- Practical use of the USB interface in PIC controllers
- SiUSBXpress Linux Driver