Octows2811 For Dummies - Understanding PWM / DMA driven waveforms

Status
Not open for further replies.

crees

Well-known member
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???
 
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
 
Status
Not open for further replies.
Back
Top