cancel
Showing results for 
Search instead for 
Did you mean: 

Variable PWM duty cycle signal on STM32F4

PPiro.1
Associate II

Hello everyone!

For last week I am struggling to use Variable PWM duty cycle to generate signal for an array of addressable LEDs (WS2812) which I had successfully running on STM32F103.

I was forced to use STM32F412 and ever since I cannot port my old code to work with the new MCU.

Few days ago I decided to shift my attempt to a register level of things and I cannot update CCR3 using DMA, to allow me changing the duty cycle simultaneously (every ARR reload).

I found this topic and it lead me to a very helpful code, which in theory would allow me to dump values into CRR3 using DMA, but the DMA doesn't seem to put any values in CCR3.

Can you please tell me if you see anything I am doing wrong in my code?

I hope it is not too much code in one post

#include "stm32f412rx.h"
 
void GPIO_Init(void);
void TIM_Init(void);
void DMA_Init(void);
 
 
const uint16_t valuesTable[] = { //	Values to be dumped CCR3 registers simultaneously
		20,	20,
		30,	10,
		20,	20,
		30,	10
};
 
 
 
void main(void){
	__HAL_RCC_TIM4_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_DMA1_CLK_ENABLE();
 
 
	GPIO_Init();
	DMA_Init();
	TIM_Init();
	
	while(1){
	}
}
 
void GPIO_Init(void){
 
	GPIOB -> MODER |= GPIO_MODER_MODE8_1;			//	ALTERNATE FUNCTION MODE
	GPIOB -> OTYPER &= ~GPIO_OTYPER_OT8;			//	PUSH-PULL TYPE
	GPIOB -> OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8_1;	//	FAST SPEED
	GPIOB -> PUPDR |= GPIO_PUPDR_PUPDR8_1;			//	PULLDOWN
	GPIOB -> AFR[1] |= (2UL << (0U));				//	ALTERNATE FUNCTION #2 (TIM4 CH3)
 
}
 
void TIM_Init(void){
 
	/*	CR1	- CONTROL REGISTER 1
	 * 	CR2 - CONTROL REGISTER 2
	 * 	PSC	- PRESCALER REGISTER
	 * 	ARR - AUTO-RELOAD REGISTER
	 * 	SR	- STATUS REGISTER
	 * 	SMCR- SLAVE MODE CONTROL REGISTER
	 * 	DIER- DMA/INTERRUPT ENABLE REGISTER
	 * 	EGR	- EVENT GENERATION REGISTER
	 * 	CCMR1-CAPTURE/COMPARE MODE REGISTER 1
	 * 	CCMR2-CAPTURE/COMPARE MODE REGISTER 2
	 * 	CCER- CAPTURE/COMPARE ENABLE REGISTER
	 * 	CNT	- COUNT REGISTER
	 * 	CCR3- CAPTURE/COMPARE 3 REGISTER IN ITS OWN
	 * 	DMAR- DMA ADDRESS FOR FULL TRANSFER
	 * */
 
 
	TIM4 -> CR1 &= ~(0x3UL <<(8U));					//	CLOCK DIVISION 1 (NO DIVISION)
	TIM4 -> CR1 &= ~(TIM_CR1_CMS_1);				//	EDGE ALIGNED MODE
	TIM4 -> CR1 &= ~(TIM_CR1_DIR);					//	COUNTER AS UPCOUNTER
	TIM4 -> CR1 &= ~(TIM_CR1_OPM);					//	ONE-PULSE MODE DISABLED
	TIM4 -> CR1 &= ~(TIM_CR1_URS);					//	UPDATE REQUEST SOURCE 
 
	TIM4 -> CCMR2 = 0
	|( 1 *			(6UL << (4U)))		//	OUTPUT COMPARE PWM 1 MODE
	|( 1 *			(1UL << (3U)))		//	OUTPUT COMPARE 3 PRELOAD ENABLE
	|( 0 *			(1UL << (2U)))		//	OUTPUT COMPARE FAST MODE ENABLE
	;
 
	TIM4 -> CCER = 0
	|( 1 *			(1UL << (8U)))		//	CAPTURE/COMPARE OC3 ACTIVE
	|( 0 *			(1UL << (9U)))		//	CC3 POLARITY ACTIVE LOW
	;
 
 
	TIM4 -> DCR	= 0
	|(17UL)								//	INDEX OF CCR3 REGISTER
	|((0UL) << (8U))					//	1 TRANSFER
	;
 
	TIM4 -> ARR	= 40;					// AUTO-RELOAD REGISTER
	TIM4 -> PSC = 10;					// PRESCALER
 
	TIM4 -> DIER |=  (1UL <<(14U));		//	UPDATE DMA REQUEST ENABLE
 
	TIM4 -> CR1 |= TIM_CR1_ARPE; 		//	AUTO-RELOAD PRELOAD ENABLE
 
	DMA1_Stream7 -> CR |= DMA_SxCR_EN;	//	DMA GO!!
	TIM4 -> CR1	|= TIM_CR1_CEN;			//	TIM	GO!!
 
}
 
void DMA_Init(void){
 
	/* CR 	- DMA STREAM CONFIGURATION REGISTER
	 * NDTR	- DMA STREAM NUMBER OF DATA TO TRANSFER
	 * PAR	- DMA STREAM PERIPHERAL ADDRESS
	 * M0AR	- DMA STREAM MEMORY ADDRESS 1
	 * M1AR	- DMA STREAM MEMORY ADDRESS 2
	 * FCR	- DMA STREAM FIFO CONTROL REGISTER
	 * */
 
	DMA1_Stream7 -> M0AR = (uint32_t)valuesTable;		//	MEMORY(BUFFER) ADDRESS
	DMA1_Stream7 ->	PAR = (uint32_t)&TIM4->DMAR;		//	PERIPHERAL ADDRESS (SHADOW REGISTER)
 
	DMA1_Stream7 -> NDTR = sizeof(valuesTable) / sizeof(valuesTable[0]);		//	TRANSFER SIZE
 
	DMA1_Stream7 -> CR = 0
	|( 1 * 			(2UL << (25U)))		//	CHANNEL 2 SELECTED								
	|( 0 *			(0UL << (23U)))		//	MBURST SINGLE TRANSFER
	|( 0 *			(0UL << (21U)))		//	PBURST SINGLE TRANSFER
	|( 0 *			(1UL << (19U)))		//	CURRENT TRGET (ONLY IN DOUBLE-BUFFER MODE)
	|( 0 *			(1UL << (18U)))		//	DOUBLE-BUFFER MODE
	|( 1 *			(1UL << (16U)))		//	PRIORITY LEVEL MEDIUM							
	|( 0 *			(0UL << (15U)))		//	PERIPHERAL INCREMENT OFFSET SIZE 0
	|( 1 *			(1UL << (13U)))		//	MEMORY DATA SIZE HALFWORD						
	|( 1 *			(1UL << (11U)))		//	PERIPH DATA SIZE HALFWORD						
	|( 1 *			(1UL << (10U)))		//	MEMORY INCREMENT MODE ENABLE					
	|( 0 *			(1UL << (9U)))		//	PERIPH INCREMENT MODE ENABLE
	|( 1 *			(1UL << (8U)))		//	CIRCULAR MODE ENABLE							
	|( 1 *			(1UL << (6U)))		//	DATA TRANSFER DIRECTION (MEM 2 PER)				
	|( 0 *			(1UL << (5U)))		//	PERIPHERAL AS FLOW CONTROLLER
	|( 0 *			(1UL << (4U)))		//	TRANSFER COMPLETE INTERRUPT	ENABLE
	|( 0 *			(1UL << (3U)))		//	HALF TRANSFER COMPLETE INTERRUPT ENABLE
	|( 0 *			(1UL << (2U)))		//	TRANSFER ERROR INTERRUPT ENABLE
	|( 0 *			(1UL << (1U)))		//	DIRECT MODE INTERRUPT ENABLE
	;
 
}

11 REPLIES 11

We are not talking about signals going in/out from/to pins, but signals which go to the DMA unit as requests (i.e. which "trigger" one DMA transfer).

These are the signals which are enabled in the upper part of TIMx_DIER register.

TIMx_UP request in DMA comes from the Update signal (i.e. signal which is generated mainly at the timer's "overflow"), as enabled by TIMx_DIER.UDE.

TIMx_CH3 request in DMA comes from the Capture-Compare unit 3, ie. when Capture or Compare occurs, as enabled by TIMx_DIER.CC3DE.

You want the DMA transfers to happen at Update, as that gives the most time for the DMA to accomplish the transfer until the next Update (which would then transfer the freshly written value from CCR3 to the "shadow" register i.e. the internal register against which the actual Compare is performed).

JW

PPiro.1
Associate II

That is exactly what I needed!

Thank you soooo much!

It worked!

I thought of TIMx_UP as some "UP"counter not "UP"date

I had a feeling that there wasn't something clicking and synchronizing the transfer together but I had no idea on how to set it up,

I find the RM all over the place and not very straight forward for someone as unexperienced as me, but I wish to learn more and more

and maybe one day I will feel confident enough to help others like you guys do.

Thank you very much JW!