]> mj.ucw.cz Git - home-hw.git/commitdiff
Neopixel square: Double-buffered DMA
authorMartin Mareš <mj@ucw.cz>
Fri, 12 Sep 2025 19:50:41 +0000 (21:50 +0200)
committerMartin Mareš <mj@ucw.cz>
Fri, 12 Sep 2025 19:50:41 +0000 (21:50 +0200)
test-neopixel-square/main.c

index 74740615919d032869389247ad6976b290c44b6e..9605a445ba10ec7e31c70ad6c59d1f3873d443b4 100644 (file)
@@ -91,31 +91,34 @@ static void delay_ms(uint ms)
 /*** Neopixels ***/
 
 #define NPIX_PERIOD 90         // timer runs on 72 MHz, so 90 periods = 1250 ns
-#define NPIX_RESET 128         // the chip needs longer reset pulse than documented
 #define NPIX_NUM_LEDS 64
+#define NPIX_RESET 6           // length of reset pulse in LED slots
+                               // The chip needs longer reset pulse than documented.
 #define B0 30
 #define B1 60
 
-byte neopixel_buf[NPIX_RESET + 24*NPIX_NUM_LEDS];
+static byte neopixel_leds[NPIX_NUM_LEDS][3];
+static byte neopixel_buf[2*24];
+static uint neopixel_index;
 
 static void neopixel_set(uint led, byte r, byte g, byte b)
 {
-       byte *buf = &neopixel_buf[NPIX_RESET + 24*led];
-
-       for (uint m=0x80; m; m >>= 1)
-               *buf++ = (g & m) ? B1 : B0;
-       for (uint m=0x80; m; m >>= 1)
-               *buf++ = (r & m) ? B1 : B0;
-       for (uint m=0x80; m; m >>= 1)
-               *buf++ = (b & m) ? B1 : B0;
+       cm_disable_interrupts();
+       neopixel_leds[led][0] = r;
+       neopixel_leds[led][1] = g;
+       neopixel_leds[led][2] = b;
+       cm_enable_interrupts();
 }
 
-
 static void neopixel_init(void)
 {
        for (uint i=0; i < NPIX_NUM_LEDS; i++)
                neopixel_set(i, 7, 7, 7);
 
+       // neopixel_buf is zero-initialized, which is a start of the reset sequence
+       memset(neopixel_buf, 0, sizeof(neopixel_buf));
+       neopixel_index = NPIX_NUM_LEDS;
+
        // TIM4 update is connected to DMA1 channel 7
 
        // FIXME: Strange programming sequence as specified in manual
@@ -136,6 +139,10 @@ static void neopixel_init(void)
        dma_set_peripheral_size(DMA1, 7, DMA_CCR_PSIZE_16BIT);
        dma_disable_peripheral_increment_mode(DMA1, 7);
 
+       dma_enable_half_transfer_interrupt(DMA1, 7);
+       dma_enable_transfer_complete_interrupt(DMA1, 7);
+       nvic_enable_irq(NVIC_DMA1_CHANNEL7_IRQ);
+
        dma_enable_channel(DMA1, 7);
 
        timer_set_prescaler(TIM4, 0);
@@ -154,6 +161,39 @@ static void neopixel_init(void)
        timer_enable_counter(TIM4);
 }
 
+static inline void next_led(byte *buf)
+{
+       if (neopixel_index < NPIX_NUM_LEDS) {
+               byte *led = neopixel_leds[neopixel_index++];
+               byte r = led[0], g = led[1], b = led[2];
+               for (uint m=0x80; m; m >>= 1)
+                       *buf++ = (g & m) ? B1 : B0;
+               for (uint m=0x80; m; m >>= 1)
+                       *buf++ = (r & m) ? B1 : B0;
+               for (uint m=0x80; m; m >>= 1)
+                       *buf++ = (b & m) ? B1 : B0;
+       } else {
+               for (uint i=0; i < 24; i++)
+                       *buf++ = 0;
+               neopixel_index++;
+               if (neopixel_index == NPIX_NUM_LEDS + NPIX_RESET)
+                       neopixel_index = 0;
+       }
+}
+
+void dma1_channel7_isr(void)
+{
+       if (DMA1_ISR & DMA_ISR_HTIF7) {
+               // Half transfer
+               DMA1_IFCR |= DMA_IFCR_CHTIF7;
+               next_led(neopixel_buf);
+       } else if (DMA1_ISR & DMA_ISR_TCIF7) {
+               // Transfer completed
+               DMA1_IFCR |= DMA_IFCR_CTCIF7;
+               next_led(neopixel_buf + 24);
+       }
+}
+
 /*** Game of Life ***/
 
 #define LN 8