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 dataThis 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;
}
}
}