cancel
Showing results for 
Search instead for 
Did you mean: 

1us timer TIM6 not working as expected (different frequency)

AAnth.1
Senior

Hi all

EDIT: I am using the STM32F407 discorvery board. Apologies for not mentioning this in the original post. 

Just when I thought I understood the timers, I noticed a fundamental understanding problem on my side. I am trying to create a 1us timer using TIM6 and interrupt which toggles an LED. So the LED should be on 1us, then off 1us etc. resulting in 2us period => 500kHz frequency See code below. However, using a logic analyzer measuring with 24MHz, I measure an LED toggle frequency of only 125kHz, so a factor of 4 lower.

Am I missing something in my prescaler or my ARR register?

16MHz clock frequency / (0+1) = 16MHz counter clock -> 1/16MHz = 62.5ns counter period

ARR is set to 15: 62.5ns*(15+1)=62.5ns*16=1us

Thank you,

#include "stm32f4xx.h"
 
 
void funcTimerInit();
void funcGPIOInit();
void funcIntInit();
 
int main(void)
{
	funcTimerInit();
	funcGPIOInit();
	funcIntInit();
 
	for(;;)
	{
	}
 
}
 
void funcTimerInit()
{
	// TIM6 setup
	// Peripheral clock
	RCC->APB1ENR |= (1<<4);	// Enables APB1 bus to use Timer 6
	// 2. Init timer
	TIM6->CR1 &= ~(1<<1);	// UDIS: Update event enabled
	TIM6->CR1 |= (1<<2);	// URS: Update request source enabled for overflow/underflow only
	TIM6->CR1 &= ~(1<<3);	// OPM: One Pulse Mode. Counter continues counting at next update event.
	TIM6->DIER |= (1<<0);	// UIE: Update interrupt enabled
	TIM6->EGR |= (1<<0);	// UG: Update generation. Re-initializes the counter and updates registers
	TIM6->PSC = 0x0000;		// Sets prescaler to 0. Timer clock is now 16MHz/(0+1)=16MHz -> 62.5ns
	TIM6->ARR = 0x000F;		// Counter goes up to (15+1)=16. 16*62.5ns=1us
	TIM6->CR1 |= (1<<0);	// Enables the timer
}
 
 
void funcGPIOInit()
{
	// Peripheral clock
	RCC->AHB1ENR |= (1<<3);	// Enables AHB1 to use GPIOD peripheral
	// 2. Init GPIO
	GPIOD->MODER |= (1<<24);	// Sets GPIOD as Output
	GPIOD->MODER &= ~(1<<25);	// Sets GPIOD as Output (redundant because bit 25 is already 0 after reset)
	GPIOD->OTYPER &= ~(1<<12);	// Set PD12 as Push Pull
	GPIOD->OSPEEDR &= ~(1<<24); // Sets speed on PD12 to low
	GPIOD->OSPEEDR &= ~(1<<25); // Sets speed on PD12 to low
}
 
void TIM6_DAC_IRQHandler()	// Overrides the weak implementation of the IRQ in the startup file
{
	static uint8_t flag = 1;
	if(flag)
	{
		// Turn LED on
		GPIOD->ODR |= (1<<12);		// Sets PD12
		flag = 0;
	}
	else
	{
		// Turn LED off
		GPIOD->ODR &= ~(1<<12);		// Clears PD12
		flag = 1;
	}
 
	TIM6->SR &= ~(1<<0);		// UIF: Update flag bit cleared after interrupt is handled
}
 
void funcIntInit()
{
	// Initialize the interrupt
	__NVIC_EnableIRQ(TIM6_DAC_IRQn);
}

12 REPLIES 12
TDK
Guru

> Thank you. I did change the code to use BSRR instead of ODR after JW's comment above. Good to know the importance of the BSRR register, though =)

Ahh, I skimmed and missed that.

> But that is clearly due to the lack of experience from my side, so if possible, could you please be so kind and elaborate it a bit more so the importance of the BSRR becomes more clear to me?

Interrupts can occur at any time and interject themselves between any instruction in your code. Since "ODR |= ..." is broken into multiple instructions (read, modify, write), an interrupt can occur between these.

Consider "ODR |= 1" in your main loop and "ODR |= 2" in the interrupt:

ODR = 0;
 
// in main loop
val = ODR;
val |= 1;
// in interrupt
val2 = ODR;
val2 |= 2;
ODR = val2;
// back in main loop
ODR = val;

At the end of this, ODR is 1, not 3.

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

[found out that I've just duplicated TDKs description, so I deleted it, sorry for the noise]

Piranha
Chief II

Agreed to Jan that this is a good task for learning hardware and software. And not using HAL and learning the hardware is the right way! Using timer ir also the right way, because simple loops and NOPs can get you in trouble sooner or later. You just don't need MHz rate interrupts. In fact, at least for a start, you don't need interrupts at all.

#define DELAY_TIMER_CLOCK 16000000
 
void Delay_Init(void)
{
	RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // Read errata regarding enabling clocks in RCC!
	TIM6->PSC = DELAY_TIMER_CLOCK / 1000000 - 1; // Counter tick period 1 us
	TIM6->CR1 = TIM_CR1_OPM; // Leave CEN and other bits cleared
}
 
void Delay_us(uint32_t us)
{
	TIM6->ARR = us - 1; // ARR must not be 0
	TIM6->CR1 |= TIM_CR1_CEN;
	while (TIM6->CR1 & TIM_CR1_CEN); // On update CEN is cleared automatically
}

Valid delay range is 2-65536 us because of ARR requirements and overflow implementation. The actual delay will be the requested time plus few CPU and bus cycles. This is probably the simplest delay implementation on STM32, which guarantees a correct minimum delay time.