cancel
Showing results for 
Search instead for 
Did you mean: 

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

SWenn.1
Senior III

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

20 REPLIES 20

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

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?

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
Senior III

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);
	}
}

> 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
Senior III

Thank you very much for the insight! I really appreciate it.....What I cannot get my head around is that first I am doing nothing else (no other peripherals other than the serial wire debug) is being done....Nothing configured and while loop is empty....My system clock has been slowed to 20MHz....I don't see how it is possible that going into an interrupt reading a counter (bare metal) and adding 1000 clock cycles wouldn't be slow enough for the processor to accurately do...I can do this all day in an MSP430 and never miss a beat at 20MHz...I purposely slowed things down and enlarged the count value to test this.

There must be something that can guarantee exact timing?? This would be absolutely useless if you wanted to take consistent continuous A/Ds for FFT work........

I have no other interrupts in the system.....Is there a way to push up my priority (my understanding is 0 is the highest) or increase CCR1 OVER UID?.....

    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);

SWenn.1
Senior III

As you suggested earlier CCR > ARR....

below code is getting me closer....still a little jitter

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 = htim3.Instance->ARR - temp + 1000;
		else
			htim3.Instance->CCR1 = htim3.Instance->CNT + 1000;
 
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	}

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);
	}

> uint32_t temp = htim3.Instance->CNT;

Note, that you are still basing your calculation on the CNT, which is counting thus subject to interrupt latency plus execution time from ISR beginning to the point of readout. If you measure precisely, you'll see distance between edges somewhat larger than 1000 counts, maybe 1050 or so.

For precise calculation you would need to advance the CCRx, so it changes as 1000-2000-3000 etc.

Also a minor correction at the rollover: note, that after ARR comes 0, so you want your formula for the (tmp + 1000) > ARR to be (tmp + 1000 - ARR - 1).

JW

SWenn.1
Senior III

The real trick here I realized is that in the MSP world I traditionally let the counter roll over at max count in which case when a value is added to CCR if it is larger than 16 bits it just wraps around nicely and all is well. I am sure if I placed a max count on the counter in the MSP world I would have the same issue. In STM world with higher clocks and 32 bit registers I guess I was thinking of limiting the max count.