Back to Home

Introduction to the world of USB-devices on the example of microcontrollers from Silicon Laboratories

microcontrollers · usb · hid · silabs · USBXpress

Introduction to the world of USB-devices on the example of microcontrollers from Silicon Laboratories

Devices from Silicon Laboratories are not widely used in amateur circles, they are far from flagships such as Atmel. However, they have both microcontrollers of the main lines in the TQFP package that are quite accessible to the mere mortal, and USB ToolStick starter kits (which was recently mentioned on the hub ). I myself began my acquaintance with microprocessor technology, working with Silabs, and quite successfully.
In this article I will tell you how you can organize a computer’s connection with the MK using the USB interface, and how Silabs tried to make it simple for the developer.
As a test, we will use the C8051F320DK motherboard, with a microcontroller of the F32x series, respectively, supporting USB hardware, and the Keil development environment uVision4.

Before you start digging towards the implementation of USB communication, you need to decide on some basic aspects of the protocol: what place the device occupies in the topology (host or slave device) and what character the information transmitted through the interface will have.

The USB architecture allows four basic types of data transfer:
  • Control transfers - used to configure devices during their connection and to control devices during operation. The protocol provides guaranteed data delivery.
  • Transfer of data ( bulk data transfers ) - this transfer without any obligation for the delivery delay and rate. Array transfers can occupy the entire bus bandwidth, free from other types of transmissions. The priority of these transmissions is the lowest; they can pause when the bus is heavily loaded. Guaranteed delivery - if an accidental error occurs, a repeat is performed. Array transfers are appropriate for exchanging data with printers, scanners, storage devices, etc.
  • Interrupt transfers - short transfers that are spontaneous in nature and should be serviced no more slowly 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, and at high speed 125 μs can also be ordered. In case of random exchange errors, a retry is performed. Interrupts are used, for example, when entering characters from the keyboard or to send a message about the movement of the mouse.
  • Isochronous transfer ( isochronous transfers ) - continuous transmission in real time, occupying the pre-agreed proportion of bus bandwidth with a guaranteed latency of delivery. They allow you to organize at full speed a channel with a band of 1.023 MB / s (or two at 0.5 MB / s), occupying 70% of the available band (the remainder can be filled with less capacious channels). At high speed, the endpoint can receive a channel of up to 24 MB / s (192 Mb / s). If an error is detected, isochronous data is not repeated - invalid packets are ignored. Isochronous transmissions 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 MK to the computer, the controller will obviously be a slave.

Creating a USB compatible HID device like a joystick


The most common and easily implemented type of USB device is HID (Human Interface Devices). The type of transmission used, standard for such devices, is interruptions. Typical representatives of this class are USB keyboards, mice, joysticks, monitor settings panels, barcode readers, card readers, etc.
The advantages of HID devices are:
  • ease of implementation;
  • compact code;
  • Windows support (no additional drivers needed).

So, we implement the simplest joystick manipulator . For example, we need a throttle stick with two (or more) buttons for the combat fur (!) That we collect in the garage. The C8051F320DK demo board has one variable resistor and 2 buttons - enough for a minimum.

Silabovtsy provide an example of microcontroller firmware in which a USB mouse with a HID interface is emulated. This example is enough for the eyes to quickly and painlessly implement most of the human interaction interfaces. As a result, in the example taken as a basis, it is necessary to process:
  1. HID device descriptor configuration
  2. data transfer procedures;
  3. HID device name descriptor.

Starting with the device descriptor

The descriptor is necessary for us in the following form: Now we will analyze in detail what is what is happening. The most important part in describing a future device is data types. It is necessary to describe the section Simulation Controls (simulation of the control), in which there is just a Throttle (throttle stick), for this we indicate:
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
}


  • the range of values ​​in which Throttle will act is 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).

With buttons, a similar story (USAGE_PAGE ( Button )):
  • the range of values ​​is 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 byte-length field, which means REPORT_COUNT (8);

All this is necessary for the operating system, now it will know how to handle the 2 bytes that it will receive from the controller, using the descriptor as the decryption key.
Yes, and also, in .h there are such lines, immediately before the hid_report_descriptor declaration: It is important here that the size of the descriptor is set after the descriptor itself has been compiled, and it must be specified so that the controller is recognized by the computer. To simplify the task of compiling a descriptor, you can use the program located at www.usb.org ( HID Descriptor Tool ). Included with the program are examples of configurations of some HID devices that can be adjusted for your task or create your own HID device.
#define HID_REPORT_DESCRIPTOR_SIZE 0x002C
#define HID_REPORT_DESCRIPTOR_SIZE_LE 0x2C00 //LITTLE ENDIAN




This ends the description of the joystick and you need to prepare the data for transfer to the PC.

Data Transfer Procedures

We find the following code in the example: In this procedure, we compile the packet to be sent, which afterwards through the tricky pointer (in fact, it’s just a structure from the pointer and its length) is transmitted by our device. The main thing is to carefully compose the package, which is what the comment hints at, and then everyone will do it without our participation. Now I’ll talk about how and where we get the VECTOR and BUTTONS variables (both, by the way, are of type unsigned char of size in bytes). The global variable VECTOR is assigned values ​​from the ADC during the occurrence of an interrupt from it: The global variable BUTTONS likewise changes the value depending on the button being pressed. Buttons are polled by interrupt from the timer. Set the timer according to personal preference.
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;
}




void ADC_Conver_ISR(void) interrupt 10
{
AD0INT = 0;

// индикация работы АЦП
if( VECTOR != ADC0H)
LED = 1;
else
LED = 0;

VECTOR = ADC0H;
}


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 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 , rewrite
#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.
image
Now in Windows, go to Control Panel-> Game Devices and see our passenger there. We look at the properties and check the functionality.
image
Low transmission speed is the main limitation of the HID-version of the device. The maximum possible data transfer rate with such an organization of exchange is 64 Kbps. Such an indicator, compared to 12 Mbps of the full speed of the USB bus, looks like a minus of the HID technology when it comes to choosing a specific USB implementation. However, for many communication tasks, the indicated speed is quite enough, and the HID architecture as a specialized tool takes its rightful place among the ways of organizing data exchange.

Generally speaking, HID devices are easy to implement on virtually any USB-capable MK. As a rule, one working example from the developers is enough, adjusting which you can get any required functionality.

Create a full-fledged USB device using Silabs USBXpress tools


But here comes the moment when you need to use your protocol for working with the device on the MK. At the same time, I would like to transfer a lot of data at high speed, and do it all with my laptop, in which there is a lot of USB and not a single COM, and even your device should be no larger than a matchbox, and sculpt on a USB-UART board on a chip FT232RL there is no way.
It was then that the guys from Silabs decided to make life easier for everyone and show the “road to the future", without breaking their teeth about writing their own firewood and firmware.
USBXpress Development Kit- This is a complete solution for the MK and the host (PC), which provides easy operation with the USB protocol using a high-level API for both parties. No special knowledge of either the USB protocol itself or driver writing is required. So write the Silabians in their guide.
image
Speaking of Programmer's Guid : occupying only 30 pages, it is extremely simple and easy to understand. I personally don’t like the examples, very crooked places are often found, programs under PC are generally better not to watch, they are extremely unreadable.
USBXpress DK is available for the C8051F32x, C8051F34x, and CP210x (USB-to-UART Bridge Controller) line microcontrollers. The USBXpress library includes a lower-level library, USB drivers for PCs and a DLL-library for developing applications at the upper level. And, of course, a set of documentation and examples.
The library implements data transfer only in BULK mode. Using all the functions of the library, their implementation will take only 3 Kbytes of Flash memory of the microcontroller.

Firmware

Let us examine one more or less simple and understandable example, similar in functionality to the previous example in HID. We won’t go into the PC application, everything will be crystal clear with it after we finish the firmware for MK.
So, the essence of the TestPanel example: we receive from the microcontroller the readings of the ADC (Potentiometer) and the built-in thermometer ( Temperature ), as well as from pressing the buttons ( Switch1State and Switch2State ), and we ourselves can blink the LEDs ( Led1 and Led2 ).
Now the required stages and delicate places that we will consider:
  1. Writing a USB descriptor;
  2. Initialization of the device and USB on board;
  3. Processing incoming data and generating an outgoing packet;
  4. 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 itself in it .

Writing a USB Descriptor

Unlike HID with its cleverly formalized structure, everything is simple here. With VID, PID and names, I think everything is clear, plus you can still 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 is the release number of the device.
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;



The nuance of initializing the device and USB on board

Now we begin the main function itself, in which the MK will tirelessly receive and transmit data. Here, as the commentary requires, first of all, it is necessary to initialize the clock for USB before its initialization, only then carry out the rest of the starting operations for MK - Initialize (); - which configures the ports, timer and ADC; then enable USB interrupts.
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();
...



Processing incoming data and generating an outgoing packet

Here we get to the most important Out_Packet - a packet received from the host; In_Packet - packet sent to the host; The bottom line is clear, MK is constantly updating the sent packet and reading the status of the received one.
//... продолжение 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
}





Interrupt handling

Now in 2 words about where we get the values ​​from the sent packet. As in the example with HID, the states of the buttons are obtained by interrupts from the timer, and the values ​​of the ADC and the thermometer are obtained by interrupts from the ADC.
Here is one subtle point - during the initialization of the ADC, we configure it so that the values ​​are converted by overflowing the timer (the same one that we use for the buttons), and the interrupt from the ADC itself occurs when the conversion is complete. And here, in addition to obtaining the values ​​of the converter at the end of the procedure, we call the API function
Block_Write (In_Packet, 8)
which sends the collected data to the computer.
Receiving commands from the computer occurs in the procedure for processing 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 Programmer's Guid. The bottom line is that the Get_Interrupt_Source () API function is called, which returns the code for the cause of the interrupt API. Next, the code is analyzed and the necessary action is performed.

PC programs

I will not disassemble the program for the computer. The Silabovites provided examples in Visual Basic and C, but without even looking at the source, connecting the library in your development environment and reading a couple of pages about complexity functions should not cause it.
Therefore, I will use the already compiled program from the example.

So, we compile the project for MK, we sew, install universal drivers for USBXpress and connect the debug board. The system will detect the new device and install drivers for it.
After installation,
image
let's
image
see what is going on in the Windows device manager: Now we run the program: We see that it found the device correctly.
image
That's it, now you can poke buttons here, blink diodes, warm your MK with your hands, see how the temperature rises.

Conclusion


In general, creating a USB device using USBXpress libraries turned out to be a faster and more transparent process than using the HID architecture. And the speed will be definitely higher. The thinnest point is that the library is closed, and it is impossible to find out how reliable this solution is, besides only the BULK data transfer mode is available.

Used and useful sources:

  1. Guk M., PC hardware interfaces. Encyclopedia. - St. Petersburg: Peter, 2002 .-- 528 s.
  2. Kurilin A.I. Silicon Labs microcontrollers with USB interface. Electronic Components Magazine No. 5, 2007
  3. Practical use of the USB interface in PIC controllers
  4. SiUSBXpress Linux Driver

Read Next