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.

Dependents:   Multi_WS2811_test

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers WS2811.cpp Source File

WS2811.cpp

00001 // 800 KHz WS2811 driver driving potentially many LED strings.
00002 // Uses 3-phase DMA
00003 // 16K SRAM less stack, etc.
00004 //
00005 // Per LED: 3 bytes (malloc'd) for RGB data
00006 //
00007 // Per LED strip / per LED
00008 //          96 bytes (static) for bit data
00009 //        + 96 bytes (static) for ones data
00010 //        = 192 bytes
00011 //
00012 //        40 LEDs max per string = 7680 bytes static
00013 //
00014 //        40 LEDs: 7680 + 40*3 = 7800 bytes
00015 //        80 LEDs: 7680 + 80*3 = 7920 bytes
00016 #include <mbed.h>
00017 #include "MKL25Z4.h"
00018 
00019 #ifndef MBED_WS2811_H
00020 #include "WS2811.h "
00021 #endif
00022 
00023 #if defined(WS2811_DEBUG_PIN)
00024 #define DEBUG_MASK (1<<WS2811_DEBUG_PIN)
00025 #define RESET_DEBUG (WS2811_IO_GPIO->PDOR &= ~DEBUG_MASK)
00026 #define SET_DEBUG (WS2811_IO_GPIO->PDOR |= DEBUG_MASK)
00027 #else
00028 #define DEBUG_MASK 0
00029 #define RESET_DEBUG (void)0
00030 #define SET_DEBUG (void)0
00031 #endif
00032 
00033 static volatile unsigned dma_done = 3;
00034 
00035 // 48 MHz clock, no prescaling.
00036 #define NSEC_TO_TICKS(nsec) ((nsec)*48/1000)
00037 #define USEC_TO_TICKS(usec) ((usec)*48)
00038 #define CLK_NSEC         1250
00039 #define tpm_period       NSEC_TO_TICKS(CLK_NSEC)
00040 #define tpm_p0_period    NSEC_TO_TICKS(250)
00041 #define tpm_p1_period    NSEC_TO_TICKS(650)
00042 #define guardtime_us     55
00043 #define guardtime_period USEC_TO_TICKS(guardtime_us)   /* guardtime minimum 50 usec. */
00044 
00045 enum DMA_MUX_SRC {
00046     DMA_MUX_SRC_TPM0_CH_0     = 24,
00047     DMA_MUX_SRC_TPM0_CH_1,
00048     DMA_MUX_SRC_TPM0_Overflow = 54,
00049 };
00050 
00051 enum DMA_CHAN {
00052     DMA_CHAN_START = 0,
00053     DMA_CHAN_0_LOW = 1,
00054     DMA_CHAN_1_LOW = 2,
00055     N_DMA_CHANNELS
00056 };
00057 
00058 // class static
00059 template <unsigned MAX_LEDS_PER_STRIP>
00060 void WS2811<MAX_LEDS_PER_STRIP>::wait_for_dma_done()
00061 {
00062     if ((DMA0->DMA[DMA_CHAN_1_LOW].DSR_BCR & DMA_DSR_BCR_BSY_MASK) == 0)
00063         return;
00064     while ((DMA0->DMA[DMA_CHAN_1_LOW].DSR_BCR & DMA_DSR_BCR_BSY_MASK))
00065         __WFI();
00066     wait_us(guardtime_us);
00067 }
00068 
00069 // class static
00070 template <unsigned MAX_LEDS_PER_STRIP>
00071 bool WS2811<MAX_LEDS_PER_STRIP>::initialized = false;
00072 
00073 // class static
00074 template <unsigned MAX_LEDS_PER_STRIP>
00075 uint32_t WS2811<MAX_LEDS_PER_STRIP>::enabledPins = 0;
00076 
00077 #define WORD_ALIGNED __attribute__ ((aligned(4)))
00078 
00079 // class static
00080 template <unsigned MAX_LEDS_PER_STRIP>
00081 struct WS2811<MAX_LEDS_PER_STRIP>::DMALayout WS2811<MAX_LEDS_PER_STRIP>::dmaData WORD_ALIGNED;
00082 
00083 // class static
00084 template <unsigned MAX_LEDS_PER_STRIP>
00085 void WS2811<MAX_LEDS_PER_STRIP>::hw_init()
00086 {
00087     if (initialized) return;
00088 
00089     dma_data_init();
00090     clock_init();
00091     dma_init();
00092     io_init();
00093     tpm_init();
00094 
00095     initialized = true;
00096 
00097     SET_DEBUG;
00098     RESET_DEBUG;
00099 }
00100 
00101 // class static
00102 template <unsigned MAX_LEDS_PER_STRIP>
00103 void WS2811<MAX_LEDS_PER_STRIP>::dma_data_init()
00104 {
00105     memset(dmaData.allOnes, 0xFF, sizeof(dmaData.allOnes));
00106 }
00107 
00108 // class static
00109 
00110 /// Enable PORTD, DMA and TPM0 clocking
00111 template <unsigned MAX_LEDS_PER_STRIP>
00112 void WS2811<MAX_LEDS_PER_STRIP>::clock_init()
00113 {
00114     SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK;
00115     SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK | SIM_SCGC6_TPM0_MASK; // Enable clock to DMA mux and TPM0
00116     SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;  // Enable clock to DMA
00117 
00118     SIM->SOPT2 |= SIM_SOPT2_TPMSRC(1); // Clock source: MCGFLLCLK or MCGPLLCLK
00119 }
00120 
00121 // class static
00122 
00123 /// Configure GPIO output pins
00124 template <unsigned MAX_LEDS_PER_STRIP>
00125 void WS2811<MAX_LEDS_PER_STRIP>::io_init()
00126 {
00127     uint32_t m = 1;
00128     for (uint32_t i = 0; i < 32; i++) {
00129         // set up each pin
00130         if (m & enabledPins) {
00131             WS2811_IO_PORT->PCR[i] = PORT_PCR_MUX(1) // GPIO
00132                               | PORT_PCR_DSE_MASK; // high drive strength
00133         }
00134         m <<= 1;
00135     }
00136 
00137     WS2811_IO_GPIO->PDDR |= enabledPins;      // set as outputs
00138 
00139 #if WS2811_MONITOR_TPM0_PWM
00140     // PTD0 CH0 monitor: TPM0, high drive strength
00141     WS2811_IO_PORT->PCR[0] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;
00142     // PTD1 CH1 monitor: TPM0, high drive strength
00143     WS2811_IO_PORT->PCR[1] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;
00144     WS2811_IO_GPIO->PDDR  |= 3;               // set as outputs
00145     WS2811_IO_GPIO->PDOR &= ~(enabledPins | 3);     // initially low
00146 #else
00147     WS2811_IO_GPIO->PDOR &= ~enabledPins;     // initially low
00148 #endif
00149 
00150 #ifdef WS2811_DEBUG_PIN
00151     WS2811_IO_PORT->PCR[WS2811_DEBUG_PIN] = PORT_PCR_MUX(1) | PORT_PCR_DSE_MASK;
00152     WS2811_IO_GPIO->PDDR |= DEBUG_MASK;
00153     WS2811_IO_GPIO->PDOR &= ~DEBUG_MASK;
00154 #endif
00155 }
00156 
00157 // class static
00158 
00159 /// Configure DMA and DMAMUX
00160 template <unsigned MAX_LEDS_PER_STRIP>
00161 void WS2811<MAX_LEDS_PER_STRIP>::dma_init()
00162 {
00163     // reset DMAMUX
00164     DMAMUX0->CHCFG[DMA_CHAN_START] = 0;
00165     DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = 0;
00166     DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = 0;
00167 
00168     // wire our DMA event sources into the first three DMA channels
00169     // t=0: all enabled outputs go high on TPM0 overflow
00170     DMAMUX0->CHCFG[DMA_CHAN_START] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_Overflow);
00171     // t=tpm_p0_period: all of the 0 bits go low.
00172     DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_0);
00173     // t=tpm_p1_period: all outputs go low.
00174     DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_1);
00175 
00176     NVIC_SetVector(DMA0_IRQn, (uint32_t)&DMA0_IRQHandler);
00177     NVIC_EnableIRQ(DMA0_IRQn);
00178 }
00179 
00180 // class static
00181 
00182 /// Configure TPM0 to do two different PWM periods at 800kHz rate
00183 template <unsigned MAX_LEDS_PER_STRIP>
00184 void WS2811<MAX_LEDS_PER_STRIP>::tpm_init()
00185 {
00186     // set up TPM0 for proper period (800 kHz = 1.25 usec ±600nsec)
00187     TPM_Type volatile *tpm = TPM0;
00188     tpm->SC = TPM_SC_DMA_MASK          // enable DMA
00189               | TPM_SC_TOF_MASK        // reset TOF flag if set
00190               | TPM_SC_CMOD(0)         // disable clocks
00191               | TPM_SC_PS(0);          // 48MHz / 1 = 48MHz clock
00192     tpm->MOD = tpm_period - 1;         // 48MHz / 800kHz
00193 
00194     // No Interrupts; High True pulses on Edge Aligned PWM
00195     tpm->CONTROLS[0].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
00196     tpm->CONTROLS[1].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
00197 
00198     // set TPM0 channel 0 for 0.35 usec (±150nsec) (0 code)
00199     // 1.25 usec * 1/3 = 417 nsec
00200     tpm->CONTROLS[0].CnV = tpm_p0_period;
00201 
00202     // set TPM0 channel 1 for 0.7 usec (±150nsec) (1 code)
00203     // 1.25 usec * 2/3 = 833 nsec
00204     tpm->CONTROLS[1].CnV = tpm_p1_period;
00205 }
00206 
00207 // class static
00208 template <unsigned MAX_LEDS_PER_STRIP>
00209 void WS2811<MAX_LEDS_PER_STRIP>::startDMA()
00210 {
00211     if (!initialized) hw_init();
00212 
00213     wait_for_dma_done();
00214 
00215     DMA_Type volatile * dma   = DMA0;
00216     TPM_Type volatile *tpm   = TPM0;
00217     uint32_t nBytes = sizeof(dmaData.start_t1_low)
00218                       + sizeof(dmaData.dmaWords)
00219                       + sizeof(dmaData.trailing_zeros_1);
00220 
00221     tpm->SC = TPM_SC_DMA_MASK        // enable DMA
00222               | TPM_SC_TOF_MASK  // reset TOF flag if set
00223               | TPM_SC_CMOD(0)   // disable clocks
00224               | TPM_SC_PS(0);    // 48MHz / 1 = 48MHz clock
00225     tpm->MOD = tpm_period - 1;       // 48MHz / 800kHz
00226 
00227     tpm->CNT = tpm_p0_period - 2 ;
00228     tpm->STATUS = 0xFFFFFFFF;
00229 
00230     dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00231     dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00232     dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00233 
00234     // t=0: all outputs go high
00235     // triggered by TPM0_Overflow
00236     // source is one word of 0 then 24 x 0xffffffff, then another 0 word
00237     dma->DMA[DMA_CHAN_START].SAR     = (uint32_t)(void*)dmaData.start_t0_high;
00238     dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
00239 
00240     // t=tpm_p0_period: some outputs (the 0 bits) go low.
00241     // Triggered by TPM0_CH0
00242     // Start 2 words before the actual data to avoid garbage pulses.
00243     dma->DMA[DMA_CHAN_0_LOW].SAR     = (uint32_t)(void*)dmaData.start_t1_low; // set source address
00244     dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
00245 
00246     // t=tpm_p1_period: all outputs go low.
00247     // Triggered by TPM0_CH1
00248     // source is constant 0x00000000 (first word of dmaWords)
00249     dma->DMA[DMA_CHAN_1_LOW].SAR     = (uint32_t)(void*)dmaData.start_t1_low; // set source address
00250     dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
00251 
00252     dma->DMA[DMA_CHAN_0_LOW].DAR
00253     = dma->DMA[DMA_CHAN_1_LOW].DAR
00254       = dma->DMA[DMA_CHAN_START].DAR
00255         = (uint32_t)(void*)&WS2811_IO_GPIO->PDOR;
00256 
00257     SET_DEBUG;
00258 
00259     dma->DMA[DMA_CHAN_0_LOW].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
00260                                        | DMA_DCR_ERQ_MASK
00261                                        | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
00262                                        | DMA_DCR_SINC_MASK // increment source each transfer
00263                                        | DMA_DCR_CS_MASK
00264                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
00265                                        | DMA_DCR_DSIZE(0); // 32-bit destination transfers
00266 
00267     dma->DMA[DMA_CHAN_1_LOW].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
00268                                        | DMA_DCR_ERQ_MASK
00269                                        | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
00270                                        | DMA_DCR_CS_MASK
00271                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
00272                                        | DMA_DCR_DSIZE(0); // 32-bit destination transfers
00273 
00274     dma->DMA[DMA_CHAN_START].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
00275                                        | DMA_DCR_ERQ_MASK
00276                                        | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
00277                                        | DMA_DCR_SINC_MASK // increment source each transfer
00278                                        | DMA_DCR_CS_MASK
00279                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
00280                                        | DMA_DCR_DSIZE(0);
00281 
00282     tpm->SC |= TPM_SC_CMOD(1);         // enable internal clocking
00283 }
00284 
00285 #if !INSTANTIATE_TEMPLATES
00286 
00287 extern "C" void DMA0_IRQHandler()
00288 {
00289     DMA_Type volatile *dma = DMA0;
00290     TPM_Type volatile *tpm = TPM0;
00291 
00292     uint32_t db;
00293 
00294     db = dma->DMA[DMA_CHAN_0_LOW].DSR_BCR;
00295     if (db & DMA_DSR_BCR_DONE_MASK) {
00296         dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00297     }
00298 
00299     db = dma->DMA[DMA_CHAN_1_LOW].DSR_BCR;
00300     if (db & DMA_DSR_BCR_DONE_MASK) {
00301         dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00302     }
00303 
00304     db = dma->DMA[DMA_CHAN_START].DSR_BCR;
00305     if (db & DMA_DSR_BCR_DONE_MASK) {
00306         dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00307     }
00308 
00309     tpm->SC = TPM_SC_TOF_MASK;  // reset TOF flag; disable internal clocking
00310 
00311     SET_DEBUG;
00312 }
00313 
00314 #endif