📜 ⬆️ ⬇️

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

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:

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:

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:
  1. HID device handle configuration;
  2. data transfer procedures;
  3. 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:

Buttons with a similar story (USAGE_PAGE ( Button )):

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.
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 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.
image
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:
  1. Writing USB descriptor;
  2. Initialization of the device and USB on board;
  3. Processing of incoming data and the formation of the 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 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:
image
Now run the program:
image
We see that she correctly found the device.
image
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:

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

Source: https://habr.com/ru/post/116709/


All Articles