#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/timer.h>
#include <libopencm3/stm32/usart.h>
+#include <libopencm3/usb/dfu.h>
+#include <libopencm3/usb/usbd.h>
#include <string.h>
+#include "interface.h"
+
/*** Hardware init ***/
static void clock_init(void)
/*** Neopixels ***/
#define NPIX_PERIOD 90 // timer runs on 72 MHz, so 90 periods = 1250 ns
-#define B0 30
-#define B1 60
-
-byte neopixel_buf[] = {
- // 64 cycles low: reset
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0,
-
- // G7 G6 G5 G4 G3 G2 G1 G0
- // R7 R6 R5 R4 R3 R2 R1 R0
- // B7 B6 B5 B4 B3 B2 B1 B0
-
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B0, B0, B0, B0,
-
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B0, B0, B0, B0,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B1, B1, B1, B1,
-
- B0, B0, B0, B0, B1, B1, B1, B1,
- B0, B0, B0, B0, B0, B0, B0, B0,
- B0, B0, B0, B0, B0, B0, B0, B0,
-};
+#define NPIX_RESET 64 // the chip needs longer reset pulse than documented
+#define NPIX_B0 30
+#define NPIX_B1 60
-static void neopixel_init(void)
-{
- // TIM4 update is connected to DMA1 channel 7
+#define NUM_LEDS 12
+static byte led_rgb[NUM_LEDS][3];
- // FIXME: Strange programming sequence as specified in manual
+static byte neopixel_buf[NPIX_RESET + NUM_LEDS*24 + 1];
+static bool neopixel_dma_running;
+static bool neopixel_want_send;
+static void neopixel_run_dma(void)
+{
+ // When STM32 is programmed using ST-Link, the DMA sometimes keeps running.
dma_channel_reset(DMA1, 7);
+ // This order of register writes is recommended in the manual.
dma_set_peripheral_address(DMA1, 7, (u32) &TIM_CCR3(TIM4));
dma_set_memory_address(DMA1, 7, (u32) neopixel_buf);
dma_set_number_of_data(DMA1, 7, ARRAY_SIZE(neopixel_buf));
dma_set_priority(DMA1, 7, DMA_CCR_PL_VERY_HIGH);
dma_set_read_from_memory(DMA1, 7);
- dma_enable_circular_mode(DMA1, 7);
dma_set_memory_size(DMA1, 7, DMA_CCR_MSIZE_8BIT);
dma_enable_memory_increment_mode(DMA1, 7);
dma_set_peripheral_size(DMA1, 7, DMA_CCR_PSIZE_16BIT);
dma_disable_peripheral_increment_mode(DMA1, 7);
+ dma_clear_interrupt_flags(DMA1, 7, DMA_TCIF);
dma_enable_channel(DMA1, 7);
+ neopixel_dma_running = 1;
+}
+
+static void neopixel_recalc(void)
+{
+ byte *buf = neopixel_buf;
+ for (uint i = 0; i < NPIX_RESET; i++)
+ *buf++ = 0;
+ for (uint i = 0; i < NUM_LEDS; i++) {
+ // The order is GRB, MSB first
+ for (uint m = 0x80; m; m >>= 1)
+ *buf++ = ((led_rgb[i][1] & m) ? NPIX_B1 : NPIX_B0);
+ for (uint m = 0x80; m; m >>= 1)
+ *buf++ = ((led_rgb[i][0] & m) ? NPIX_B1 : NPIX_B0);
+ for (uint m = 0x80; m; m >>= 1)
+ *buf++ = ((led_rgb[i][2] & m) ? NPIX_B1 : NPIX_B0);
+ }
+ *buf++ = NPIX_PERIOD;
+
+ neopixel_run_dma();
+ neopixel_want_send = 0;
+}
+
+static void neopixel_init(void)
+{
+ // TIM4 is always running and producing DMA requests on each update
+ // (connected to DMA1 channel 7). When we have something to send,
+ // the DMA is enabled.
timer_set_prescaler(TIM4, 0);
timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
timer_set_dma_on_update_event(TIM4);
TIM_DIER(TIM4) |= TIM_DIER_UDE;
+ led_rgb[0][1] = 0xaa;
+
timer_enable_counter(TIM4);
+ neopixel_recalc();
+}
+
+static bool neopixel_ready(void)
+{
+ if (!neopixel_dma_running)
+ return 1;
+
+ if (!dma_get_interrupt_flag(DMA1, 7, DMA_TCIF))
+ return 0;
+
+ dma_disable_channel(DMA1, 7);
+ neopixel_dma_running = 0;
+ return 1;
+}
+
+/*** USB ***/
+
+static usbd_device *usbd_dev;
+
+enum usb_string {
+ STR_MANUFACTURER = 1,
+ STR_PRODUCT,
+ STR_SERIAL,
+};
+
+static char usb_serial_number[13];
+
+static const char *usb_strings[] = {
+ "United Computer Wizards",
+ "Neopixel Indicators",
+ usb_serial_number,
+};
+
+static const struct usb_device_descriptor device = {
+ .bLength = USB_DT_DEVICE_SIZE,
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = 0xFF,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .bMaxPacketSize0 = 64,
+ .idVendor = NPIX_USB_VENDOR,
+ .idProduct = NPIX_USB_PRODUCT,
+ .bcdDevice = NPIX_USB_VERSION,
+ .iManufacturer = STR_MANUFACTURER,
+ .iProduct = STR_PRODUCT,
+ .iSerialNumber = STR_SERIAL,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb_endpoint_descriptor endpoints[] = {{
+ // Bulk end-point for sending LED values
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0x01,
+ .bmAttributes = USB_ENDPOINT_ATTR_BULK,
+ .wMaxPacketSize = sizeof(led_rgb),
+ .bInterval = 1,
+}};
+
+static const struct usb_interface_descriptor iface = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = 0xFF,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .iInterface = 0,
+ .endpoint = endpoints,
+};
+
+static const struct usb_dfu_descriptor dfu_function = {
+ .bLength = sizeof(struct usb_dfu_descriptor),
+ .bDescriptorType = DFU_FUNCTIONAL,
+ .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
+ .wDetachTimeout = 255,
+ .wTransferSize = 1024,
+ .bcdDFUVersion = 0x0100,
+};
+
+static const struct usb_interface_descriptor dfu_iface = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = 0xFE,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 1,
+ .iInterface = 0,
+
+ .extra = &dfu_function,
+ .extralen = sizeof(dfu_function),
+};
+
+static const struct usb_interface ifaces[] = {{
+ .num_altsetting = 1,
+ .altsetting = &iface,
+}, {
+ .num_altsetting = 1,
+ .altsetting = &dfu_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 = 100, // multiplied by 2 mA
+ .interface = ifaces,
+};
+
+static byte usb_configured;
+static uint8_t usbd_control_buffer[64];
+
+static void dfu_detach_complete(usbd_device *dev UNUSED, struct usb_setup_data *req UNUSED)
+{
+ // Reset to bootloader, which implements the rest of DFU
+ debug_printf("Switching to DFU\n");
+ debug_flush();
+ scb_reset_core();
+}
+
+static enum usbd_request_return_codes dfu_control_cb(usbd_device *dev UNUSED,
+ struct usb_setup_data *req,
+ uint8_t **buf UNUSED,
+ uint16_t *len UNUSED,
+ void (**complete)(usbd_device *dev, struct usb_setup_data *req))
+{
+ if (req->bmRequestType != 0x21 || req->bRequest != DFU_DETACH)
+ return USBD_REQ_NOTSUPP;
+
+ *complete = dfu_detach_complete;
+ return USBD_REQ_HANDLED;
+}
+
+static void ep01_cb(usbd_device *dev, uint8_t ep UNUSED)
+{
+ // We received a frame from the USB host
+ uint len = usbd_ep_read_packet(dev, 0x01, led_rgb, sizeof(led_rgb));
+ debug_printf("USB: Host sent %u bytes\n", len);
+ neopixel_want_send = 1;
+}
+
+static void set_config_cb(usbd_device *dev, uint16_t wValue UNUSED)
+{
+ usbd_register_control_callback(
+ dev,
+ USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
+ USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
+ dfu_control_cb);
+ usbd_ep_setup(dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, ep01_cb);
+ usb_configured = 1;
+}
+
+static void reset_cb(void)
+{
+ debug_printf("USB: Reset\n");
+ usb_configured = 0;
+}
+
+static volatile bool usb_event_pending;
+
+void usb_lp_can_rx0_isr(void)
+{
+ /*
+ * We handle USB in the main loop to avoid race conditions between
+ * USB interrupts and other code. However, we need an interrupt to
+ * up the main loop from sleep.
+ *
+ * We set up only the low-priority ISR, because high-priority ISR handles
+ * only double-buffered bulk transfers and isochronous transfers.
+ */
+ nvic_disable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+ usb_event_pending = 1;
+}
+
+static void usb_init(void)
+{
+ // Simulate USB disconnect
+ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12);
+ gpio_clear(GPIOA, GPIO11 | GPIO12);
+ delay_ms(100);
+
+ usbd_dev = usbd_init(
+ &st_usbfs_v1_usb_driver,
+ &device,
+ &config,
+ usb_strings,
+ ARRAY_SIZE(usb_strings),
+ usbd_control_buffer,
+ sizeof(usbd_control_buffer)
+ );
+ usbd_register_reset_callback(usbd_dev, reset_cb);
+ usbd_register_set_config_callback(usbd_dev, set_config_cb);
+ usb_event_pending = 1;
}
/*** Main ***/
usart_init();
tick_init();
neopixel_init();
+ usb_init();
debug_printf("Hello, world!\n");
+ u32 last_blink = 0;
+ u32 last_send = 0;
+
for (;;) {
- // wait_for_interrupt();
- debug_led(1);
- for (int i=4; i<8; i++)
- neopixel_buf[64+i] = B1;
- delay_ms(100);
- debug_led(0);
- for (int i=4; i<8; i++)
- neopixel_buf[64+i] = B0;
- delay_ms(500);
+ if (ms_ticks - last_blink >= 100) {
+ debug_led_toggle();
+ last_blink = ms_ticks;
+ led_rgb[NUM_LEDS - 1][1] ^= 0x33;
+ neopixel_want_send = 1;
+ }
+
+ if (usb_event_pending) {
+ usbd_poll(usbd_dev);
+ usb_event_pending = 0;
+ nvic_clear_pending_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+ nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
+ }
+
+ if (neopixel_ready()) {
+ if (neopixel_want_send || ms_ticks - last_send >= 100) {
+ // Re-send every 100 ms
+ neopixel_recalc();
+ last_send = ms_ticks;
+ }
+ }
+
+ wait_for_interrupt();
}
return 0;