diff --git a/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/Makefile b/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/Makefile new file mode 100644 index 0000000..3458512 --- /dev/null +++ b/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/Makefile @@ -0,0 +1,24 @@ +## +## This file is part of the libopencm3 project. +## +## Copyright (C) 2012 Alexandru Gagniuc +## +## 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 . +## + +BINARY = usb_bulk_dev + +LDSCRIPT = ../ek-lm4f120xl.ld + +include ../../Makefile.include diff --git a/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/README.md b/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/README.md new file mode 100644 index 0000000..df443e5 --- /dev/null +++ b/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/README.md @@ -0,0 +1,61 @@ +usb_bulk_dev +============ + +This example demonstrates the following: +* Setting up polled USB endpoints +* Setting up interrupt driven USB endpoints +* Using the UART as a debug tool + +USB module +---------- + +Several USB endpoints are being set up: +* EP1 OUT - interrupt driven RX endpoint +* EP2 IN - interrupt driven TX endpoint +* EP3 OUT - polled RX endpoint +* EP4 IN - polled TX endpoint +* EP5 OUT - polled RX endpoint with unaligned buffer +* EP6 IN - polled TX endpoint with unaligned buffer + +These endpoints do not transfer any meaningful data. Instead, they try to push +data in and out as fast as possible by writing it to the FIFOs or reading it +from the FIFOs. + +The interrupt driven endpoints only read or write a packet during a callback +from the USB driver. Since the USB driver is run entirely from the USB ISR, +these callbacks are essentially interrupt driven. + +The polled endpoints try to continuously read and write data. Even though +usbd_ep_read/write_packet is called continuously for these endpoints, the USB +driver will only write a packet to the TX FIFO if it is empty, and only read +a packet from the FIFO if one has arrived. + +The endpoints with a misaligned buffer show the performance drop when the buffer +is not aligned to a 4 byte boundary. 32-bit memory accesses to the buffer are +downgraded to 8-bit accesses by the hardware. + +Clock change module +------------------- + +Pressing SW2 toggles the system clock between 80MHz, 57MHz, 40MHz, 30MHz, 20MHz, +and 16MHz by changing the PLL divisor. + +Pressing SW1 bypasses the PLL completely, and runs off the raw 16MHz clock +provided by the external crystal oscillator. + +Changing the system clock on-the-fly does not affect the USB peripheral. It is +possible to change the system clock while benchmarking the USB endpoint. + +The current system clock is printed on the debug interface. This allows testing +the performance of the USB endpoints under different clocks. + +Debug module +------------ + +printf() support is provided via UART0. The UART0 pins are connected to the +CDCACM interface on the ICDI chip, so no extra hardware is necessary to check +the debug output. Just connect the debug USB cable and use a terminal program to +open the ACM port with 921600-8N1. + +For example: +> $ picocom /dev/ttyACM0 -b921600 diff --git a/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/usb_bulk_dev.c b/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/usb_bulk_dev.c new file mode 100644 index 0000000..cf37cdd --- /dev/null +++ b/examples/lm4f/stellaris-ek-lm4f120xl/usb_bulk_dev/usb_bulk_dev.c @@ -0,0 +1,473 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2013 Alexandru Gagniuc + * + * 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 . + */ + +/** + * \addtogroup Examples + * + * Establishes a basic USB devices with interrupt-driven and polled IN and OUT + * bulk endpoints. + */ +#include +#include +#include +#include +#include + +#include +void uart_setup(void); + +/* ============================================================================= + * = Clock control definitions + * ---------------------------------------------------------------------------*/ + +/* This is how the RGB LED is connected on the stellaris launchpad */ +#define RGB_PORT GPIOF +enum { + LED_R = GPIO1, + LED_G = GPIO3, + LED_B = GPIO2, +}; + +/* This is how the user switches are connected to GPIOF */ +enum { + USR_SW1 = GPIO4, + USR_SW2 = GPIO0, +}; + +/* The divisors we loop through when the user presses SW2 */ +enum { + PLL_DIV_80MHZ = 5, + PLL_DIV_57MHZ = 7, + PLL_DIV_40MHZ = 10, + PLL_DIV_30MHZ = 13, + PLL_DIV_20MHZ = 20, + PLL_DIV_16MHZ = 25, +}; + +static const u8 plldiv[] = { + PLL_DIV_80MHZ, + PLL_DIV_57MHZ, + PLL_DIV_40MHZ, + PLL_DIV_30MHZ, + PLL_DIV_20MHZ, + PLL_DIV_16MHZ, + 0 +}; + +/* The PLL divisor we are currently on */ +static size_t ipll = 0; +/* Are we bypassing the PLL, or not? */ +static bool bypass = false; + +/* ============================================================================= + * = USB descriptors + * ---------------------------------------------------------------------------*/ + +static const struct usb_device_descriptor dev_descr = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0110, + .bDeviceClass = 0xff, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0xc03e, + .idProduct = 0xb007, + .bcdDevice = 0x0110, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +static const struct usb_endpoint_descriptor bulk_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, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x03, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 64, + .bInterval = 1, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x84, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 64, + .bInterval = 1, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x05, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 64, + .bInterval = 1, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x86, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 64, + .bInterval = 1, +}}; + +static const struct usb_interface_descriptor bulk_iface[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 6, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .iInterface = 0, + + .endpoint = bulk_endp, + + .extra = NULL, + .extralen = 0, +}}; + +static const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = bulk_iface, +}}; + +static const struct usb_config_descriptor config_descr = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, + .bMaxPower = 0x32, + .interface = ifaces, +}; + +extern usbd_driver lm4f_usb_driver; +static usbd_device *bulk_dev; +static u8 usbd_control_buffer[128]; +static u8 config_set = 0; + +static const char *usb_strings[] = { + "libopencm3", + "usb_dev_bulk", + "none", + "DEMO", +}; + +/* ============================================================================= + * = USB Module + * ---------------------------------------------------------------------------*/ + +/* + * Mux the USB pins to their analog function + */ +static void usb_setup(void) +{ + /* USB pins are connected to port D */ + periph_clock_enable(RCC_GPIOD); + /* Mux USB pins to their analog function */ + gpio_mode_setup(GPIOD, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO4 | GPIO5); +} + +/* + * Enable USB interrupts + * + * We don't enable the USB peripheral clock here, but we need it on in order to + * acces USB registers. Hence, this must be called after usbd_init(). + */ +static void usb_ints_setup(void) +{ + u8 usbints; + /* Gimme some interrupts */ + usbints = USB_INT_RESET | USB_INT_DISCON | USB_INT_RESUME | + USB_INT_SUSPEND | USB_INT_SOF; + usb_enable_interrupts(usbints, 0xff, 0xff); + nvic_enable_irq(NVIC_USB0_IRQ); +} + +/* + * Callback for the interrupt-driven OUT endpoint + * + * This gets called whenever a new OUT packet has arrived. + */ +static void bulk_rx_cb(usbd_device * usbd_dev, u8 ep) +{ + char buf[64] __attribute__ ((aligned(4))); + + (void)ep; + + /* Read the packet to clear the FIFO and make room for a new packet */ + usbd_ep_read_packet(usbd_dev, 0x01, buf, 64); +} + +/* + * Callback for the interrupt-driven IN endpoint + * + * This gets called whenever an IN packet has been successfully transmitted. + */ +static void bulk_tx_cb(usbd_device * usbd_dev, u8 ep) +{ + char buf[64] __attribute__ ((aligned(4))); + + (void)ep; + + /* Keep sending packets */ + usbd_ep_write_packet(usbd_dev, 0x82, buf, 64); +} + +/* + * Initialize the USB configuration + * + * Called after the host issues a SetConfiguration request. + */ +static void set_config(usbd_device * usbd_dev, u16 wValue) +{ + u8 data[64] __attribute__ ((aligned(4))); + + (void)wValue; + printf("Configuring endpoints.\n\r"); + usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, bulk_rx_cb); + usbd_ep_setup(usbd_dev, 0x82, USB_ENDPOINT_ATTR_BULK, 64, bulk_tx_cb); + usbd_ep_setup(usbd_dev, 0x03, USB_ENDPOINT_ATTR_BULK, 64, NULL); + usbd_ep_setup(usbd_dev, 0x84, USB_ENDPOINT_ATTR_BULK, 64, NULL); + usbd_ep_setup(usbd_dev, 0x05, USB_ENDPOINT_ATTR_BULK, 64, NULL); + usbd_ep_setup(usbd_dev, 0x86, USB_ENDPOINT_ATTR_BULK, 64, NULL); + + /* The main loop will not touch the EPs until this is set */ + config_set = 1; + + /* + * "Bootstrap" the callback-based endpoint + * Data will stay in the FIFO until the host reads it. Once it's sent + * our callback kicks in and writes another packet in the FIFO. + */ + usbd_ep_write_packet(bulk_dev, 0x82, data, 64); + printf("Done.\n\r"); +} + +/* ============================================================================= + * = Clock control module + * ---------------------------------------------------------------------------*/ + +/* + * Setup the buttons and interrupts + */ +static void button_setup(void) +{ + /* + * Configure GPIOF + * This port is used to control the RGB LED + */ + periph_clock_enable(RCC_GPIOF); + + /* + * Now take care of our buttons + */ + const u32 btnpins = USR_SW1 | USR_SW2; + + /* + * PF0 is a locked by default. We need to unlock it before we can + * re-purpose it as a GPIO pin. + */ + gpio_unlock_commit(GPIOF, USR_SW2); + /* Configure pins as inputs, with pull-up. */ + gpio_mode_setup(GPIOF, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, btnpins); + + /* Trigger interrupt on rising-edge (when button is depressed) */ + gpio_configure_trigger(GPIOF, GPIO_TRIG_EDGE_RISE, btnpins); + /* Finally, Enable interrupt */ + gpio_enable_interrupts(GPIOF, btnpins); + /* Enable the interrupt in the NVIC as well */ + nvic_enable_irq(NVIC_GPIOF_IRQ); +} + +/* ============================================================================= + * = A main() function which does not need to do too much + * ---------------------------------------------------------------------------*/ + +int main(void) +{ + u8 data[65] __attribute__ ((aligned(4))); + + gpio_enable_ahb_aperture(); + rcc_sysclk_config(OSCSRC_MOSC, XTAL_16M, PLL_DIV_80MHZ); + + /* We use the UART for debugging */ + uart_setup(); + /* And the buttons for changing the system clock on-the-fly */ + button_setup(); + + /* Mux the GPIO pins to the USB peripheral */ + usb_setup(); + /* Let the stack take care of the rest */ + bulk_dev = usbd_init(&lm4f_usb_driver, &dev_descr, &config_descr, + usb_strings, 4, + usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(bulk_dev, set_config); + /* Enable the interrupts. */ + usb_ints_setup(); + + /* HALT! Don't touch the EP's until we configure them */ + while (!config_set) ; + + /* + * For our polled endpoints, we just read and write continuously. The + * driver will only move data in or out of the FIFOs if it is safe to + * do so. + */ + while (1) { + usbd_ep_read_packet(bulk_dev, 0x03, data, 64); + usbd_ep_write_packet(bulk_dev, 0x84, data, 64); + /* + * On endpoints 5 and 6, we deliberately misalign the buffer. + * This degrades the endpoint performance. + */ + usbd_ep_read_packet(bulk_dev, 0x05, data + 1, 64); + usbd_ep_write_packet(bulk_dev, 0x86, data + 1, 64); + } + + /* Never reached */ + return 0; +} + +/* ============================================================================= + * = USB interrupt service routine. All the magic happens here + * ---------------------------------------------------------------------------*/ +void usb0_isr(void) +{ + usbd_poll(bulk_dev); +} + +/* ============================================================================= + * = GPIO interrupt service routine. Pressing a button gets us here. + * ---------------------------------------------------------------------------*/ + +void gpiof_isr(void) +{ + u8 serviced_irqs = 0; + + if (gpio_is_interrupt_source(GPIOF, USR_SW1)) { + /* SW1 was just depressed */ + bypass = !bypass; + if (bypass) { + rcc_pll_bypass_enable(); + /* + * The divisor is still applied to the raw clock. + * Disable the divisor, or we'll divide the raw clock. + */ + SYSCTL_RCC &= ~SYSCTL_RCC_USESYSDIV; + printf("Changing system clock to 16MHz MOSC\n\r"); + } else { + rcc_change_pll_divisor(plldiv[ipll]); + printf("Changing system clock to %iMHz\n\r", + 400 / plldiv[ipll]); + } + /* Clear interrupt source */ + serviced_irqs |= USR_SW1; + } + + if (gpio_is_interrupt_source(GPIOF, USR_SW2)) { + /* SW2 was just depressed */ + if (!bypass) { + if (plldiv[++ipll] == 0) + ipll = 0; + printf("Changing system clock to %iMHz\n\r", + 400 / plldiv[ipll]); + rcc_change_pll_divisor(plldiv[ipll]); + } + /* Clear interrupt source */ + serviced_irqs |= USR_SW2; + } + + gpio_clear_interrupt_flag(GPIOF, serviced_irqs); +} + +/* ============================================================================= + * = Debug module + * ---------------------------------------------------------------------------*/ + +#include +#include + +/* + * Initialize the UART + */ +void uart_setup(void) +{ + /* Enable GPIOA in run mode. */ + periph_clock_enable(RCC_GPIOA); + /* Configure PA0 and PA1 as alternate function pins */ + gpio_set_af(GPIOA, 1, GPIO0 | GPIO1); + + /* Enable the UART clock */ + periph_clock_enable(RCC_UART0); + /* Slight delay before we can access the UART registers */ + __asm__("nop"); + __asm__("nop"); + __asm__("nop"); + /* Disable the UART while we mess with its setings */ + uart_disable(UART0); + /* Configure the UART clock source */ + uart_clock_from_piosc(UART0); + /* Set communication parameters */ + uart_set_baudrate(UART0, 921600); + /* Set 8N1 */ + uart_set_databits(UART0, 8); + uart_set_parity(UART0, UART_PARITY_NONE); + uart_set_stopbits(UART0, 1); + /* Enable FIFOs */ + UART_LCRH(UART0) |= UART_LCRH_FEN; + /* Now that we're done messing with the settings, enable the UART */ + uart_enable(UART0); +} + +/* + * Write to the debug port + * + * This is called whenever printf is used. We write stdio to the UART. + */ +int _write(int file, char *ptr, int len) +{ + int i; + + if (file == 1) { + for (i = 0; i < len; i++) + uart_send_blocking(UART0, ptr[i]); + return i; + } + + errno = EIO; + return -1; +}