USB
module reference#include <usb.h>Makefile :
usb
is already included by default
USB is, as its name implies, universal and probably doesn't need much presentation from a user point of view. Usually, you plug your device and it just works. The protocol to achieve this behind the scenes is very interesting however, and before going into the specifics of libtungsten's USB API, we'll go through some generic USB concepts that you will need in order to use it.
USB is a master/slave protocol (like I2C or SPI for instance). The master is called the Host (usually a computer) and the slave is called the Device (a keyboard, a mass storage peripheral, a microcontroller, ...). A Host can manage multiple Devices (up to 127). The Host is responsible for all communications, the Devices cannot talk without being asked.
In order to be so versatile, the USB protocol takes a hierarcherical approch :
Each device has a Vendor ID and a Product ID, which are both 2-byte values that identify respectively the manufacturer and the model of the device. For instance, Atmel's Vendor ID is 0x03EB
(they bought that from the corporation that developped and maintains the protocol) and Carbide's Product ID is 0xCABD
.
Multiple configurations are used by devices which need to completely change the way they are perceived by the host according to some software settings. This is rarely used (most devices only have one default configuration) and we will ignore this level from now on.
An interface represents a feature offered by a device. Devices can have multiple interfaces to allow multiple features to be offered through a single cable. For example, a wireless receiver can provide two interfaces : a keyboard and a mouse. A multi-function printer can provide a Printer interface and a Scanner interface. Devices with multiple interfaces are pretty common and offer a lot of flexibility to manufacturers.
An endpoint is a memory buffer shared by the host and the device to exchange requests and data. Endpoints have a number, a direction, and a type which can be :
When the host detects that a new device is plugged in, a process takes place where the host asks the device what configurations, interfaces and endpoints it provides. This is called enumeration. It is based on small chunks of data called descriptors. A descriptor is simply a small, standard data structure that gives some information on a related feature. For example, there are device descriptors, interface descriptors, endpoint descriptors, and so on, and an interface descriptor tells the host what feature this interface provides (via standard Class, Subclass and Protocol identifiers for standard features such as a keyboard or a printer, or a specific identifier for other custom features).
USB is a powerful but complex protocol and all of the above represents a good amount of new concepts and terminology to assimilate, but with this you already have a fair overview of the way it works. If you want to learn more, here are two great references :
Atmel's ATSAM4 microcontrollers support the USB2.0 full-speed protocol in both Device and Host mode. Currently, only the Device mode is implemented in libtungsten. Once the USB subsystem is initialized with initDevice()
, the library takes care of the whole process of enumeration by the host when the cable is connected. The API described below provides methods to extend the Control endpoint, create new endpoints, and define callbacks on some protocol-related events.
While the protocol is complex, using the library to perform basic request and data transfer with the host (which probably covers the majority of cases) is actually quite simple. All you need to do is call initDevice()
(no argument required) to initialize the module and setControlHandler()
to register a handler that will be called whenever there is a request from the host. See the examples for more in-depth information.
Important note about IN endpoints and their handlers : the handler is not called when a IN request is received (as you might have thought), but before that, as soon as the current bank is ready to receive some data. This is because the USB protocol requires that, when the host sends an IN request, the device should answer immediately (or at least, within a short delay) and there is no time for handlers to be called and data to be moved around at this moment. Instead, the data should be prepared beforehand in the bank and is sent by the hardware when the request is received. Note that this can take an indefinite amount of time : there is no guarantee that the host will ever request this data. If it does, as soon as the transfer is finished, the IN handler is called again to fill the bank with the next chunk of data. In case the host hasn't read the data after some time and you want to change it (for instance if you want to stream some real-time measurements to the host, or if a request on the Control endpoint asks the device to provide something else on this endpoint), you can use abortINTransfer()
to drop the current data and ask the IN handler to be called again to fill the bank with some new data.
For more advance usage, the device, configuration and interface descriptors are declared as extern
, meaning that they can be modified directly by your code. For example, you can change the bMaxPower setting in the configuration descriptor like this :
USB::_configurationDescriptor.bMaxPower=100;
And for even more advance features, take a look at the Hacking section below.
If you simply want your board to be able to connect to a computer through USB in order to receive some commands and respond some data, here are the steps to follow :
Core::init(); SCIF::enableRCFAST(SCIF::RCFASTFrequency::RCFAST_12MHZ); PM::setMainClockSource(PM::MainClockSource::RCFAST);With Carbide, this is automatically done in
Carbide::init();
USB::initDevice();Optionnally, this is where you can define
vendorId
, productId
and deviceRevision
:
USB::initDevice(0x03EB, 0xCABD, 0x0001);
USB::setStringDescriptor(USB::StringDescriptors::MANUFACTURER, ...); USB::setStringDescriptor(USB::StringDescriptors::PRODUCT, ...);
int myUSBControlHandler(USB::SetupPacket &lastSetupPacket, uint8_t* data, int size);
USB::setControlHandler(myUSBControlHandler);
myUSBControlHandler()
, check the 1-byte user-defined request code from lastSetupPacket.bRequest
and the transfer direction from lastSetupPacket.direction
(either USB::EPDir::OUT
for data sent by the computer, or USB::EPDir::IN
for data requested by the computer that you need to send) :IN
transfers, copy the data you need to send (according to the request code) into data
, up to size
bytes (never copy more than that into the buffer), and return
the number of bytes that you are sending (which is always equal to or lower than size
)OUT
transfers, this is usually a command that you need to process; however, do not process any lengthy command in the handler (which is called in an interrupt context), but handle it in your main loop instead :size
bytes from data
sent by the computer into your own buffer, and save the request code as wellUSB::setEndpointBusy();
to prevent the computer from sending another request while you are processing this oneUSB::setEndpointReady();
to allow another command to be sentlastSetupPacket.handled = true;
to acknowledge that you understood this command; otherwise, the driver will send an error response by default# Import the USB library import usb.core import usb.util # Get the device with its vendorId and productId dev = usb.core.find(idVendor=0x03EB, idProduct=0xCABD) if dev is None: raise ValueError('Device not found') dev.set_configuration() # Send (0x40) the custom request 0xAB with some data and a 2-second timeout dev.ctrl_transfer(0x40, 0xAB, 0, 0, "hello world", 2000) # Request (0xC0) up to 10 bytes of data with the custom request 0xCD print(dev.ctrl_transfer(0xC0, 0xCD, 0, 0, size, 2000))
Carbide::init()
with the autoBootloaderReset
flag set to true
.descriptor
is either MANUFACTURER
, PRODUCT
or SERIALNUMBER
and size
is the length of the string, up to 30 characters.type
(BULK
, INTERRUPT
or ISOCHRONOUS
) and direction
(IN
or OUT
). The banks are the shared memory buffers that are used to store sent or received data. The endpoint can be configured to use either 1 or 2 banks by setting nBanks
to SINGLE
or DOUBLE
: a single bank is easier to manage, but using two banks allows to transfer large amounts of data more quickly by working on one bank while the other is being read or written by the hardware. Each bank size
can be 8, 16, 32 or 64-byte long (respectively SIZE8
, SIZE16
, SIZE32
, SIZE64
). The endpoint number is returned and should be saved for future operation on the endpoint.Register a handler to be called when the device is connected to a host.
This handler will be called by interrupt, do not use it to perform lengthy tasks.
Register a handler to be called when the device is disconnected from the host.
This handler will be called by interrupt, do not use it to perform lengthy tasks.
Register a handler to be called when the host sends a Start Of Frame (SOF) packet. These packets are sent every millisecond to mark the start of a new frame (a frame is a low-level protocol concept) and can be used as a fairly precise time reference if needed.
This handler will be called by interrupt, do not use it to perform lengthy tasks.
SetupPacket
structure, but its most important fields are :
bRequest
is a 1-byte (uint8_t
) user-defined request codehandled
is a boolean that your handler should set to true
if the request was recognised; otherwise, the library will automatically send a STALL
in response to the host to inform it that this request is not supporteddirection
is either EPDir::IN
or EPDir::OUT
and is used when there is a need to transfer data with the requestdata
and size
are used when there is a data transfer involved :
lastSetupPacket.direction == USB::EPDir::IN
, you can write up to size
bytes to send to the host into data
and you need to return
the actual number of bytes writtenlastSetupPacket.direction == USB::EPDir::OUT
, you can read up to size
bytes sent by the host from data
This handler will be called by interrupt, do not use it to perform lengthy tasks.
Register a handler to be called when a request is received for the given endpointNumber
. handlerType
defines the type of request that this handler can manage, either IN
, OUT
or SETUP
(used for the Control endpoint).
This handler will be called by interrupt, do not use it to perform lengthy tasks.
Mark the given endpoint as busy. Any new request to this endpoint will be delayed until setEndpointReady()
is called on the same endpoint. If the endpoint number is ommited, this will apply to the Control endpoint by default.
Use this in order to temporarily block an endpoint and prevent a new request to arrive when you are still processing the previous one. This can easily happen because the computer is much quicker than the microcontroller, and would cause a race condition difficult to debug.
Mark the given endpoint as ready to process a request again, after it was temporarily blocked with setEndpointBusy()
. If the endpoint number is ommited, this will apply to the Control endpoint by default.
true
if the IN interrupt is enabled on this endpoint.true
if the OUT interrupt is enabled on this endpoint.USB is a powerful but complex protocol and only the most basic features are implemented, both at the driver level and the USB specification level.
One of the features you might want to have that is not currently implemented in the library is defining multiple interfaces for your device. For this, you would need to :
_interfaceDescriptor
with the required class and subclass_bankEP0
is large enough to store your new descriptor_configurationDescriptor.wTotalLength
in newEndpoint()
to add the length of your descriptor in the default configurationep0INHandler()
to copy your descriptor into the bank at the end of the default interface descriptor when the hosts requests the configuration descriptor (it's the if
branch where requestType
equals STANDARD
, bRequest
equals USBREQ_GET_DESCRIPTOR
, and descriptorType
equals USBDESC_CONFIGURATION
)#ifndef _USB_H_ #define _USB_H_ #include <stdint.h> #include "gpio.h" // This module allows the chip to communicate on an USB // bus, either as Device or Host. Only device mode is currently // supported by the driver. // References : // http://www.beyondlogic.org/usbnutshell/usb1.shtml // "USB Complete Fourth Edition" by Jan Axelson namespace USB { // Peripheral memory space base address const uint32_t USB_BASE = 0x400A5000; // Registers addresses const uint32_t OFFSET_UDCON = 0x0000; // Device General Control Register const uint32_t OFFSET_UDINT = 0x0004; // Device Global Interrupt Register const uint32_t OFFSET_UDINTCLR = 0x0008; // Device Global Interrupt Clear Register const uint32_t OFFSET_UDINTSET = 0x000C; // Device Global Interrupt Set Register const uint32_t OFFSET_UDINTE = 0x0010; // Device Global Interrupt Enable Register const uint32_t OFFSET_UDINTECLR = 0x0014; // Device Global Interrupt Enable Clear Register const uint32_t OFFSET_UDINTESET = 0x0018; // Device Global Interrupt Enable Set Register const uint32_t OFFSET_UERST = 0x001C; // Endpoint Enable/Reset Register const uint32_t OFFSET_UDFNUM = 0x0020; // Device Frame Number Register const uint32_t OFFSET_UECFG0 = 0x0100; // Endpoint n Configuration Register const uint32_t OFFSET_UESTA0 = 0x0130; // Endpoint n Status Register const uint32_t OFFSET_UESTA0CLR = 0x0160; // Endpoint n Status Clear Register const uint32_t OFFSET_UESTA0SET = 0x0190; // Endpoint n Status Set Register const uint32_t OFFSET_UECON0 = 0x01C0; // Endpoint n Control Register const uint32_t OFFSET_UECON0SET = 0x01F0; // Endpoint n Control Set Register const uint32_t OFFSET_UECON0CLR = 0x0220; // Endpoint n Control Clear Register const uint32_t OFFSET_UHCON = 0x0400; // Host General Control Register const uint32_t OFFSET_UHINT = 0x0404; // Host Global Interrupt Register const uint32_t OFFSET_UHINTCLR = 0x0408; // Host Global Interrupt Clear Register const uint32_t OFFSET_UHINTSET = 0x040C; // Host Global Interrupt Set Register const uint32_t OFFSET_UHINTE = 0x0410; // Host Global Interrupt Enable Register const uint32_t OFFSET_UHINTECLR = 0x0414; // Host Global Interrupt Enable Clear Register const uint32_t OFFSET_UHINTESET = 0x0418; // Host Global Interrupt Enable Set Register const uint32_t OFFSET_UPRST = 0x041C; // Pipe Enable/Reset Register const uint32_t OFFSET_UHFNUM = 0x0420; // Host Frame Number Register const uint32_t OFFSET_UHSOFC = 0x0424; // Host Start Of Frame Control Register const uint32_t OFFSET_UPCFG0 = 0x0500; // Pipe n Configuration Register const uint32_t OFFSET_UPSTA0 = 0x0530; // Pipe n Status Register const uint32_t OFFSET_UPSTA0CLR = 0x0560; // Pipe n Status Clear Register const uint32_t OFFSET_UPSTA0SET = 0x0590; // Pipe n Status Set Register const uint32_t OFFSET_UPCON0 = 0x05C0; // Pipe n Control Register const uint32_t OFFSET_UPCON0SET = 0x05F0; // Pipe n Control Set Register const uint32_t OFFSET_UPCON0CLR = 0x0620; // Pipe n Control Clear Register const uint32_t OFFSET_UPINRQ0 = 0x0650; // Pipe n IN Request Register const uint32_t OFFSET_USBCON = 0x0800; // General Control Register const uint32_t OFFSET_USBSTA = 0x0804; // General Status Register const uint32_t OFFSET_USBSTACLR = 0x0808; // General Status Clear Register const uint32_t OFFSET_USBSTASET = 0x080C; // General Status Set Register const uint32_t OFFSET_UVERS = 0x0818; // IP Version Register const uint32_t OFFSET_UFEATURES = 0x081C; // IP Features Register const uint32_t OFFSET_UADDRSIZE = 0x0820; // IP PB Address Size Register const uint32_t OFFSET_UNAME1 = 0x0824; // IP Name Register 1 const uint32_t OFFSET_UNAME2 = 0x0828; // IP Name Register 2 const uint32_t OFFSET_USBFSM = 0x082C; // USB Finite State Machine Status Register const uint32_t OFFSET_UDESC = 0x0830; // USB Descriptor address // Subregisters const uint32_t USBCON_FRZCLK = 14; const uint32_t USBCON_USBE = 15; const uint32_t USBCON_UIMOD = 24; // Datasheet says 25, that's wrong const uint32_t USBSTA_VBUSRQ = 9; const uint32_t USBSTA_SPEED = 12; const uint32_t USBSTA_CLKUSABLE = 14; const uint32_t USBSTA_SUSPEND = 16; const uint32_t UDCON_UADD = 0; const uint32_t UDCON_ADDEN = 7; const uint32_t UDCON_DETACH = 8; const uint32_t UDCON_RMWKUP = 9; const uint32_t UDCON_LS = 12; const uint32_t UDCON_GNAK = 17; const uint32_t UDINT_SUSP = 0; const uint32_t UDINT_SOF = 2; const uint32_t UDINT_EORST = 3; const uint32_t UDINT_WAKEUP = 4; const uint32_t UDINT_EORSM = 5; const uint32_t UDINT_UPRSM = 6; const uint32_t UDINT_EP0INT = 12; const uint32_t UECFG_EPBK = 2; const uint32_t UECFG_EPSIZE = 4; const uint32_t UECFG_EPDIR = 8; const uint32_t UECFG_EPTYPE = 11; const uint32_t UECFG_REPNB = 17; const uint32_t UECON_TXINE = 0; const uint32_t UECON_RXOUTE = 1; const uint32_t UECON_RXSTPE = 2; const uint32_t UECON_ERRORFE = 2; const uint32_t UECON_NAKOUTE = 3; const uint32_t UECON_NAKINE = 4; const uint32_t UECON_STALLEDE = 6; const uint32_t UECON_CRCERRE = 6; const uint32_t UECON_NREPLY = 8; const uint32_t UECON_RAMACERE = 11; const uint32_t UECON_NBUSYBKE = 12; const uint32_t UECON_KILLBK = 13; const uint32_t UECON_FIFOCON = 14; const uint32_t UECON_RSTDT = 18; const uint32_t UECON_STALLRQ = 19; const uint32_t UECON_BUSY0E = 24; const uint32_t UECON_BUSY1E = 25; const uint32_t UESTA_TXINI = 0; const uint32_t UESTA_RXOUTI = 1; const uint32_t UESTA_RXSTPI = 2; const uint32_t UESTA_ERRORFI = 2; const uint32_t UESTA_NAKOUTI = 3; const uint32_t UESTA_NAKINI = 4; const uint32_t UESTA_STALLEDI = 6; const uint32_t UESTA_CRCERRI = 6; const uint32_t UESTA_DTSEQ = 8; const uint32_t UESTA_RAMACERI = 11; const uint32_t UESTA_NBUSYBK = 12; const uint32_t UESTA_CURRBK = 14; const uint32_t UESTA_CTRLDIR = 17; // Endpoints RAM registers const int N_EP_MAX = 8; const int EP_DESCRIPTOR_SIZE = 8; const int EP_DESCRIPTOR_BANK_SIZE = 4; const int EP_ADDR = 0; const int EP_PCKSIZE = 1; const int PCKSIZE_BYTE_COUNT = 0; const int PCKSIZE_BYTE_COUNT_MASK = 0x00007FFF; const int PCKSIZE_MULTI_PACKET_SIZE = 16; const int PCKSIZE_MULTI_PACKET_SIZE_MASK = 0x7FFF0000; const int PCKSIZE_AUTO_ZLP = 31; const int EP_CTR_STA = 2; const int CTR_STA_STALLRQ_NEXT = 0; const int CTR_STA_CRCERR = 16; const int CTR_STA_OVERF = 17; const int CTR_STA_UNDERF = 18; // Error codes const Error::Code ERR_CLOCK_NOT_USABLE = 0x0001; // USB Device states enum class State { SUSPEND, POWERED, DEFAULT, ADDRESS, CONFIGURED }; // USB requests codes const int USBREQ_GET_STATUS = 0; const int USBREQ_CLEAR_FEATURE = 1; const int USBREQ_SET_FEATURE = 3; const int USBREQ_SET_ADDRESS = 5; const int USBREQ_GET_DESCRIPTOR = 6; const int USBREQ_SET_DESCRIPTOR = 7; const int USBREQ_GET_CONFIGURATION = 8; const int USBREQ_SET_CONFIGURATION = 9; const int USBREQ_GET_INTERFACE = 10; const int USBREQ_SET_INTERFACE = 11; const int USBREQ_SYNC_FRAME = 12; // USB Device features const int USBFEAT_DEVICE_REMOTE_WAKEUP = 1; // USB descriptors const int USBDESC_DEVICE = 1; const int USBDESC_CONFIGURATION = 2; const int USBDESC_STRING = 3; const int USBDESC_INTERFACE = 4; const int USBDESC_ENDPOINT = 5; const int USBDESC_DEVICE_QUALIFIER = 6; const int USBDESC_OTHER_SPEED_CONFIGURATION = 7; const int USBDESC_INTERFACE_POWER = 8; struct DeviceDescriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdUSB; uint8_t bDeviceClass; uint8_t bDeviceSubClass; uint8_t bDeviceProtocol; uint8_t bMaxPacketSize0; uint16_t idVendor; uint16_t idProduct; uint16_t bcdDevice; uint8_t iManufacturer; uint8_t iProduct; uint8_t iSerialNumber; uint8_t bNumConfigurations; }; struct ConfigurationDescriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t wTotalLength; uint8_t bNumInterfaces; uint8_t bConfigurationValue; uint8_t iConfiguration; uint8_t bmAttributes; uint8_t bMaxPower; }; struct InterfaceDescriptor { uint8_t bLength; uint8_t bDescriptorType; uint8_t bInterfaceNumber; uint8_t bAlternateSetting; uint8_t bNumEndpoints; uint8_t bInterfaceClass; uint8_t bInterfaceSubclass; uint8_t bInterfaceProtocol; uint8_t iInterface; }; struct EndpointDescriptor { uint8_t bLength; uint8_t bDescriptorType; uint8_t bEndpointAddress; uint8_t bmAttributes; uint16_t wMaxPacketSize; uint8_t bInterval; }; struct String0Descriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t wLANGID0; }; struct StringDescriptor { uint8_t bLength; uint8_t bDescriptorType; const char16_t* bString; }; extern DeviceDescriptor _deviceDescriptor; extern ConfigurationDescriptor _configurationDescriptor; extern InterfaceDescriptor _interfaceDescriptor; // String descriptors enum class StringDescriptors { MANUFACTURER, PRODUCT, SERIALNUMBER, NUMBER // Number of string descriptors }; const int MAX_STRING_DESCRIPTOR_SIZE = 30; const char16_t DEFAULT_IMANUFACTURER[] = u"libtungsten"; const char16_t DEFAULT_IPRODUCT[] = u"Carbide"; const char16_t DEFAULT_SERIALNUMBER[] = u"beta"; extern String0Descriptor _string0Descriptor; extern StringDescriptor _stringDescriptors[]; // Default descriptor ids const uint16_t DEFAULT_VENDOR_ID = 0x03eb; // Atmel Corp vendor ID const uint16_t DEFAULT_PRODUCT_ID = 0xcabd; const uint16_t DEFAULT_DEVICE_REVISION = 0x0001; // Endpoints enum class EPBanks { SINGLE = 0, DOUBLE = 1 }; enum class EPSize { SIZE8 = 0b000, SIZE16 = 0b001, SIZE32 = 0b010, SIZE64 = 0b011, SIZE128 = 0b100, // /!\ EP sizes above 64 are only possible for SIZE256 = 0b101, // isochronous transfers in low- and full-speed, SIZE512 = 0b110, // according to the USB specification SIZE1024 = 0b111 }; const int EP_SIZES[] = { 8, 16, 32, 64, 128, 256, 512, 1024 }; enum class EPDir { OUT = 0, IN = 1 }; enum class EPType { CONTROL = 0b00, ISOCHRONOUS = 0b01, BULK = 0b10, INTERRUPT = 0b11 }; enum class EPHandlerType { SETUP, IN, OUT, NUMBER }; struct EndpointConfig { bool enabled; EPType type; EPDir direction; EPBanks nBanks; EPSize size; uint8_t* bank0; uint8_t* bank1; int (*handlers[static_cast<int>(EPHandlerType::NUMBER)])(int); // Array of function pointers of EPHandlerType EndpointDescriptor descriptor; }; using Endpoint = int; // Helper type to manage endpoints, created by newEndpoint() const Endpoint EP_ERROR = -1; extern const int BANK_EP0_SIZE; // Packets enum class SetupRequestType { STANDARD = 0b00, CLASS = 0b01, VENDOR = 0b10 }; enum class SetupRecipient { DEVICE = 0b00000, INTERFACE = 0b00001, ENDPOINT = 0b00010, OTHER = 0b00011 }; struct SetupPacket { uint8_t bmRequestType; uint8_t bRequest; uint16_t wValue; uint16_t wIndex; uint16_t wLength; EPDir direction; SetupRequestType requestType; SetupRecipient recipent; bool handled; }; // Package-dependant, defined in pins_sam4l_XX.cpp extern const struct GPIO::Pin PIN_DM; extern const struct GPIO::Pin PIN_DP; // Module API void initDevice(uint16_t vendorId=DEFAULT_VENDOR_ID, uint16_t productId=DEFAULT_PRODUCT_ID, uint16_t deviceRevision=DEFAULT_DEVICE_REVISION); void setStringDescriptor(StringDescriptors descriptor, const char* string, int size); Endpoint newEndpoint(EPType type, EPDir direction, EPBanks nBanks, EPSize size, uint8_t* bank0, uint8_t* bank1=nullptr); void setConnectedHandler(void (*handler)()); void setDisconnectedHandler(void (*handler)()); void setStartOfFrameHandler(void (*handler)()); void setControlHandler(int (*handler)(SetupPacket &lastSetupPacket, uint8_t* data, int size)); void setEndpointHandler(Endpoint endpointNumber, EPHandlerType handlerType, int (*handler)(int)); void setEndpointBusy(Endpoint endpointNumber=0); void setEndpointReady(Endpoint endpointNumber=0); void enableINInterrupt(Endpoint endpointNumber); void abortINTransfer(Endpoint endpointNumber); void disableINInterrupt(Endpoint endpointNumber); bool isINInterruptEnabled(Endpoint endpointNumber); void enableOUTInterrupt(Endpoint endpointNumber); void disableOUTInterrupt(Endpoint endpointNumber); bool isOUTInterruptEnabled(Endpoint endpointNumber); void remoteWakeup(); void interruptHandler(); int ep0SETUPHandler(int unused); int ep0INHandler(int unused); int ep0OUTHandler(int size); } #endif
#include "usb.h" #include "core.h" #include "pm.h" #include "scif.h" #include "utils.h" #include "gpio.h" #include <string.h> namespace USB { // Current state of the driver during enumeration volatile State _state = State::SUSPEND; volatile State _stateBeforeSuspend = State::POWERED; // Endpoints // The three least significant bits of UDESC.UDESCA are always zero according to the datasheet, // so _epRAMDescriptors must be aligned to an 8-bytes boundary struct EndpointConfig _endpoints[N_EP_MAX]; uint32_t _epRAMDescriptors[N_EP_MAX * EP_DESCRIPTOR_SIZE] __attribute__ ((aligned (0b1000))); volatile int _nEndpoints = 0; // Bank for EP0 // This must be large enough to store the whole configuration descriptor : // 9 (config desc size) + 9 (iface desc size) + 8 (N_EP_MAX) * 7 (ep desc size) = 74 bytes max // and must be a multiple of the endpoint size (64) const int BANK_EP0_SIZE = 512; uint8_t _bankEP0[BANK_EP0_SIZE]; // Struct where the parameters of the last Setup request are stored SetupPacket _lastSetupPacket; volatile bool _setupPacketAvailable = false; volatile bool _doEnableAddress = false; volatile bool _deviceRemoteWakeup = false; // Default device descriptor content DeviceDescriptor _deviceDescriptor = { .bLength = 18, .bDescriptorType = 0x01, .bcdUSB = 0x0200, // USB version 2.0.0 .bDeviceClass = 0x00, .bDeviceSubClass = 0x00, .bDeviceProtocol = 0x00, .bMaxPacketSize0 = 64, .idVendor = DEFAULT_VENDOR_ID, .idProduct = DEFAULT_PRODUCT_ID, .bcdDevice = DEFAULT_DEVICE_REVISION, .iManufacturer = static_cast<uint8_t>(StringDescriptors::MANUFACTURER) + 1, .iProduct = static_cast<uint8_t>(StringDescriptors::PRODUCT) + 1, .iSerialNumber = static_cast<uint8_t>(StringDescriptors::SERIALNUMBER) + 1, .bNumConfigurations = 1 }; // Default configuration descriptor content // Full configuration descriptor content, with subordinate interface and endpoint descriptors ConfigurationDescriptor _configurationDescriptor = { .bLength = 9, .bDescriptorType = 0x02, .wTotalLength = 18, .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0xA0, // TODO : bus power settings .bMaxPower = 150 // In units of 2mA, therefore 300mA }; InterfaceDescriptor _interfaceDescriptor = { .bLength = 9, .bDescriptorType = 0x04, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 0, .bInterfaceClass = 0xFF, // TODO cf p109 .bInterfaceSubclass = 0xFF, .bInterfaceProtocol = 0xFF, .iInterface = 0, }; // String0 descriptor, list of supported languages // See http://www.usb.org/developers/docs/USB_LANGIDs.pdf String0Descriptor _string0Descriptor = { .bLength = 4, .bDescriptorType = 0x03, .wLANGID0 = 0x0409, // English (United States) }; // List of available String descriptors. This must match the enum StringDescriptors. // bLength should be 2 bytes longer to account for bLength and bDescriptorType, // but also 2 bytes shorter to remove the null-terminating character at the end // of the string : sizeof() + 2 - 2 = sizeof() StringDescriptor _stringDescriptors[] = { { // Device.iManufacturer .bLength = sizeof(DEFAULT_IMANUFACTURER), .bDescriptorType = 0x03, .bString = DEFAULT_IMANUFACTURER }, { // Device.iProduct .bLength = sizeof(DEFAULT_IPRODUCT), .bDescriptorType = 0x03, .bString = DEFAULT_IPRODUCT }, { // Device.iSerialNumber .bLength = sizeof(DEFAULT_SERIALNUMBER), .bDescriptorType = 0x03, .bString = DEFAULT_SERIALNUMBER } }; char16_t _customStringDescriptors[static_cast<int>(StringDescriptors::NUMBER)][MAX_STRING_DESCRIPTOR_SIZE]; extern uint8_t INTERRUPT_PRIORITY; // User handlers void (*_connectedHandler)() = nullptr; void (*_disconnectedHandler)() = nullptr; void (*_startOfFrameHandler)() = nullptr; int (*_controlHandler)(SetupPacket &_lastSetupPacket, uint8_t* data, int size) = nullptr; // Initialize the USB controller in Device mode void initDevice(uint16_t vendorId, uint16_t productId, uint16_t deviceRevision) { // Init state _state = State::SUSPEND; // Enable the clocks required by the USB PM::enablePeripheralClock(PM::CLK_USB_HSB); PM::enablePeripheralClock(PM::CLK_USB); SCIF::enableRCFAST(SCIF::RCFASTFrequency::RCFAST_12MHZ); SCIF::enablePLL(1, 0, SCIF::GCLKSource::RCFAST, 12000000UL); SCIF::enableGenericClock(SCIF::GCLKChannel::GCLK7_USB, SCIF::GCLKSource::PLL); // Check the clock if (!((*(volatile uint32_t*)(USB_BASE + OFFSET_USBSTA)) & (1 << USBSTA_CLKUSABLE))) { Error::happened(Error::Module::USB, ERR_CLOCK_NOT_USABLE, Error::Severity::CRITICAL); } // Enable signal pins GPIO::enablePeripheral(PIN_DM); GPIO::enablePeripheral(PIN_DP); // USBCON (General Control Register) : set USB controller in Device mode (*(volatile uint32_t*)(USB_BASE + OFFSET_USBCON)) = 0 << USBCON_FRZCLK // FRZCLK : unfreeze input clocks | 1 << USBCON_USBE // USBE : enable controller | 1 << USBCON_UIMOD; // UIMOD : set in device mode // Initialize memory buffers memset(_endpoints, 0, sizeof(_endpoints)); memset(_epRAMDescriptors, 0, sizeof(_epRAMDescriptors)); // UDESC (USB Descriptor Address) : set descriptor vector address (*(volatile uint32_t*)(USB_BASE + OFFSET_UDESC)) = (uint32_t)_epRAMDescriptors; // UDCON (Device General Control Register) : attach pullup to start device enumeration (*(volatile uint32_t*)(USB_BASE + OFFSET_UDCON)) = 0 << UDCON_LS // LS : full-speed mode | 0 << UDCON_DETACH; // DETACH : attach device // Configure the device descriptor _deviceDescriptor.idVendor = vendorId; _deviceDescriptor.idProduct = productId; _deviceDescriptor.bcdDevice = deviceRevision; // Init the first endpoint (mandatory Control Endpoint) EndpointConfig* ep = &_endpoints[0]; ep->enabled = true; ep->type = EPType::CONTROL; ep->direction = EPDir::OUT; ep->nBanks = EPBanks::SINGLE; ep->size = EPSize::SIZE64; ep->bank0 = _bankEP0; setEndpointHandler(0, EPHandlerType::SETUP, ep0SETUPHandler); setEndpointHandler(0, EPHandlerType::IN, ep0INHandler); setEndpointHandler(0, EPHandlerType::OUT, ep0OUTHandler); _nEndpoints = 1; // Enable interrupts (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTESET)) = 1 << UDINT_SUSP | 1 << UDINT_EORST | 1 << UDINT_WAKEUP; Core::setInterruptHandler(Core::Interrupt::USBC, interruptHandler); Core::enableInterrupt(Core::Interrupt::USBC, INTERRUPT_PRIORITY); } // Set the content of a custom string descriptor void setStringDescriptor(StringDescriptors descriptor, const char* string, int size) { // Check the input string size if (size > MAX_STRING_DESCRIPTOR_SIZE) { size = MAX_STRING_DESCRIPTOR_SIZE; } // Since the input string is a char* but the USB protocol expects a char16*, we need to // copy each character manually char16_t* customString = _customStringDescriptors[static_cast<int>(descriptor)]; for (int i = 0; i < size; i++) { if (string[i] == 0) { size = i; break; } customString[i] = string[i]; } // The descriptor is also twice as long as the input string _stringDescriptors[static_cast<int>(descriptor)].bLength = 2 * size + 2; _stringDescriptors[static_cast<int>(descriptor)].bString = customString; } // Initialize an endpoint Endpoint newEndpoint(EPType type, EPDir direction, EPBanks nBanks, EPSize size, uint8_t* bank0, uint8_t* bank1) { // Check endpoint number const int n = _nEndpoints; if (n >= N_EP_MAX) { return EP_ERROR; } _nEndpoints++; // Internal configuration EndpointConfig* ep = &_endpoints[n]; ep->enabled = true; ep->type = type; ep->direction = direction; ep->nBanks = nBanks; ep->size = size; ep->bank0 = bank0; ep->bank1 = bank1; if (_state >= State::DEFAULT) { // Reset and enable the endpoint (*(volatile uint32_t*)(USB_BASE + OFFSET_UERST)) &= ~(1 << n); (*(volatile uint32_t*)(USB_BASE + OFFSET_UERST)) |= 1 << n; // RAM descriptor configuration (*(volatile uint32_t*)(USB_BASE + OFFSET_UECFG0 + n * 4)) = static_cast<int>(nBanks) << UECFG_EPBK // EPBK : single/multi-bank endpoint | static_cast<int>(size) << UECFG_EPSIZE // EPSIZE : N-bytes long banks | static_cast<int>(direction) << UECFG_EPDIR // EPDIR : direction IN/OUT | static_cast<int>(type) << UECFG_EPTYPE; // EPTYPE : endpoint type (Control, ...) // Descriptor configuration ep->descriptor.bLength = 7; ep->descriptor.bDescriptorType = 0x05; ep->descriptor.bEndpointAddress = n | ((direction == EPDir::IN ? 1 : 0) << 7); ep->descriptor.bmAttributes = static_cast<int>(type); ep->descriptor.wMaxPacketSize = EP_SIZES[static_cast<int>(size)]; ep->descriptor.bInterval = 10; // Set up descriptor for bank0 //memset(bank0, 0, EP_SIZES[static_cast<int>(size)]); const int offset0 = n * EP_DESCRIPTOR_SIZE; _epRAMDescriptors[offset0 + EP_ADDR] = (uint32_t)bank0; _epRAMDescriptors[offset0 + EP_PCKSIZE] = 0; _epRAMDescriptors[offset0 + EP_CTR_STA] = 0; // Set up descriptor for bank1 if (nBanks == EPBanks::DOUBLE && bank1 != nullptr) { //memset(bank1, 0, EP_SIZES[static_cast<int>(size)]); const int offset1 = n * EP_DESCRIPTOR_SIZE + EP_DESCRIPTOR_BANK_SIZE; _epRAMDescriptors[offset1 + EP_ADDR] = (uint32_t)bank1; _epRAMDescriptors[offset1 + EP_PCKSIZE] = 0; _epRAMDescriptors[offset1 + EP_CTR_STA] = 0; } // Enable interrupts (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTESET)) = 1 << (UDINT_EP0INT + n); (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0SET + n * 4)) = (type == EPType::CONTROL ? 1 << UECON_RXSTPE : 0) // SETUP packet | 1 << UECON_RXOUTE; // OUT packet (always enabled ) // Update the configuration and interface descriptors with the new endpoint // -1 because endpoint 0 is not counted _configurationDescriptor.wTotalLength = _configurationDescriptor.bLength + _interfaceDescriptor.bLength + (_nEndpoints > 1 ? (_nEndpoints - 1) * ep->descriptor.bLength : 0); _interfaceDescriptor.bNumEndpoints = _nEndpoints - 1; } return n; } // Main interrupt handler called when an USB-related event happens void interruptHandler() { uint32_t udint = (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINT)); uint32_t udinte = (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTE)); // Suspend if (udinte & udint & (1 << UDINT_SUSP)) { // Save the current state _stateBeforeSuspend = _state; // Update the driver state _state = State::SUSPEND; // Disable this interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTECLR)) = 1 << UDINT_SUSP; // Clear all interrupts (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTCLR)) = 0xFFFFFFFF; // Enable the WAKEUP interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTESET)) = 1 << UDINT_WAKEUP; // Freeze clock (*(volatile uint32_t*)(USB_BASE + OFFSET_USBCON)) |= 1 << USBCON_FRZCLK; // FRZCLK : freeze input clocks // Call user handler if (_disconnectedHandler != nullptr) { _disconnectedHandler(); } // Since clocks are frozen, don't do anything more return; } // Wake up if (udinte & udint & (1 << UDINT_WAKEUP)) { // Restore the driver state _state = _stateBeforeSuspend; // Unfreeze clock (*(volatile uint32_t*)(USB_BASE + OFFSET_USBCON)) &= ~(uint32_t)(1 << USBCON_FRZCLK); // FRZCLK : unfreeze input clocks // Disable this interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTECLR)) = 1 << UDINT_WAKEUP; // Clear interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTCLR)) = 1 << UDINT_WAKEUP; // Enable the SUSP interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTCLR)) = 1 << UDINT_SUSP; (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTESET)) = 1 << UDINT_SUSP; // Call user handler if (_connectedHandler != nullptr) { _connectedHandler(); } } // End of reset if (udinte & udint & (1 << UDINT_EORST)) { // Clear interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTCLR)) = 1 << UDINT_EORST; // Reset state _state = State::DEFAULT; _stateBeforeSuspend = State::POWERED; _setupPacketAvailable = false; _doEnableAddress = false; // Recreate endpoints _nEndpoints = 0; for (int i = 0; i < N_EP_MAX; i++) { if (_endpoints[i].enabled) { newEndpoint(_endpoints[i].type, _endpoints[i].direction, _endpoints[i].nBanks, _endpoints[i].size, _endpoints[i].bank0, _endpoints[i].bank1); } else { break; } } } // Start of frame if (udinte & udint & (1 << UDINT_SOF)) { // Clear interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UDINTCLR)) = 1 << UDINT_SOF; // Call user handler if (_startOfFrameHandler != nullptr) { _startOfFrameHandler(); } } // Endpoints for (int i = 0; i < _nEndpoints; i++) { if (_endpoints[i].enabled && (udinte & udint & (1 << (UDINT_EP0INT + i)))) { // Check which packet type has been received in UESTA volatile uint32_t* uesta = (volatile uint32_t*)(USB_BASE + OFFSET_UESTA0 + i * 4); volatile uint32_t* uecon = (volatile uint32_t*)(USB_BASE + OFFSET_UECON0 + i * 4); EndpointConfig* ep = &_endpoints[i]; // SETUP packet if (*uecon & *uesta & (1 << UESTA_RXSTPI)) { // Call the handler to read the packet content if (ep->handlers[static_cast<int>(EPHandlerType::SETUP)] != nullptr) { ep->handlers[static_cast<int>(EPHandlerType::SETUP)](0); } // Clear interrupt to acknoledge the packet and free the bank (*(volatile uint32_t*)(USB_BASE + OFFSET_UESTA0CLR + i * 4)) = 1 << UESTA_RXSTPI; } // IN packet // Note : this is not called when an IN packet is received, but whenever the bank is ready // to receive the content for the next IN packet. This is because the data must be sent // immediately after the IN packet, with no time for a handler call, and must therefore be // prepared beforehand. if (*uecon & *uesta & (1 << UESTA_TXINI)) { // Call the handler to write the packet content int bytesToSend = 0; if (ep->handlers[static_cast<int>(EPHandlerType::IN)] != nullptr) { bytesToSend = ep->handlers[static_cast<int>(EPHandlerType::IN)](0); } _epRAMDescriptors[i * EP_DESCRIPTOR_SIZE + EP_PCKSIZE] = (1 << PCKSIZE_AUTO_ZLP) | (bytesToSend & PCKSIZE_BYTE_COUNT_MASK); // Multi-packet mode is automatically enabled if BYTE_COUNT (ie bytesToSend) is larger than // the endpoint size (UECFG.EPSIZE) // Clear interrupt (this will send the packet for a Control endpoint) (*(volatile uint32_t*)(USB_BASE + OFFSET_UESTA0CLR + i * 4)) = 1 << UESTA_TXINI; // If this is an IN endpoint, clear FIFOCON to free the bank and allow the // hardware to send the data at the next IN packet if (ep->type != EPType::CONTROL && ep->direction == EPDir::IN) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0CLR + i * 4)) = 1 << UECON_FIFOCON; } } // OUT packet if (*uecon & *uesta & (1 << UESTA_RXOUTI)) { // Number of bytes received int receivedPacketSize = _epRAMDescriptors[i * EP_DESCRIPTOR_SIZE + EP_PCKSIZE] & PCKSIZE_BYTE_COUNT_MASK; // Call the handler to read the packet content if (ep->handlers[static_cast<int>(EPHandlerType::OUT)] != nullptr) { ep->handlers[static_cast<int>(EPHandlerType::OUT)](receivedPacketSize); } // Clear interrupt (*(volatile uint32_t*)(USB_BASE + OFFSET_UESTA0CLR + i * 4)) = 1 << UESTA_RXOUTI; // If this is an OUT endpoint, clear FIFOCON to free the bank if (ep->type != EPType::CONTROL && ep->direction == EPDir::OUT) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0CLR + i * 4)) = 1 << UECON_FIFOCON; } // If this is an IN or No Data transfer, an OUT packet means the transfer is finished or aborted if (_lastSetupPacket.direction == EPDir::IN || _lastSetupPacket.wLength == 0) { // Disable IN interrupt } } } } } // Handlers for endpoint 0 int ep0SETUPHandler(int unused) { // Read data received in bankEP0 const int EP_N = 0; // Endpoint number : 0 in this handler uint8_t* bank = (uint8_t*)_epRAMDescriptors[EP_N * EP_DESCRIPTOR_SIZE + EP_ADDR]; _lastSetupPacket = { .bmRequestType = bank[0], .bRequest = bank[1], .wValue = uint16_t(bank[2] | bank[3] << 8), .wIndex = uint16_t(bank[4] | bank[5] << 8), .wLength = uint16_t(bank[6] | bank[7] << 8), .direction = static_cast<EPDir>((bank[0] >> 7) & 0b1), .requestType = static_cast<SetupRequestType>((bank[0] >> 5) & 0b11), .recipent = static_cast<SetupRecipient>(bank[0] & 0b11111), .handled = false }; // Kill any pending IN transfer abortINTransfer(0); // If this is an IN or No Data transfer if (_lastSetupPacket.direction == EPDir::IN || _lastSetupPacket.wLength == 0) { // Enable the IN interrupt to answer this request (or ACK it with a ZLP for a No Data transfer) enableINInterrupt(0); } else { // OUT // Configure the MULTI_PACKET_SIZE to allow multi-packet mode for OUT transfers _epRAMDescriptors[EP_N * EP_DESCRIPTOR_SIZE + EP_PCKSIZE] = (BANK_EP0_SIZE << PCKSIZE_MULTI_PACKET_SIZE) & PCKSIZE_MULTI_PACKET_SIZE_MASK; // Disable the IN interrupt to allow the OUT handler to be called disableINInterrupt(0); } // Mark that a SETUP packet has been received _setupPacketAvailable = true; return 0; } int ep0INHandler(int unused) { const int EP_N = 0; // Endpoint number : 0 in this handler // Answer the last SETUP packet received if (_setupPacketAvailable) { // Standard request if (_lastSetupPacket.requestType == SetupRequestType::STANDARD) { uint8_t* bank = (uint8_t*)_epRAMDescriptors[EP_N * EP_DESCRIPTOR_SIZE + EP_ADDR]; // Request recipient : device if (_lastSetupPacket.recipent == SetupRecipient::DEVICE) { // GET_STATUS standard request if (_lastSetupPacket.bRequest == USBREQ_GET_STATUS) { _lastSetupPacket.handled = true; bank[0] = 0x00; bank[1] = static_cast<int>(_deviceRemoteWakeup) << 1; // TODO : self-powered bit return 2; // USBREQ_CLEAR_FEATURE standard request } else if (_lastSetupPacket.bRequest == USBREQ_CLEAR_FEATURE) { if (_lastSetupPacket.wValue == USBFEAT_DEVICE_REMOTE_WAKEUP) { _lastSetupPacket.handled = true; _deviceRemoteWakeup = false; return 0; } // USBREQ_SET_FEATURE standard request } else if (_lastSetupPacket.bRequest == USBREQ_SET_FEATURE) { if (_lastSetupPacket.wValue == USBFEAT_DEVICE_REMOTE_WAKEUP) { _lastSetupPacket.handled = true; _deviceRemoteWakeup = true; return 0; } // GET_DESCRIPTOR standard request } else if (_lastSetupPacket.bRequest == USBREQ_GET_DESCRIPTOR) { // Select descriptor to send const uint8_t descriptorType = _lastSetupPacket.wValue >> 8; const uint8_t descriptorIndex = _lastSetupPacket.wValue & 0xFF; // Default device descriptor if (descriptorType == USBDESC_DEVICE && descriptorIndex == 0) { _lastSetupPacket.handled = true; // Copy the descriptor to the bank const int length = min(_deviceDescriptor.bLength, _lastSetupPacket.wLength); memcpy(bank, (uint8_t*)&_deviceDescriptor, length); return length; // Default configuration and interface descriptors } else if (descriptorType == USBDESC_CONFIGURATION && descriptorIndex == 0) { _lastSetupPacket.handled = true; // Copy the configuration and interface descriptors to the bank uint8_t* bankCursor = bank; int length = min(_configurationDescriptor.wTotalLength, _lastSetupPacket.wLength); memcpy(bankCursor, (uint8_t*)&_configurationDescriptor, _configurationDescriptor.bLength); bankCursor += _configurationDescriptor.bLength; memcpy(bankCursor, (uint8_t*)&_interfaceDescriptor, _interfaceDescriptor.bLength); bankCursor += _interfaceDescriptor.bLength; for (int i = 1; i < _nEndpoints; i++) { // This loop starts at 1 because the descriptor of endpoint 0 must not be sent memcpy(bankCursor, (uint8_t*)&_endpoints[i].descriptor, _endpoints[i].descriptor.bLength); bankCursor += _endpoints[i].descriptor.bLength; } return length; // String0 descriptor (list of available languages) } else if (descriptorType == USBDESC_STRING && descriptorIndex == 0) { _lastSetupPacket.handled = true; // Copy the descriptor to the bank const int length = min(_string0Descriptor.bLength, _lastSetupPacket.wLength); memcpy(bank, (uint8_t*)&_string0Descriptor, length); return length; // String descriptor } else if (descriptorType == USBDESC_STRING && descriptorIndex <= static_cast<int>(StringDescriptors::NUMBER)) { _lastSetupPacket.handled = true; // Copy the descriptor to the bank const StringDescriptor stringDescriptor = _stringDescriptors[descriptorIndex - 1]; const int length = min(stringDescriptor.bLength, _lastSetupPacket.wLength); memcpy(bank, (uint8_t*)&(stringDescriptor), 2); // bLength and bDescriptorType memcpy(bank + 2, stringDescriptor.bString, length - 2); // Actual string content return length; } // SET_ADDRESS standard request } else if (_lastSetupPacket.bRequest == USBREQ_SET_ADDRESS) { _lastSetupPacket.handled = true; // After a SET_ADDRESS request, the new address is configured (UDCON.UADD) but // not enabled (UDCON.ADDEN) immediately because the device must finish the // current transaction with the default address. // The IN interrupt is triggered for the first just after the SETUP request to // prepare the answer of the next IN transfer (request ACK). During this transfer // the address is written to UDCON but must not be enabled yet. When the ACK transfer // is sent, the bank is free once again and the IN interrupt is triggered a second // time. Since there is no Status Stage in a SET_ADDRESS request, this is a good // time to enable the address. if (!_doEnableAddress) { // Write address (disabled) uint32_t udcon = (*(volatile uint32_t*)(USB_BASE + OFFSET_UDCON)); (*(volatile uint32_t*)(USB_BASE + OFFSET_UDCON)) = (udcon & ~(uint32_t)(0xFF << UDCON_UADD)) | _lastSetupPacket.wValue << UDCON_UADD; // UADD : USB device address // Enable the flag _doEnableAddress = true; } else { // Enable address (*(volatile uint32_t*)(USB_BASE + OFFSET_UDCON)) |= 1 << UDCON_ADDEN; // ADDEN : enable address // There is no Status phase for a SET_ADDRESS, // so disable IN interrupt now (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0CLR)) = 1 << UECON_TXINE; // Move through the next enumeration step _state = State::ADDRESS; // Disable the flag _doEnableAddress = false; } // Answer with a ZLP return 0; // SET_CONFIGURATION standard request } else if (_lastSetupPacket.bRequest == USBREQ_SET_CONFIGURATION) { if (_state == State::ADDRESS || _state == State::CONFIGURED) { const uint8_t configurationId = _lastSetupPacket.wValue & 0xFF; if (configurationId == 0) { _lastSetupPacket.handled = true; // The device is no longer configured, reset to Address state _state = State::ADDRESS; // Answer with a ZLP return 0; // There is only one configuration available } else if (configurationId == 1) { _lastSetupPacket.handled = true; // The device is now configured _state = State::CONFIGURED; // Answer with a ZLP return 0; } } } // Request recipient : interface } else if (_lastSetupPacket.recipent == SetupRecipient::INTERFACE) { // uint8_t interface = _lastSetupPacket.wIndex & 0xFF; // Currently unused // GET_STATUS standard request if (_lastSetupPacket.bRequest == USBREQ_GET_STATUS) { _lastSetupPacket.handled = true; bank[0] = 0x00; // Always zero, reserved for future use bank[1] = 0x00; return 2; // GET_INTERFACE standard request } else if (_lastSetupPacket.bRequest == USBREQ_GET_INTERFACE) { // Not implemented // SET_INTERFACE standard request } else if (_lastSetupPacket.bRequest == USBREQ_SET_INTERFACE) { // Not implemented } // Request recipient : endpoint } else if (_lastSetupPacket.recipent == SetupRecipient::ENDPOINT) { // GET_STATUS standard request if (_lastSetupPacket.bRequest == USBREQ_GET_STATUS) { _lastSetupPacket.handled = true; bank[0] = 0x00; // Always zero for endpoint zero bank[1] = 0x00; return 2; // CLEAR_FEATURE standard request } else if (_lastSetupPacket.bRequest == USBREQ_CLEAR_FEATURE) { // Not implemented for endpoint zero // SET_FEATURE standard request } else if (_lastSetupPacket.bRequest == USBREQ_SET_FEATURE) { // Not implemented for endpoint zero } } } else if (_lastSetupPacket.requestType == SetupRequestType::VENDOR) { // Call user handler if this is a IN or No Data request. For an OUT request with data, // the user handler will be called by ep0OUTHandler() when the data has been received if (_controlHandler != nullptr) { // Make sure the IN handler will not be called again when the IN response is sent disableINInterrupt(0); if (_lastSetupPacket.direction == EPDir::IN || _lastSetupPacket.wLength == 0) { int bytesToSend = _controlHandler(_lastSetupPacket, _bankEP0, min(_lastSetupPacket.wLength, BANK_EP0_SIZE)); return min(_lastSetupPacket.wLength, bytesToSend); } // For an OUT request, _lastSetupPacket.handled was set previously by ep0OUTHandler() } } // If the request couldn't be handled, send a STALL if (!_lastSetupPacket.handled) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0SET)) = 1 << UECON_STALLRQ; return 0; } } else { // No SETUP packet available disableINInterrupt(0); } return 0; } int ep0OUTHandler(int size) { const int EP_N = 0; // Endpoint number : 0 in this handler if (_lastSetupPacket.direction == EPDir::IN) { // This is an ACK from the host _setupPacketAvailable = false; disableINInterrupt(0); } else { // This is an OUT packet containing data if (_controlHandler != nullptr) { int size = _epRAMDescriptors[EP_N * EP_DESCRIPTOR_SIZE + EP_PCKSIZE] & PCKSIZE_BYTE_COUNT_MASK; _controlHandler(_lastSetupPacket, _bankEP0, min(size, BANK_EP0_SIZE)); } // Enable the IN interrupt to ACK the received data enableINInterrupt(0); } return 0; } // Handlers management // Set an endpoint handler void setEndpointHandler(Endpoint endpointNumber, EPHandlerType handlerType, int (*handler)(int)) { EndpointConfig* ep = &_endpoints[endpointNumber]; if (ep->enabled) { ep->handlers[static_cast<int>(handlerType)] = handler; } } // Mark the endpoint as busy : all requests will be NACKed until setEndpointReady() is called // on this endpoint void setEndpointBusy(Endpoint endpointNumber) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0SET + endpointNumber * 4)) = 1 << UECON_BUSY0E; } // Mark the endpoint as ready to receive data again, after a call to setEndpointBusy() void setEndpointReady(Endpoint endpointNumber) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0CLR + endpointNumber * 4)) = 1 << UECON_BUSY0E; } // Enable the IN interrupt on the specified endpoint void enableINInterrupt(Endpoint endpointNumber) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0SET + endpointNumber * 4)) = 1 << UECON_TXINE; } // Clear the bank of the specified endpoint. This is useful if the bank data was prepared // by an IN handler, but no IN packet was sent by the host yet, and this data is now obsolete void abortINTransfer(Endpoint endpointNumber) { // Disable the IN interrupt disableINInterrupt(endpointNumber); _epRAMDescriptors[endpointNumber * EP_DESCRIPTOR_SIZE + EP_PCKSIZE] = 0; // Kill the bank (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0SET + endpointNumber * 4)) = 1 << UECON_KILLBK; // Wait for the end of the procedure while ((*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0 + endpointNumber * 4)) & 1 << UECON_KILLBK); } // Disable the IN interrupt on the specified endpoint void disableINInterrupt(Endpoint endpointNumber) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0CLR + endpointNumber * 4)) = 1 << UECON_TXINE; } // Return true if the IN interrupt is enabled on the specified endpoint bool isINInterruptEnabled(Endpoint endpointNumber) { return (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0 + endpointNumber * 4)) & (1 << UECON_TXINE); } // Enable the OUT interrupt on the specified endpoint void enableOUTInterrupt(Endpoint endpointNumber) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0SET + endpointNumber * 4)) = 1 << UECON_RXOUTE; } // Disable the OUT interrupt on the specified endpoint void disableOUTInterrupt(Endpoint endpointNumber) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0CLR + endpointNumber * 4)) = 1 << UECON_RXOUTE; } // Return true if the OUT interrupt is enabled on the specified endpoint bool isOUTInterruptEnabled(Endpoint endpointNumber) { return (*(volatile uint32_t*)(USB_BASE + OFFSET_UECON0 + endpointNumber * 4)) & (1 << UECON_RXOUTE); } // User handlers void setConnectedHandler(void (*handler)()) { _connectedHandler = handler; } void setDisconnectedHandler(void (*handler)()) { _disconnectedHandler = handler; } void setStartOfFrameHandler(void (*handler)()) { _startOfFrameHandler = handler; } void setControlHandler(int (*handler)(SetupPacket &lastSetupPacket, uint8_t* data, int size)) { _controlHandler = handler; } // Misc functions void remoteWakeup() { // TODO if (_state == State::SUSPEND) { (*(volatile uint32_t*)(USB_BASE + OFFSET_UDCON)) |= 1 << UDCON_RMWKUP; } } }