usb_cdcacm.c
Go to the documentation of this file.
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/cm3/scb.h> // reset utilities
#include <libopencm3/cm3/nvic.h> // interrupt handler
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/usb/usbd.h> // USB library
#include <libopencm3/usb/cdc.h> // USB CDC library
#include <libopencm3/usb/dfu.h> // DFU definitions
/* own libraries */
#include "global.h" // global utilities
#include "usb_cdcacm.h" // USB CDC ACM header and definitions
#define USB_DATA_TRANSFER_SIZE 64 // 64 is the maximum for full speed devices
volatile bool usb_cdcacm_connecting = false;
static uint8_t usbd_control_buffer[128] = {0};
static usbd_device *usb_device = NULL;
static volatile bool first_connection = false;
/* output ring buffer, index, and available memory */
static uint8_t tx_buffer[256] = {0};
static volatile uint16_t tx_i = 0;
static volatile uint16_t tx_used = 0;
static volatile bool tx_lock = false;
static const struct usb_device_descriptor usb_cdcacm_device_descriptor = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = USB_CLASS_CDC,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = 0x1209,
.idProduct = 0x4256,
.bcdDevice = 0x0000,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1,
};
static const struct usb_endpoint_descriptor usb_cdcacm_data_endpoints[] = {{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x02,
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
.wMaxPacketSize = USB_DATA_TRANSFER_SIZE,
.bInterval = 1,
},{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x82,
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
.wMaxPacketSize = USB_DATA_TRANSFER_SIZE,
.bInterval = 1,
}};
static const struct usb_endpoint_descriptor usb_cdcacm_communication_endpoints[] = {{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x81,
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
.wMaxPacketSize = 16,
.bInterval = 255,
}};
static const struct {
struct usb_cdc_header_descriptor header;
struct usb_cdc_call_management_descriptor call_mgmt;
struct usb_cdc_acm_descriptor acm;
struct usb_cdc_union_descriptor cdc_union;
} __attribute__((packed)) usb_cdcacm_functional_descriptors = {
.header = {
.bFunctionLength = sizeof(struct usb_cdc_header_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_HEADER,
.bcdCDC = 0x0110,
},
.call_mgmt = {
.bFunctionLength = sizeof(struct usb_cdc_call_management_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
.bmCapabilities = 0,
.bDataInterface = 1,
},
.acm = {
.bFunctionLength = sizeof(struct usb_cdc_acm_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_ACM,
.bmCapabilities = 0,
},
.cdc_union = {
.bFunctionLength = sizeof(struct usb_cdc_union_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_UNION,
.bControlInterface = 0,
.bSubordinateInterface0 = 1,
},
};
static const struct usb_interface_descriptor usb_cdcacm_communication_interface = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_CDC,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = USB_CDC_PROTOCOL_NONE,
.iInterface = 0,
.endpoint = usb_cdcacm_communication_endpoints,
.extra = &usb_cdcacm_functional_descriptors,
.extralen = sizeof(usb_cdcacm_functional_descriptors),
};
static const struct usb_interface_descriptor usb_cdcacm_data_interface = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 1,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_DATA,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
.endpoint = usb_cdcacm_data_endpoints,
};
static const struct usb_dfu_descriptor usb_dfu_functional = {
.bLength = sizeof(struct usb_dfu_descriptor),
.bDescriptorType = DFU_FUNCTIONAL,
.bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
.wDetachTimeout = 200,
.wTransferSize = sizeof(usbd_control_buffer),
.bcdDFUVersion = 0x0110,
};
static const struct usb_interface_descriptor usb_dfu_interface = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 2,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = 0xFE,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 1,
.iInterface = 4,
.extra = &usb_dfu_functional,
.extralen = sizeof(usb_dfu_functional),
};
static const struct usb_interface usb_cdcacm_interfaces[] = {{
.num_altsetting = 1,
.altsetting = &usb_cdcacm_communication_interface,
}, {
.num_altsetting = 1,
.altsetting = &usb_cdcacm_data_interface,
}, {
.num_altsetting = 1,
.altsetting = &usb_dfu_interface,
}};
static const struct usb_config_descriptor usb_cdcacm_configuration_descriptor = {
.bLength = USB_DT_CONFIGURATION_SIZE,
.bDescriptorType = USB_DT_CONFIGURATION,
.wTotalLength = 0,
.bNumInterfaces = LENGTH(usb_cdcacm_interfaces),
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = 0x80,
.bMaxPower = 0xfa,
// end of header
.interface = usb_cdcacm_interfaces,
};
static const char *usb_strings[] = {
"CuVoodoo",
"BusVoodoo multi-protocol debugging adapter",
#if BUSVOODOO_HARDWARE_VERSION==0
"0",
#else
(char[]){'A'+BUSVOODOO_HARDWARE_VERSION-1, 0},
#endif
"DFU bootloader (runtime mode)",
};
static void usb_disconnect(void)
{
#if defined(MAPLE_MINI)
// disconnect USB D+ using dedicated DISC line/circuit on PB9
rcc_periph_clock_enable(RCC_GPIOB);
gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO9);
gpio_set(GPIOB, GPIO9);
for (uint32_t i = 0; i < 0x2000; i++) { // wait for at least 10 ms
__asm__("nop");
}
#else
// pull USB D+ low for a short while
rcc_periph_clock_enable(RCC_GPIOA);
gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO12);
gpio_clear(GPIOA, GPIO12);
for (uint32_t i = 0; i < 0x2000; i++) { // wait for at least 10 ms
__asm__("nop");
}
#endif
}
static void usb_dfu_detach(usbd_device *usbd_dev, struct usb_setup_data *req)
{
(void)usbd_dev; // variable not used
(void)req; // variable not used
RCC_CSR |= RCC_CSR_RMVF; // clear reset flag for the bootloader to detect the core reset
usb_disconnect(); // USB detach (disconnect to force re-enumeration)
scb_reset_core(); // reset device (only the core, to the peripheral stay configured)
while (true); // wait for the reset to happen
}
static enum usbd_request_return_codes usb_cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
{
if (usb_dfu_interface.bInterfaceNumber==req->wIndex) { // check if request is for DFU
switch (req->bRequest) {
case DFU_DETACH: // USB detach requested
*complete = usb_dfu_detach; // detach after reply
break;
case DFU_GETSTATUS: // get status
(*buf)[0] = DFU_STATUS_OK;; // set OK status
(*buf)[1] = 0; // set null poll timeout
(*buf)[2] = 0; // set null poll timeout
(*buf)[3] = 0; // set null poll timeout
(*buf)[4] = STATE_APP_IDLE; // application is running
(*buf)[5] = 0; // string not used
*len = 6; // set length of buffer to return
break;
default: // other requests are not supported
return USBD_REQ_NOTSUPP;
}
} else if (usb_cdcacm_communication_interface.bInterfaceNumber==req->wIndex) { // check if request is for CDC
switch (req->bRequest) {
case USB_CDC_REQ_SET_CONTROL_LINE_STATE:
usb_cdcacm_connecting = (0!=req->wValue); // check if terminal is open (windows set the control line state before a terminal opens the port, but with value 0)
//bool dtr = (req->wValue & (1 << 0)) ? true : false;
//bool rts = (req->wValue & (1 << 1)) ? true : false;
/* the 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.
*/
uint8_t reply[10] = {0};
struct usb_cdc_notification *notif = (void *)reply;
/* 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;
reply[8] = req->wValue & 3;
reply[9] = 0;
usbd_ep_write_packet(usbd_dev, usb_cdcacm_communication_endpoints[0].bEndpointAddress, reply, LENGTH(reply)); // send the reply from communication endpoint (we can't use the complete callback because we are not using the current control endpoint)
break;
case USB_CDC_REQ_SET_LINE_CODING:
// ignore if length is wrong
if (*len < sizeof(struct usb_cdc_line_coding)) {
return USBD_REQ_NOTSUPP;
}
// line coding is ignored
// to get the line coding
// struct usb_cdc_line_coding *coding = (struct usb_cdc_line_coding *)*buf;
break;
default:
return USBD_REQ_NOTSUPP;
}
}
return USBD_REQ_HANDLED;
}
static void usb_cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void)ep; // not used
char usb_data[USB_DATA_TRANSFER_SIZE] = {0}; // buffer to read data
uint16_t usb_length = 0; // length of incoming data
// receive data
usb_length = usbd_ep_read_packet(usbd_dev, usb_cdcacm_data_endpoints[0].bEndpointAddress, usb_data, sizeof(usb_data));
if (usb_length) { // copy received data
for (uint16_t i=0; i<usb_length && i<LENGTH(usb_data); i++) { // only until buffer is full
user_input_store(usb_data[i]); // store user data
}
}
}
static void usb_cdcacm_data_tx_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void)ep; // not used
static bool usb_tx_ongoing = false; // if USB transmission is already ongoing
if (!usbd_dev && usb_tx_ongoing) { // putchar is trying to send data but a transmission is already ongoing
return;
}
if (0==tx_used || !first_connection) { // verify if we can send and there is something to send
usb_tx_ongoing = false; // transmission ended
return;
}
if (!tx_lock) { // ensure no data is in being put in the buffer
tx_lock = true; // get the lock (no dead lock should occur since putchar should not be used in interrupts)
usb_tx_ongoing = true; // remember we started transmission
uint16_t usb_length = (tx_used > USB_DATA_TRANSFER_SIZE ? USB_DATA_TRANSFER_SIZE : tx_used); // length of data to be transmitted (respect max packet size)
usb_length = (usb_length > (LENGTH(tx_buffer)-tx_i) ? LENGTH(tx_buffer)-tx_i : usb_length); // since here we use the source array not as ring buffer, only go up to the end
usb_length = usbd_ep_write_packet(usb_device, 0x82, (void*)(&tx_buffer[tx_i]), usb_length); // transmit data (put into USB FIFO)
tx_i = (tx_i+usb_length)%LENGTH(tx_buffer); // update location on buffer
tx_used -= usb_length; // update used size
tx_lock = false; // release lock
} else {
usbd_ep_write_packet(usb_device, usb_cdcacm_data_endpoints[1].bEndpointAddress, NULL, 0); // trigger empty tx for a later callback
}
usbd_poll(usb_device); // ensure the data gets sent
}
static void usb_cdcacm_communication_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void)ep; // not used
(void)usbd_dev; // not used
first_connection |= usb_cdcacm_connecting; // check if port has been opened
if (tx_used>0 && first_connection) { // if buffer is not empty
usb_cdcacm_data_tx_cb(NULL, 0); // send available data
}
}
static void usb_cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
{
(void)wValue; // not used
usbd_ep_setup(usbd_dev, usb_cdcacm_communication_endpoints[0].bEndpointAddress, usb_cdcacm_communication_endpoints[0].bmAttributes, usb_cdcacm_communication_endpoints[0].wMaxPacketSize, usb_cdcacm_communication_cb); // set communication endpoint
usbd_ep_setup(usbd_dev, usb_cdcacm_data_endpoints[0].bEndpointAddress, usb_cdcacm_data_endpoints[0].bmAttributes, usb_cdcacm_data_endpoints[0].wMaxPacketSize, usb_cdcacm_data_rx_cb); // set outgoing (from host) data endpoint
usbd_ep_setup(usbd_dev, usb_cdcacm_data_endpoints[1].bEndpointAddress, usb_cdcacm_data_endpoints[1].bmAttributes, usb_cdcacm_data_endpoints[1].wMaxPacketSize, usb_cdcacm_data_tx_cb); // set incoming (to host) data endpoint
usbd_register_control_callback(usbd_dev, USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, usb_cdcacm_control_request);
}
void usb_cdcacm_setup(void)
{
// initialize USB
rcc_periph_reset_pulse(RST_USB); // reset USB peripheral
usb_disconnect(); // disconnect to force re-enumeration
rcc_periph_clock_enable(RCC_GPIOA); // enable clock for GPIO used for USB
rcc_periph_clock_enable(RCC_USB); // enable clock for USB domain
usb_device = usbd_init(&st_usbfs_v1_usb_driver, &usb_cdcacm_device_descriptor, &usb_cdcacm_configuration_descriptor, usb_strings, LENGTH(usb_strings), usbd_control_buffer, sizeof(usbd_control_buffer));
usbd_register_set_config_callback(usb_device, usb_cdcacm_set_config);
nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ); // enable interrupts (to not have to poll all the time)
// reset buffer states
tx_i = 0;
tx_used = 0;
tx_lock = false; // release lock
usb_cdcacm_connecting = false; // clear flag
first_connection = false; // reset first connection detection
}
void usb_cdcacm_putchar(char c)
{
if (!usb_device) {
return;
}
while (tx_lock); // wait for lock to be released
tx_lock = true; // put lock on transmit buffer
if (tx_used<LENGTH(tx_buffer)) { // buffer not full
tx_buffer[(tx_i+tx_used)%LENGTH(tx_buffer)] = c; // put character in buffer
tx_used++; // update used buffer
} else { // buffer full (might be that no terminal is connected to this serial)
tx_i = (tx_i+1)%LENGTH(tx_buffer); // shift start
tx_buffer[(tx_i+tx_used)%LENGTH(tx_buffer)] = c; // overwrite old data
}
tx_lock = false; // release lock on transmit buffer
usb_cdcacm_data_tx_cb(NULL, 0); // send data over USB when possible
}
void usb_lp_can_rx0_isr(void) {
usbd_poll(usb_device);
}
USB_DATA_TRANSFER_SIZE
#define USB_DATA_TRANSFER_SIZE
maximum packet size for USB data transfer
Definition: usb_cdcacm.c:40
usb_cdcacm_communication_cb
static void usb_cdcacm_communication_cb(usbd_device *usbd_dev, uint8_t ep)
USB CDC ACM communication callback.
Definition: usb_cdcacm.c:410
header
struct usb_cdc_header_descriptor header
header
Definition: usb_cdcacm.c:110
tx_used
static volatile uint16_t tx_used
how much data needs to be transmitted
Definition: usb_cdcacm.c:51
cdc_union
struct usb_cdc_union_descriptor cdc_union
descriptor
Definition: usb_cdcacm.c:113
usb_dfu_interface
static const struct usb_interface_descriptor usb_dfu_interface
USB DFU interface descriptor.
Definition: usb_cdcacm.c:195
usb_cdcacm_communication_interface
static const struct usb_interface_descriptor usb_cdcacm_communication_interface
USB CDC interface descriptor.
Definition: usb_cdcacm.c:146
usb_cdcacm_configuration_descriptor
static const struct usb_config_descriptor usb_cdcacm_configuration_descriptor
USB CDC ACM configuration descriptor.
Definition: usb_cdcacm.c:223
user_input_store
void user_input_store(char c)
store user input
Definition: global.c:157
usb_cdcacm_connecting
volatile bool usb_cdcacm_connecting
flag set to true when user is connected to USB CDC ACM port (e.g.
Definition: usb_cdcacm.c:42
usb_cdcacm_putchar
void usb_cdcacm_putchar(char c)
send character over USB (non-blocking)
Definition: usb_cdcacm.c:455
usb_cdcacm_communication_endpoints
static const struct usb_endpoint_descriptor usb_cdcacm_communication_endpoints[]
USB CDC ACM communication endpoints.
Definition: usb_cdcacm.c:96
__attribute__
static const struct @1 __attribute__((packed))
USB CDC ACM functional descriptor.
Definition: usb_cdcacm.c:114
first_connection
static volatile bool first_connection
used to detect when the first connection occurred
Definition: usb_cdcacm.c:46
global.h
global definitions and methods (API)
usb_dfu_detach
static void usb_dfu_detach(usbd_device *usbd_dev, struct usb_setup_data *req)
DFU detach (disconnect USB and perform core reset)
Definition: usb_cdcacm.c:276
usb_cdcacm_data_interface
static const struct usb_interface_descriptor usb_cdcacm_data_interface
USB CDC ACM data class interface descriptor.
Definition: usb_cdcacm.c:166
usbd_control_buffer
static uint8_t usbd_control_buffer[128]
buffer to be used for control requests
Definition: usb_cdcacm.c:44
usb_cdcacm_setup
void usb_cdcacm_setup(void)
setup USB CDC ACM peripheral
Definition: usb_cdcacm.c:436
usb_cdcacm_control_request
static enum usbd_request_return_codes usb_cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, void(**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
incoming USB CDC ACM control request
Definition: usb_cdcacm.c:295
usb_dfu_functional
static const struct usb_dfu_descriptor usb_dfu_functional
USB DFU functional descriptor.
Definition: usb_cdcacm.c:183
acm
struct usb_cdc_acm_descriptor acm
descriptor
Definition: usb_cdcacm.c:112
tx_lock
static volatile bool tx_lock
if the transmit buffer is currently being written
Definition: usb_cdcacm.c:52
usb_strings
static const char * usb_strings[]
USB string table.
Definition: usb_cdcacm.c:239
usb_cdcacm_device_descriptor
static const struct usb_device_descriptor usb_cdcacm_device_descriptor
USB CDC ACM device descriptor.
Definition: usb_cdcacm.c:57
usb_disconnect
static void usb_disconnect(void)
disconnect USB by pulling down D+ to for re-enumerate
Definition: usb_cdcacm.c:251
tx_i
static volatile uint16_t tx_i
current position if transmitted data
Definition: usb_cdcacm.c:50
usb_lp_can_rx0_isr
void usb_lp_can_rx0_isr(void)
USB interrupt service routine called when data is received.
Definition: usb_cdcacm.c:474
call_mgmt
struct usb_cdc_call_management_descriptor call_mgmt
call management descriptor
Definition: usb_cdcacm.c:111
tx_buffer
static uint8_t tx_buffer[256]
ring buffer for data to transmit
Definition: usb_cdcacm.c:49
usb_cdcacm.h
library for USB CDC ACM communication (API)
LENGTH
#define LENGTH(x)
get the length of an array
Definition: global.h:26
usb_cdcacm_data_endpoints
static const struct usb_endpoint_descriptor usb_cdcacm_data_endpoints[]
USB CDC ACM data endpoints.
Definition: usb_cdcacm.c:77
usb_device
static usbd_device * usb_device
structure holding all the info related to the USB device
Definition: usb_cdcacm.c:45
usb_cdcacm_interfaces
static const struct usb_interface usb_cdcacm_interfaces[]
USB CDC ACM interface descriptor.
Definition: usb_cdcacm.c:211
usb_cdcacm_data_rx_cb
static void usb_cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
USB CDC ACM data received callback.
Definition: usb_cdcacm.c:357
usb_cdcacm_data_tx_cb
static void usb_cdcacm_data_tx_cb(usbd_device *usbd_dev, uint8_t ep)
USB CDC ACM data transmitted callback.
Definition: usb_cdcacm.c:378
usb_cdcacm_set_config
static void usb_cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue)
set USB CDC ACM configuration
Definition: usb_cdcacm.c:425