diff options
author | Karl Palsson <karlp@tweak.net.au> | 2016-09-26 22:04:02 +0000 |
---|---|---|
committer | Karl Palsson <karlp@tweak.net.au> | 2016-09-26 22:04:02 +0000 |
commit | c0ec94e4c94371d22c334396c5d937d0a682de78 (patch) | |
tree | 8fbf2576d64ebc3cb708b08c63e5863fe2c220d2 | |
parent | 19d3225fd66573a2a62ece83158cd5551255f2ad (diff) | |
download | olsndot-c0ec94e4c94371d22c334396c5d937d0a682de78.tar.gz olsndot-c0ec94e4c94371d22c334396c5d937d0a682de78.tar.bz2 olsndot-c0ec94e4c94371d22c334396c5d937d0a682de78.zip |
ubs-serial-rs485: First solidly working version.
Needs major refactoring to split out the _actual_ arch dependent pieces.
Needs parity and more complete baud rate support.
Needs rs485 support.
-rw-r--r-- | tests/usb-serial-rs485/Makefile.stm32f4-disco | 38 | ||||
-rw-r--r-- | tests/usb-serial-rs485/README | 21 | ||||
-rw-r--r-- | tests/usb-serial-rs485/main-stm32f4-disco.c | 169 | ||||
-rw-r--r-- | tests/usb-serial-rs485/ringb.c | 59 | ||||
-rw-r--r-- | tests/usb-serial-rs485/ringb.h | 60 | ||||
-rw-r--r-- | tests/usb-serial-rs485/syscfg.h | 50 | ||||
-rw-r--r-- | tests/usb-serial-rs485/usb_cdcacm-arch.c | 124 | ||||
-rw-r--r-- | tests/usb-serial-rs485/usb_cdcacm.c | 310 | ||||
-rw-r--r-- | tests/usb-serial-rs485/usb_cdcacm.h | 56 |
9 files changed, 887 insertions, 0 deletions
diff --git a/tests/usb-serial-rs485/Makefile.stm32f4-disco b/tests/usb-serial-rs485/Makefile.stm32f4-disco new file mode 100644 index 0000000..1c60091 --- /dev/null +++ b/tests/usb-serial-rs485/Makefile.stm32f4-disco @@ -0,0 +1,38 @@ +## +## This file is part of the libopencm3 project. +## +## This library is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This library 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 Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with this library. If not, see <http://www.gnu.org/licenses/>. +## + +BOARD = stm32f4-disco +PROJECT = usb-serial-rs485-$(BOARD) +BUILD_DIR = bin-$(BOARD) + +SHARED_DIR = ../../shared + +CFILES = main-$(BOARD).c +CFILES += usb_cdcacm.c usb_cdcacm-arch.c +CFILES += ringb.c +CFILES += trace.c trace_stdio.c + +VPATH += $(SHARED_DIR) +INCLUDES += $(patsubst %,-I%, . $(SHARED_DIR)) +OPENCM3_DIR=../../libopencm3 + +# Copy this to some arch shared? +DEVICE=stm32f405xg +OOCD_FILE= ../../openocd/openocd.stm32f4-disco.cfg + +include ../../rules.mk + diff --git a/tests/usb-serial-rs485/README b/tests/usb-serial-rs485/README new file mode 100644 index 0000000..e3d4948 --- /dev/null +++ b/tests/usb-serial-rs485/README @@ -0,0 +1,21 @@ +------------------------------------------------------------------------------ +README +------------------------------------------------------------------------------ + +This implements a USB CDC-ACM device, connected to USART2 (PA2-tx/PA3-rx) +An interrupt driven tx ring buffer is used for usb->serial, and a similar +interrupt driven rx ring buffer is used for serial->usb. +Baud rates from 2400-460800 are tested with zmodem transfers in both directions. + +Parity not yet finished, and baudrates below 1200 need fiddling with clock prescalers. +When it's finished, rs485 support should be available via the TC interrupt. + +A GPIO is toggled around USART transmissions to control an RS485 line +transceiver, following ST's app note. The RS485 driver enable line is, +by default, just the onboard red LEDs. + +The orange/blue LEDs indicate tx/rx activity, but especially for the rx line, +you won't really see it without heavy data rates. + +This example is heavily based on the existing usb_cdcacm and usart demos. + diff --git a/tests/usb-serial-rs485/main-stm32f4-disco.c b/tests/usb-serial-rs485/main-stm32f4-disco.c new file mode 100644 index 0000000..f024e46 --- /dev/null +++ b/tests/usb-serial-rs485/main-stm32f4-disco.c @@ -0,0 +1,169 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Karl Palsson <karlp@tweak.net.au> + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <libopencm3/cm3/nvic.h> +#include <libopencm3/stm32/gpio.h> +#include <libopencm3/stm32/rcc.h> +#include <libopencm3/stm32/usart.h> + +#include <stdio.h> +#include "syscfg.h" +#include "usb_cdcacm.h" +#include "ringb.h" +#include "trace.h" + +#define ER_DEBUG +#ifdef ER_DEBUG +#define ER_DPRINTF(fmt, ...) \ + do { printf(fmt, ## __VA_ARGS__); } while (0) +#else +#define ER_DPRINTF(fmt, ...) \ + do { } while (0) +#endif + + +static usbd_device *usbd_dev; +static struct ringb rx_ring; +static uint8_t rx_ring_data[64]; +struct ringb tx_ring; +static uint8_t tx_ring_data[128]; +bool nakked = false; + +static void usart_setup(void) +{ + /* Enable the USART2 interrupt. */ + nvic_enable_irq(NVIC_USART2_IRQ); + + /* USART2 pins are on port A */ + rcc_periph_clock_enable(RCC_GPIOA); + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3); + gpio_set_af(GPIOA, GPIO_AF7, GPIO2 | GPIO3); + + /* Enable clocks for USART2. */ + rcc_periph_clock_enable(RCC_USART2); + + /* Setup USART2 parameters. */ + usart_set_baudrate(USART2, 115200); + usart_set_databits(USART2, 8); + usart_set_stopbits(USART2, USART_STOPBITS_1); + usart_set_mode(USART2, USART_MODE_TX_RX); + usart_set_parity(USART2, USART_PARITY_NONE); + usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE); + + /* Enable USART2 Receive interrupt. */ + usart_enable_rx_interrupt(USART2); + + /* Finally enable the USART. */ + usart_enable(USART2); +} + +void usart2_isr(void) +{ + // usbser-rxne() + /* Check if we were called because of RXNE. */ + if (usart_get_interrupt_source(USART2, USART_SR_RXNE)) { + gpio_set(LED_RX_PORT, LED_RX_PIN); + uint8_t c = usart_recv(USART2); + if (ringb_put(&rx_ring, c)) { + // good, + } else { + // fatal, you should always have drained by now. + // (when you've got it all ironed out, _actually_ + // just drop and count drops), but not yet... + ER_DPRINTF("rx buffer full\n"); + while(1); + } + gpio_clear(LED_RX_PORT, LED_RX_PIN); + } + // usbser-irq-txe() + if (usart_get_interrupt_source(USART2, USART_SR_TXE)) { + if (ringb_depth(&tx_ring) == 0) { + // turn off tx empty interrupts, nothing left to send + usart_disable_tx_interrupt(USART2); + ER_DPRINTF("OFF\n"); + // Turn on tx complete interrupts, for rs485 de +// USART_CR1(USART2) |= ~USART_CR1_TCIE; + } else { + int c = ringb_get(&tx_ring); + usart_send(USART2, c); + } + } + // usbser-irq-txc? rs485 is auto on some devices, but can be emulated anyway +// if (usart_get_interrupt_source(USART2, USART_SR_TC)) { +// ER_DPRINTF("TC"); +// // turn off the complete irqs, we're done now. +// USART_SR(USART2) &= ~USART_SR_TC; +// USART_CR1(USART2) &= ~USART_CR1_TCIE; +// gpio_clear(LED_TX_PORT, LED_TX_PIN); +// gpio_clear(RS485DE_PORT, RS485DE_PIN); +// } +} + +/* Y0, moron, nothing's stopping rx irqs from happening, have fun when you overflow temp buffer! */ +static void task_drain_rx(struct ringb *r) { + uint8_t zero_copy_is_for_losers[sizeof(rx_ring_data)]; + int zci = 0; + int c = ringb_get(r); + while (c >= 0) { + zero_copy_is_for_losers[zci++] = c; + c = ringb_get(r); + } + if (zci) { + trace_send16(STIMULUS_RING_DRAIN, zci); + glue_data_received_cb(zero_copy_is_for_losers, zci); + } + +} + +int main(void) +{ + rcc_clock_setup_hse_3v3(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]); + ER_DPRINTF("And we're alive!\n"); + /* Leds and rs485 are on port D */ + rcc_periph_clock_enable(RCC_GPIOD); + gpio_mode_setup(LED_RX_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, + LED_RX_PIN | LED_TX_PIN); + gpio_mode_setup(RS485DE_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, + RS485DE_PIN); + + usart_setup(); + ringb_init(&rx_ring, rx_ring_data, sizeof(rx_ring_data)); + ringb_init(&tx_ring, tx_ring_data, sizeof(tx_ring_data)); + + usb_cdcacm_init(&usbd_dev); + + ER_DPRINTF("Looping...\n"); + volatile int i = 0; + while (1) { + usbd_poll(usbd_dev); + if (i++ > 500) { + // hacktastic + if (ringb_depth(&tx_ring) < 64 && nakked) { + usbd_ep_nak_set(usbd_dev, 1, 0); + nakked = false; + } + + task_drain_rx(&rx_ring); + i = 0; + } + + } + +} + diff --git a/tests/usb-serial-rs485/ringb.c b/tests/usb-serial-rs485/ringb.c new file mode 100644 index 0000000..245325b --- /dev/null +++ b/tests/usb-serial-rs485/ringb.c @@ -0,0 +1,59 @@ + +#include "ringb.h" +void ringb_init(struct ringb *ring, uint8_t *buf, int len) +{ + ring->buf_len = len; + ring->buf = buf; + ring->idx_r = 0; + ring->idx_w = 0; +} + +int ringb_get(struct ringb *ring) { +#if 1 + int rval; + if (ring->idx_r != ring->idx_w) { + rval = ring->buf[ring->idx_r]; + ring->idx_r = (ring->idx_r + 1) % ring->buf_len; + return rval; + } + return -1; +#else + if (((ring->idx_w - ring->idx_r) % ring->buf_len) >= 0) { + int rval = ring->buf[ring->idx_r]; + ring->idx_r = (ring->idx_r + 1) % ring->buf_len; + return rval; + } else { + return -1; + } +#endif +} + +bool ringb_put(struct ringb *ring, uint8_t c) { +#if 1 + unsigned int next = (ring->idx_w + 1) % ring->buf_len; + if (next != ring->idx_r) { + ring->buf[ring->idx_w] = c; + ring->idx_w = next; + return true; + } + return false; +#else + if (((ring->idx_w - ring->idx_r) % ring->buf_len) == 0) { + return false; + } + ring->buf[ring->idx_w] = c; + ring->idx_w = (ring->idx_w + 1) % ring->buf_len; + return true; +#endif + +} + + +void ringb_flush(struct ringb *ring) { + ring->idx_r = 0; + ring->idx_w = 0; +} + +int ringb_depth(struct ringb *ring) { + return ((unsigned int)(ring->idx_w - ring->idx_r) % ring->buf_len); +}
\ No newline at end of file diff --git a/tests/usb-serial-rs485/ringb.h b/tests/usb-serial-rs485/ringb.h new file mode 100644 index 0000000..c3d5356 --- /dev/null +++ b/tests/usb-serial-rs485/ringb.h @@ -0,0 +1,60 @@ +/* + * Karl Palsson <karlp@tweak.net.au> + * Considered to be released under your choice of: + * MIT/ISC/Apache2/BSD2Clause/GPLv2 + * If you're looking for elegant compact performance you've come to the wrong + * place hombre. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> + +struct ringb { + volatile unsigned int idx_r; + volatile unsigned int idx_w; + uint8_t *buf; + int buf_len; +}; + +/** + * Load up a ring buffer. Always suceeds + * @param ring struct saving state, provided by the user + * @param buf where the data will be kept + * @param len size of buf in in elements. + */ +void ringb_init(struct ringb *ring, uint8_t *buf, int len); + +/** + * push data in + * @param ring + * @param c + * @return true if space was available + */ +bool ringb_put(struct ringb *ring, uint8_t c); + +/** + * pull data out + * @param ring + * @return -1 for no data, uint8_t range for valid. + */ +int ringb_get(struct ringb *ring); + +/** + * Toss data and reset to empty + * @param ring + */ +void ringb_flush(struct ringb *ring); + +int ringb_depth(struct ringb *ring); + + + +#ifdef __cplusplus +} +#endif diff --git a/tests/usb-serial-rs485/syscfg.h b/tests/usb-serial-rs485/syscfg.h new file mode 100644 index 0000000..7e2a6d8 --- /dev/null +++ b/tests/usb-serial-rs485/syscfg.h @@ -0,0 +1,50 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Karl Palsson <karlp@tweak.net.au> + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SYSCFG_H +#define SYSCFG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* TODO: should really make a stm32f4discovery.h file... */ + +#define LED_RX_PORT GPIOD +#define LED_RX_PIN GPIO15 /* Blue, but you won't see this one much */ +#define LED_TX_PORT GPIOD +#define LED_TX_PIN GPIO13 /* orange */ +/* On stm32f4 discovery, this is actually another led... */ +#define RS485DE_PORT GPIOD +#define RS485DE_PIN GPIO14 /* red */ + +#define STREAM_USART2_TX 6 + +#define STIMULUS_RING_DRAIN 2 +#define STIMULUS_RING_PUSH 3 +#define STIMULUS_TXC 4 +#define STIMULUS_TX 5 + + +#ifdef __cplusplus +} +#endif + +#endif /* SYSCFG_H */ + diff --git a/tests/usb-serial-rs485/usb_cdcacm-arch.c b/tests/usb-serial-rs485/usb_cdcacm-arch.c new file mode 100644 index 0000000..8680906 --- /dev/null +++ b/tests/usb-serial-rs485/usb_cdcacm-arch.c @@ -0,0 +1,124 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Karl Palsson <karlp@tweak.net.au> + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <libopencm3/cm3/nvic.h> +#include <libopencm3/stm32/dma.h> +#include <libopencm3/stm32/gpio.h> +#include <libopencm3/stm32/rcc.h> +#include <libopencm3/stm32/usart.h> + +#include "usb_cdcacm.h" +#include "syscfg.h" +#include "ringb.h" + +extern bool out_in_progress; + +void usb_cdcacm_setup_pre_arch(void) +{ + rcc_periph_clock_enable(RCC_GPIOA); + rcc_periph_clock_enable(RCC_OTGFS); + + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, + GPIO9 | GPIO11 | GPIO12); + gpio_set_af(GPIOA, GPIO_AF10, GPIO9 | GPIO11 | GPIO12); + +} + +void usb_cdcacm_setup_post_arch(void) +{ +} + +// hacktastic +extern struct ringb tx_ring; +void glue_send_data_cb(uint8_t *buf, uint16_t len) +{ + if (len == 0) { + return; + } + gpio_set(LED_TX_PORT, LED_TX_PIN); + gpio_set(RS485DE_PORT, RS485DE_PIN); + for (int x = 0; x < len; x++) { + if (!ringb_put(&tx_ring, buf[x])) { + // failed to process usb traffic properly. + // should _never_ happen, means we failed to nak in time. + // this is _never_recoverable beyond watchdog reset. + while(1); + } + usart_enable_tx_interrupt(USART2); + } +} + +void glue_set_line_state_cb(uint8_t dtr, uint8_t rts) +{ + (void) dtr; + (void) rts; + // LM4f has an implementation of this if you're keen +} + +int glue_set_line_coding_cb(uint32_t baud, uint8_t databits, + enum usb_cdc_line_coding_bParityType cdc_parity, + enum usb_cdc_line_coding_bCharFormat cdc_stopbits) +{ + int uart_parity; + int uart_stopbits; + + if (databits < 8 || databits > 9) { + return 0; + } + + /* Be careful here, ST counts parity as a data bit */ + switch (cdc_parity) { + case USB_CDC_NO_PARITY: + uart_parity = USART_PARITY_NONE; + break; + case USB_CDC_ODD_PARITY: + uart_parity = USART_PARITY_ODD; + databits++; + break; + case USB_CDC_EVEN_PARITY: + uart_parity = USART_PARITY_EVEN; + databits++; + break; + default: + return 0; + } + + switch (cdc_stopbits) { + case USB_CDC_1_STOP_BITS: + uart_stopbits = USART_STOPBITS_1; + break; + case USB_CDC_2_STOP_BITS: + uart_stopbits = USART_STOPBITS_2; + break; + default: + return 0; + } + + /* Disable the UART while we mess with its settings */ + usart_disable(USART2); + /* Set communication parameters */ + usart_set_baudrate(USART2, baud); + usart_set_databits(USART2, databits); + usart_set_parity(USART2, uart_parity); + usart_set_stopbits(USART2, uart_stopbits); + /* Back to work. */ + usart_enable(USART2); + + return 1; +} diff --git a/tests/usb-serial-rs485/usb_cdcacm.c b/tests/usb-serial-rs485/usb_cdcacm.c new file mode 100644 index 0000000..8617d2b --- /dev/null +++ b/tests/usb-serial-rs485/usb_cdcacm.c @@ -0,0 +1,310 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz> + * Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com> + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see <http://www.gnu.org/licenses/>. + */ +/* + * This package is _meant_ to be platform independent, just a full + * cdc-acm impl, with callbacks + */ + +#include "usb_cdcacm.h" + +#include <stdio.h> +#include <stdlib.h> +#include <libopencm3/usb/usbd.h> +#include <libopencm3/usb/cdc.h> +#include <libopencm3/cm3/scb.h> + +#include "ringb.h" + +#define ER_DEBUG +#ifdef ER_DEBUG +#define ER_DPRINTF(fmt, ...) \ + do { printf(fmt, ## __VA_ARGS__); } while (0) +#else +#define ER_DPRINTF(fmt, ...) \ + do { } while (0) +#endif + + +uint8_t usbd_control_buffer[128]; +usbd_device *acm_dev; + +bool out_in_progress; + +static const struct usb_device_descriptor dev = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x2000, + .bDeviceClass = USB_CLASS_CDC, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0xc03e, + .idProduct = 0xb007, + .bcdDevice = 0x2000, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +/* + * This notification endpoint isn't implemented. According to CDC spec it's + * optional, but its absence causes a NULL pointer dereference in the + * Linux cdc_acm driver. + */ +static const struct usb_endpoint_descriptor comm_endp[] = {{ + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x83, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 16, + .bInterval = 1, +}}; + +static const struct usb_endpoint_descriptor data_endp[] = {{ + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x01, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 64, + .bInterval = 1, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x82, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 64, + .bInterval = 1, +}}; + +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 = { + .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 = (1 << 1), + }, + .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 comm_iface[] = {{ + .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_AT, + .iInterface = 0, + + .endpoint = comm_endp, + + .extra = &cdcacm_functional_descriptors, + .extralen = sizeof(cdcacm_functional_descriptors) +}}; + +static const struct usb_interface_descriptor data_iface[] = {{ + .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_endp, +}}; + +static const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = comm_iface, +}, { + .num_altsetting = 1, + .altsetting = data_iface, +}}; + +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 2, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, + .bMaxPower = 0x32, + + .interface = ifaces, +}; + +static const char *usb_strings[] = { + "libopencm3", + "usb_to_serial_cdcacm", + "none", + "DEMO", +}; + +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)) +{ + uint8_t dtr, rts; + + (void) complete; + (void) buf; + (void) usbd_dev; + + switch (req->bRequest) { + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + { + /* + * 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. + */ + + dtr = (req->wValue & (1 << 0)) ? 1 : 0; + rts = (req->wValue & (1 << 1)) ? 1 : 0; + ER_DPRINTF("CTRLRQ: Set Line state: dtr:%d rts: %d\n", dtr, rts); + + glue_set_line_state_cb(dtr, rts); + + return 1; + } + case USB_CDC_REQ_SET_LINE_CODING: + { + struct usb_cdc_line_coding *coding; + + if (*len < sizeof (struct usb_cdc_line_coding)) + return 0; + + coding = (struct usb_cdc_line_coding *) *buf; + ER_DPRINTF("CTRLRQ: line coding: %lu(%u:%u:%u)\n", coding->dwDTERate, + coding->bDataBits, coding->bParityType, coding->bCharFormat); + return glue_set_line_coding_cb(coding->dwDTERate, + coding->bDataBits, + coding->bParityType, + coding->bCharFormat); + } + } + return 0; +} + +extern bool nakked; +extern struct ringb tx_ring; +static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) +{ + uint8_t buf[64]; + /* nak right now, we're not sure whether we'll be able to even process this!*/ + usbd_ep_nak_set(usbd_dev, ep, 1); + int len = usbd_ep_read_packet(usbd_dev, ep, buf, 64); + ER_DPRINTF("Hrx%db\n", len); + // push all of what we got into our outbound fifo + glue_send_data_cb(buf, len); + if (ringb_depth(&tx_ring) < 64) { + ER_DPRINTF("ACK\n"); + usbd_ep_nak_set(usbd_dev, ep, 0); + } else { + nakked = true; + } +} + +static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue) +{ + (void) wValue; + + 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, NULL); + 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); +} + +/* FIXME - need to report this! */ +void cdcacm_line_state_changed_cb(uint8_t linemask) +{ + const int size = sizeof (struct usb_cdc_notification) + 2; + uint8_t buf[size]; + + struct usb_cdc_notification *notify = (void *) buf; + notify->bmRequestType = 0xa1; + notify->bNotification = USB_CDC_NOTIFY_SERIAL_STATE; + notify->wValue = 0; + notify->wIndex = 1; + notify->wLength = 2; + uint16_t *data = (void *) &buf[sizeof (struct usb_cdc_notification)]; + *data = linemask; + + while (usbd_ep_write_packet(acm_dev, 0x83, buf, size) == size); +} + +void glue_data_received_cb(uint8_t *buf, uint16_t len) +{ + ER_DPRINTF("Drx %db\n", len); + usbd_ep_write_packet(acm_dev, 0x82, buf, len); +} + +void usb_cdcacm_init(usbd_device **usbd_dev) +{ + usb_cdcacm_setup_pre_arch(); + + *usbd_dev = usbd_init(&otgfs_usb_driver, &dev, &config, usb_strings, 4, + usbd_control_buffer, sizeof (usbd_control_buffer)); + acm_dev = *usbd_dev; + usbd_register_set_config_callback(acm_dev, cdcacm_set_config); + + usb_cdcacm_setup_post_arch(); +} diff --git a/tests/usb-serial-rs485/usb_cdcacm.h b/tests/usb-serial-rs485/usb_cdcacm.h new file mode 100644 index 0000000..3228d0a --- /dev/null +++ b/tests/usb-serial-rs485/usb_cdcacm.h @@ -0,0 +1,56 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Karl Palsson <karlp@tweak.net.au> + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see <http://www.gnu.org/licenses/>. + */ +/* + * This is the header file for a usb_cdcacm implmentation, usb_cdcacm.c is the + * platform independent portion, and usb_cdcacm-arch.c should be re-implemented + * for other platforms. + */ + +#ifndef USB_CDCACM_H +#define USB_CDCACM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <libopencm3/usb/usbd.h> +#include <libopencm3/usb/cdc.h> + + void usb_cdcacm_init(usbd_device **usb_dev); + void usb_cdcacm_setup_pre_arch(void); + void usb_cdcacm_setup_post_arch(void); + void cdcacm_send_data(uint8_t *buf, uint16_t len); + void cdcacm_line_state_changed_cb(uint8_t linemask); + + /* Call this if you have data to send to the usb host */ + void glue_data_received_cb(uint8_t *buf, uint16_t len); + /* These will be called by usb_cdcacm code */ + void glue_send_data_cb(uint8_t *buf, uint16_t len); + + void glue_set_line_state_cb(uint8_t dtr, uint8_t rts); + int glue_set_line_coding_cb(uint32_t baud, uint8_t databits, + enum usb_cdc_line_coding_bParityType cdc_parity, + enum usb_cdc_line_coding_bCharFormat cdc_stopbits); + +#ifdef __cplusplus +} +#endif + +#endif /* USB_CDCACM_H */ + |