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