cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 General-purpose timer cookbook - Example of chapter 6.2 not working as intended

TTepp
Associate

Hi everybody,

I'm trying to test the examples of the General-purpose timer cookbook (AN4776) with a STM32 Nucleo-F446RE board and a breadboard with LEDs and resistors.

This is my first time to use timers for PWM.

I copied and adapted the code of chapter 3.2 and it is working as described:

The timer TIM1 is counting up until reaching the value of CCR1 and changes the GPIO output level from LOW to HIGH until it reaches the value of ARR. The counter starts again.

The most interesting code lines are:

  • TIM1->CCMR1 |= TIM_OCMODE_PWM2; // see reference manual on page 505: In upcounting, channel 1 is inactive as long as TIMx_CNT<TIMx_CCR1 else active. In downcounting, channel 1 is active as long as TIMx_CNT>TIMx_CCR1 else inactive.
  • TIM1->CCER |= TIM_OCPOLARITY_HIGH; // see reference manual on page 510 CC1E

Then I tried the code of chapter 6.2. The intended behavior is that TIM2 triggers TIM1 and the output is HIGH until CCR1 is reached. Then the output change back to LOW until ARR is reached. This is repeated (value of repition counter) and after the last repeat the output remain LOW.

But when I tried the code with my board it does not work as intended. When testing it with my board TIM1 is counting up until CCR1 and change the polarity from LOW to HIGH (so the opposite of what is intended) until ARR is reached. Then the polarity changes back from HIGH to LOW and when reaching CCR1 the polarity change from LOW to HIGH and so on. If the last pulse is send (repitition counter) the output polarity is LOW until TIM2 triggers TIM1 again and start the counting.

So the output is the same as in chapter 3.2 although the example of chapter 6.2 uses PWM mode 1 (In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1 else inactive. In downcounting, channel 1 is inactive (OC1REF=‘0’) as long as TIMx_CNT>TIMx_CCR1 else active (OC1REF=’1’).

This is my code:

void timer1_Init(){
	/* TIM1 is on APB2 with clock speed 180MHz */
	/* activate clock for TIM1 peripheral */
	__HAL_RCC_TIM1_CLK_ENABLE();
 
	/* neue Struktur */
	/* Prescaler */
	TIM1->PSC = 35999;
	/* Auto-Reload-Register */
	TIM1->ARR = 19999;
	/* repetition counter if pulse should be displayed more than 1 time */
	TIM1->RCR = 0;
	/* Set the Capture Compare Register: Pulse */
	TIM1->CCR1 = 4999;
	/* Set Clock */
	TIM1->CR1 &= ~ TIM_CR1_CKD;
	TIM1->CR1 |= TIM_CLOCKDIVISION_DIV1;
	/* set counter mode */
	TIM1->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
	TIM1->CR1 |= TIM_COUNTERMODE_UP;
	/* Reset the Output Compare Mode Bits */
	TIM1->CCMR1 &= ~TIM_CCMR1_OC1M;
	TIM1->CCMR1 &= ~TIM_CCMR1_CC1S;
	/* Select the Output Compare (OC) Mode */
	/*
	 * Mode 1: In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1
	 * else inactive. In downcounting, channel 1 is inactive (OC1REF=‘0’) as long as
	 * TIMx_CNT>TIMx_CCR1 else active (OC1REF=’1’).
	 *
	 * Mode 2: In upcounting, channel 1 is inactive as long as TIMx_CNT<TIMx_CCR1
	 * else active. In downcounting, channel 1 is active as long as TIMx_CNT>TIMx_CCR1 else
	 * inactive.
	 */
	TIM1->CCMR1 |= TIM_OCMODE_PWM1; //(TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1);
 
	/********** One Pulse Mode Configuration **********/
	/* One Pulse Mode */
	TIM1->CR1 |= TIM_CR1_OPM;
	/********** Slave Mode configuration: Trigger Mode **********/
	TIM1->SMCR &= ~TIM_SMCR_TS;
	TIM1->SMCR |= TIM_TS_ITR1;
	TIM1->SMCR &= ~TIM_SMCR_SMS;
	TIM1->SMCR |= (TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1); // = TIM_SLAVEMODE_TRIGGER
	/*************************************************/
	/* Set the Output Compare Preload enable bit for channel 1 */
	TIM1->CCMR1 |= TIM_CCMR1_OC1PE;
	/* update event to reload registers - set the UG Bit to enable UEV  */
	TIM1->EGR = TIM_EGR_UG;
	/* Enable the TIM1 Main Output */
	TIM1->BDTR |= TIM_BDTR_MOE;
	/* Reset and set the Output N Polarity level to LOW */
	TIM1->CCER &= ~TIM_CCER_CC1P;
 
	/* Set the Output Compare Polarity */
	TIM1->CCER |= TIM_OCPOLARITY_LOW; //TIM_CCER_CC1P;
	/* Enable the Capture compare channel 1 on High Level*/
	TIM1->CCER |= TIM_CCER_CC1E;
	
	/* Initialization of GPIO_PIN_8 for PWM output */
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitStruct.Pin = GPIO_PIN_8;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	
}
 
void timer2_Init(){
	/* TIM2 is on APB1 with clock speed 90MHz */
	/* activate clock for TIM2 peripheral */
	__HAL_RCC_TIM2_CLK_ENABLE();
	/* Prescaler */
	TIM2->PSC = 44999; //44999; /* Oszillator might have problems. Prescaler of 44999 should get at period of 0,0005 but doesn't */
	/* set counter mode */
	TIM2->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
	TIM2->CR1 |= TIM_COUNTERMODE_UP;
	/* Auto-Reload Register */
	TIM2->ARR = 19999; //19999;
	/* Set Clock Division */
	TIM2->CR1 &= ~ TIM_CR1_CKD;
	TIM2->CR1 |= TIM_CLOCKDIVISION_DIV1;
 
	/* Update Event - if this timer is configured as Master with output TRGO_UPDATE
	 * the slave timer TIM 1 will get a trigger and run one time
	 *
	 * This bit can be set by software, it is automatically cleared by hardware.
	 * 0: No action
	 * 1: Reinitialize the counter and generates an update of the registers. Note that the prescaler
	 * counter is cleared too (anyway the prescaler ratio is not affected). For more see manual. */
	TIM2->EGR = TIM_EGR_UG;
 
	/* Set Clock Source */
	TIM2->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_TS | TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP);
 
	/* Master Configuration */
	TIM2->CR2 &= ~TIM_CR2_MMS;
	TIM2->CR2 |= TIM_TRGO_UPDATE;
 
 
	/* Enable Counter: */
	TIM2->CR1 = TIM_CR1_CEN;
 
}

I connected a resistor (330) and an LED in series to GPIOA_8.

I also configured a third timer with an interrupt handler to toggle a second LED to see when TIM2 (master) start counting.

Can anybody help me please?

Please find attached the code and a screenshot of the current behavior of chapter 6.2

3 REPLIES 3

> PWM mode 1 (In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1 else inactive.

Yes, but you do

> TIM1->CCER |= TIM_OCPOLARITY_LOW; //TIM_CCER_CC1P;

and as in Cube,

#define TIM_OCPOLARITY_LOW                (TIM_CCER_CC1P)

it means you set CCER.CC1P, which means, the output on the pin is inverted version of OCxREF, i.e. low while CNT < CCR1 and high when CCR1<= CNT <= ARR - and this is what you observed.

Some style remarks:

  • don't do read-modify-write to registers, where you know what resulting value you want to write into - basically all timer registers. Simply write the register only once, ORing together all the constants you want to be set. Note, you did just that in the very last line of your program - indeed, CR1 in your program needs only CEN to be set (although maybe you did that by mistake :-) ).
  • don't use the Cube defined constants. Stick to the constants in CMSIS-mandated device header ([Cube]/Drivers/CMSIS/Device/ST/[STM32 family]/Include/[stm32XXXXXxxl.h]). In that way you both get rid of the Cube "library" entirely, and also won't get caught by such confusingly named constants.

JW

TTepp
Associate

Hi @Community member​ ,

thank you very much for your reply ��

About PWM mode 1: I found the same in the reference manual on page 510 as you described. So I agree that I have to change the Capture/Compare 1 output polarity to high (the initial value is all zero of the register so I don't need to write a value or am I wrong? Please apologize but this is my first projekt with a microcontroller). But it is the oposite of what is written in the timer cookbook (maybe a mistake in the file).

The good news is: the output polarity is high while CNT < CCR1 and low when CCR1 <= CNT <= ARR.

The bad news is: after TIM1 reaches the value of ARR and waits for the next trigger of TIM2 (master) the output polarity change to high while idleing �� . I thought if TIM1 finishes the output gets disabled and the output polarity is low as described in figure 35 in the timer cookbook. So how can I fix this?

0690X000008iOiBQAU.png

About your remarks (just for clarification as I'm from Germany and maybe there could be a loss of translation on my side.

1.) If I get this correct and look at my code of the slave mode configuration I should write "TIM1->SMCR |= (TIM_TS_ITR1 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1);" instead of "TIM1->SMCR |= TIM_TS_ITR1;" and "TIM1->SMCR |= (TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1);"?

So the code where I reset the value of the specific bits in the registers is also not necessary as the register is all 0x00 initially?

2.) OK so for example I changed "TIM1->CCMR1 |= TIM_OCMODE_PWM1;" to "TIM1->CCMR1 |= (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1);".

So my code looks finally like this with regards to you remarks (hope I did everything as described by your remarks. If not I would be really happy about a little snippet):

void timer1_Init(){
	/* TIM1 is on APB2 with clock speed 180MHz */
	/* activate clock for TIM1 peripheral */
	__HAL_RCC_TIM1_CLK_ENABLE();
 
	/* neue Struktur */
	/* Prescaler */
	TIM1->PSC = 35999;
	/* Auto-Reload-Register */
	TIM1->ARR = 19999;
	/* repetition counter if pulse should be displayed more than 1 time */
	TIM1->RCR = 1;
	/* Set the Capture Compare Register: Pulse */
	TIM1->CCR1 = 4999;
 
	/********** Control Register 1 **********/
	TIM1->CR1 |= TIM_CR1_OPM; //setze das dritte Bit
 
	/********** Capture Compare Mode Register 1 **********/
	/* Select the Output Compare (OC) Mode */
	/*
	 * Mode 1: In upcounting, channel 1 is active as long as TIMx_CNT<TIMx_CCR1
	 * else inactive. In downcounting, channel 1 is inactive (OC1REF=‘0’) as long as
	 * TIMx_CNT>TIMx_CCR1 else active (OC1REF=’1’).
	 *
	 * Mode 2: In upcounting, channel 1 is inactive as long as TIMx_CNT<TIMx_CCR1
	 * else active. In downcounting, channel 1 is active as long as TIMx_CNT>TIMx_CCR1 else
	 * inactive.
	 */
	TIM1->CCMR1 |= (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1);
	/* Set the Output Compare Preload enable bit for channel 1 */
	TIM1->CCMR1 |= TIM_CCMR1_OC1PE;
 
	/********** Slave Mode Control Register **********/
	/********** Slave Mode configuration: Trigger Mode **********/
	TIM1->SMCR |= (TIM_TS_ITR1 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1);
	/*************************************************/
 
	/********** Event Generation Register **********/
	/* update event to reload registers - set the UG Bit to enable UEV  */
	TIM1->EGR |= TIM_EGR_UG;
 
	/********** Break and Dead Time Register **********/
	/* Enable the TIM1 Main Output */
	TIM1->BDTR |= TIM_BDTR_MOE;
 
	/********** Capture Compare Enable Register **********/
	TIM1->CCER |= TIM_CCER_CC1E;
 
	/* Initialization of GPIO_PIN_8 for PWM output */
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitStruct.Pin = GPIO_PIN_8;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
 
void timer2_Init(){
	/* TIM2 is on APB1 with clock speed 90MHz */
	/* activate clock for TIM2 peripheral */
	__HAL_RCC_TIM2_CLK_ENABLE();
	/* Prescaler */
	TIM2->PSC = 44999; 
	/* Auto-Reload Register */
	TIM2->ARR = 19999; //19999;
	/* Update Event - if this timer is configured as Master with output TRGO_UPDATE
	 * the slave timer TIM 1 will get a trigger and run one time
	 *
	 * This bit can be set by software, it is automatically cleared by hardware.
	 * 0: No action
	 * 1: Reinitialize the counter and generates an update of the registers. Note that the prescaler
	 * counter is cleared too (anyway the prescaler ratio is not affected). For more see manual. */
	TIM2->EGR |= TIM_EGR_UG;
 
	/* Master Configuration */
	TIM2->CR2 |= TIM_TRGO_UPDATE;
	TIM2->SMCR |= TIM_SMCR_MSM;
 
	/* Enable Counter: */
	TIM2->CR1 |= TIM_CR1_CEN;
}

Hope you can help me @Community member​ ��

Maybe you misunderstand the meaning of TIMx_CCER.CCxP - it does not define an absolute polarity; contrary, it simply switches on an invertor between OCxRef signal (i.e. in case of some of the PWM modes, output of the CCRx vs. CNT comparator) and the output pin. Look at the Output stage of capture/compare channel figure in the timer chapter - the function of CCxP bit is quite clear from that diagram.

In other words, PWM1 with CCxP=0 is exactly the same as PWM2 with CCxP=1, and vice versa, PWM1 with CCxP=1 is exactly the same as PWM2 with CCxP=0.

This sounds like a redundancy and it indeed is, until you get into the more complex settings of the advanced timers. Even then it does not make much of a difference, but don't ask me why is it so, I did not design the thing.

So, if you use sasy PWM1 and CCxP=0 and the one-pulse mode (repeat or not), the timer ends up "parking" after the last rollover, ie. with CNT = 0; that means, TIMx_CNT<TIMx_CCR1 => OCxREF=1 => output pin is high. So you were happy with PWM2 or inverted PWM1 after all?

> I should write "TIM1->SMCR |= (TIM_TS_ITR1 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1);"

---

No. Write

TIM1->SMCR = (TIM_TS_ITR1 | TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1);

This also clears the bits you (I prefer "clear" to ST's use of "reset", as the former clearly means "set to 0" whereas the latter means "set to whatever the initial value is").

Even better, you may write

TIM1->SMCR = 0
  | (0b001 << TIM_SMCR_TS_Pos)      // ITR1, here it means TIM2->TIM1
  | (0b110 << TIM_SMCR_SMS_Pos)  // PWM1 mode, ---,___
;

the constants referring directly to values you see in RM.

(If you don't use gcc, replace 0b001 for just 1 or 0x1, as you prefer, and 0b110 for 6 or 0x6)

(ST should've created symbols for values in those bitfields, clearly indicating their usage, directly in the CMSIS-mandated device header file, and do so in a systemic way - but they refuse to do so and that's for a longer discussion).

But this all is just style, you don't need to follow it, it's much of a personal preference (although based on some experience, too).

JW