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 12:45 AM
Your problem is, that executing the ISR (Interrupt Service Routine) takes much longer than 16 cycles - only the hardware entry into ISR takes 12 cycles.
Other remarks:
Start post with stating which STM32 are you using.
Use GPIO_BSRR to change the output state, rather than GPIO_ODR.
Don't Read-Modify-Write (RMW) when clearing TIM_SR flags, rather, write directly.
You might want to consider using symbols defined in device header for individual register bits for better readability.
JW
2020-06-13 12:58 AM
Hi Jan
Apologies that I forgot to mention the uC I used. I updated my original post accordingly. Regarding the actual problem:
If I understand you correctly, the underlying problem is that my uC clock speed (16MHz) is too low. So I should use the PLL to run at a much higher clock frequency and then adjust the prescalers and the ARR registers.
Is that correctly understood?
Regarding your other remarks:
Thank you for your input. I will take them into consideration step by step once I get more used to uC programming.
2020-06-13 01:10 AM
Increasing system clock will help, but generally, you don't want to run interrupts at a MHz rate, as that will eat up much of the system performance.
You report above a 250kHz ISR rate (125kHz output frequency with the toggle) at 16MHz system clock, that's 64 cycles per ISR. That's pretty typical for a relatively short ISR. You intend to run the ISR at 1MHz rate, so even at the maximum system clock 168MHz it will eat up more than a third of the available processor time.
Strive to do as much of the heavy lifting as possible using hardware. Toggling a pin is a task for PWM.
I understand that you do this for testing/learning purposes.
JW
PS. Also, in your code, observe the Delay after an RCC peripheral clock enabling erratum.
PS2. Clear the status register early in the ISR.
2020-06-13 01:40 AM
Hi again
Thank you for your support on this topic. I truly appreciate that. Perhaps I am focused on the wrong solution for a given problem: I need some delays because I want to initialize a HD44780 display which requires different delays (and from the website I found as a reference) down to 20us. On old Atmel 8bit uC, I remember that they came with delay functions using software delay (empty for loops).
Since I seem to have difficulties finding an equivalent delay function for the STM32F407, I decided to use a timer for this purpose.
Or is it overkill to use a timer when initializing the display and I should stick to the empty for loop?
PS:
I do not want to use HAL at this point in time, in order to get a better understanding.
2020-06-13 02:19 AM
Ah that...
The HD44780 was part of a family of peripherals designed for a particular family of Hitachi controllers - they connected straighforwardly to the controllers' external bus, and the hardware matched exactly the protocol of that bus, so from software point of side, it was a matter of writing into a given set of registers, that was all. That were the times when mcus and thus their external buses clocked generally at a few MHz, that's where the timing is from.
Connecting it to other controllers such as the AVRs or '51s became a bit of a pain, as they lacked the hardware, so the interface is generally bit-banged. Clocks went up to a few more MHz up to lower 10s of MHz, so delays were necessary, but that was easy to pull off using a few NOPs or a short loopdelay. It did not hurt performance too much.
Now enter the world of 100MHz+ core mcus. Doing things in software is unwieldly and suddenly controlling the HD44780 became a daunting task, if one does not want to lose much of the computing power. Maybe the FMC can be coaxed into emulating the Hitachi bus, sort-of - I never went down that path but I'm also not sure it's worth it. So, yes, this may be the case, when you may want to do things in a timer interrupt.
Still, you might to want to ask yourself - is it necessary to do it in a tight timing? The timing constraints of the Hitachi bus are *minimums*, so you can stretch it longer, making the interrupts perhaps less often. At the end of the day, what is your target data throughput anyway?
My 2 eurocents.
JW
2020-06-13 02:41 AM
I really do not have a particular application for my uC such as motor control or so. I simply want to play with the STM32F407 discovery board and found some old Atmega leftovers in a bag hidden in the basement, where I found this LCD.
So when getting familiar with ADCs on the STM32F407 at some point in the near future, I thought it would be a nice add-on to my code to output the value to the LCD.
But let me take a step back here, since I bought the HD44780 more than 10years back or so. Googling around, I found LCDs with I2C interface. I actually believe that I will purchase one of those since I2C is another topic I really would like to learn. So I could kill two birds with one stone.
Thank you,
2020-06-13 02:48 AM
I2C may be frustrating.
I'd recommend you to try the HD44780-compatible displays first. Should be fun.
JW
2020-06-13 06:11 AM
> GPIOD->ODR |= (1<<12); // Sets PD12
Use the BSRR register to set/reset individual bits. Otherwise this is going to cause issues if you use GPIOD pins in other parts of your code.
2020-06-13 07:32 AM
Hi
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 :)
One question regarding the purpose of BSRR, since I'm having difficulties translating the engineering language in a language I understand. From the reference manual:
"The purpose of the GPIOx_BSRR register is to allow atomic read/modify accesses to any of the GPIO registers. In this way, there is no risk of an IRQ occurring between the read and the modify access"
I do understand the text, and can better understand your recommendation of using BSRR. But I cannot think of a scenario where it could occur that IRQ is called between a read and modify access of the ODR register.
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?
Thank you.