Add USB CDC and loop-back test
This commit is contained in:
commit
7234eb8360
34 changed files with 15808 additions and 0 deletions
349
stm32f103c8t6/src/usb_cdc.c
Normal file
349
stm32f103c8t6/src/usb_cdc.c
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include "usb_cdc.h"
|
||||
#include "usb_util.h"
|
||||
#include "led.h"
|
||||
#include "config.h"
|
||||
|
||||
// Ring buffer for received data
|
||||
static uint8_t USBCDC_RXBuffer[CONFIG_USB_RINGBUFFER_SIZE];
|
||||
// Where the next received byte will be written to
|
||||
static uint8_t *volatile USBCDC_RXBufferWrite = USBCDC_RXBuffer;
|
||||
// Where the receive function reads the next byte from the buffer
|
||||
static uint8_t *volatile USBCDC_RXBufferRead = USBCDC_RXBuffer;
|
||||
|
||||
// Is set true when the receive ring buffer is too full to hold another USB
|
||||
// packet and the USB endpoint is halted.
|
||||
static volatile bool USBCDC_RXSuspended = false;
|
||||
|
||||
// Ring buffer for data to be sent
|
||||
static uint8_t USBCDC_TXBuffer[CONFIG_USB_RINGBUFFER_SIZE];
|
||||
// Where the USB transmit procedure will read the next byte from
|
||||
static uint8_t *volatile USBCDC_TXBufferRead = USBCDC_TXBuffer;
|
||||
// Where the send routine will write the next byte into the buffer
|
||||
static uint8_t *volatile USBCDC_TXBufferWrite = USBCDC_TXBuffer;
|
||||
|
||||
// True as long as a USB in transmission is ongoing. Is set to false in
|
||||
// USBCDC_BuildInPacket() after an USB interrupt when the TX ring buffer is
|
||||
// empty
|
||||
static volatile bool USBCDC_TXTransferring = false;
|
||||
|
||||
USBCDC_LineCoding_t USBCDC_LineCoding =
|
||||
{
|
||||
.dwDTERate = 9600,
|
||||
.bCharFormat = 0,
|
||||
.bParityType = 0,
|
||||
.bDataBits = 8
|
||||
};
|
||||
|
||||
void USBCDC_InitEndpoints(void)
|
||||
{
|
||||
// Endpoint 1: Interrupt endpoint for notifications (buffer size 8)
|
||||
USB_BTABLE_ENTRIES[1].COUNT_TX_0 = 0;
|
||||
USB_BTABLE_ENTRIES[1].ADDR_TX_0 = 0xc0;
|
||||
|
||||
USB_SetEPR(&(USB->EP1R), USB_EPR_EP_TYPE_INTERRUPT
|
||||
| USB_EPR_STAT_TX_NAK | USB_EPR_STAT_RX_DISABLED
|
||||
| (1 << USB_EP1R_EA_Pos));
|
||||
|
||||
// Endpoint 2: Data in (buffer size 64)
|
||||
USB_BTABLE_ENTRIES[2].COUNT_TX = 0;
|
||||
USB_BTABLE_ENTRIES[2].ADDR_TX = 0x100;
|
||||
|
||||
USB_SetEPR(&(USB->EP2R), USB_EPR_EP_TYPE_BULK
|
||||
| USB_EPR_STAT_TX_NAK | USB_EPR_STAT_RX_DISABLED
|
||||
| (2 << USB_EP2R_EA_Pos));
|
||||
|
||||
|
||||
// Endpoint 3: Data out (buffer size 64)
|
||||
#if USBCDC_ENDPOINT_SIZE == 64
|
||||
USB_BTABLE_ENTRIES[3].COUNT_RX = USB_EP_RXCOUNT_BL_SIZE | (1 << 10);
|
||||
#else
|
||||
#error Endpoint size currently unsupported
|
||||
#endif
|
||||
USB_BTABLE_ENTRIES[3].ADDR_RX = 0x140;
|
||||
|
||||
USB_SetEPR(&(USB->EP3R), USB_EPR_EP_TYPE_BULK
|
||||
| USB_EPR_STAT_TX_DISABLED | USB_EPR_STAT_RX_VALID
|
||||
| (3 << USB_EP3R_EA_Pos));
|
||||
}
|
||||
|
||||
void USBCDC_HandleLineCodingPacket(int length)
|
||||
{
|
||||
// Sanity check for packet length (the align byte is not counted here,
|
||||
// because it is not transmitted)
|
||||
if(length != sizeof(USBCDC_LineCoding_t) - 1)
|
||||
{
|
||||
__asm__ volatile("bkpt");
|
||||
}
|
||||
|
||||
USB_PMAToMemory((uint8_t*)&USBCDC_LineCoding, USB_BTABLE_ENTRIES[0].ADDR_RX,
|
||||
sizeof(USBCDC_LineCoding_t));
|
||||
USB_EP0OutPacketHandler = NULL;
|
||||
}
|
||||
|
||||
void USBCDC_HandleInterfaceSetup(USB_SetupPacket_t *sp,
|
||||
const void **reply_data, int *reply_length, uint8_t *reply_response)
|
||||
{
|
||||
switch(sp->bRequest)
|
||||
{
|
||||
case USBCDC_REQ_GET_LINE_CODING:
|
||||
*reply_data = (const uint8_t*)&USBCDC_LineCoding;
|
||||
*reply_length = sizeof(USBCDC_LineCoding_t);
|
||||
break;
|
||||
|
||||
case USBCDC_REQ_SET_LINE_CODING:
|
||||
*reply_response = USB_EP_TX_VALID;
|
||||
*reply_data = NULL;
|
||||
|
||||
// The line coding will be sent in a separate packet (which won't be
|
||||
// a setup packet). We thus set a handler which will be called for
|
||||
// the next out transaction on endpoint 0.
|
||||
USB_EP0OutPacketHandler = USBCDC_HandleLineCodingPacket;
|
||||
break;
|
||||
|
||||
case USBCDC_REQ_SET_CONTROL_LINE_STATE:
|
||||
// Ignored at the moment. wValue contains the control signal bitmap:
|
||||
// Bit 0 corresponds to DTR, bit 1 to RTS.
|
||||
*reply_response = USB_EP_TX_VALID;
|
||||
*reply_data = NULL;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Not implemented
|
||||
__asm__ volatile("bkpt");
|
||||
*reply_response = USB_EP_TX_STALL;
|
||||
*reply_data = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDC_HandleDataOut(void)
|
||||
{
|
||||
// Use a local variable for the ring buffer write pointer to avoid the
|
||||
// volatile access overhead (which is fine because the pointer isn't
|
||||
// modified anywhere else
|
||||
uint8_t *ring_buffer = USBCDC_RXBufferWrite;
|
||||
|
||||
// Copy packet from PMA into the ring buffer. Duplicating the code here is
|
||||
// easier than reusing USB_PMAToMemory() because the PMA's addressing
|
||||
// weirdness creates a lot of edge cases with the ring buffer wraparound.
|
||||
int packet_length = USB_BTABLE_ENTRIES[3].COUNT_RX & 0x3ff;
|
||||
uint16_t pma_offset = USB_BTABLE_ENTRIES[3].ADDR_RX;
|
||||
uint8_t *pma = (uint8_t*)(USB_PMA_ADDR + 2 * pma_offset);
|
||||
for(int i = 0; i < packet_length; i++)
|
||||
{
|
||||
*ring_buffer++ = *pma++;
|
||||
if(ring_buffer >= USBCDC_RXBuffer + CONFIG_USB_RINGBUFFER_SIZE)
|
||||
{
|
||||
ring_buffer = USBCDC_RXBuffer;
|
||||
}
|
||||
if(i & 1)
|
||||
{
|
||||
pma += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the ring buffer write pointer one element past the last one written
|
||||
// in this function
|
||||
USBCDC_RXBufferWrite = ring_buffer;
|
||||
|
||||
// Determine the free space in the ring buffer and if another packet will
|
||||
// fit in
|
||||
int ring_buffer_space = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
|
||||
if(ring_buffer_space < 0)
|
||||
{
|
||||
ring_buffer_space += CONFIG_USB_RINGBUFFER_SIZE;
|
||||
}
|
||||
if(ring_buffer_space >= USBCDC_ENDPOINT_SIZE)
|
||||
{
|
||||
// Set EP3 ready for the next packet only when there is enough space in
|
||||
// the ring buffer
|
||||
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_VALID);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_NAK);
|
||||
USBCDC_RXSuspended = true;
|
||||
}
|
||||
}
|
||||
|
||||
int USBCDC_ReceiveData(void *buffer, int length)
|
||||
{
|
||||
int available = USBCDC_RXBufferWrite - USBCDC_RXBufferRead;
|
||||
if(available < 0)
|
||||
{
|
||||
available += CONFIG_USB_RINGBUFFER_SIZE;
|
||||
}
|
||||
if(available < length)
|
||||
{
|
||||
length = available;
|
||||
}
|
||||
if(length <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int buffer_length = length;
|
||||
|
||||
int until_wraparound = USBCDC_RXBuffer + CONFIG_USB_RINGBUFFER_SIZE
|
||||
- USBCDC_RXBufferRead;
|
||||
if(buffer_length >= until_wraparound)
|
||||
{
|
||||
memcpy(buffer, USBCDC_RXBufferRead, until_wraparound);
|
||||
buffer_length -= until_wraparound;
|
||||
buffer += until_wraparound;
|
||||
USBCDC_RXBufferRead = USBCDC_RXBuffer;
|
||||
}
|
||||
|
||||
memcpy(buffer, USBCDC_RXBufferRead, buffer_length);
|
||||
USBCDC_RXBufferRead += buffer_length;
|
||||
|
||||
// If receiving has been suspended because the ring buffer is too full,
|
||||
// resume if there is now enough space for another packet
|
||||
if(USBCDC_RXSuspended)
|
||||
{
|
||||
int ring_buffer_free = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
|
||||
if(ring_buffer_free >= USBCDC_ENDPOINT_SIZE)
|
||||
{
|
||||
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_VALID);
|
||||
USBCDC_RXSuspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
// Build a USB in packet out of data from the transmit ring buffer and mark it
|
||||
// valid for the host to fetch it
|
||||
static void USBCDC_BuildInPacket(void)
|
||||
{
|
||||
// Wait while there's still data being transmitted
|
||||
while(USB_BTABLE_ENTRIES[2].COUNT_TX);
|
||||
|
||||
int packet_length = 0;
|
||||
|
||||
int ring_buffer_bytes = USBCDC_TXBufferWrite - USBCDC_TXBufferRead;
|
||||
|
||||
if(ring_buffer_bytes < 0)
|
||||
{
|
||||
// Ring buffer read address is bigger than the write address, meaning
|
||||
// the write has wrapped around. Let's first deal with the data until
|
||||
// the end of the buffer
|
||||
int until_wraparound = USBCDC_TXBuffer + CONFIG_USB_RINGBUFFER_SIZE
|
||||
- USBCDC_TXBufferRead;
|
||||
if(until_wraparound > USBCDC_ENDPOINT_SIZE)
|
||||
{
|
||||
// More bytes to be sent than there is space in the PMA buffer
|
||||
packet_length = USBCDC_ENDPOINT_SIZE;
|
||||
USB_MemoryToPMA(USB_BTABLE_ENTRIES[2].ADDR_TX, USBCDC_TXBufferRead,
|
||||
packet_length);
|
||||
USBCDC_TXBufferRead += packet_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The rest of the buffer until the end of the ring buffer will fit
|
||||
// into the PMA buffer, so we can transmit everything and reset the
|
||||
// read pointer to the ring buffer's beginning.
|
||||
packet_length = until_wraparound;
|
||||
USB_MemoryToPMA(USB_BTABLE_ENTRIES[2].ADDR_TX, USBCDC_TXBufferRead,
|
||||
packet_length);
|
||||
USBCDC_TXBufferRead = USBCDC_TXBuffer;
|
||||
}
|
||||
}
|
||||
else if(ring_buffer_bytes > 0)
|
||||
{
|
||||
// This /could/ have been an `if` instead of an `else if` to fill up
|
||||
// the PMA buffer completely if the ring buffer contents wrap around and
|
||||
// are larger than the PMA area if both parts are combined. However,
|
||||
// the PMA area only supports 16-bit accesses and an unaligned read
|
||||
// pointer would lead to problems at the wraparound boundary unless
|
||||
// specially handeled. Just sending two packets here is way simpler
|
||||
// than adding handling for all border cases.
|
||||
|
||||
if(ring_buffer_bytes > USBCDC_ENDPOINT_SIZE)
|
||||
{
|
||||
packet_length = USBCDC_ENDPOINT_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet_length = ring_buffer_bytes;
|
||||
}
|
||||
|
||||
USB_MemoryToPMA(USB_BTABLE_ENTRIES[2].ADDR_TX, USBCDC_TXBufferRead,
|
||||
packet_length);
|
||||
USBCDC_TXBufferRead += packet_length;
|
||||
}
|
||||
|
||||
if(packet_length != 0)
|
||||
{
|
||||
USBCDC_TXTransferring = true;
|
||||
|
||||
// Mark the in packet, which is now stored in PMA memory, as valid
|
||||
USB_BTABLE_ENTRIES[2].COUNT_TX = packet_length;
|
||||
USB_SetEPTXStatus(&(USB->EP2R), USB_EP_TX_VALID);
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDC_SendData(const void *data, int length)
|
||||
{
|
||||
while(length > 0)
|
||||
{
|
||||
// Block until ring buffer is no longer full
|
||||
int ring_buffer_space = 0;
|
||||
while(ring_buffer_space == 0)
|
||||
{
|
||||
// The ring buffer is empty when read and write pointer are the
|
||||
// same. It is considered full when the write pointer is one spot
|
||||
// before the read pointer. One index is thus unused to make the
|
||||
// distinction between empty and full possible.
|
||||
ring_buffer_space = USBCDC_TXBufferRead - USBCDC_TXBufferWrite - 1;
|
||||
if(ring_buffer_space < 0)
|
||||
{
|
||||
ring_buffer_space += CONFIG_USB_RINGBUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// If more data is supplied than would fit in the ring buffer, copy as
|
||||
// much as possible in this iteration.
|
||||
int store_length = ring_buffer_space;
|
||||
if(length < store_length)
|
||||
{
|
||||
store_length = length;
|
||||
}
|
||||
length -= store_length;
|
||||
|
||||
// If copying all data into the ring buffer needs a write pointer
|
||||
// wraparound, copy the part before the ring buffer 'end' first
|
||||
int until_wraparound = USBCDC_TXBuffer + CONFIG_USB_RINGBUFFER_SIZE
|
||||
- USBCDC_TXBufferWrite;
|
||||
if(store_length >= until_wraparound)
|
||||
{
|
||||
memcpy(USBCDC_TXBufferWrite, data, until_wraparound);
|
||||
USBCDC_TXBufferWrite = USBCDC_TXBuffer;
|
||||
store_length -= until_wraparound;
|
||||
data += until_wraparound;
|
||||
}
|
||||
|
||||
memcpy(USBCDC_TXBufferWrite, data, store_length);
|
||||
USBCDC_TXBufferWrite += store_length;
|
||||
data += store_length;
|
||||
|
||||
// If there isn't already data being transferred, the process has to be
|
||||
// kicked off. The later USB packets will be built from the USB transfer
|
||||
// complete interrupts.
|
||||
if(!USBCDC_TXTransferring)
|
||||
{
|
||||
USBCDC_BuildInPacket();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDC_HandleDataInComplete(void)
|
||||
{
|
||||
if(USBCDC_TXBufferRead == USBCDC_TXBufferWrite)
|
||||
{
|
||||
USBCDC_TXTransferring = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
USBCDC_BuildInPacket();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue