Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 2 of 2

Thread: Octows2811 For Dummies - Understanding PWM / DMA driven waveforms

  1. #1
    Senior Member crees's Avatar
    Join Date
    Dec 2016
    Location
    Utah
    Posts
    216

    Octows2811 For Dummies - Understanding PWM / DMA driven waveforms

    First the octows2811 is brilliant way to generate the ws2811 protocol! I want to really understand every bit of how this code does it job.

    I have made some comments to try to break this up and looking for corrections on my assumptions. I have placed ////////////// marks for comments on the code. More questions to follow below.

    Code below is portions of Octows2811 key points I am focusing on. Please see the actual library for full code.

    Code:
    ////////// Set ones as all 1's 11111111 
    
    static uint8_t ones = 0xFF; 
    
    //////////// Set PWM TIMING Values
    
    #define WS2811_TIMING_T0H  60
    #define WS2811_TIMING_T1H  176
    ......
    
    //////////// SET UP TIMER based off of MCU TYPE
    
    #if defined(__MK20DX128__)
    	FTM1_SC = 0;
    	FTM1_CNT = 0;
    	uint32_t mod = (F_BUS + frequency / 2) / frequency;
    	FTM1_MOD = mod - 1;
    	FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0);
    	FTM1_C0SC = 0x69;
    	FTM1_C1SC = 0x69;
    	FTM1_C0V = (mod * WS2811_TIMING_T0H) >> 8;
    	FTM1_C1V = (mod * WS2811_TIMING_T1H) >> 8;
    	// pin 16 triggers DMA(port B) on rising edge
    	CORE_PIN16_CONFIG = PORT_PCR_IRQC(1)|PORT_PCR_MUX(3);
    	//CORE_PIN4_CONFIG = PORT_PCR_MUX(3); // testing only
    
    #elif defined(__MK20DX256__)
    	FTM2_SC = 0;
    	FTM2_CNT = 0;
    	uint32_t mod = (F_BUS + frequency / 2) / frequency;
    	FTM2_MOD = mod - 1;
    	FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0);
    	FTM2_C0SC = 0x69;
    	FTM2_C1SC = 0x69;
    	FTM2_C0V = (mod * WS2811_TIMING_T0H) >> 8;
    	FTM2_C1V = (mod * WS2811_TIMING_T1H) >> 8;
    	// pin 32 is FTM2_CH0, PTB18, triggers DMA(port B) on rising edge
    	// pin 25 is FTM2_CH1, PTB19
    	CORE_PIN32_CONFIG = PORT_PCR_IRQC(1)|PORT_PCR_MUX(3);
    	//CORE_PIN25_CONFIG = PORT_PCR_MUX(3); // testing only
    
    #elif defined(__MK64FX512__) || defined(__MK66FX1M0__)
    	FTM2_SC = 0;
    	FTM2_CNT = 0;
    	uint32_t mod = (F_BUS + frequency / 2) / frequency;
    	FTM2_MOD = mod - 1;
    	FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0);
    	FTM2_C0SC = 0x69;
    	FTM2_C1SC = 0x69;
    	FTM2_C0V = (mod * WS2811_TIMING_T0H) >> 8;
    	FTM2_C1V = (mod * WS2811_TIMING_T1H) >> 8;
    	// FTM2_CH0, PTA10 (not connected), triggers DMA(port A) on rising edge
    	PORTA_PCR10 = PORT_PCR_IRQC(1)|PORT_PCR_MUX(3);
    
    #elif defined(__MKL26Z64__)
    	FTM2_SC = 0;
    	FTM2_CNT = 0;
    	uint32_t mod = F_CPU / frequency;
    	FTM2_MOD = mod - 1;
    	FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0);
    	FTM2_C0SC = FTM_CSC_CHF | FTM_CSC_MSB | FTM_CSC_ELSB;
    	FTM2_C1SC = FTM_CSC_CHF | FTM_CSC_MSB | FTM_CSC_ELSB;
    	TPM2_C0V = mod - ((mod * WS2811_TIMING_T1H) >> 8);
    	TPM2_C1V = mod - ((mod * WS2811_TIMING_T1H) >> 8) + ((mod * WS2811_TIMING_T0H) >> 8);
    
    #endif
    
    ////////////// PREPARE DMA 1, 2, 3 VALUES
    
    	// DMA channel #1 sets WS2811 high at the beginning of each cycle
    	dma1.source(ones);
    	dma1.destination(GPIOD_PSOR);
    	dma1.transferSize(1);
    	dma1.transferCount(bufsize);
    	dma1.disableOnCompletion();
    
    	// DMA channel #2 writes the pixel data at 23% of the cycle
    	dma2.sourceBuffer((uint8_t *)frameBuffer, bufsize);
    	dma2.destination(GPIOD_PDOR);
    	dma2.transferSize(1);
    	dma2.transferCount(bufsize);
    	dma2.disableOnCompletion();
    
    	// DMA channel #3 clear all the pins low at 69% of the cycle
    	dma3.source(ones);
    	dma3.destination(GPIOD_PCOR);
    	dma3.transferSize(1);
    	dma3.transferCount(bufsize);
    	dma3.disableOnCompletion();
    	dma3.interruptAtCompletion();
    
    ///////// DEFINE TRIGGERS FROM PORTB=DMA1 High Bit 1? AND TIMERS FOR THE DMA 2  3 CHANNELS? DEPENDING ON CHIP TYPE
    
    #if defined(__MK20DX128__)
    	// route the edge detect interrupts to trigger the 3 channels
    	dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_PORTB);
    	dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM1_CH0);
    	dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM1_CH1);
    	DMAPriorityOrder(dma3, dma2, dma1);
    #elif defined(__MK20DX256__)
    	// route the edge detect interrupts to trigger the 3 channels
    	dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_PORTB);
    	dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_CH0);
    	dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_CH1);
    	DMAPriorityOrder(dma3, dma2, dma1);
    #elif defined(__MK64FX512__) || defined(__MK66FX1M0__)
    	// route the edge detect interrupts to trigger the 3 channels
    	dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_PORTA);
    	dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_CH0);
    	dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_CH1);
    	DMAPriorityOrder(dma3, dma2, dma1);
    #elif defined(__MKL26Z64__)
    	// route the timer interrupts to trigger the 3 channels
    	dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_TPM2_CH0);
    	dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_TPM2_CH1);
    	dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM2_OV);
    #endif
    
    	// enable a done interrupts when channel #3 completes
    	dma3.attachInterrupt(isr);
    	//pinMode(9, OUTPUT); // testing: oscilloscope trigger
    }
    
    void OctoWS2811::isr(void)
    {
    	//digitalWriteFast(9, HIGH);
    	//Serial1.print(".");
    	//Serial1.println(dma3.CFG->DCR, HEX);
    	//Serial1.print(dma3.CFG->DSR_BCR > 24, HEX);
    	dma3.clearInterrupt();
    #if defined(__MKL26Z64__)
    	GPIOD_PCOR = 0xFF;
    #endif
    	//Serial1.print("*");
    	update_completed_at = micros();
    	update_in_progress = 0;
    	//digitalWriteFast(9, LOW);
    }
    
    int OctoWS2811::busy(void)
    {
    	if (update_in_progress) return 1;
    	// busy for 50 (or 300 for ws2813) us after the done interrupt, for WS2811 reset
    	if (micros() - update_completed_at < 300) return 1;
    	return 0;
    }
    
    void OctoWS2811::show(void)
    {
    	// wait for any prior DMA operation
    	//Serial1.print("1");
    	while (update_in_progress) ;
    	//Serial1.print("2");
    	// it's ok to copy the drawing buffer to the frame buffer
    	// during the 50us WS2811 reset time
    	if (drawBuffer != frameBuffer) {
    		// TODO: this could be faster with DMA, especially if the
    		// buffers are 32 bit aligned... but does it matter?
    		memcpy(frameBuffer, drawBuffer, stripLen * 24);
    	}
    	// wait for WS2811 reset
    	while (micros() - update_completed_at < 300) ;
    	// ok to start, but we must be very careful to begin
    	// without any prior 3 x 800kHz DMA requests pending
    
    
    ///////////  DEPENDING ON CHIP TYPE SET THE REGISTERS APPROPRIATELY AND SEND DMA VALUES (DMA x ENABLE?)
    
    
    #if defined(__MK20DX128__)
    	uint32_t cv = FTM1_C0V;
    	noInterrupts();
    	// CAUTION: this code is timing critical.
    	while (FTM1_CNT <= cv) ;
    	while (FTM1_CNT > cv) ; // wait for beginning of an 800 kHz cycle
    	while (FTM1_CNT < cv) ;
    	FTM1_SC = 0;            // stop FTM1 timer (hopefully before it rolls over)
    	FTM1_CNT = 0;
    	update_in_progress = 1;
    	//digitalWriteFast(9, HIGH); // oscilloscope trigger
    	PORTB_ISFR = (1<<0);    // clear any prior rising edge
    	uint32_t tmp __attribute__((unused));
    	FTM1_C0SC = 0x28;
    	tmp = FTM1_C0SC;        // clear any prior timer DMA triggers
    	FTM1_C0SC = 0x69;
    	FTM1_C1SC = 0x28;
    	tmp = FTM1_C1SC;
    	FTM1_C1SC = 0x69;
    	dma1.enable();
    	dma2.enable();          // enable all 3 DMA channels
    	dma3.enable();
    	FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // restart FTM1 timer
    	//digitalWriteFast(9, LOW);
    
    #elif defined(__MK20DX256__)
    	FTM2_C0SC = 0x28;
    	FTM2_C1SC = 0x28;
    	uint32_t cv = FTM2_C0V;
    	noInterrupts();
    	// CAUTION: this code is timing critical.
    	while (FTM2_CNT <= cv) ;
    	while (FTM2_CNT > cv) ; // wait for beginning of an 800 kHz cycle
    	while (FTM2_CNT < cv) ;
    	FTM2_SC = 0;             // stop FTM2 timer (hopefully before it rolls over)
    	FTM2_CNT = 0;
    	update_in_progress = 1;
    	//digitalWriteFast(9, HIGH); // oscilloscope trigger
    	PORTB_ISFR = (1<<18);    // clear any prior rising edge
    	uint32_t tmp __attribute__((unused));
    	FTM2_C0SC = 0x28;
    	tmp = FTM2_C0SC;         // clear any prior timer DMA triggers
    	FTM2_C0SC = 0x69;
    	FTM2_C1SC = 0x28;
    	tmp = FTM2_C1SC;
    	FTM2_C1SC = 0x69;
    	dma1.enable();
    	dma2.enable();           // enable all 3 DMA channels
    	dma3.enable();
    	FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // restart FTM2 timer
    	//digitalWriteFast(9, LOW);
    
    #elif defined(__MK64FX512__) || defined(__MK66FX1M0__)
    	FTM2_C0SC = 0x28;
    	FTM2_C1SC = 0x28;
    	uint32_t cv = FTM2_C1V;
    	noInterrupts();
    	// CAUTION: this code is timing critical.
    	while (FTM2_CNT <= cv) ;
    	while (FTM2_CNT > cv) ; // wait for beginning of an 800 kHz cycle
    	while (FTM2_CNT < cv) ;
    	FTM2_SC = 0;             // stop FTM2 timer (hopefully before it rolls over)
    	FTM2_CNT = 0;
    	update_in_progress = 1;
    	//digitalWriteFast(9, HIGH); // oscilloscope trigger
    	#if defined(__MK64FX512__)
    	asm("nop");
    	#endif
    	PORTA_ISFR = (1<<10);    // clear any prior rising edge
    	uint32_t tmp __attribute__((unused));
    	FTM2_C0SC = 0x28;
    	tmp = FTM2_C0SC;         // clear any prior timer DMA triggers
    	FTM2_C0SC = 0x69;
    	FTM2_C1SC = 0x28;
    	tmp = FTM2_C1SC;
    	FTM2_C1SC = 0x69;
    	dma1.enable();
    	dma2.enable();           // enable all 3 DMA channels
    	dma3.enable();
    	FTM2_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // restart FTM2 timer
    	//digitalWriteFast(9, LOW);
    
    #elif defined(__MKL26Z64__)
    	uint32_t sc __attribute__((unused)) = FTM2_SC;
    	uint32_t cv = FTM2_C1V;
    	noInterrupts();
    	while (FTM2_CNT <= cv) ;
    	while (FTM2_CNT > cv) ; // wait for beginning of an 800 kHz cycle
    	while (FTM2_CNT < cv) ;
    	FTM2_SC = 0;		// stop FTM2 timer (hopefully before it rolls over)
    	update_in_progress = 1;
    	//digitalWriteFast(9, HIGH); // oscilloscope trigger
    	dma1.clearComplete();
    	dma2.clearComplete();
    	dma3.clearComplete();
    	uint32_t bufsize = stripLen*24;
    	dma1.transferCount(bufsize);
    	dma2.transferCount(bufsize);
    	dma3.transferCount(bufsize);
    	dma2.sourceBuffer((uint8_t *)frameBuffer, bufsize);
    	// clear any pending event flags
    	FTM2_SC = FTM_SC_TOF;
    	FTM2_C0SC = FTM_CSC_CHF | FTM_CSC_MSB | FTM_CSC_ELSB | FTM_CSC_DMA;
    	FTM2_C1SC = FTM_CSC_CHF | FTM_CSC_MSB | FTM_CSC_ELSB | FTM_CSC_DMA;
    	// clear any prior pending DMA requests
    	dma1.enable();
    	dma2.enable();		// enable all 3 DMA channels
    	dma3.enable();
    	FTM2_CNT = 0; // writing any value resets counter
    	FTM2_SC = FTM_SC_DMA | FTM_SC_CLKS(1) | FTM_SC_PS(0);
    	//digitalWriteFast(9, LOW);
    #endif
    	//Serial1.print("3");
    	interrupts();
    	//Serial1.print("4");
    }
    If I understand this correctly we are basically setting the values to generate two kinds of wave forms to satisfy a 0 and 1 in a synthesized NRZ. We use DMA to process the buffer values (0 or 1) to the 8 Pins. DMA1 sets the bits high (all 1's?). DMA 2 triggers the actual pixel data buffer, 0's pull the wave form low and 1's keep it high until DMA 3 brings them all low (0 ??)

    The question I have is in this code snippet and how its done.

    Code:
    	// DMA channel #3 clear all the pins low at 69% of the cycle
    	dma3.source(ones); ///Is this all FF values (1's)???  this is my confusion.  I would think we should be all 0's  Or is setting the DMA the same value do the opposite???
    	dma3.destination(GPIOD_PCOR);
    	dma3.transferSize(1);
    	dma3.transferCount(bufsize);
    	dma3.disableOnCompletion();
    	dma3.interruptAtCompletion();
    on dma3.source(ones).... Is this all FF values (1's)??? this is my confusion. I would think we should be all 0's Or is setting the DMA the same value do the opposite???

  2. #2
    Senior Member crees's Avatar
    Join Date
    Dec 2016
    Location
    Utah
    Posts
    216
    It helps to read the manual often referred here in the forums. A good friend of mine pointed me to it as well. My answer is on page 1335 * 1336. dma3.source(ones) for GPIOD_PCOR according to the manual uses 1 to clear the port and reset its state to 0.

    The GPIOx_PSOR PDOR PCOR is corresponds to REGISTER, SET and CLEAR

    PSOR Data state of the pins high or low.
    PDOR Set will set the pins 1 or 0
    PCOR Clear will set all pins 0 (requires a value of 1 or FF in the case of all 8 pins)

    https://www.pjrc.com/teensy/K20P64M72SF1RM.pdf

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •