From e814f7c06c668636dca6e238a17c6ac9f4e4c989 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 20 Feb 2022 16:06:07 +0100 Subject: [PATCH] Indicators: Rougly working version --- indicators/firmware/Makefile | 2 +- indicators/firmware/interface.h | 17 ++ indicators/firmware/main.c | 356 +++++++++++++++++++++++++------- 3 files changed, 294 insertions(+), 81 deletions(-) create mode 100644 indicators/firmware/interface.h diff --git a/indicators/firmware/Makefile b/indicators/firmware/Makefile index 7b9a622..9964b54 100644 --- a/indicators/firmware/Makefile +++ b/indicators/firmware/Makefile @@ -5,6 +5,6 @@ LIB_OBJS=util-debug.o WITH_BOOT_LOADER=1 WITH_DFU_FLASH=1 -DFU_ARGS=-d 4242:000a +DFU_ARGS=-d 4242:0009 include $(ROOT)/mk/bluepill.mk diff --git a/indicators/firmware/interface.h b/indicators/firmware/interface.h new file mode 100644 index 0000000..3936e0e --- /dev/null +++ b/indicators/firmware/interface.h @@ -0,0 +1,17 @@ +/* + * Neopixel Indicators -- Interface Definitions + * + * (c) 2022 Martin Mareš + */ + +#define NPIX_USB_VENDOR 0x4242 +#define NPIX_USB_PRODUCT 0x0009 +#define NPIX_USB_VERSION 0x0100 + +/* + * Endpoints: + * + * 0x01 = bulk endpoint + * Accepts up to 12 3-byte records (R, G, B), each describing one LED. + * If less than 12 records are sent, the remaining LEDs are left unmodified. + */ diff --git a/indicators/firmware/main.c b/indicators/firmware/main.c index b677dec..23f250a 100644 --- a/indicators/firmware/main.c +++ b/indicators/firmware/main.c @@ -15,9 +15,13 @@ #include #include #include +#include +#include #include +#include "interface.h" + /*** Hardware init ***/ static void clock_init(void) @@ -91,88 +95,29 @@ static void delay_ms(uint ms) /*** 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); @@ -180,7 +125,36 @@ static void neopixel_init(void) 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); @@ -195,7 +169,211 @@ static void neopixel_init(void) 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 ***/ @@ -207,19 +385,37 @@ int main(void) 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; -- 2.39.2