Forked

Fork of Multi_WS2811 by Ned Konz

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 
00017 #include "MKL25Z4.h"
00018 #include "LedStrip.h"
00019 #include "WS2811.h"
00020 
00021 //
00022 // Configuration
00023 //
00024 
00025 // Define MONITOR_TPM0_PWM as non-zero to monitor PWM timing on PTD0 and PTD1
00026 // PTD0 TPM0/CH0 PWM_1 J2/06
00027 // PTD1 TPM0/CH1 PWM_2 J2/12 (also LED_BLUE)
00028 #define MONITOR_TPM0_PWM 0
00029 
00030 // define DEBUG_PIN to identify a pin in PORTD used for debug output
00031 // #define DEBUG_PIN 4 /* PTD4 debugOut */
00032 
00033 #ifdef DEBUG_PIN
00034 #define DEBUG 0
00035 #endif
00036 
00037 #if DEBUG
00038 #define DEBUG_MASK (1<<DEBUG_PIN)
00039 #define RESET_DEBUG (IO_GPIO->PDOR &= ~DEBUG_MASK)
00040 #define SET_DEBUG (IO_GPIO->PDOR |= DEBUG_MASK)
00041 #else
00042 #define DEBUG_MASK 0
00043 #define RESET_DEBUG (void)0
00044 #define SET_DEBUG (void)0
00045 #endif
00046 
00047 static PORT_Type volatile * const IO_PORT = PORTC;
00048 static GPIO_Type volatile * const IO_GPIO = PTC;
00049 
00050 // 48 MHz clock, no prescaling.
00051 #define NSEC_TO_TICKS(nsec) ((nsec)*48/1000)
00052 #define USEC_TO_TICKS(usec) ((usec)*48)
00053 static const uint32_t CLK_NSEC = 1250;
00054 static const uint32_t tpm_period    = NSEC_TO_TICKS(CLK_NSEC);
00055 static const uint32_t tpm_p0_period = NSEC_TO_TICKS(250);
00056 static const uint32_t tpm_p1_period = NSEC_TO_TICKS(650);
00057 static const uint32_t guardtime_period = USEC_TO_TICKS(55);   // guardtime minimum 50 usec.
00058 
00059 enum DMA_MUX_SRC {
00060     DMA_MUX_SRC_TPM0_CH_0     = 24,
00061     DMA_MUX_SRC_TPM0_CH_1,
00062     DMA_MUX_SRC_TPM0_Overflow = 54,
00063 };
00064 
00065 enum DMA_CHAN {
00066     DMA_CHAN_START = 0,
00067     DMA_CHAN_0_LOW = 1,
00068     DMA_CHAN_1_LOW = 2,
00069     N_DMA_CHANNELS
00070 };
00071 
00072 volatile bool WS2811::dma_done = true;
00073 
00074 // class static
00075 bool WS2811::initialized = false;
00076 
00077 // class static
00078 uint32_t WS2811::enabledPins = 0;
00079 
00080 #define WORD_ALIGNED __attribute__ ((aligned(4)))
00081 
00082 #define DMA_LEADING_ZEROS  2
00083 #define BITS_PER_RGB       24
00084 #define DMA_TRAILING_ZEROS 1
00085 
00086 static struct {
00087     uint32_t start_t1_low[ DMA_LEADING_ZEROS ];
00088     uint32_t dmaWords[ BITS_PER_RGB * MAX_LEDS_PER_STRIP ];
00089     uint32_t trailing_zeros_1[ DMA_TRAILING_ZEROS ];
00090 
00091     uint32_t start_t0_high[ DMA_LEADING_ZEROS - 1 ];
00092     uint32_t allOnes[ BITS_PER_RGB * MAX_LEDS_PER_STRIP ];
00093     uint32_t trailing_zeros_2[ DMA_TRAILING_ZEROS + 1 ];
00094 } dmaData WORD_ALIGNED;
00095 
00096 // class static
00097 void WS2811::hw_init()
00098 {
00099     if (initialized) return;
00100 
00101     dma_data_init();
00102     clock_init();
00103     dma_init();
00104     io_init();
00105     tpm_init();
00106 
00107     initialized = true;
00108 
00109     SET_DEBUG;
00110     RESET_DEBUG;
00111 }
00112 
00113 // class static
00114 void WS2811::dma_data_init()
00115 {
00116     memset(dmaData.allOnes, 0xFF, sizeof(dmaData.allOnes));
00117 
00118 #if DEBUG
00119     for (unsigned i = 0; i < BITS_PER_RGB * MAX_LEDS_PER_STRIP; i++)
00120         dmaData.dmaWords[i] = DEBUG_MASK;
00121 #endif
00122 }
00123 
00124 // class static
00125 
00126 /// Enable PORTD, DMA and TPM0 clocking
00127 void WS2811::clock_init()
00128 {
00129     SIM->SCGC5 |= SIM_SCGC5_PORTC_MASK;
00130     SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK | SIM_SCGC6_TPM0_MASK; // Enable clock to DMA mux and TPM0
00131     SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;  // Enable clock to DMA
00132 
00133     SIM->SOPT2 |= SIM_SOPT2_TPMSRC(1); // Clock source: MCGFLLCLK or MCGPLLCLK
00134 }
00135 
00136 // class static
00137 
00138 /// Configure GPIO output pins
00139 void WS2811::io_init()
00140 {
00141     uint32_t m = 1;
00142     for (uint32_t i = 0; i < 32; i++) {
00143         // set up each pin
00144         if (m & enabledPins) {
00145             IO_PORT->PCR[i] = PORT_PCR_MUX(1) // GPIO
00146                               | PORT_PCR_DSE_MASK; // high drive strength
00147         }
00148         m <<= 1;
00149     }
00150 
00151     IO_GPIO->PDDR |= enabledPins;      // set as outputs
00152 
00153 #if MONITOR_TPM0_PWM
00154     // PTD0 CH0 monitor: TPM0, high drive strength
00155     IO_PORT->PCR[0] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;
00156     // PTD1 CH1 monitor: TPM0, high drive strength
00157     IO_PORT->PCR[1] = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK;
00158     IO_GPIO->PDDR  |= 3;               // set as outputs
00159     IO_GPIO->PDOR &= ~(enabledPins | 3);     // initially low
00160 #else
00161     IO_GPIO->PDOR &= ~enabledPins;     // initially low
00162 #endif
00163 
00164 #if DEBUG
00165     IO_PORT->PCR[DEBUG_PIN] = PORT_PCR_MUX(1) | PORT_PCR_DSE_MASK;
00166     IO_GPIO->PDDR |= DEBUG_MASK;
00167     IO_GPIO->PDOR &= ~DEBUG_MASK;
00168 #endif
00169 }
00170 
00171 // class static
00172 
00173 /// Configure DMA and DMAMUX
00174 void WS2811::dma_init()
00175 {
00176     // reset DMAMUX
00177     DMAMUX0->CHCFG[DMA_CHAN_START] = 0;
00178     DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = 0;
00179     DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = 0;
00180 
00181     // wire our DMA event sources into the first three DMA channels
00182     // t=0: all enabled outputs go high on TPM0 overflow
00183     DMAMUX0->CHCFG[DMA_CHAN_START] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_Overflow);
00184     // t=tpm_p0_period: all of the 0 bits go low.
00185     DMAMUX0->CHCFG[DMA_CHAN_0_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_0);
00186     // t=tpm_p1_period: all outputs go low.
00187     DMAMUX0->CHCFG[DMA_CHAN_1_LOW] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(DMA_MUX_SRC_TPM0_CH_1);
00188 
00189     NVIC_SetVector(DMA0_IRQn, (uint32_t)&DMA0_IRQHandler);
00190     NVIC_EnableIRQ(DMA0_IRQn);
00191 }
00192 
00193 // class static
00194 
00195 /// Configure TPM0 to do two different PWM periods at 800kHz rate
00196 void WS2811::tpm_init()
00197 {
00198     // set up TPM0 for proper period (800 kHz = 1.25 usec ±600nsec)
00199     TPM_Type volatile *tpm = TPM0;
00200     tpm->SC = TPM_SC_DMA_MASK          // enable DMA
00201               | TPM_SC_TOF_MASK        // reset TOF flag if set
00202               | TPM_SC_CMOD(0)         // disable clocks
00203               | TPM_SC_PS(0);          // 48MHz / 1 = 48MHz clock
00204     tpm->MOD = tpm_period - 1;         // 48MHz / 800kHz
00205 
00206     // No Interrupts; High True pulses on Edge Aligned PWM
00207     tpm->CONTROLS[0].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
00208     tpm->CONTROLS[1].CnSC = TPM_CnSC_MSB_MASK | TPM_CnSC_ELSB_MASK | TPM_CnSC_DMA_MASK;
00209 
00210     // set TPM0 channel 0 for 0.35 usec (±150nsec) (0 code)
00211     // 1.25 usec * 1/3 = 417 nsec
00212     tpm->CONTROLS[0].CnV = tpm_p0_period;
00213 
00214     // set TPM0 channel 1 for 0.7 usec (±150nsec) (1 code)
00215     // 1.25 usec * 2/3 = 833 nsec
00216     tpm->CONTROLS[1].CnV = tpm_p1_period;
00217 
00218     NVIC_SetVector(TPM0_IRQn, (uint32_t)&TPM0_IRQHandler);
00219     NVIC_EnableIRQ(TPM0_IRQn);
00220 }
00221 
00222 WS2811::WS2811(unsigned n, unsigned pinNumber)
00223     : LedStrip(n)
00224     , pinMask(1U << pinNumber)
00225 {
00226     enabledPins |= pinMask;
00227     initialized = false;
00228 }
00229 
00230 // class static
00231 void WS2811::startDMA()
00232 {
00233     hw_init();
00234     
00235     wait_for_dma_done();
00236     dma_done = false;
00237 
00238     DMA_Type volatile * dma   = DMA0;
00239     TPM_Type volatile *tpm   = TPM0;
00240     uint32_t nBytes = sizeof(dmaData.start_t1_low)
00241                       + sizeof(dmaData.dmaWords)
00242                       + sizeof(dmaData.trailing_zeros_1);
00243 
00244     tpm->SC = TPM_SC_DMA_MASK        // enable DMA
00245               | TPM_SC_TOF_MASK  // reset TOF flag if set
00246               | TPM_SC_CMOD(0)   // disable clocks
00247               | TPM_SC_PS(0);    // 48MHz / 1 = 48MHz clock
00248     tpm->MOD = tpm_period - 1;       // 48MHz / 800kHz
00249 
00250     tpm->CNT = tpm_p0_period - 2 ;
00251     tpm->STATUS = 0xFFFFFFFF;
00252 
00253     dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00254     dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00255     dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00256 
00257     // t=0: all outputs go high
00258     // triggered by TPM0_Overflow
00259     // source is one word of 0 then 24 x 0xffffffff, then another 0 word
00260     dma->DMA[DMA_CHAN_START].SAR     = (uint32_t)(void*)dmaData.start_t0_high;
00261     dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
00262 
00263     // t=tpm_p0_period: some outputs (the 0 bits) go low.
00264     // Triggered by TPM0_CH0
00265     // Start 2 words before the actual data to avoid garbage pulses.
00266     dma->DMA[DMA_CHAN_0_LOW].SAR     = (uint32_t)(void*)dmaData.start_t1_low; // set source address
00267     dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
00268 
00269     // t=tpm_p1_period: all outputs go low.
00270     // Triggered by TPM0_CH1
00271     // source is constant 0x00000000 (first word of dmaWords)
00272     dma->DMA[DMA_CHAN_1_LOW].SAR     = (uint32_t)(void*)dmaData.start_t1_low; // set source address
00273     dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_BCR_MASK & nBytes; // length of transfer in bytes
00274 
00275     dma->DMA[DMA_CHAN_0_LOW].DAR
00276     = dma->DMA[DMA_CHAN_1_LOW].DAR
00277       = dma->DMA[DMA_CHAN_START].DAR
00278         = (uint32_t)(void*)&IO_GPIO->PDOR;
00279 
00280     SET_DEBUG;
00281 
00282     dma->DMA[DMA_CHAN_0_LOW].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
00283                                        | DMA_DCR_ERQ_MASK
00284                                        | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
00285                                        | DMA_DCR_SINC_MASK // increment source each transfer
00286                                        | DMA_DCR_CS_MASK
00287                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
00288                                        | DMA_DCR_DSIZE(0); // 32-bit destination transfers
00289 
00290     dma->DMA[DMA_CHAN_1_LOW].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
00291                                        | DMA_DCR_ERQ_MASK
00292                                        | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
00293                                        | DMA_DCR_CS_MASK
00294                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
00295                                        | DMA_DCR_DSIZE(0); // 32-bit destination transfers
00296 
00297     dma->DMA[DMA_CHAN_START].DCR     = DMA_DCR_EINT_MASK // enable interrupt on end of transfer
00298                                        | DMA_DCR_ERQ_MASK
00299                                        | DMA_DCR_D_REQ_MASK // clear ERQ on end of transfer
00300                                        | DMA_DCR_SINC_MASK // increment source each transfer
00301                                        | DMA_DCR_CS_MASK
00302                                        | DMA_DCR_SSIZE(0) // 32-bit source transfers
00303                                        | DMA_DCR_DSIZE(0);
00304 
00305     tpm->SC |= TPM_SC_CMOD(1);         // enable internal clocking
00306 }
00307 
00308 void WS2811::writePixel(unsigned n, uint8_t *p)
00309 {
00310     uint32_t *dest = dmaData.dmaWords + n * BITS_PER_RGB;
00311     writeByte(*p++, pinMask, dest + 0); // G
00312     writeByte(*p++, pinMask, dest + 8); // R
00313     writeByte(*p, pinMask, dest + 16); // B
00314 }
00315 
00316 // class static
00317 void WS2811::writeByte(uint8_t byte, uint32_t mask, uint32_t *dest)
00318 {
00319     for (uint8_t bm = 0x80; bm; bm >>= 1) {
00320         // MSBit first
00321         if (byte & bm)
00322             *dest |= mask;
00323         else
00324             *dest &= ~mask;
00325         dest++;
00326     }
00327 }
00328 
00329 void WS2811::begin()
00330 {
00331     blank();
00332     show();
00333 }
00334 
00335 void WS2811::blank()
00336 {
00337     memset(pixels, 0x00, numPixelBytes());
00338 
00339 #if DEBUG
00340     for (unsigned i = DMA_LEADING_ZEROS; i < DMA_LEADING_ZEROS + BITS_PER_RGB; i++)
00341         dmaData.dmaWords[i] = DEBUG_MASK;
00342 #else
00343     memset(dmaData.dmaWords, 0x00, sizeof(dmaData.dmaWords));
00344 #endif
00345 }
00346 
00347 void WS2811::show()
00348 {
00349 
00350     uint16_t i, n = numPixels(); // 3 bytes per LED
00351     uint8_t *p = pixels;
00352 
00353     for (i=0; i<n; i++ ) {
00354         writePixel(i, p);
00355         p += 3;
00356     }
00357 }
00358 
00359 extern "C" void DMA0_IRQHandler()
00360 {
00361     DMA_Type volatile *dma = DMA0;
00362     TPM_Type volatile *tpm = TPM0;
00363 
00364     uint32_t db;
00365 
00366     db = dma->DMA[DMA_CHAN_0_LOW].DSR_BCR;
00367     if (db & DMA_DSR_BCR_DONE_MASK) {
00368         dma->DMA[DMA_CHAN_0_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00369     }
00370 
00371     db = dma->DMA[DMA_CHAN_1_LOW].DSR_BCR;
00372     if (db & DMA_DSR_BCR_DONE_MASK) {
00373         dma->DMA[DMA_CHAN_1_LOW].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00374     }
00375 
00376     db = dma->DMA[DMA_CHAN_START].DSR_BCR;
00377     if (db & DMA_DSR_BCR_DONE_MASK) {
00378         dma->DMA[DMA_CHAN_START].DSR_BCR = DMA_DSR_BCR_DONE_MASK; // clear/reset DMA status
00379     }
00380 
00381     tpm->SC = TPM_SC_TOF_MASK;  // reset TOF flag; disable internal clocking
00382 
00383     SET_DEBUG;
00384 
00385     // set TPM0 to interrrupt after guardtime
00386     tpm->MOD = guardtime_period - 1; // 48MHz * 55 usec
00387     tpm->CNT = 0;
00388     tpm->SC  = TPM_SC_PS(0)        // 48MHz / 1 = 48MHz clock
00389                | TPM_SC_TOIE_MASK  // enable interrupts
00390                | TPM_SC_CMOD(1);   // and internal clocking
00391 }
00392 
00393 extern "C" void TPM0_IRQHandler()
00394 {
00395     TPM0->SC = 0; // disable internal clocking
00396     TPM0->SC = TPM_SC_TOF_MASK;        
00397     RESET_DEBUG;
00398     WS2811::dma_done = true;
00399 }