Skip to main content
SWenn.1
December 31, 2022
Solved

stm32l476 nucleo board --- experimenting with output compare timer 3 channel 1??

  • December 31, 2022
  • 14 replies
  • 3879 views

Good afternoon everyone....

I am trying to learn more about the stm32 and am using a nucleo L476 and an oscilloscope. In particular I want to understand more about the output compares of the timers. I have read many an app-note , user guide and data sheet regarding these and am now at the stage of testing stuff out.....I am running into complete confusion with respect to this...

I have a 26MHz clock with a prescaler of 26 -1 (should be a 1MHz Cnt clock). I have the ARR register loaded with 100 - 1 (should create an UIF event every 100us). I am using CubeMX to set this up. There are a few questions:

  1. I had to code the macro (see below) bcz CubeMx doesn't seem to check the UIE bit when setting up as compare output.....Can anyone comment as to why?
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);

The following below is my ISR.....My attempt was to have UIF interrupt me every 10khz (see above comment on this), turn the output on and arm the CCR with counter + 10. My thought was that after this expires (CNTR clock is 1MHz so my expectation was 10us later) I would get a CC1R interrupt and then clear the output using FORCED_ACTIVE and INACTIVE statements. There are a few things here:

  1. I notice with breakpoints set neither the macro nor the call after it clear the UIF flag....Why is this??(I have attached pic below showing enables or IRQs set and flags set
  2. I never get into the CC1 portion of the interrupt...any thoughts as to why?? as the expression solves to a '1'
  3. Where do I find the various priorities of the flags that can cause an interrupt in TIMER3?? is the UIE higher priority than the CCxE?
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
	if (htim == &htim3)
	{
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
		{
			if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1))
			{
				__HAL_TIM_CLEAR_FLAG(htim, TIM_IT_CC1);
				htim->Instance->SR &= ~TIM_SR_CC1IF;
				htim->Instance->CCR1 = 0;
				htim->Instance->CCMR1 = 0; //clears mode output bits --- freezes output
				htim->Instance->CCMR1 |= TIM_OCMODE_FORCED_INACTIVE; //forces output to 0??
			}
 
			else if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE))
			{
				__HAL_TIM_CLEAR_FLAG(htim, TIM_IT_UPDATE); //clear UIF Flag
				htim->Instance->SR &= ~TIM_SR_UIF;
				htim->Instance->CCR1 = htim->Instance->CNT + 10;
				htim->Instance->CCMR1 = 0; //clears mode output bits --- freezes output
				htim->Instance->CCMR1 |= TIM_OCMODE_FORCED_ACTIVE; //set output to 1 immediately
			}
 
		}
	}
}

0693W00000Y7AAaQAN.png

This topic has been closed for replies.
Best answer by waclawek.jan

> htim3.Instance->CCR1 = htim3.Instance->CNT + 1000;

This does not result in interrupts exactly 1000 clocks apart. CNT keeps counting after the compare happens, so the next CCR1 includes interrupt latency and execution time, so the second CCR1 will be something like 2050, the third 3100, etc.

And when CCRx>ARR, compare thus interrupt happens at Update.

JW​

PS. note that if you have other, higher or same priority, interrupts​ in your system, interrupt latency may increase dramatically

14 replies

S.Ma
January 1, 2023

Use minimal compiler optimisation code to be able to put breakpoints in most places, even use asm nop as breakpoint spots.

Try using output compare first to make a pwm, and using jumper wire, feed it to the same timer, other channel, programmed as input capture. This may help with minimal lab equipment.needed (although oscilloscope -not logic analysers- is prime debug hw tool for embedded devices)

gbm
January 1, 2023

To make the thing simple, get rid of HAL completely and operate on timer registers only. This way you get full control over the timer register settings and of course also reduce the number of lines in your code by at least 80%.

How do you know that the flags are not cleared? They are getting set every 100 us, so you can't notice them being cleared using the debugger. The timer is still running when you single-step.

Do not use logic operations on TIM SR for clearing the flags - this is very common error leading to events being lost.

Wrong:

htim->Instance->SR &= ~TIM_SR_UIF;

Good:

htim->Instance->SR = ~TIM_SR_UIF;

SWenn.1
SWenn.1Author
January 1, 2023

I have breakpoints right within the ISR Callback on the line that should clear the flag. I step over this and the state of the flag doesn't clear. I've set the break on both the HAL and the register line and neither do the job.

To your point about the Wrong: Good:

Yes the &= and |= is a habit of mine....I think I am safe doing your way bcz the register in question only supports writes of 0.  In MSP world I never have issues with this , would I really see issues in the STM by doing this?

January 1, 2023

Please check out the following instruction, from stm32l4xx_hal_tim.h:

#define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__)   ((__HANDLE__)->Instance->SR = ~(__INTERRUPT__))

KnarfB
January 1, 2023

How do you start the timer?

You may slow down everything by a factor of say 1000 by using a different prescaler. Then you might be able to better watch events, maybe with help of printf tracing, a cheap logic analyzer at the outputs, LED blinking, and so on.

Using of HAL or register level is a matter of the learning path you prefer. Mixing both to achieve relatively simple things might add unneccessary complexity.

hth

KnarfB

gbm
January 1, 2023

You step over this, and the flag clears the it sets again in 100 us. You cant's see it cleared.

Using &= means "clear the selected flag and all other flags set during the execution of this statement". It's not what you want - you may write more than one 0 to the SR this way.

SWenn.1
SWenn.1Author
January 1, 2023

got it on the first point...thanks

Not sure how &= clears other flags....

TIM_SR_UIF = 0x0000 0001 ---> ~TIM_SR_UIF = 0xFFFF FFF7 ----> "anding" this with anything ONLY affects bit 0, which according to the document is how you clear bit 0, by writing a 0 to the flag....all other bits will stay as they are AND be unaffected. This is what you want so when you leave the ISR callback you go right back into it if another flag has been sent...Withing the callback you must have conditional if statements to catch each and every flag....am I missing something here???

For what it's worth:

I am used to doing if else statements within the ISR with each of my if/else statements checking ONLY ONE flag. That way you only have to process a short piece of code within the ISR assuming only one flag has been set (which in my experience is most always the case). In the event two flags are set it is no big deal bcz you service the first, pop out and immediately pop back in to service the second. If I were to clear all flags I would have to attend to all flags in the ISR I would have to have an IF for each flag and go thru the process independent of whether the flag was set or not which would spend processing time and keep the processor awake (ie in high power mode).....I am coming at it from a background of having products that must sleep at 3uA - 5uA (max) of power and last battery life of 8 years.

gbm
January 5, 2023

Yes, you are missing a lot.

The statement:

TIM->SR &= ~TIM_SR_UIF;

is actually executed as:

(1) temp = TIM->SR;

(2) temp &= ~TIM_SR_UIF;

(3) TIM->SR = temp;

where temp is an ARM processor register.

Now think what will happen if another timer event flag, like CC1IF, is set by timer hardware after (1) and before (3) is executed. This flag will be cleared by (3) because it has a value of 0 in temp and your software will have no chance to notice it being set - you will loose the CC1IF event in your ISR..

An old beginner's problem with STM32 timer flags... ;)

S.Ma
January 2, 2023

Sanity check : when breakpoint, the hw maybe still running. Watching timer registers will steal the code's job. Relaunch the code after breakpoint and watching timer values. Is timer frozen when breakpoint?

SWenn.1
SWenn.1Author
January 2, 2023

I have a SystemClk of 80MHz, an AHB1 clock of 20MHz, a TIM3 divisor of 4 and the counter period set to 500....Effectively giving me an UIF every 10kHz. I was able to get code working by deleting the HAL callback within TIM3_IRQHandler and write my own....I've written my own function within the IRQ and it is working....below is a snapshot of code (with 2 breakpoints and registers) I do have some questions....

  1. Placing one breakpoint with the UIF section I see counter and CCR1 register changing as expected....Placing that breakpoint RIGHT after flag clearing and yet I don't see the register update to a 0??
  2. Placing one breakpoint with the CCI1F section I see counter register changing as expected....Placing that breakpoint RIGHT after flag clearing and yet I don't see the register update to a 0??
  3. Placing both breakpoints it seems I only stop on the one that I previously set and it NEVER stops on the second one....So if I placed it in UIF then enable the CCI1F it never goes there and always goes with UIF, same if I placed it in CCI1F then enable UIF it never goes there and always goes into CCI1F section???
  4. Follow up....Can someone point me to information pertaining how many clock cycles it takes to enter and exit ISRs?

I understand breakpoints can be tricky especially within ISR but if the IDE isn't yielding accurate results how can you troubleshoot using it??? I have the Compiler optimizations set to None and Debugger set to Maximum.

0693W00000Y7DLWQA3.png

waclawek.jan
January 2, 2023

If you want to see the flags being cleared, the respective DBGMCU_APB1FZR1 bit to stop timer while debugging, otherwise it will keep running and setting the flags faster than the debugger succeeds to read it out.

The flags in SR are set regardless of whether respective interrupt in DIER is enabled or not. If you want the ISR to handle only the enabled interrupt, you must check which interrupt is enabled and act accordingly. The simplest way is to mask SR with content of DIER (note that position of respective flags matches the bits in DIER, it's no coincidence, it's deliberately designed so this operation can be done easily).

> how many clock cycles it takes to enter and exit ISRs?

Many.

JW

SWenn.1
SWenn.1Author
January 2, 2023

So at this end I am hearing you say (The simplest way is to mask SR with content of DIER (note that position of respective flags matches the bits in DIER)

if (htim3.Instance->SR & htim3.Instance->DIER)

How do you handle if both the CC1IE and UIE are enabled? You still have to check flags, doesn't it make more sense to do what I did above in the picture?

waclawek.jan
January 2, 2023

Yes, you still have to handle the individual interrupt sources separately.

However, how do you do this, is up to you. In microcontrollers, there's no "one perfect" way, there are several options and which one of them is optimal, is a application dependent.

The generic pattern to handle ISR with multiple sources is:

  • read status register only once, as the first thing in the ISR
  • optionally read any other related register at this point, although there's no guarantee
  • mask with enabled
  • use result to clear status bits
  • handle each and every bit (i.e. don't use a switch or if-then-else chain)

In case of timer it's something like

void TIM3_IRQHandler(void) {
 uint32_t sr;
 
 sr = TIM3->SR;
 sr &= TIM3->DIER;
 TIM3->SR = ~sr;
 if (sr & TIM_SR_UIF) {
 // process update/overflow
 }
 if (sr & TIM_SR_CC1IF) {
 // process capture/compare
 }
 // etc.
}

But as I've said, this is no gospel.

JW

SWenn.1
SWenn.1Author
January 2, 2023

0693W00000Y7EX4QAN.jpgI have attached a very basic test that just isn't working as I would expect. Can someone tell me what I am missing here???? I have reduced the code to 20MHz on APB1, using Timer3, pre-scaler set to 0, period set to 20000 - 1...effectively the clock is 20MHz with an interrupt being generated at 1ms (20000/20MHz). I have programmed the CCR1 register with 1000. This SHOULD give an interrupt every 50us as the compare register only cares about the counter NOT the period setting (20000 - 1). Now what I care about is a "consistent and reliable compare output". I don't care about the 1ms interrupt. Below shows the code as well as a scope image. Notice that every 10th pulse is shorter than the other pulses. The UIE bit is NOT set AND the condition in the ISR should never get encountered when UIF goes off every 1ms, BUT as you can see the UIF flag going off toggles the pin and causes a jittery 10th pulse ie 1ms (20000 / 20MHz). If I change the period to 10000 -1 the jittery pulse moves to 5 apart instead of 10 which just supports that the UIF flag is causing this. How come I cannot get these pulses from STM32 product to be this precise??? (beyond frustrated)....This is as bare bones code as I can do and it still is NOT working....Scope says it all.

Notice ONLY CC1IE enabled.

0693W00000Y7EXEQA3.pngCode called just before while(1){}

htim3.Instance->DIER |= TIM_DIER_CC1IE;
 htim3.Instance->CCR1 = 1000;
 htim3.Instance->CR1 |= TIM_CR1_CEN;

Setup for htim3 (done via cubeMX):

htim3.Instance = TIM3;
 htim3.Init.Prescaler = 0;
 htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim3.Init.Period = 20000 - 1;
 htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
 if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
 {
 Error_Handler();
 }
 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
 if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_TIM_OC_Init(&htim3) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_TIMING;
 sConfigOC.Pulse = 0;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;

The ISR that does it all:

void TIM3_IRQHandler(void)
{
	if (htim3.Instance->SR & TIM_SR_CC1IF)
	{
		htim3.Instance->SR = ~TIM_SR_CC1IF;
		htim3.Instance->CCR1 = htim3.Instance->CNT + 1000;
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	}
}

waclawek.jan
waclawek.janBest answer
January 3, 2023

> htim3.Instance->CCR1 = htim3.Instance->CNT + 1000;

This does not result in interrupts exactly 1000 clocks apart. CNT keeps counting after the compare happens, so the next CCR1 includes interrupt latency and execution time, so the second CCR1 will be something like 2050, the third 3100, etc.

And when CCRx>ARR, compare thus interrupt happens at Update.

JW​

PS. note that if you have other, higher or same priority, interrupts​ in your system, interrupt latency may increase dramatically

SWenn.1
SWenn.1Author
January 3, 2023

Thank YOU so much for your help....The following code is jitter free and looks great!!!!

void TIM3_IRQHandler(void)
{
 /* USER CODE BEGIN TIM3_IRQn 0 */
	/*check update IRQ flag*/
	if (htim3.Instance->SR & TIM_SR_CC1IF)
	{
 
		uint32_t temp = htim3.Instance->CNT;
 
		htim3.Instance->SR = ~TIM_SR_CC1IF;
 
		if (temp + 1000 > htim3.Instance->ARR)
			htim3.Instance->CCR1 = (temp + 1000) - htim3.Instance->ARR;
		else
			htim3.Instance->CCR1 = temp + 1000;
 
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	}