cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H7 DMA not transferring correct values into TIM15 CCR1 register, why?

CLeo.1
Senior II

Hi everyone,

I feel like I am running out of options as to why this is occurring as the RM isn't that accurate to setup DMA + PWM using Timers.

The goal is to use TIM15 as a PWM generator running @ 800kHz running until 24 pulses are outputted and then stop until I command it to run again. Within these 24 pulses different duty cycle can occur from 64% for HIGH and 32% for LOW. This TIM15 is using the Repetition Counter register to indicate when 24 pulses as gone by.

What I have working thus far is a nice 24 pulse PWM stream but with only LOW (32%) present even though my buffer that gets transferred to CCR1 contains HIGH values (64%)

The configuration for the TIM15 are as fellow:

LL_DMA_InitTypeDef      WS2812B_Config_DMA;
	LL_GPIO_InitTypeDef     WS2812B_Config_GPIO;
	LL_TIM_InitTypeDef      WS2812B_Config_TIM15;
	LL_TIM_OC_InitTypeDef   WS2812B_Config_OC;
	LL_TIM_BDTR_InitTypeDef WS2812B_Config_BDTR;
 
	LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM15);
	LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOA);
	LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
 
	LL_TIM_StructInit(&WS2812B_Config_TIM15);
	LL_TIM_OC_StructInit(&WS2812B_Config_OC);
	LL_TIM_BDTR_StructInit(&WS2812B_Config_BDTR);
	LL_GPIO_StructInit(&WS2812B_Config_GPIO);
	LL_DMA_StructInit(&WS2812B_Config_DMA);
 
	//Configure GPIO
	WS2812B_Config_GPIO.Alternate  = LL_GPIO_AF_4;
	WS2812B_Config_GPIO.Mode       = LL_GPIO_MODE_ALTERNATE;
	WS2812B_Config_GPIO.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	WS2812B_Config_GPIO.Pin        = LL_GPIO_PIN_2;
	WS2812B_Config_GPIO.Pull       = LL_GPIO_PULL_DOWN;
	WS2812B_Config_GPIO.Speed      = LL_GPIO_SPEED_FREQ_VERY_HIGH;
	LL_GPIO_Init(GPIOA, &WS2812B_Config_GPIO);
 
	//Configure DMA
	WS2812B_Config_DMA.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
	WS2812B_Config_DMA.FIFOMode = LL_DMA_FIFOMODE_DISABLE;
	WS2812B_Config_DMA.MemBurst = LL_DMA_MBURST_SINGLE;
	WS2812B_Config_DMA.MemoryOrM2MDstAddress = (uint32_t)WS28128B_BUFF;
	WS2812B_Config_DMA.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
	WS2812B_Config_DMA.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
	WS2812B_Config_DMA.Mode = LL_DMA_MODE_NORMAL;
	WS2812B_Config_DMA.NbData = MAX_BUFF;
	WS2812B_Config_DMA.PeriphBurst = LL_DMA_PBURST_SINGLE;
	WS2812B_Config_DMA.PeriphOrM2MSrcAddress = (uint32_t)&TIM15->CCR1;
	WS2812B_Config_DMA.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
	WS2812B_Config_DMA.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
	WS2812B_Config_DMA.PeriphRequest = LL_DMAMUX1_REQ_TIM15_UP;
	WS2812B_Config_DMA.Priority = LL_DMA_PRIORITY_LOW;
 
	//Interrupt: DMA
	LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_4);
 
	NVIC_EnableIRQ(DMA1_Stream4_IRQn);
	NVIC_SetPriority(DMA1_Stream4_IRQn, 0);
 
	NVIC_EnableIRQ(TIM15_IRQn);
	NVIC_SetPriority(TIM15_IRQn, 0);
 
	LL_DMA_Init(DMA1, LL_DMA_STREAM_4, &WS2812B_Config_DMA);
 
	WS2812B_Config_TIM15.Autoreload    = (240e6/WS2812B_FREQ) - 1;
	WS2812B_Config_TIM15.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
	WS2812B_Config_TIM15.CounterMode   = LL_TIM_COUNTERMODE_UP;
	WS2812B_Config_TIM15.Prescaler     = 0;
	WS2812B_Config_TIM15.RepetitionCounter = MAX_BUFF - 1;
	LL_TIM_Init(TIM15, &WS2812B_Config_TIM15);
 
	WS2812B_Config_OC.CompareValue = 0;
	WS2812B_Config_OC.OCIdleState = LL_TIM_OCIDLESTATE_LOW;
	WS2812B_Config_OC.OCMode = LL_TIM_OCMODE_PWM1;
	WS2812B_Config_OC.OCNIdleState = LL_TIM_OCIDLESTATE_LOW;
	WS2812B_Config_OC.OCNPolarity = LL_TIM_OCPOLARITY_HIGH;
	WS2812B_Config_OC.OCNState = LL_TIM_OCSTATE_DISABLE;
	WS2812B_Config_OC.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
	WS2812B_Config_OC.OCState = LL_TIM_OCSTATE_ENABLE;
	LL_TIM_OC_Init(TIM15, LL_TIM_CHANNEL_CH1, &WS2812B_Config_OC);
 
	WS2812B_Config_BDTR.AutomaticOutput = LL_TIM_AUTOMATICOUTPUT_DISABLE;
	WS2812B_Config_BDTR.Break2Filter = LL_TIM_BREAK2_FILTER_FDIV1;
	WS2812B_Config_BDTR.Break2Polarity = LL_TIM_BREAK2_POLARITY_LOW;
	WS2812B_Config_BDTR.Break2State = LL_TIM_BREAK2_DISABLE;
	WS2812B_Config_BDTR.BreakFilter = LL_TIM_BREAK_FILTER_FDIV1;
	WS2812B_Config_BDTR.BreakPolarity = LL_TIM_BREAK_POLARITY_LOW;
	WS2812B_Config_BDTR.BreakState = LL_TIM_BREAK_DISABLE;
	WS2812B_Config_BDTR.DeadTime = 0;
	WS2812B_Config_BDTR.LockLevel = LL_TIM_LOCKLEVEL_OFF;
	WS2812B_Config_BDTR.OSSIState = LL_TIM_OSSI_DISABLE;
	WS2812B_Config_BDTR.OSSRState = LL_TIM_OSSR_DISABLE;
	LL_TIM_BDTR_Init(TIM15, &WS2812B_Config_BDTR);
 
	LL_TIM_OC_EnablePreload(TIM15, LL_TIM_CHANNEL_CH1);
	LL_TIM_EnableARRPreload(TIM15);
	LL_TIM_EnableDMAReq_UPDATE(TIM15);
	LL_TIM_EnableIT_UPDATE(TIM15);
	LL_TIM_GenerateEvent_UPDATE(TIM15);
	LL_TIM_ClearFlag_UPDATE(TIM15);
	LL_TIM_EnableAllOutputs(TIM15);

The interrupts that are currently active are DMA1_Stream 4 Transfer Complete & TIM15_Update. For the DMA, I just clear the TC4 interrupt to indicated that everything from the buffer has been transferred over to CCR1 and clear it.

The TIM15_Update interrupt indicates that 24 pulses has gone and I handle it accordingly

The interrupts in how they are setup are as fellow:

extern "C" void DMA1_Stream4_IRQHandler() {
 
	if (LL_DMA_IsActiveFlag_TC4(DMA1)) {
		LL_DMA_ClearFlag_TC4(DMA1);
	}
 
}
 
extern "C" void TIM15_IRQHandler() {
 
	if (LL_TIM_IsActiveFlag_UPDATE(TIM15)) {
		LL_TIM_ClearFlag_UPDATE(TIM15);
		LL_TIM_DisableCounter(TIM15);
		LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_4);
		WS2812B::block = false;
	}
}

The buffer contents that I've been using:

LOW = 0x5F = 32% duty cycle

HIGH = 0xBF = 64% duty cycle

WS28128B_BUFF[0]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[1]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[2]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[3]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[4]	uint32_t	0xbf (Hex)	
WS28128B_BUFF[5]	uint32_t	0xbf (Hex)	
WS28128B_BUFF[6]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[7]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[8]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[9]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[10]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[11]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[12]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[13]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[14]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[15]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[16]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[17]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[18]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[19]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[20]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[21]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[22]	uint32_t	0x5f (Hex)	
WS28128B_BUFF[23]	uint32_t	0x5f (Hex)	

Picture of logic analyzer

0693W00000QKCpUQAX.png 

Why arent I seeing my HIGHs ?

1 ACCEPTED SOLUTION

Accepted Solutions
gbm
Lead III

My guess: because the DMA is triggered after 24 pulses, not on every pulse, as a result of using repetition counter. Also, make sure that CCR1 is buffered.

Another problem: the buffer contains 32-bit data, but DMA is programmed for 16-bit words.

Honestly, there are at least two more efficient ways to output WS2812 data - using UART or SPI.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

View solution in original post

2 REPLIES 2
gbm
Lead III

My guess: because the DMA is triggered after 24 pulses, not on every pulse, as a result of using repetition counter. Also, make sure that CCR1 is buffered.

Another problem: the buffer contains 32-bit data, but DMA is programmed for 16-bit words.

Honestly, there are at least two more efficient ways to output WS2812 data - using UART or SPI.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

Hi! You were completely right as I misunderstood what repetition meant, quickly corrected that by returning that register to 0x00.

Corrected for the DMA already, good eye.

Sure, but i mean its easier to implement