Skip to content

Instantly share code, notes, and snippets.

@shima-529
Created April 22, 2020 12:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shima-529/2138a003ab26c884585e5e9128169ee2 to your computer and use it in GitHub Desktop.
Save shima-529/2138a003ab26c884585e5e9128169ee2 to your computer and use it in GitHub Desktop.
#include "stm32f0xx.h"
#include <usb_struct.h>
#include <usb_desc/usb_packet.h>
#include <usb_desc/usb_descriptor.h>
#include <stdio.h>
#include <string.h>
//#define iprintf(...)
/* CHANGED
* - do not change addr_rx & count_rx while transfer flow
*/
/* Pointer Notation
* - type = uintptr_t : This is an pointer for PMA.
* Local offset of PMA is stored.
* - type = void * or others
* : This is an pointer for main memory.
*/
/* xfer/recv calls
* - On reception of SETUP packet ==> call send() if necessary
* - On IN/OUT ==> call send_next() or stop()
*/
volatile char usb_is_ready;
// External Descriptors
extern const USBDeviceDescriptor dev_desc;
extern const USBInterfaceDescriptor if_desc;
extern const USBStringDescriptor string_desc[];
extern const uint8_t USBD_CDC_CfgHSDesc[];
extern const __attribute__((aligned(2))) uint8_t conf_desc[];
const uint8_t __attribute__((aligned(2))) ReportDescriptor[50];
// Struct for USB Peripheral
typedef struct {
uint16_t addr_tx;
uint16_t count_tx;
uint16_t addr_rx;
uint16_t count_rx;
} PacketBuffer;
// Some macros
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define USB_PMASIZE 1024
#define USB_EPnR(n) *(volatile uint16_t *)((uintptr_t)&USB->EP0R + 4 * (n))
enum USBSetupPacketRequest {
GET_STATUS = 0, CLEAR_FEATURE = 1, SET_FEATURE = 3,
SET_ADDRESS = 5, GET_DESCRIPTOR = 6, SET_DESCRIPTOR = 7,
GET_CONFIGURATION = 8, SET_CONFIGURATION = 9,
GET_INTERFACE = 10, SET_INTERFACE = 11, SYNCH_FRAME = 12
};
enum USBDescriptorType {
DEVICE = 1, CONFIGURATION = 2, STRING = 3, INTERFACE = 4,
ENDPOINT = 5, DEVICE_QUALIFIER = 6,
OTHER_SPEED_CONFIGURATION = 7, INTERFACE_POWER = 8,
HID_REPORT = 0x22
};
// Vars for PMA buffer
typedef struct {
uint16_t setup_packet;
uint16_t device_desc;
uint16_t config_desc;
uint16_t string_desc[10];
uint16_t report_desc;
} PMAOffsets;
typedef struct {
PMAOffsets offsets;
uint16_t sp;
} PMAInfo;
volatile static PMAInfo pma_allocation_info;
// FSM
typedef enum {
ST_RESET, SETUP, IN, OUT, STATUS_IN, STATUS_OUT, ST_ERROR
} EP0_fsm;
// Storage Var for the coming xfer
typedef struct {
uint16_t ptr;
uint16_t current_pos;
uint16_t len;
} DataInfo;
typedef struct {
uint8_t packet_size;
PacketBuffer *pb_ptr;
DataInfo xfer_info;
DataInfo recv_info;
EP0_fsm fsm;
USBSetupPacket last_setup;
} USBEndpointInfo;
volatile static USBEndpointInfo ep_info[2];
// Here begin Utility Funcs
uint16_t alloc(int size) {
const uint16_t ret = pma_allocation_info.sp;
pma_allocation_info.sp += size;
if( size & 1 ) pma_allocation_info.sp++;
return ret;
}
static void pma_out(void *dest, const uintptr_t src, int size) {
const volatile uint16_t *src_p = (void *)(src + USB_PMAADDR);
volatile uint16_t *dest_p = dest;
if( size & 1 ) size++;
const uint16_t wSize = size / 2;
for(int i = 0; i < wSize; i++) {
dest_p[i] = src_p[i];
}
}
static void pma_in(const uintptr_t dest, const void *src, int size) {
const volatile uint16_t *src_p = src;
volatile uint16_t *dest_p = (void *)(dest + USB_PMAADDR);
if( size & 1 ) size++;
const uint16_t wSize = size / 2;
for(int i = 0; i < wSize; i++) {
dest_p[i] = src_p[i];
}
}
void usb_init(void) {
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
USB->CNTR &= ~USB_CNTR_PDWN;
for(volatile int i=0; i<100; i++); // wait for voltage to be stable
USB->CNTR &= ~USB_CNTR_FRES;
USB->BCDR |= USB_BCDR_DPPU; // pull-up DP so as to notify the connection to the host
USB->CNTR = USB_CNTR_RESETM; // enable reset interrupt
NVIC_SetPriority(USB_IRQn, 0); // Highest Priority
NVIC_EnableIRQ(USB_IRQn);
}
static void usb_endpoint_set_status(int endp, uint16_t status, uint16_t mask) {
const uint16_t reg = USB_EPnR(endp);
USB_EPnR(endp) = (reg & (USB_EPREG_MASK | mask)) ^ status;
}
static void search_descriptor(uint16_t wValue) {
const uint8_t desc_type = (wValue >> 8) & 0xFF;
const uint16_t wLength = ep_info[0].last_setup.wLength;
volatile PMAOffsets *offsets = &pma_allocation_info.offsets;
switch( desc_type ) {
case DEVICE: // Device Descriptor
if( offsets->device_desc == 0 ) {
offsets->device_desc = alloc(sizeof(USBDeviceDescriptor));
pma_in(offsets->device_desc, &dev_desc, sizeof(USBDeviceDescriptor));
}
ep_info[0].xfer_info.ptr = offsets->device_desc;
ep_info[0].xfer_info.len = MIN(wLength, sizeof(USBDeviceDescriptor));
break;
case CONFIGURATION:
if( offsets->config_desc == 0 ) {
offsets->config_desc = alloc(67);
pma_in(offsets->config_desc, conf_desc, 67);
}
ep_info[0].xfer_info.ptr = offsets->config_desc;
ep_info[0].xfer_info.len = MIN(wLength, 67);
break;
case STRING:
{
const uint8_t desc_no = wValue & 0xFF;
const int len = string_desc[desc_no].bLength;
if( offsets->string_desc[desc_no] == 0 ) {
offsets->string_desc[desc_no] = alloc(len);
pma_in(offsets->string_desc[desc_no], &string_desc[desc_no], len);
}
ep_info[0].xfer_info.ptr = offsets->string_desc[desc_no];
ep_info[0].xfer_info.len = MIN(wLength, len);
break;
}
case HID_REPORT:
if( offsets->report_desc == 0 ) {
offsets->report_desc = alloc(50);
pma_in(offsets->report_desc, ReportDescriptor, 50);
}
ep_info[0].xfer_info.ptr = offsets->report_desc;
ep_info[0].xfer_info.len = MIN(wLength, 50);
usb_is_ready = 1;
break;
default:
return;
break;
}
}
static void usb_ep_send(int ep_num, uint16_t addr, uint16_t size) {
ep_info[ep_num].pb_ptr->addr_tx = addr;
ep_info[ep_num].pb_ptr->count_tx = size;
usb_endpoint_set_status(ep_num, USB_EP_TX_VALID, USB_EPTX_STAT);
}
void usb_ep_receive(int ep_num, uint16_t addr, uint16_t size) {
if( ep_info[ep_num].pb_ptr->addr_rx == 0 ) {
ep_info[ep_num].pb_ptr->addr_rx = addr;
if( size > 62 ) {
ep_info[ep_num].pb_ptr->count_rx = ((size / 32) - 1) << 10;
ep_info[ep_num].pb_ptr->count_rx |= 1 << 15;
}else{
ep_info[ep_num].pb_ptr->count_rx = size << 10;
}
}
usb_endpoint_set_status(ep_num, USB_EP_RX_VALID, USB_EPRX_STAT);
}
const volatile static uint16_t non_zero = 0;
static void usb_ep0_handle_setup(void) {
const volatile USBSetupPacket *p = &ep_info[0].last_setup;
if( p->bRequest == GET_DESCRIPTOR ) {
search_descriptor(p->wValue);
usb_ep_send(0, ep_info[0].xfer_info.ptr, MIN(ep_info[0].packet_size, ep_info[0].xfer_info.len));
}
if( p->bRequest == GET_CONFIGURATION ) {
uint16_t addr;
pma_in(addr = alloc(1), (const void *)&non_zero, 1);
usb_ep_send(0, addr, 1);
}
if( p->bRequest == SET_CONFIGURATION ) {
iprintf("SET_CONF: 0x%04x\n", p->wValue);
}
}
static void usb_ep0_prepare_for_status(void) {
if( ep_info[0].fsm == STATUS_OUT ) {
usb_ep_receive(0, 0, 0);
}
if( ep_info[0].fsm == STATUS_IN ) {
usb_ep_send(0, 0, 0);
}
}
static void usb_ep0_handle_status(void) {
if( ep_info[0].last_setup.bRequest == SET_ADDRESS ) {
USB->DADDR = USB_DADDR_EF | (ep_info[0].last_setup.wValue & 0x7F);
}
// prepare for the next SETUP transaction
usb_ep_receive(0, pma_allocation_info.offsets.setup_packet,
sizeof(USBSetupPacket)
);
}
static void usb_ep0_handle_in(void) {
// Now data is left to be xferred.
// This means the last IN transaction is completed but the data length is not enough.
if( ep_info[0].fsm != IN ) return; // malfunction of fsm
const uint16_t packet_size = ep_info[0].packet_size;
const uint16_t len_to_be_xferred = ep_info[0].xfer_info.len - packet_size;
// const uint16_t pos = ep_info[0].xfer_info.current_pos;
if( len_to_be_xferred > 0 && len_to_be_xferred <= packet_size ) { // IN transaction will end this time.
ep_info[0].xfer_info.current_pos += packet_size;
ep_info[0].xfer_info.len -= packet_size;
usb_ep_send(0, ep_info[0].xfer_info.current_pos, ep_info[0].xfer_info.len);
}else{
// unsupported.
}
}
// Inputs: current state, g_current_setup_packet
// Outputs: next state
static EP0_fsm next_state_ep0(void) {
const volatile USBSetupPacket *last_setup = &ep_info[0].last_setup;
const volatile DataInfo *xfer_info = &ep_info[0].xfer_info;
// const volatile DataInfo *recv_info = &ep_info[0].recv_info;
switch(ep_info[0].fsm) {
case SETUP:
if( last_setup->bmRequestType & 0x80 ) {
return (last_setup->wLength != 0) ? IN : STATUS_IN;
}else{
return (last_setup->wLength != 0) ? OUT : STATUS_IN;
}
break;
case IN:
if( xfer_info->len > ep_info[0].packet_size ) {
return IN;
}
return STATUS_OUT;
break;
case OUT:
return STATUS_IN;
break;
case STATUS_IN:
break;
case STATUS_OUT:
break;
default:
break;
}
return ST_ERROR;
}
static void dump_setup_packet(void) {
iprintf("bmResuestType: 0x%02x\n", ep_info[0].last_setup.bmRequestType);
iprintf("bRequest: %d\n", ep_info[0].last_setup.bRequest);
iprintf("wValue: 0x%x\n", ep_info[0].last_setup.wValue);
iprintf("wIndex: 0x%x\n", ep_info[0].last_setup.wIndex);
iprintf("wLength: 0x%x\n", ep_info[0].last_setup.wLength);
}
volatile int8_t __attribute__((aligned(2))) report[4] = {
0, // Button
0, // dX
0, // dY
};
uint16_t addr_report;
void USB_IRQHandler(void) {
uint16_t flag = USB->ISTR;
if( flag & USB_ISTR_RESET ) {
iprintf("\nRESET\n");
USB->ISTR &= ~USB_ISTR_RESET;
USB->CNTR |= USB_CNTR_CTRM | USB_CNTR_RESETM;
memset((void *)&pma_allocation_info, 0, sizeof(PMAInfo));
memset((void *)ep_info, 0, sizeof(USBEndpointInfo) * 2);
USB->BTABLE = alloc(sizeof(PacketBuffer) * 2);
USB->DADDR = USB_DADDR_EF; // enable the functionality
// initialize EP0
ep_info[0].fsm = ST_RESET;
ep_info[0].packet_size = 64;
ep_info[0].pb_ptr = (PacketBuffer *)((uintptr_t)USB->BTABLE + USB_PMAADDR);
ep_info[0].pb_ptr->addr_rx = 0;
USB->EP0R = USB_EP_CONTROL;
pma_allocation_info.offsets.setup_packet = alloc(64);
usb_ep_receive(0,
pma_allocation_info.offsets.setup_packet,
ep_info[0].packet_size
);
// initialize EP1
ep_info[1].packet_size = 64;
ep_info[1].pb_ptr = (PacketBuffer *)((uintptr_t)USB->BTABLE + USB_PMAADDR + sizeof(PacketBuffer));
USB->EP1R = USB_EP_INTERRUPT | 0x01;
addr_report = alloc(3); // size of report, in this case.
pma_in(addr_report, (const void *)report, 3);
usb_ep_send(1,
addr_report,
3
);
}
while( (flag = USB->ISTR) & USB_ISTR_CTR ) {
const uint16_t ep_num = flag & USB_ISTR_EP_ID;
const uint16_t epreg = USB_EPnR(ep_num);
if( ep_num == 0 ) {
if( epreg & USB_EP_CTR_RX ) {
// DIR == 1 : IRQ by SETUP transaction. CTR_RX is set.
USB_EPnR(0) = epreg & ~USB_EP_CTR_RX & USB_EPREG_MASK;
if( epreg & USB_EP_SETUP ) {
ep_info[ep_num].fsm = SETUP;
pma_out(
(void *)&ep_info[ep_num].last_setup,
pma_allocation_info.offsets.setup_packet,
sizeof(USBSetupPacket)
);
iprintf("** SETUP\n");
dump_setup_packet();
usb_ep0_handle_setup();
ep_info[ep_num].fsm = next_state_ep0();
usb_ep0_prepare_for_status();
}
else{
// IRQ by OUT transaction
if( ep_info[ep_num].fsm == STATUS_OUT ) {
iprintf("STATUS_OUT\n");
usb_ep0_handle_status();
}else if( ep_info[ep_num].fsm == OUT ) {
ep_info[ep_num].fsm = next_state_ep0();
usb_ep0_prepare_for_status();
}
}
}
if( epreg & USB_EP_CTR_TX ){
// DIR = 0 : IRQ by IN transaction. CTR_TX is set.
USB_EPnR(0) = epreg & ~USB_EP_CTR_TX & USB_EPREG_MASK;
if( ep_info[ep_num].fsm == STATUS_IN ) {
iprintf("STATUS_IN\n");
usb_ep0_handle_status();
}else if( ep_info[ep_num].fsm == IN ) {
iprintf("IN\n");
ep_info[ep_num].fsm = next_state_ep0();
usb_ep0_handle_in();
usb_ep0_prepare_for_status();
}
}
}else if( ep_num == 1 ){
USB_EPnR(1) = epreg & ~USB_EP_CTR_TX & USB_EPREG_MASK;
pma_in(addr_report, (const void *)report, 3);
usb_ep_send(1,
addr_report,
3
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment