try to fix lost USB TX packet failed, but code improved

This commit is contained in:
King Kévin 2016-04-14 23:40:38 +02:00
parent ec57456537
commit 8d22030107
1 changed files with 52 additions and 44 deletions

View File

@ -16,6 +16,7 @@
* @file usb_cdcacm.c
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2016
* @bug sometimes usbd_ep_write_packet packets get lost, even if the return value is right. I have no idea why
*/
/* standard libraries */
@ -31,6 +32,7 @@
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/usb/usbd.h> // USB library
#include <libopencm3/usb/cdc.h> // USB CDC library
#include <libopencm3/cm3/sync.h> // synchronisation utilities
#include "usb_cdcacm.h" // USB CDC ACM header and definitions
@ -192,19 +194,19 @@ static const char *usb_strings[] = {
"STM32F1",
};
/** buffer to be used for control requests */
static uint8_t usbd_control_buffer[128];
/** structure holding all the info related to the USB device */
static usbd_device *usb_device;
static uint8_t usbd_control_buffer[128] = {0}; /**< buffer to be used for control requests */
static usbd_device *usb_device = NULL; /**< structure holding all the info related to the USB device */
static bool connected = false; /**< is the USB device is connected to a host */
/* input and output ring buffer, indexes, and available memory */
static uint8_t rx_buffer[CDCACM_BUFFER] = {0}; /**< ring buffer for received data */
static volatile uint8_t rx_i = 0; /**< current position of read received data */
static volatile uint8_t rx_used = 0; /**< how much data has been received and not red */
mutex_t rx_lock = MUTEX_UNLOCKED; /**< lock to update rx_i or rx_used */
static uint8_t tx_buffer[CDCACM_BUFFER] = {0}; /**< ring buffer for data to transmit */
static volatile uint8_t tx_i = 0; /**< current position if transmitted data */
static volatile uint8_t tx_used = 0; /**< how much data needs to be transmitted */
mutex_t tx_lock = MUTEX_UNLOCKED; /**< lock to update tx_i or tx_used */
volatile uint8_t cdcacm_received = 0; // same as rx_used, but since the user can write this variable we don't rely on it
/** @brief disconnect USB by pulling down D+ to for re-enumerate */
@ -247,18 +249,26 @@ static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *
(void)usbd_dev;
switch (req->bRequest) {
case USB_CDC_REQ_SET_CONTROL_LINE_STATE: {
bool dtr = (req->wValue & (1 << 0)) ? true : false;
bool rts = (req->wValue & (1 << 1)) ? true : false;
if (dtr || rts) { // host opened serial port
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // start transmitting
}
case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
connected = req->wValue ? true : false; // check if terminal is open
//bool dtr = (req->wValue & (1 << 0)) ? true : false;
//bool rts = (req->wValue & (1 << 1)) ? true : false;
/* this Linux cdc_acm driver requires this to be implemented
* even though it's optional in the CDC spec, and we don't
* advertise it in the ACM functional descriptor.
*/
return 1;
}
char local_buf[10];
struct usb_cdc_notification *notif = (void *)local_buf;
/* we echo signals back to host as notification. */
notif->bmRequestType = 0xA1;
notif->bNotification = USB_CDC_NOTIFY_SERIAL_STATE;
notif->wValue = 0;
notif->wIndex = 0;
notif->wLength = 2;
local_buf[8] = req->wValue & 3;
local_buf[9] = 0;
usbd_ep_write_packet(usbd_dev, 0x83, local_buf, 10);
break;
case USB_CDC_REQ_SET_LINE_CODING:
// ignore if length is wrong
if (*len < sizeof(struct usb_cdc_line_coding)) {
@ -275,11 +285,11 @@ static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *
scb_reset_system(); // reset device
while (true); // wait for the reset to happen
}
return 1;
break;
default:
return 0;
}
return 0;
return 1;
}
/** @brief USB CDC ACM data received callback
@ -289,7 +299,6 @@ static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *
static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void)ep;
(void)usbd_dev;
char usb_data[64] = {0}; // buffer to read data
uint16_t usb_length = 0; // length of incoming data
@ -314,21 +323,20 @@ static void cdcacm_data_tx_cb(usbd_device *usbd_dev, uint8_t ep)
(void)ep;
(void)usbd_dev;
char usb_data[64] = {0}; // buffer to send data
uint16_t usb_length = 0; // length of transmitted data
/* transmit data */
if (tx_used) { // copy received data
for (usb_length=0; usb_length<sizeof(usb_data) && usb_length<tx_used; usb_length++) { // only until buffer is full
usb_data[usb_length] = tx_buffer[(tx_i+usb_length)%sizeof(tx_buffer)]; // put data in transmit data
}
// this could lead to a lock down
// while(usbd_ep_write_packet(usb_device, 0x82, usb_data, usb_length)==0);
// this is less critical
uint8_t transmitted = usbd_ep_write_packet(usb_device, 0x82, usb_data, usb_length); // try to transmit data
tx_i = (tx_i+transmitted)%sizeof(tx_buffer); // update location on buffer
tx_used -= transmitted; // update used size
if (!usbd_dev || !connected || !tx_used) { // verify if we can send and there is something to send
return;
}
if (mutex_trylock(&tx_lock)) { // try to get lock
uint8_t usb_length = (tx_used > 64 ? 64 : tx_used); // length of data to be transmitted (respect max packet size)
usb_length = (usb_length > (sizeof(tx_buffer)-tx_i) ? sizeof(tx_buffer)-tx_i : usb_length); // since here we use the source array not as ring buffer, only go up to the end
while (usb_length != usbd_ep_write_packet(usb_device, 0x82, (void*)(&tx_buffer[tx_i]), usb_length)); // ensure data is written into transmit buffer
tx_i = (tx_i+usb_length)%sizeof(tx_buffer); // update location on buffer
tx_used -= usb_length; // update used size
mutex_unlock(&tx_lock); // release lock
} else {
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // trigger empty tx for a later callback
}
usbd_poll(usb_device); // ensure the data gets sent
}
/** @brief set USB CDC ACM configuration
@ -338,7 +346,6 @@ static void cdcacm_data_tx_cb(usbd_device *usbd_dev, uint8_t ep)
static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
{
(void)wValue;
(void)usbd_dev;
usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, cdcacm_data_rx_cb);
usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, cdcacm_data_tx_cb);
@ -349,6 +356,7 @@ static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
void cdcacm_setup(void)
{
connected = false; // start with USB not connected
usb_disconnect(); // force re-enumerate (useful after a restart or if there is a bootloader using another USB profile)
/* initialize USB */
@ -356,16 +364,16 @@ void cdcacm_setup(void)
usbd_register_set_config_callback(usb_device, cdcacm_set_config);
/* enable interrupts (to not have to poll all the time) */
nvic_enable_irq(NVIC_USB_WAKEUP_IRQ);
nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ); // without this USB isn't detected by the host
/* reset buffer states */
rx_i = 0;
rx_used = 0;
mutex_unlock(&rx_lock);
cdcacm_received = 0;
/* start sending */
usbd_ep_write_packet(usb_device, 0x82, NULL, 0);
tx_i = 0;
tx_used = 0;
mutex_unlock(&tx_lock);
}
char cdcacm_getchar(void)
@ -382,22 +390,22 @@ char cdcacm_getchar(void)
void cdcacm_putchar(char c)
{
if (!usb_device || !connected) {
return;
}
mutex_lock(&tx_lock); // get lock to prevent race condition
if (tx_used<sizeof(tx_buffer)) { // buffer not full
tx_buffer[(tx_i+tx_used)%sizeof(tx_buffer)] = c; // put character in buffer
tx_used++; // update used buffer
} else { // buffer full
} else { // buffer full (might be that no terminal is connected to this serial)
tx_i = (tx_i+1)%sizeof(tx_buffer); // shift start
tx_buffer[(tx_i+tx_used)%sizeof(tx_buffer)] = c; // overwrite old data
}
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // trigger tx (not sure why cdcacm_data_tx_cb doesn't work else)
}
/** @brief USB interrupt service routine called on wake-up event */
void usb_wakeup_isr(void) {
usbd_poll(usb_device);
mutex_unlock(&tx_lock); // release lock
usbd_ep_write_packet(usb_device, 0x82, NULL, 0); // trigger tx callback
}
/** @brief USB interrupt service routine called when data is received */
void usb_lp_can_rx0_isr(void) {
usbd_poll(usb_device);
usbd_poll(usb_device);
}