improved USB serial Rx and implemented buffered Tx

This commit is contained in:
stevstrong 2016-10-21 22:31:27 +02:00
parent 082a78e3a6
commit 0ec7e72b01
4 changed files with 153 additions and 125 deletions

View File

@ -261,18 +261,28 @@ static ONE_DESCRIPTOR String_Descriptor[N_STRING_DESCRIPTORS] = {
/* I/O state */ /* I/O state */
#define CDC_SERIAL_BUFFER_SIZE 512 #define CDC_SERIAL_RX_BUFFER_SIZE 256 // must be power of 2
#define CDC_SERIAL_RX_BUFFER_SIZE_MASK (CDC_SERIAL_RX_BUFFER_SIZE-1)
/* Received data */ /* Received data */
static volatile uint8 vcomBufferRx[CDC_SERIAL_BUFFER_SIZE]; static volatile uint8 vcomBufferRx[CDC_SERIAL_RX_BUFFER_SIZE];
/* Read index into vcomBufferRx */ /* Write index to vcomBufferRx */
static volatile uint32 rx_offset = 0; static volatile uint32 rx_head;
/* Number of bytes left to transmit */ /* Read index from vcomBufferRx */
static volatile uint32 n_unsent_bytes = 0; static volatile uint32 rx_tail;
/* Are we currently sending an IN packet? */
static volatile uint8 transmitting = 0; #define CDC_SERIAL_TX_BUFFER_SIZE 256 // must be power of 2
/* Number of unread bytes */ #define CDC_SERIAL_TX_BUFFER_SIZE_MASK (CDC_SERIAL_TX_BUFFER_SIZE-1)
static volatile uint32 n_unread_bytes = 0; // Tx data
static volatile uint8 vcomBufferTx[CDC_SERIAL_TX_BUFFER_SIZE];
// Write index to vcomBufferTx
static volatile uint32 tx_head;
// Read index from vcomBufferTx
static volatile uint32 tx_tail;
// Are we currently sending an IN packet?
static volatile int8 transmitting;
/* Other state (line coding, DTR/RTS) */ /* Other state (line coding, DTR/RTS) */
@ -393,30 +403,34 @@ void usb_cdcacm_putc(char ch) {
; ;
} }
/* This function is blocking. /* This function is non-blocking.
* *
* It copies data from a usercode buffer into the USB peripheral TX * It copies data from a user buffer into the USB peripheral TX
* buffer, and returns the number of bytes copied. */ * buffer, and returns the number of bytes copied. */
uint32 usb_cdcacm_tx(const uint8* buf, uint32 len) { uint32 usb_cdcacm_tx(const uint8* buf, uint32 len)
/* Last transmission hasn't finished, so abort. */ {
while ( usb_cdcacm_is_transmitting()>0 ) ; // wait for end of transmission if (len==0) return 0; // no data to send
/* We can only put USB_CDCACM_TX_EPSIZE bytes in the buffer. */ uint32 head = tx_head; // load volatile variable
if (len > USB_CDCACM_TX_EPSIZE) { uint32 tx_unsent = (head - tx_tail) & CDC_SERIAL_TX_BUFFER_SIZE_MASK;
len = USB_CDCACM_TX_EPSIZE;
}
/* Queue bytes for sending. */ // We can only put bytes in the buffer if there is place
if (len) { if (len > (CDC_SERIAL_TX_BUFFER_SIZE-tx_unsent-1) ) {
usb_copy_to_pma(buf, len, USB_CDCACM_TX_ADDR); len = (CDC_SERIAL_TX_BUFFER_SIZE-tx_unsent-1);
} }
// We still need to wait for the interrupt, even if we're sending if (len==0) return 0; // buffer full
// zero bytes. (Sending zero-size packets is useful for flushing
// host-side buffers.) uint16 i;
usb_set_ep_tx_count(USB_CDCACM_TX_ENDP, len); // copy data from user buffer to USB Tx buffer
n_unsent_bytes = len; for (i=0; i<len; i++) {
transmitting = 1; vcomBufferTx[head] = buf[i];
usb_set_ep_tx_stat(USB_CDCACM_TX_ENDP, USB_EP_STAT_TX_VALID); head = (head+1) & CDC_SERIAL_TX_BUFFER_SIZE_MASK;
}
tx_head = head; // store volatile variable
if (transmitting<0) {
vcomDataTxCb(); // initiate data transmission
}
return len; return len;
} }
@ -424,69 +438,73 @@ uint32 usb_cdcacm_tx(const uint8* buf, uint32 len) {
uint32 usb_cdcacm_data_available(void) { uint32 usb_cdcacm_data_available(void) {
return n_unread_bytes; return (rx_head - rx_tail) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
} }
uint8 usb_cdcacm_is_transmitting(void) { uint8 usb_cdcacm_is_transmitting(void) {
return transmitting; return ( transmitting>0 ? transmitting : 0);
} }
uint16 usb_cdcacm_get_pending(void) { uint16 usb_cdcacm_get_pending(void) {
return n_unsent_bytes; return (tx_head - tx_tail) & CDC_SERIAL_TX_BUFFER_SIZE_MASK;
} }
/* Nonblocking byte receive. /* Non-blocking byte receive.
* *
* Copies up to len bytes from our private data buffer (*NOT* the PMA) * Copies up to len bytes from our private data buffer (*NOT* the PMA)
* into buf and deq's the FIFO. */ * into buf and deq's the FIFO. */
uint32 usb_cdcacm_rx(uint8* buf, uint32 len) { uint32 usb_cdcacm_rx(uint8* buf, uint32 len)
{
/* Copy bytes to buffer. */ /* Copy bytes to buffer. */
uint32 n_copied = usb_cdcacm_peek(buf, len); uint32 n_copied = usb_cdcacm_peek(buf, len);
/* Mark bytes as read. */ /* Mark bytes as read. */
n_unread_bytes -= n_copied; uint16 tail = rx_tail; // load volatile variable
rx_offset = (rx_offset + n_copied) % CDC_SERIAL_BUFFER_SIZE; tail = (tail + n_copied) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
rx_tail = tail; // store volatile variable
/* If all bytes have been read, re-enable the RX endpoint, which uint32 rx_unread = (rx_head - tail) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
* was set to NAK when the current batch of bytes was received. */ // If buffer was emptied to a pre-set value, re-enable the RX endpoint
if (n_unread_bytes == 0) { if ( rx_unread <= 64 ) { // experimental value, gives the best performance
usb_set_ep_rx_count(USB_CDCACM_RX_ENDP, USB_CDCACM_RX_EPSIZE);
usb_set_ep_rx_stat(USB_CDCACM_RX_ENDP, USB_EP_STAT_RX_VALID); usb_set_ep_rx_stat(USB_CDCACM_RX_ENDP, USB_EP_STAT_RX_VALID);
} }
return n_copied; return n_copied;
} }
/* Nonblocking byte lookahead. /* Non-blocking byte lookahead.
* *
* Looks at unread bytes without marking them as read. */ * Looks at unread bytes without marking them as read. */
uint32 usb_cdcacm_peek(uint8* buf, uint32 len) { uint32 usb_cdcacm_peek(uint8* buf, uint32 len)
{
int i; int i;
uint32 head = rx_offset; uint32 tail = rx_tail;
uint32 rx_unread = (rx_head-tail) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
if (len > n_unread_bytes) { if (len > rx_unread) {
len = n_unread_bytes; len = rx_unread;
} }
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
buf[i] = vcomBufferRx[head]; buf[i] = vcomBufferRx[tail];
head = (head + 1) % CDC_SERIAL_BUFFER_SIZE; tail = (tail + 1) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
} }
return len; return len;
} }
uint32 usb_cdcacm_peek_ex(uint8* buf, uint32 offset, uint32 len) { uint32 usb_cdcacm_peek_ex(uint8* buf, uint32 offset, uint32 len)
{
int i; int i;
uint32 head = (rx_offset + offset) % CDC_SERIAL_BUFFER_SIZE; uint32 tail = (rx_tail + offset) & CDC_SERIAL_RX_BUFFER_SIZE_MASK ;
uint32 rx_unread = (rx_head-tail) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
if (len + offset > n_unread_bytes) { if (len + offset > rx_unread) {
len = n_unread_bytes - offset; len = rx_unread - offset;
} }
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
buf[i] = vcomBufferRx[head]; buf[i] = vcomBufferRx[tail];
head = (head + 1) % CDC_SERIAL_BUFFER_SIZE; tail = (tail + 1) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
} }
return len; return len;
@ -495,12 +513,12 @@ uint32 usb_cdcacm_peek_ex(uint8* buf, uint32 offset, uint32 len) {
/* Roger Clark. Added. for Arduino 1.0 API support of Serial.peek() */ /* Roger Clark. Added. for Arduino 1.0 API support of Serial.peek() */
int usb_cdcacm_peek_char() int usb_cdcacm_peek_char()
{ {
if (n_unread_bytes == 0) if (usb_cdcacm_data_available() == 0)
{ {
return -1; return -1;
} }
return vcomBufferRx[rx_offset]; return vcomBufferRx[rx_tail];
} }
uint8 usb_cdcacm_get_dtr() { uint8 usb_cdcacm_get_dtr() {
@ -534,41 +552,75 @@ int usb_cdcacm_get_n_data_bits(void) {
return line_coding.bDataBits; return line_coding.bDataBits;
} }
/* /*
* Callbacks * Callbacks
*/ */
static void vcomDataTxCb(void)
static void vcomDataTxCb(void) { {
n_unsent_bytes = 0; uint32 tail = tx_tail; // load volatile variable
transmitting = 0; uint32 tx_unsent = (tx_head - tail) & CDC_SERIAL_TX_BUFFER_SIZE_MASK;
if (tx_unsent==0) {
if ( (--transmitting)==0) goto flush; // no more data to send
return; // it was already flushed, keep Tx endpoint disabled
}
transmitting = 1;
// We can only send up to USB_CDCACM_TX_EPSIZE bytes in the endpoint.
if (tx_unsent > USB_CDCACM_TX_EPSIZE) {
tx_unsent = USB_CDCACM_TX_EPSIZE;
}
// copy the bytes from USB Tx buffer to PMA buffer
uint32 *dst = usb_pma_ptr(USB_CDCACM_TX_ADDR);
uint16 tmp = 0;
uint16 val;
int i;
for (i = 0; i < tx_unsent; i++) {
val = vcomBufferTx[tail];
tail = (tail + 1) & CDC_SERIAL_TX_BUFFER_SIZE_MASK;
if (i&1) {
*dst++ = tmp | (val<<8);
} else {
tmp = val;
}
}
if ( tx_unsent&1 ) {
*dst = tmp;
}
tx_tail = tail; // store volatile variable
flush:
// enable Tx endpoint
usb_set_ep_tx_count(USB_CDCACM_TX_ENDP, tx_unsent);
usb_set_ep_tx_stat(USB_CDCACM_TX_ENDP, USB_EP_STAT_TX_VALID);
} }
static void vcomDataRxCb(void) {
uint32 ep_rx_size; static void vcomDataRxCb(void)
uint32 tail = (rx_offset + n_unread_bytes) % CDC_SERIAL_BUFFER_SIZE; {
uint8 ep_rx_data[USB_CDCACM_RX_EPSIZE]; uint32 head = rx_head; // load volatile variable
uint32 ep_rx_size = usb_get_ep_rx_count(USB_CDCACM_RX_ENDP);
// This copy won't overwrite unread bytes as long as there is
// enough room in the USB Rx buffer for next packet
uint32 *src = usb_pma_ptr(USB_CDCACM_RX_ADDR);
uint16 tmp = 0;
uint8 val;
uint32 i; uint32 i;
usb_set_ep_rx_stat(USB_CDCACM_RX_ENDP, USB_EP_STAT_RX_NAK);
ep_rx_size = usb_get_ep_rx_count(USB_CDCACM_RX_ENDP);
/* This copy won't overwrite unread bytes, since we've set the RX
* endpoint to NAK, and will only set it to VALID when all bytes
* have been read. */
usb_copy_from_pma((uint8*)ep_rx_data, ep_rx_size,
USB_CDCACM_RX_ADDR);
for (i = 0; i < ep_rx_size; i++) { for (i = 0; i < ep_rx_size; i++) {
vcomBufferRx[tail] = ep_rx_data[i]; if (i&1) {
tail = (tail + 1) % CDC_SERIAL_BUFFER_SIZE; val = tmp>>8;
} else {
tmp = *src++;
val = tmp&0xFF;
}
vcomBufferRx[head] = val;
head = (head + 1) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
} }
rx_head = head; // store volatile variable
n_unread_bytes += ep_rx_size; uint32 rx_unread = (head - rx_tail) & CDC_SERIAL_RX_BUFFER_SIZE_MASK;
// only enable further Rx if there is enough room to receive one more packet
if ( n_unread_bytes == 0 ) { if ( rx_unread < (CDC_SERIAL_RX_BUFFER_SIZE-USB_CDCACM_RX_EPSIZE) ) {
usb_set_ep_rx_count(USB_CDCACM_RX_ENDP, USB_CDCACM_RX_EPSIZE); usb_set_ep_rx_stat(USB_CDCACM_RX_ENDP, USB_EP_STAT_RX_VALID);
usb_set_ep_rx_stat(USB_CDCACM_RX_ENDP, USB_EP_STAT_RX_VALID); }
}
if (rx_hook) { if (rx_hook) {
rx_hook(USB_CDCACM_HOOK_RX, 0); rx_hook(USB_CDCACM_HOOK_RX, 0);
@ -646,10 +698,11 @@ static void usbReset(void) {
SetDeviceAddress(0); SetDeviceAddress(0);
/* Reset the RX/TX state */ /* Reset the RX/TX state */
n_unread_bytes = 0; rx_head = 0;
n_unsent_bytes = 0; rx_tail = 0;
rx_offset = 0; tx_head = 0;
transmitting = 0; tx_tail = 0;
transmitting = -1;
} }
static RESULT usbDataSetup(uint8 request) { static RESULT usbDataSetup(uint8 request) {

View File

@ -100,44 +100,23 @@ size_t n = 0;
size_t USBSerial::write(const char *str) { size_t USBSerial::write(const char *str) {
size_t n = 0; size_t n = 0;
this->write(str, strlen(str)); this->write((const uint8*)str, strlen(str));
return n; return n;
} }
size_t USBSerial::write(const void *buf, uint32 len) { size_t USBSerial::write(const uint8 *buf, uint32 len)
{
size_t n = 0; size_t n = 0;
if (!this->isConnected() || !buf) { if (!this->isConnected() || !buf) {
return 0; return 0;
} }
uint32 txed = 0; uint32 txed = 0;
uint32 old_txed = 0; while (txed < len) {
uint32 start = millis(); txed += usb_cdcacm_tx((const uint8*)buf + txed, len - txed);
uint32 sent = 0;
while (txed < len && (millis() - start < USB_TIMEOUT)) {
sent = usb_cdcacm_tx((const uint8*)buf + txed, len - txed);
txed += sent;
if (old_txed != txed) {
start = millis();
}
old_txed = txed;
} }
return n;
#if 0
// this code leads to a serious performance drop and appears to be
// unnecessary - everything seems to work fine without, -jcw, 2015-11-05
// see http://stm32duino.com/posting.php?mode=quote&f=3&p=7746
if (sent == USB_CDCACM_TX_EPSIZE) {
while (usb_cdcacm_is_transmitting() != 0) {
}
/* flush out to avoid having the pc wait for more data */
usb_cdcacm_tx(NULL, 0);
}
#endif
return n;
} }
int USBSerial::available(void) { int USBSerial::available(void) {
@ -168,14 +147,10 @@ void USBSerial::flush(void)
return; return;
} }
uint32 USBSerial::read(void *buf, uint32 len) { uint32 USBSerial::read(uint8 * buf, uint32 len) {
if (!buf) {
return 0;
}
uint32 rxed = 0; uint32 rxed = 0;
while (rxed < len) { while (rxed < len) {
rxed += usb_cdcacm_rx((uint8*)buf + rxed, len - rxed); rxed += usb_cdcacm_rx(buf + rxed, len - rxed);
} }
return rxed; return rxed;
@ -183,13 +158,13 @@ uint32 USBSerial::read(void *buf, uint32 len) {
/* Blocks forever until 1 byte is received */ /* Blocks forever until 1 byte is received */
int USBSerial::read(void) { int USBSerial::read(void) {
uint8 b; int8 b;
/* /*
this->read(&b, 1); this->read(&b, 1);
return b; return b;
*/ */
if (usb_cdcacm_rx(&b, 1)==0) if (usb_cdcacm_rx((uint8*)&b, 1)==0)
{ {
return -1; return -1;
} }

View File

@ -53,7 +53,7 @@ public:
virtual int available(void);// Changed to virtual virtual int available(void);// Changed to virtual
uint32 read(void *buf, uint32 len); uint32 read(uint8 * buf, uint32 len);
// uint8 read(void); // uint8 read(void);
// Roger Clark. added functions to support Arduino 1.0 API // Roger Clark. added functions to support Arduino 1.0 API

View File

@ -349,8 +349,8 @@ static inline void usb_clear_status_out(uint8 ep) {
void usb_copy_to_pma(const uint8 *buf, uint16 len, uint16 pma_offset); void usb_copy_to_pma(const uint8 *buf, uint16 len, uint16 pma_offset);
void usb_copy_from_pma(uint8 *buf, uint16 len, uint16 pma_offset); void usb_copy_from_pma(uint8 *buf, uint16 len, uint16 pma_offset);
static inline void* usb_pma_ptr(uint32 offset) { static inline uint32 * usb_pma_ptr(uint32 offset) {
return (void*)(USB_PMA_BASE + 2 * offset); return (uint32*)(USB_PMA_BASE + 2 * offset);
} }
/* /*