Test program for my Multi_WS2811 library that started out as a fork of heroic/WS2811. My library uses hardware DMA on the FRDM-KL25Z to drive up to 16 strings of WS2811 or WS2812 LEDs in parallel.

Dependencies:   Multi_WS2811 mbed MMA8451Q

Fork of WS2811 by Heroic Robotics

NOTE: I have accidentally pushed changes for another fork of this program that I used in the recent Georgetown Carnival Power Tool Races. When I get some time, I will restore the test program to its original glory.

You can see my power tool racer (Nevermore's Revenge) here

/media/uploads/bikeNomad/img_0482.jpg

This tests my FRDM-KL25Z multi-string WS2811/WS2812 library. It uses the accelerometer to change the rainbow phase on two strings of LEDs as well as the touch sense to change brightness.

A video of this program in operation is here.

Here is the library that I developed to run the LEDs:

Import libraryMulti_WS2811

Library allowing up to 16 strings of 60 WS2811 or WS2812 LEDs to be driven from a single FRDM-KL25Z board. Uses hardware DMA to do a full 800 KHz rate without much CPU burden.

Files at this revision

API Documentation at this revision

Comitter:
bikeNomad
Date:
Sat Jan 04 00:32:16 2014 +0000
Parent:
29:a76075c853ee
Child:
31:c9eee3a33826
Commit message:
used TPM0 to time guard time at end of DMA.

Changed in this revision

WS2811.cpp Show annotated file Show diff for this revision Revisions of this file
WS2811.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
--- a/WS2811.cpp	Fri Jan 03 19:17:24 2014 +0000
+++ b/WS2811.cpp	Sat Jan 04 00:32:16 2014 +0000
@@ -28,7 +28,7 @@
 #define MONITOR_TPM0_PWM 0
 
 // define DEBUG_PIN to identify a pin in PORTD used for debug output
-// #define DEBUG_PIN 3 /* PTD3 debugOut */
+// #define DEBUG_PIN 4 /* PTD4 debugOut */
 
 #ifdef DEBUG_PIN
 #define DEBUG 1
@@ -49,10 +49,12 @@
 
 // 48 MHz clock, no prescaling.
 #define NSEC_TO_TICKS(nsec) ((nsec)*48/1000)
+#define USEC_TO_TICKS(usec) ((usec)*48)
 static const uint32_t CLK_NSEC = 1250;
 static const uint32_t tpm_period    = NSEC_TO_TICKS(CLK_NSEC);
 static const uint32_t tpm_p0_period = NSEC_TO_TICKS(250);
 static const uint32_t tpm_p1_period = NSEC_TO_TICKS(650);
+static const uint32_t guardtime_period = USEC_TO_TICKS(55);   // guardtime minimum 50 usec.
 
 enum DMA_MUX_SRC {
     DMA_MUX_SRC_TPM0_CH_0     = 24,
@@ -67,15 +69,12 @@
     N_DMA_CHANNELS
 };
 
-static volatile bool dma_done = true;
+volatile bool WS2811::dma_done = true;
 
 // class static
 bool WS2811::initialized = false;
 
 // class static
-Timer WS2811::guardtime;
-
-// class static
 uint32_t WS2811::enabledPins = 0;
 
 #define WORD_ALIGNED __attribute__ ((aligned(4)))
@@ -95,18 +94,10 @@
 } dmaData WORD_ALIGNED;
 
 // class static
-bool WS2811::is_dma_done()
-{
-    return dma_done;
-}
-
-// class static
 void WS2811::hw_init()
 {
     if (initialized) return;
 
-    guardtime.start();
-
     dma_data_init();
     clock_init();
     dma_init();
@@ -114,6 +105,9 @@
     tpm_init();
 
     initialized = true;
+
+    SET_DEBUG;
+    RESET_DEBUG;
 }
 
 // class static
@@ -192,6 +186,7 @@
     // t=tpm_p1_period: all outputs go low.
     DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_1);
 
+    NVIC_SetVector(DMA0_IRQn, (uint32_t)&DMA0_IRQHandler);
     NVIC_EnableIRQ(DMA0_IRQn);
 }
 
@@ -203,6 +198,7 @@
     // set up TPM0 for proper period (800 kHz = 1.25 usec ±600nsec)
     TPM_Type volatile *tpm = TPM0;
     tpm->SC = TPM_SC_DMA_MASK          // enable DMA
+              | TPM_SC_TOF_MASK        // reset TOF flag if set
               | TPM_SC_CMOD(0)         // disable clocks
               | TPM_SC_PS(0);          // 48MHz / 1 = 48MHz clock
     tpm->MOD = tpm_period - 1;         // 48MHz / 800kHz
@@ -211,13 +207,16 @@
     tpm->CONTROLS[0].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
     tpm->CONTROLS[1].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
 
-    // set TPM0 channel 0 for 0.35 usec (±150nsec) / 0.8 usec (±150nsec) (0 code)
+    // set TPM0 channel 0 for 0.35 usec (±150nsec) (0 code)
     // 1.25 usec * 1/3 = 417 nsec
     tpm->CONTROLS[0].CnV = tpm_p0_period;
 
-    // set TPM0 channel 1 for 0.7 usec (±150nsec) / 0.6 usec (±150nsec) (1 code)
+    // set TPM0 channel 1 for 0.7 usec (±150nsec) (1 code)
     // 1.25 usec * 2/3 = 833 nsec
     tpm->CONTROLS[1].CnV = tpm_p1_period;
+
+    NVIC_SetVector(TPM0_IRQn, (uint32_t)&TPM0_IRQHandler);
+    NVIC_EnableIRQ(TPM0_IRQn);
 }
 
 WS2811::WS2811(unsigned n, unsigned pinNumber)
@@ -225,12 +224,16 @@
     , pinMask(1U << pinNumber)
 {
     enabledPins |= pinMask;
+    initialized = false;
 }
 
 // class static
 void WS2811::startDMA()
 {
     hw_init();
+    
+    wait_for_dma_done();
+    dma_done = false;
 
     DMA_Type volatile * dma   = DMA0;
     TPM_Type volatile *tpm   = TPM0;
@@ -238,7 +241,12 @@
                       + sizeof(dmaData.dmaWords)
                       + sizeof(dmaData.trailing_zeros_1);
 
-    tpm->SC &= ~TPM_SC_CMOD_MASK; // disable internal clocking
+    tpm->SC = TPM_SC_DMA_MASK        // enable DMA
+              | TPM_SC_TOF_MASK  // reset TOF flag if set
+              | TPM_SC_CMOD(0)   // disable clocks
+              | TPM_SC_PS(0);    // 48MHz / 1 = 48MHz clock
+    tpm->MOD = tpm_period - 1;       // 48MHz / 800kHz
+
     tpm->CNT = tpm_p0_period - 2 ;
     tpm->STATUS = 0xFFFFFFFF;
 
@@ -269,16 +277,6 @@
       = dma->DMA[DMA_CHAN_START].DAR
         = (uint32_t)(void*)&IO_GPIO->PDOR;
 
-    // wait until done
-    while (!is_dma_done()) {
-        __WFI();
-    }
-
-    // ensure sufficient guard time
-    while (guardtime.read_us() < 50) {
-        __NOP();
-    }
-
     SET_DEBUG;
 
     dma->DMA[DMA_CHAN_0_LOW].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
@@ -304,7 +302,6 @@
                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
                                        | DMA_DCR_DSIZE(0);
 
-
     tpm->SC |= TPM_SC_CMOD(1);         // enable internal clocking
 }
 
@@ -361,26 +358,42 @@
 
 extern "C" void DMA0_IRQHandler()
 {
-    DMA_Type volatile * dma = DMA0;
-    TPM_Type volatile *tpm   = TPM0;
+    DMA_Type volatile *dma = DMA0;
+    TPM_Type volatile *tpm = TPM0;
 
-    uint32_t db = dma->DMA[DMA_CHAN_START].DSR_BCR;
-    if (db & DMA_DSR_BCR_DONE_MASK) {
-        dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK;  // clear/reset DMA status
-    }
+    uint32_t db;
 
     db = dma->DMA[DMA_CHAN_0_LOW].DSR_BCR;
     if (db & DMA_DSR_BCR_DONE_MASK) {
-        dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK;  // clear/reset DMA status
+        dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
     }
 
     db = dma->DMA[DMA_CHAN_1_LOW].DSR_BCR;
     if (db & DMA_DSR_BCR_DONE_MASK) {
         dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
-        dma_done                         = true;
-        tpm->SC &= ~TPM_SC_CMOD_MASK; // disable internal clocking
-        RESET_DEBUG;
-        WS2811::guardtime.reset();
+    }
+
+    db = dma->DMA[DMA_CHAN_START].DSR_BCR;
+    if (db & DMA_DSR_BCR_DONE_MASK) {
+        dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
     }
+
+    tpm->SC = TPM_SC_TOF_MASK;  // reset TOF flag; disable internal clocking
+
+    SET_DEBUG;
+
+    // set TPM0 to interrrupt after guardtime
+    tpm->MOD = guardtime_period - 1; // 48MHz * 55 usec
+    tpm->CNT = 0;
+    tpm->SC  = TPM_SC_PS(0)        // 48MHz / 1 = 48MHz clock
+               | TPM_SC_TOIE_MASK  // enable interrupts
+               | TPM_SC_CMOD(1);   // and internal clocking
 }
 
+extern "C" void TPM0_IRQHandler()
+{
+    TPM0->SC = 0; // disable internal clocking
+    TPM0->SC = TPM_SC_TOF_MASK;        
+    RESET_DEBUG;
+    WS2811::dma_done = true;
+}
--- a/WS2811.h	Fri Jan 03 19:17:24 2014 +0000
+++ b/WS2811.h	Sat Jan 04 00:32:16 2014 +0000
@@ -22,6 +22,7 @@
 #define MAX_LEDS_PER_STRIP 60
 
 extern "C" void DMA0_IRQHandler();
+extern "C" void TPM0_IRQHandler();
 
 class WS2811 : public LedStrip
 {
@@ -33,7 +34,6 @@
     virtual void blank();
 
     static void startDMA();
-    static bool is_dma_done();
 
 private:
     uint32_t pinMask;
@@ -44,7 +44,8 @@
 
     static bool initialized;
     static uint32_t enabledPins;
-    static Timer guardtime;
+    static volatile bool dma_done;
+    static void wait_for_dma_done() { while (!dma_done) __WFI(); }
 
     static void writeByte(uint8_t byte, uint32_t mask, uint32_t *dest);
 
@@ -55,7 +56,7 @@
         static void tpm_init();
         static void dma_data_init();
         
-    friend void DMA0_IRQHandler();
+    friend void TPM0_IRQHandler();
 };
 
 #endif
--- a/main.cpp	Fri Jan 03 19:17:24 2014 +0000
+++ b/main.cpp	Sat Jan 04 00:32:16 2014 +0000
@@ -27,6 +27,7 @@
 MMA8451Q acc(PTE25, PTE24, MMA8451_I2C_ADDRESS);
 PwmOut rled(LED_RED);
 PwmOut gled(LED_GREEN);
+// LED_BLUE is on PTD1
 
 float touchPercentage;
 unsigned frames;
@@ -99,7 +100,6 @@
             break;
         }
 
-        wait(0.1);
         showSolidColor(lightStrip1, r, g, b);
         showSolidColor(lightStrip2, r, g, b);
         WS2811::startDMA();