diff --git a/lib/usb_cdcacm.c b/lib/usb_cdcacm.c index 0c65985..4d01ed2 100644 --- a/lib/usb_cdcacm.c +++ b/lib/usb_cdcacm.c @@ -16,6 +16,7 @@ * @file usb_cdcacm.c * @author King Kévin * @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 // Cortex M3 utilities #include // USB library #include // USB CDC library +#include // 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 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