-
-
Notifications
You must be signed in to change notification settings - Fork 2
04. Control Packets
Now we start to decouple the core functionality that will never change from the bits and bobs that vary from device to device and respond to this first request.
Go read USB in a NutShell if not done already. This knowledge will be expected from now on.
Requests on a control endpoint work by first sending a Setup-Package, which we need to parse. Then follows an optional transfer of data (in this case the device descriptor) followed by a Status-Package of the other party to confirm successful reception. We can ignore the status packages of the host, as the host, not the device, has to initiate a retry anyway.
The first package (from a windows machine at least) is a Device Descriptor Request. We will focus on answering that one in this step. This will also lay the groundwork for handling all the control-requests.
Also we will add a way to properly copy data to and from USB-SRAM.
First we create a structure containing the information from the setup packet.
typedef struct {
unsigned char RequestType;
unsigned char Request;
unsigned short Value;
unsigned short Index;
unsigned short Length;
} USB_SETUP_PACKET;
The GetDescriptor
- and SetDescriptor
-Request of the device split the Value-field into two one byte fields. Because the encoding of the USB packets is little endian (low byte first), the first byte of the Value-field is the lower half and will actually contain the second field. A union will split the field correctly.
typedef struct {
unsigned char RequestType;
unsigned char Request;
union {
unsigned short Value;
struct {
unsigned char DescriptorIndex;
unsigned char DescriptorType;
};
};
unsigned short Index;
unsigned short Length;
} USB_SETUP_PACKET;
We create usb_config
to host all the stuff that can be configured and is not fundamental USB functionality. In the Header, we first define the data structure for the Device Descriptor.
typedef struct {
unsigned char Length;
unsigned char Type;
unsigned short USBVersion;
unsigned char DeviceClass;
unsigned char DeviceSubClass;
unsigned char DeviceProtocol;
unsigned char MaxPacketSize;
unsigned short VendorID;
unsigned short ProductID;
unsigned short DeviceVersion;
unsigned char strManufacturer;
unsigned char strProduct;
unsigned char strSerialNumber;
unsigned char Configurations;
} USB_DESCRIPTOR_DEVICE;
Then we create a getter for the device configuration and the configuration itself in the usb_config.c
-file.
static const USB_DESCRIPTOR_DEVICE DeviceDescriptor = {
.Length = 18,
.Type = 0x01,
.USBVersion = 0x0200,
.DeviceClass = 0x00,
.DeviceSubClass = 0x00,
.DeviceProtocol = 0x00,
.MaxPacketSize = 64,
.VendorID = 0x0483,
.ProductID = 0x5740,
.DeviceVersion = 0x0001,
.strManufacturer = 0,
.strProduct = 0,
.strSerialNumber = 0,
.Configurations = 1};
USB_DESCRIPTOR_DEVICE *USB_GetDeviceDescriptor() {
return &DeviceDescriptor;
}
In the CTR-Handler, if we receive messages from EP0 we transfer them to a dedicated handler for control messages, so that we can keep everything in one place. For this we need to check the USB_ISTR
for the endpoint that received the interrupt.
else if ((USB->ISTR & USB_ISTR_CTR) != 0) {
if((USB->ISTR & USB_ISTR_EP_ID) == 0) {
USB_HandleControl();
}
}
In the USB_HandleControl
we can either check the direction flag of USB_ISTR
or just use the RX and TX indicators in the Endpoint register. We can ignore the Transmit part at the moment and just clear the flag. RX we also need to clear to make room for the next transmission.
For receiving, we need to check for a SETUP-Packet. If we receive one of those, we have to abandon any other transmission we had in the control pipeline and start anew.
static void USB_HandleControl() {
if(USB->EP0R & USB_EP_CTR_RX) {
// We received a control message
if(USB->EP0R & USB_EP_SETUP) {
}
USB_SetEP(&USB->EP0R, USB_EP_RX_VALID, USB_EP_CTR_RX | USB_EP_RX_VALID);
}
if(USB->EP0R & USB_EP_CTR_TX) {
// We just sent a control message
USB_SetEP(&USB->EP0R, 0x00, USB_EP_CTR_TX);
}
}
We now parse the Packet in the packet buffer as a Setup packet and act accordingly. And because the setup can do a lot of different stuff, we put that in a separate function to keep things clean.
if (USB->EP0R & USB_EP_SETUP) {
USB_SETUP_PACKET *setup = EP0_Buf[0];
USB_HandleSetup(setup);
}
To process the setup packet, we need to check the recipient (bits 4-0 of Request Type) and the Request itself.
static void USB_HandleSetup(USB_SETUP_PACKET *setup) {
if ((setup->RequestType & 0x0F) == 0) { // Device Requests
switch (setup->Request) {
case 0x06: // Get Descriptor
break;
}
}
}
Now we get the device descriptor and put it into the transmission buffer. As all access to the USB-SRAM can only be done in half-words, we need to manually copy them as the compiler will use word-size transfers. Using byte-sized transfers will also mess up the memory. We mark the arguments as volatile to inform the compiler that the memory access has side effects and should not be optimized.
static void USB_CopyMemory(volatile short *source, volatile short *target, short length) {
for (int i = 0; i < length / 2; i++) {
target[i] = source[i];
}
if(length % 2 == 1) {
((char*)target)[length - 1] = ((char*)source)[length - 1];
}
}
In the GetDescriptor request, we now fetch our descriptor and copy it into the transmit buffer. Then we set the size of the data and enable the transmission. We also add the next expected request for debugging purposes.
case 0x05: // Set Address
__BKPT();
break;
case 0x06: { // Get Descriptor
USB_DESCRIPTOR_DEVICE *descriptor = USB_GetDeviceDescriptor();
USB_CopyMemory(descriptor, EP0_Buf[1], sizeof(USB_DESCRIPTOR_DEVICE));
BTable[0].COUNT_TX = sizeof(USB_DESCRIPTOR_DEVICE);
USB_SetEP(&USB->EP0R, USB_EP_TX_VALID, USB_EP_TX_VALID);
} break;
}
You can check if the EP0_Buf[1]
contains the descriptor in the correct layout. If the copy memory function is not working correctly, each byte might be occuring twice.
The enumeration should now take up to 30s before failing. The device manager should change the entry to Unknown USB Device (Set Address Failed)
.
The breakpoint of the SetAddress-Message should trigger after a while.