]> mj.ucw.cz Git - home-hw.git/commitdiff
Indicators: Rougly working version
authorMartin Mares <mj@ucw.cz>
Sun, 20 Feb 2022 15:06:07 +0000 (16:06 +0100)
committerMartin Mares <mj@ucw.cz>
Sun, 20 Feb 2022 15:06:07 +0000 (16:06 +0100)
indicators/firmware/Makefile
indicators/firmware/interface.h [new file with mode: 0644]
indicators/firmware/main.c

index 7b9a6222d5b0c835f57d61183aa3a012600e945b..9964b5494086b4d427ba8bfe595b4eb3d510e23a 100644 (file)
@@ -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 (file)
index 0000000..3936e0e
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ *     Neopixel Indicators -- Interface Definitions
+ *
+ *     (c) 2022 Martin Mareš <mj@ucw.cz>
+ */
+
+#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.
+ */
index b677dec2198d00c2da3632f4ba919f503a64a83c..23f250aa3f376ac3004a0eae1cc250042f3001b5 100644 (file)
 #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)
@@ -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;