2016-01-18 16:15:23 +01:00
/* 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/>.
*
*/
/* Copyright (c) 2016 King Kévin <kingkevin@cuvoodoo.info> */
/* this library handles the USB CDC ACM */
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdio.h> // standard I/O facilities
# include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
# include <libopencm3/stm32/rcc.h> // real-time control clock library
# include <libopencm3/stm32/gpio.h> // general purpose input output library
# include <libopencm3/cm3/nvic.h> // interrupt handler
2016-01-28 21:21:50 +01:00
# include <libopencm3/cm3/scb.h> // reset utilities
2016-01-18 16:15:23 +01:00
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
# include <libopencm3/usb/usbd.h> // USB library
# include <libopencm3/usb/cdc.h> // USB CDC library
# include "usb_cdcacm.h" // USB CDC ACM header and definitions
2016-01-28 21:21:50 +01:00
/* USB devices descriptor
* as defined in USB CDC specification section 5
*/
2016-01-18 16:15:23 +01:00
static const struct usb_device_descriptor device_descriptor = {
. bLength = USB_DT_DEVICE_SIZE , // the size of this header in bytes, 18
. bDescriptorType = USB_DT_DEVICE , // a value of 1 indicates that this is a device descriptor
. bcdUSB = 0x0200 , // this device supports USB 2.0
. bDeviceClass = USB_CLASS_CDC , // use the CDC device class
2016-01-28 21:21:50 +01:00
. bDeviceSubClass = 0 , // unused
. bDeviceProtocol = 0 , // unused
2016-01-18 16:15:23 +01:00
. bMaxPacketSize0 = 64 , // packet size for endpoint zero in bytes
. idVendor = 0xc440 , // Vendor ID (CuVo...)
. idProduct = 0x0d00 , // product ID within the Vendor ID space (...odoo)
. bcdDevice = 0x0100 , // version number for the device
. iManufacturer = 1 , // the index of the string in the string table that represents the name of the manufacturer of this device.
. iProduct = 2 , // the index of the string in the string table that represents the name of the product
. iSerialNumber = 3 , // the index of the string in the string table that represents the serial number of this item in string form.
. bNumConfigurations = 1 , // the number of possible configurations this device has
} ;
static const struct usb_endpoint_descriptor data_endpoints [ ] = { {
. bLength = USB_DT_ENDPOINT_SIZE , // the size of the endpoint descriptor in bytes
. bDescriptorType = USB_DT_ENDPOINT , // a value of 5 indicates that this describes an endpoint
. bEndpointAddress = 0x01 , // OUT (from host) direction (0<<7), endpoint 1
. bmAttributes = USB_ENDPOINT_ATTR_BULK , // bulk mode
. wMaxPacketSize = 64 , // maximum packet size
. bInterval = 1 , // the frequency, in number of frames, that we're going to be sending data
} , {
. bLength = USB_DT_ENDPOINT_SIZE , // the size of the endpoint descriptor in bytes
. bDescriptorType = USB_DT_ENDPOINT , // a value of 5 indicates that this describes an endpoint
. bEndpointAddress = 0x82 , // IN (to host) direction (1<<7), endpoint 2
. bmAttributes = USB_ENDPOINT_ATTR_BULK , // bulk mode
. wMaxPacketSize = 64 , // maximum packet size
. bInterval = 1 , // the frequency, in number of frames, that we're going to be sending data
} } ;
/* This notification endpoint isn't implemented. According to CDC spec its
* optional , but its absence causes a NULL pointer dereference in Linux
* cdc_acm driver .
*/
static const struct usb_endpoint_descriptor communication_endpoints [ ] = { {
. bLength = USB_DT_ENDPOINT_SIZE , // the size of the endpoint descriptor in bytes
. bDescriptorType = USB_DT_ENDPOINT , // a value of 5 indicates that this describes an endpoint
. bEndpointAddress = 0x83 , // IN (to host) direction (1<<7), endpoint 3
. bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT , // interrupt mode
. wMaxPacketSize = 16 , // maximum packet size
. bInterval = 255 , // the frequency, in number of frames, that we're going to be sending data
} } ;
2016-01-28 21:21:50 +01:00
/* functional descriptor
* as defined in USB CDC specification section 5.2 .3
*/
2016-01-18 16:15:23 +01:00
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 ) ) cdcacm_functional_descriptors = {
. header = {
. bFunctionLength = sizeof ( struct usb_cdc_header_descriptor ) ,
. bDescriptorType = CS_INTERFACE ,
. bDescriptorSubtype = USB_CDC_TYPE_HEADER ,
. bcdCDC = 0x0110 ,
} ,
. call_mgmt = {
2016-01-28 21:21:50 +01:00
. bFunctionLength = sizeof ( struct usb_cdc_call_management_descriptor ) ,
2016-01-18 16:15:23 +01:00
. 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 ,
} ,
} ;
2016-01-28 21:21:50 +01:00
/* communication class interface descriptor
* as defined in USB CDC specification section 5.1 .3
*/
2016-01-18 16:15:23 +01:00
static const struct usb_interface_descriptor 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 = communication_endpoints ,
. extra = & cdcacm_functional_descriptors ,
. extralen = sizeof ( cdcacm_functional_descriptors ) ,
} } ;
2016-01-28 21:21:50 +01:00
/* data class interface descriptor
* as defined in USB CDC specification section 5.1 .3
*/
2016-01-18 16:15:23 +01:00
static const struct usb_interface_descriptor 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 = data_endpoints ,
} } ;
static const struct usb_interface interfaces [ ] = { {
. num_altsetting = 1 ,
. altsetting = communication_interface ,
} , {
. num_altsetting = 1 ,
. altsetting = data_interface ,
} } ;
static const struct usb_config_descriptor config = {
. bLength = USB_DT_CONFIGURATION_SIZE , // the length of this header in bytes
. bDescriptorType = USB_DT_CONFIGURATION , // a value of 2 indicates that this is a configuration descriptor
. wTotalLength = 0 , // this should hold the total size of the configuration descriptor including all sub interfaces. it is automatically filled in by the USB stack in libopencm3
. bNumInterfaces = 2 , // the number of interfaces in this configuration
. bConfigurationValue = 1 , // the index of this configuration
. iConfiguration = 0 , // a string index describing this configuration (zero means not provided)
2016-01-28 21:21:50 +01:00
. bmAttributes = 0x80 , // self powered (0<<6), supports remote wakeup (0<<5)
2016-01-18 16:15:23 +01:00
. bMaxPower = 0x32 , // the maximum amount of current that this device will draw in 2mA units
// end of header
. interface = interfaces , // pointer to an array of interfaces
} ;
/* string table (starting with index 1) */
const char * usb_strings [ ] = {
" CuVoodoo " ,
" CDC-ACM " ,
" 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 ;
/* input and output ring buffer, indexes, and available memory */
static uint8_t rx_buffer [ CDCACM_BUFFER ] = { 0 } ;
static volatile uint8_t rx_i = 0 ;
static volatile uint8_t rx_used = 0 ;
2016-01-28 21:21:50 +01:00
static uint8_t tx_buffer [ CDCACM_BUFFER ] = { 0 } ;
static volatile uint8_t tx_i = 0 ;
static volatile uint8_t tx_used = 0 ;
2016-01-18 16:15:23 +01:00
/* show the user how much data received over USB is ready */
volatile uint8_t cdcacm_received = 0 ; // same as rx_used, but since the user can write this variable we don't rely on it
2016-02-18 10:39:08 +01:00
/* disconnect USB by pulling down D+ to for re-enumerate */
static void usb_disconnect ( void )
{
/* short USB disconnect to force re-enumerate */
# if defined(SYSTEM_BOARD) || defined(BLUE_PILL)
// 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 + + ) {
__asm__ ( " nop " ) ;
}
# elif 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 + + ) {
__asm__ ( " nop " ) ;
}
gpio_clear ( GPIOB , GPIO9 ) ;
# endif
}
2016-01-18 16:15:23 +01:00
static int 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 ) )
{
( void ) complete ;
( void ) buf ;
( void ) usbd_dev ;
switch ( req - > bRequest ) {
2016-01-28 21:21:50 +01:00
case USB_CDC_REQ_SET_CONTROL_LINE_STATE : {
bool dtr = ( req - > wValue & ( 1 < < 0 ) ) ? true : false ;
bool rts = ( req - > wValue & ( 1 < < 1 ) ) ? true : false ;
2016-01-29 00:26:12 +01:00
if ( dtr | | rts ) { // host opened serial port
usbd_ep_write_packet ( usb_device , 0x82 , NULL , 0 ) ; // start transmitting
}
2016-01-28 21:21:50 +01:00
/* 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 ;
2016-01-18 16:15:23 +01:00
}
2016-01-28 21:21:50 +01:00
case USB_CDC_REQ_SET_LINE_CODING :
// ignore if length is wrong
if ( * len < sizeof ( struct usb_cdc_line_coding ) ) {
return 0 ;
}
// get the line coding
struct usb_cdc_line_coding * coding = ( struct usb_cdc_line_coding * ) * buf ;
/* reset device is the data bits is set to 5
* this is used to allowing rebooting the device in DFU mode for reflashing
* to reset the device from the host you can use stty - - file / dev / ttyACM0 115200 raw cs5
*/
if ( coding - > bDataBits = = 5 ) {
2016-02-18 10:39:08 +01:00
usb_disconnect ( ) ; // force re-enumerate after reset
2016-01-28 21:21:50 +01:00
scb_reset_system ( ) ; // reset device
while ( true ) ; // wait for the reset to happen
}
return 1 ;
default :
2016-01-18 16:15:23 +01:00
return 0 ;
}
return 0 ;
}
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
/* receive data */
usb_length = usbd_ep_read_packet ( usbd_dev , 0x01 , usb_data , sizeof ( usb_data ) ) ;
if ( usb_length ) { // copy received data
for ( uint16_t i = 0 ; i < usb_length & & rx_used < sizeof ( rx_buffer ) ; i + + ) { // only until buffer is full
rx_buffer [ ( rx_i + rx_used ) % sizeof ( rx_buffer ) ] = usb_data [ i ] ; // put character in buffer
rx_used + + ; // update used buffer
}
cdcacm_received = rx_used ; // update available data
}
}
2016-01-28 21:21:50 +01:00
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 ( rx_buffer ) ; // update location on buffer
tx_used - = transmitted ; // update used size
}
}
2016-01-18 16:15:23 +01:00
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 ) ;
2016-01-28 21:21:50 +01:00
usbd_ep_setup ( usbd_dev , 0x82 , USB_ENDPOINT_ATTR_BULK , 64 , cdcacm_data_tx_cb ) ;
2016-01-18 16:15:23 +01:00
usbd_ep_setup ( usbd_dev , 0x83 , USB_ENDPOINT_ATTR_INTERRUPT , 16 , NULL ) ;
usbd_register_control_callback ( usbd_dev , USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE , USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT , cdcacm_control_request ) ;
}
2016-02-18 10:39:08 +01:00
2016-01-18 16:15:23 +01:00
void cdcacm_setup ( void )
{
2016-02-18 10:39:08 +01:00
usb_disconnect ( ) ; // force re-enumerate
2016-01-18 16:15:23 +01:00
/* initialize USB */
usb_device = usbd_init ( & st_usbfs_v1_usb_driver , & device_descriptor , & config , usb_strings , 3 , usbd_control_buffer , sizeof ( usbd_control_buffer ) ) ;
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 ) ;
/* reset buffer states */
rx_i = 0 ;
rx_used = 0 ;
cdcacm_received = 0 ;
2016-01-28 21:21:50 +01:00
/* start sending */
usbd_ep_write_packet ( usb_device , 0x82 , NULL , 0 ) ;
2016-01-18 16:15:23 +01:00
}
/* get character from USB CDC ACM (blocking) */
char cdcacm_getchar ( void )
{
while ( ! rx_used ) { // idle until data is available
2016-02-18 10:39:08 +01:00
__WFI ( ) ; // sleep until interrupt (not sure if it's a good idea here)
2016-01-18 16:15:23 +01:00
}
char to_return = rx_buffer [ rx_i ] ; // get the next available character
rx_i = ( rx_i + 1 ) % sizeof ( rx_buffer ) ; // update used buffer
rx_used - - ; // update used buffer
cdcacm_received = rx_used ; // update available data
return to_return ;
}
/* put character on USB CDC ACM (blocking) */
void cdcacm_putchar ( char c )
{
2016-01-28 21:21:50 +01:00
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
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)
2016-01-18 16:15:23 +01:00
}
void usb_wakeup_isr ( void ) {
usbd_poll ( usb_device ) ;
}
void usb_lp_can_rx0_isr ( void ) {
usbd_poll ( usb_device ) ;
}