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.
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
Generated on Wed Jul 13 2022 13:18:58 by
1.7.2
Generic WS2811/WS2812