incorrect micros() value for stm32f411re

Post here first, or if you can't find a relevant section!
ldelana
Posts: 7
Joined: Mon Nov 23, 2020 6:21 pm

incorrect micros() value for stm32f411re

Post by ldelana »

I am unable to retrieve a correct value of micros(), following my setup:
- STM32F411RE mcu
- external frequency generated by a pwm 50% duty connected to PC2
- in the hardware interrupt routine I save delta of micros() into a E1_period_us variable
- for testing purpose I setup PC10 output pin when interrupt happens

In following scope D7 is the signal generated externally while D0 is the signal generated by digitalWrite inside the interrupt, as seen perfectly aligned with RAISING

Image

but the problem is that measured period is wrong ( 1241us instead of expected 1221us ) as seen in follow serial output

period:1241.00 freq:805.80

Note
  • the problem not appears if using stm32f103
  • an equivalent code running on Arduino Nano produces expected results like follow:
    period:1220.00 freq:819.67
Following the code I used, any suggestion on how I can modify to reduce gap real and detected frequency is really appreciated.

Code: Select all

#include <limits.h>

unsigned long timeDiff(unsigned long now, unsigned long start)
{
    if (start <= now)
        return now - start;
    else
        return (ULONG_MAX - start) + now + 1;
}

#define SERIAL_BAUD 115200
#define E1_PIN PC2
#define M1_PIN PC10

volatile bool E1_started = false;
volatile float E1_period_us = 0.0;
volatile uint32_t E1_m = micros();

volatile bool state = false;

void ISR_E1()
{
    if (!E1_started)
    {
        E1_started = true;
        E1_m = micros();
    }
    else
    {
        uint32_t m = micros();
        E1_period_us = timeDiff(m, E1_m);
        E1_m = m;
    }

    digitalWrite(M1_PIN, state ? HIGH : LOW);
    state = !state;
}

void setup()
{
    Serial.begin(SERIAL_BAUD);
    Serial.println("SETUP");

    pinMode(E1_PIN, INPUT);
    pinMode(M1_PIN, OUTPUT);

    attachInterrupt(E1_PIN, ISR_E1, RISING);
}

uint32_t m = millis();

void loop()
{
    uint32_t m2 = millis();
    if (timeDiff(m2, m) > 1000)
    {
        m = m2;
        Serial.print("period:");
        Serial.print(E1_period_us);

        Serial.print(" freq:");
        Serial.println(1.0 / ((float)E1_period_us * 1e-6));
    }
}
Following ArduinoNano equivalent code where external pwm need to be attached on digital pin 2

Code: Select all

#include <limits.h>

#define PWM_PIN 2

unsigned long timeDiff(unsigned long now, unsigned long start)
{
    if (start <= now)
        return now - start;
    else
        return (ULONG_MAX - start) + now + 1;
}

volatile bool E1_started = false;
volatile float E1_period_us = 0.0;
volatile uint32_t E1_m = micros();

void int0_isr()
{
    if (!E1_started)
    {
        E1_started = true;
        E1_m = micros();
    }
    else
    {
        uint32_t m = micros();
        E1_period_us = timeDiff(m, E1_m);
        E1_m = m;
    }
}

void setup()
{
    Serial.begin(115200);
    Serial.println("SETUP");

    pinMode(PWM_PIN, INPUT);

    attachInterrupt(0, int0_isr, RISING);
}

uint32_t m = millis();

void loop()
{
    uint32_t m2 = millis();
    if (timeDiff(m2, m) > 1000)
    {
        m = m2;
        Serial.print("period:");
        Serial.print(E1_period_us);

        Serial.print(" freq:");
        Serial.println(1.0 / ((float)E1_period_us * 1e-6));        
    }
}
by mlundin » Fri Nov 27, 2020 10:01 am
If you check the STM32Duino source code for the variant NUCLEO_F767ZI: https://github.com/stm32duino/Arduino_C ... ariant.cpp you can see that it is configured with the HSI clock as source for the PLL. This clock has an accuracy on the order of 0.5 to 1% that can explain your results, that would mean your results are not a problem with the micros() code, but depends on the core clock configuration for the board.
Go to full post

stevestrong
Posts: 390
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 7
Location: Munich, Germany
Contact:

Re: incorrect micros() value for stm32f411re

Post by stevestrong »

Which board and which core do you use?

With which frequency runs the MCU? I assume that faster than F103 and nano, so that the difference can be caused by this.
The F4x family MCUs run faster (has improved pipeline processing) when not branching compared to F103 MCUs.

Anyway, your coding logic is not the best. You should store the value of micros() at the very beginning of the ISR and process it later within the if-else body.

ldelana
Posts: 7
Joined: Mon Nov 23, 2020 6:21 pm

Re: incorrect micros() value for stm32f411re

Post by ldelana »

Thanks for suggestion, I tried moving millis() to the top but nothing changes,
stm32f446re runs 180Mhz and I got the problem explained
while with stm32f103 running at 72Mhz or ArduinoNano at 16Mhz the problem not appears.

I don't understand exactly why an higher mcu freq should cause 20us distance from correct measurement for the ISR hit, I suspect it might related to the board interfacing itself; for a proof of concept I will try with another board nucleo144-767zi actually I haven't here with cpu that runs at 216Mhz and I'll let know if something changes.

stevestrong
Posts: 390
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 7
Location: Munich, Germany
Contact:

Re: incorrect micros() value for stm32f411re

Post by stevestrong »

ldelana wrote:
Mon Nov 23, 2020 11:50 pm
Thanks for suggestion, I tried moving millis() to the top but nothing changes,
Well, you shouldn't just move one line to the top, but rewrite the code so that micros() is read only one time within the ISR.

Code: Select all

void int0_isr()
{
    uint32_t m = micros();
    if (!E1_started)
    {
        E1_started = true;
    }
    else
    {
        E1_period_us = timeDiff(m, E1_m);
    }
    E1_m = m;
}

Faster MCUs get smaller micros value for the true part of "if" section where you start measurement, that is why the result is larger than with slower MCU.

Oh, btw, timeDiff() I would implement this way:

Code: Select all

uint32 timeDiff(uint32 now, uint32 start)
{
    return (now - start); // the subtraction of unsigned values will always yield positive value
}
However, for exact measurement I would use a hardware timer.

ldelana
Posts: 7
Joined: Mon Nov 23, 2020 6:21 pm

Re: incorrect micros() value for stm32f411re

Post by ldelana »

thanks you stevestrong for suggestions, I tried but the results still the same, even with another board with stm32-f767zi;

only two notes about what you pointed:

1) call micros only one time, I achieved this already in my initial code because of the `if` branch micros() will be executed only 1 time foreach isr ( only flash code results bigger )

Code: Select all

void int0_isr()
{
    if (!E1_started) // this branch executes first isr hit, then never executed because of E1_started become true
    {
        E1_started = true;
        E1_m = micros();
    }
    else // this branch execute 2th hit and others because E1_started now true
    {
        uint32_t m = micros();
        E1_period_us = timeDiff(m, E1_m);
        E1_m = m;
    }
}
btw I tried even with an isr like follow

Code: Select all

void int0_isr()
{
    uint32_t m = micros();
    E1_period_us = m-E1_m;    
    E1_m = m;
}
but I still got period=1165us instead of expected 1145us

2) nice catch for timeDiff optimization, thanks

stevestrong
Posts: 390
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 7
Location: Munich, Germany
Contact:

Re: incorrect micros() value for stm32f411re

Post by stevestrong »

ldelana wrote:
Wed Nov 25, 2020 1:50 pm
1) call micros only one time, I achieved this already in my initial code because of the `if` branch micros() will be executed only 1 time foreach isr ( only flash code results bigger )
I know but the point is that the time from starting the ISR till read micros is different for the "if" true or false branches in your version. My version eliminates that issue.

Maybe you find interesting to measure a pulse width using hardware timer from this example: https://github.com/rogerclarkmelbourne/ ... MInput.ino

ldelana
Posts: 7
Joined: Mon Nov 23, 2020 6:21 pm

Re: incorrect micros() value for stm32f411re

Post by ldelana »

Maybe you find interesting to measure a pulse width using hardware timer from this example: https://github.com/rogerclarkmelbourne/ ... MInput.ino
Thanks again stevestrong for the hint, I'll sure give a try.

Meanwhile I managed to make it work using another framework mbed, that was not my original intention, but might useful to this https://github.com/stm32duino/Arduino_C ... -355992687 fact about getCurrentMicro() that's used in turn by micros().

mbed code test:

Code: Select all

#include <mbed.h>
#include "util.h"

#define SBUF 80
#define FBUF 20

#define PRINT_DELAY 250ms

static BufferedSerial pc(PD_5, PD_6);
char buf[SBUF];
Timer t;
InterruptIn int0(PD_0);

volatile uint32_t E1_m = 0.0;
volatile uint32_t E1_period_us = 0.0;
volatile bool E1_started = false;

void int0_isr()
{
    if (!E1_started) // this branch executes first isr hit, then never executed because of E1_started become true
    {
        E1_started = true;
        E1_m = t.read_us();
    }
    else // this branch execute 2th hit and others because E1_started now true
    {
        uint32_t m = t.read_us();
        E1_period_us = m - E1_m;
        E1_m = m;
    }
}

int main()
{
  t.start();
  int0.rise(int0_isr);
    
  pc.set_baud(115200);
  pc.set_format(8, BufferedSerial::None, 1);

  while (true)
  {
    float freq = (float)1e6 / E1_period_us;
    char buf2[FBUF];              

    sprintf(buf, "period: %lu us ; freq: %s Hz\n", E1_period_us, _float_to_char(freq, buf2, FBUF));
    pc.write(buf, strlen(buf));
    
    ThisThread::sleep_for(PRINT_DELAY);
  }
}
result:

detected period,freq matches those from logic analyzer; EDIT: btw, with higher freqs 10khz computed values isn't stable even within this framework ; I think the the way to go is the one you suggested ( hardware timers ).

Image

dannyf
Posts: 33
Joined: Sat Jul 04, 2020 7:46 pm

Re: incorrect micros() value for stm32f411re

Post by dannyf »

result
you will notice that your reading changes a little bit, partly because of the sequence of reading the timer can differ based on if you are trying to read the leading edge or the trailing edge.

one way to do that is to always read the timer first in the isr

ldelana
Posts: 7
Joined: Mon Nov 23, 2020 6:21 pm

Re: incorrect micros() value for stm32f411re

Post by ldelana »

btw, with higher freqs 10khz computed values isn't stable even within this framework
I'm sorry I seen from the scope that the instability in the signal was coming from the signal generator itself, so I setup a more stable pwm generator at 10khz signal using an stm32f108 and then I compared stm32duino vs mbed.

Following results at 1Khz ( first 10 sec ):

stm32duino

Code: Select all

SETUP
period:1015.00 freq:985.22
period:1015.00 freq:985.22
period:1016.00 freq:984.25
period:1016.00 freq:984.25
period:1014.00 freq:986.19
period:1015.00 freq:985.22
period:1015.00 freq:985.22
period:1015.00 freq:985.22
period:1014.00 freq:986.19
period:1016.00 freq:984.25
mbed

Code: Select all

SETUP
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 999 us ; freq: 1001.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
period: 1000 us ; freq: 1000.00 Hz
Following results at 10Khz ( first 10 sec ):

stm32duino

Code: Select all

SETUP
period:102.00 freq:9803.92
period:102.00 freq:9803.92
period:101.00 freq:9900.99
period:102.00 freq:9803.92
period:102.00 freq:9803.92
period:102.00 freq:9803.92
period:102.00 freq:9803.92
period:101.00 freq:9803.92
period:102.00 freq:9803.92
period:101.00 freq:9900.99
mbed

Code: Select all

SETUP
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
period: 101 us ; freq: 9900.99 Hz
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
period: 100 us ; freq: 10000.00 Hz
Then I tried within timer / counters from this (https://github.com/stm32duino/STM32Exam ... apture.ino) example but results still not accurate

Code: Select all

START
Channel:1
Frequency = 0
Frequency = 9836
Frequency = 9853
Frequency = 9853
Frequency = 9856
Frequency = 9854
Frequency = 9856
Frequency = 9858
Frequency = 9855
Frequency = 9849
you will notice that your reading changes a little bit, partly because of the sequence of reading the timer can differ based on if you are trying to read the leading edge or the trailing edge.
I agree to the fact that read_us should go to the first instruction because interrupt generated on trigger and each cycle spent in something else results in a prolong of the read_us value, btw the `if` branch should consume about 1cycle clock (ie. 5ns at 180mhz) and should not interfere with final measurement; what I pointed here is that there is something in stm32duino that cause a later value for read_us() not experienced using the same approach with different framework.

Note: using STM32F108C8 with stm32duino I got better results while the problem applies only to stm32f4 and stm32f7 families that I tested:

stm32duino on stm32f1

Code: Select all

SETUP
period:100 freq:10000.00
period:101 freq:9900.99
period:100 freq:10000.00
period:100 freq:10000.00
period:100 freq:10000.00
period:100 freq:10000.00
period:100 freq:10000.00
period:100 freq:10000.00
period:100 freq:10000.00
period:100 freq:10000.00

mlundin
Posts: 35
Joined: Wed Nov 04, 2020 1:20 pm
Answers: 2

Re: incorrect micros() value for stm32f411re

Post by mlundin »

I ran your code, from first post, on a DISCO F4 and the results are perfect. fin=800Hz and period:1250.00 freq:800.00, occasionally period is 1251.

What system clock configuration is active on your boards ?

Post Reply

Return to “General discussion”