Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of Multi_WS2811 by
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 }
Generated on Sun Jul 17 2022 11:35:39 by
1.7.2
