2020-06-13 12:37 AM
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);
}
2020-06-13 07:44 AM
> 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.
2020-06-13 10:25 AM
[found out that I've just duplicated TDKs description, so I deleted it, sorry for the noise]
2020-06-14 05:58 AM
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.