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
TDK
Guru

Don't you want to write values to CCRx and not DMAR? The CCRx registers have shadow registers themselves.

If you feel a post has answered your question, please click "Accept as Solution".
PPiro.1
Associate II

Thank you for the response and the reason I went with the DMAR was because it was implemented by someone else in their code, I pretty much went with

what they did, hoping I could at least get what they did and make changes from there.

I just changed it to CCR3 and got the same result, nothing is being sent to the CCR3.

I also tried, generating a signal that would send all ones to the LEDs and all of them lit up, but that was just a PWM at the right

duty cycle and ARR period. That confirmed that the PWM works fine but I still cannot vary the CCR value to imitate the ones and zeroes in the stream.

> TIM4 -> DIER |= (1UL <<(14U)); // UPDATE DMA REQUEST ENABLE

Are you sure? I don't have RM at hand but this doesn't sound right to me.

Why don't you use the symbols from the CMSIS-mandated device header?

JW

0693W000000VVo9QAG.png

PPiro.1
Associate II

Hi JW,

I'm sorry, I had this register set as:

TIM4 -> DIER |= TIM_DIER_UDE

and it didn't work for me so I started to mess around and forgot to put it back to what it was originally.

It is back to TIM4 -> DIER |= TIM_DIER_UDE .

The reason I didn't use CMSIS mandated header was because I was getting confused with what each one of them meant and I

went to the RM and started plugging the values from RM.

I will go back and use CMSIS defines to make it easier to read for you, thank you for getting back!

Whatever, just read out the registers' content and check/post

JW

PPiro.1
Associate II

I cleaned it up a little

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] |= (1UL << (0U));				//	ALTERNATE FUNCTION #2 (TIM4 CH3)
 
}
 
 
void DMA_Init(void){
 
 
	DMA1_Stream7 -> M0AR = (uint32_t)valuesTable;		//	MEMORY 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 * 			DMA_SxCR_CHSEL_2)	//	CHANNEL 2 SELECTED
	|( 1 *			DMA_SxCR_PL_0)		//	PRIORITY LEVEL MEDIUM
	|( 1 *			DMA_SxCR_MSIZE_0)	//	MEMORY DATA SIZE HALFWORD
	|( 1 *			DMA_SxCR_PSIZE_0)	//	PERIPH DATA SIZE HALFWORD
	|( 1 *			DMA_SxCR_MINC)		//	MEMORY INCREMENT MODE ENABLE
	|( 1 *			DMA_SxCR_CIRC)		//	CIRCULAR MODE ENABLE
	|( 1 *			DMA_SxCR_DIR_0)		//	DATA TRANSFER DIRECTION (MEM 2 PER)
	;
 
}
 
void TIM_Init(void){
 
	TIM4 -> CR1 &= ~(TIM_CR1_CKD_1);			//	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);
 
	TIM4 -> CCMR2 = 0
	|( 1 *			(TIM_CCMR2_OC3M_2 << (1U)))	//	OUTPUT COMPARE PWM 1 MODE
	|( 1 *			(TIM_CCMR2_OC3PE))		//	OUTPUT COMPARE 3 PRELOAD ENABLE
	;
 
	TIM4 -> CCER = 0
	|( 1 *			(TIM_CCER_CC3E))		//	CAPTURE/COMPARE OC3 ACTIVE
	;
 
	TIM4 -> BDTR = 0 | TIM_BDTR_MOE;
 
	TIM4 -> DCR	= 0
	|(15UL)								//	INDEX OF CCR3 REGISTER
	|(TIM_DCR_DBL_0)					//	1 TRANSFER
	;
 
	TIM4 -> ARR	= 40;
	TIM4 -> PSC = 10;
 
	TIM4 -> DIER |=  TIM_DIER_UDE;		//	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!!
 
}

> |( 1 * DMA_SxCR_CHSEL_2) // CHANNEL 2 SELECTED

No. DMA_SxCR_CHSEL_2 is bit 2 of the CHSEL bitfield, so this sets the CHSEL bitfield to 0b100 = 4 i.e. you've selected channel 4.

You want

|( 2 * DMA_SxCR_CHSEL_0) // CHANNEL 2 SELECTED

or, maybe visually better, if you have newer header file (the ***_Pos symbols were not defined in the headers in the SPL era)

| (2 << DMA_SxCR_CHSEL_Pos)

> TIM4 -> DIER |= TIM_DIER_UDE; // UPDATE DMA REQUEST ENABLE

yes, that's better than triggering DMA from CC3, but then you need not Channel 7 but channel 6:


_legacyfs_online_stmicro_images_0693W000000VZOK.png

If after doing this still no cigar, read out the registers' content in debugger and check/post.

JW

PPiro.1
Associate II

I'm sorry if I ask a dumb question,

but what is the difference between TIM4_CH3 and TIM4_UP can they be used on the same GPIO Pin?

Do I need to use different CCR registers? would it be CCR1 CCR2 or so on?

In the data sheet for the MCU Alternate functions for the pin I have to use are TIM4_CH3 or TIM10_CH1.

I am totally clueless about the TIMx_UP