8 years, 4 months ago.

Wait function shows incorrect timing on Nucleo F103 board

Hello Community,

I might have found a bug in the wait function when used with Nucleo F103BR boards. I need some simple port toggling after 150us, 1ms and 100ms.

Upon measuring the timings via oscilloscope I see myself confronted with wrong timings at the I/O pins 150µs are 34ms 1ms 65ms and so on

I also verified the problem with 2 other F103 boards to rule out a hardware bug The same code on a F411 Nucleo shows correct timing behavior.

The code of the application can be found in this discussion: https://forums.mbed.com/t/wait-function-shows-incorrect-timing-on-nucleo-board/1673/2

your faithfully, Chris

Question relating to:

Affordable and flexible platform to ease prototyping using a STM32F103RBT6 microcontroller.

3 Answers

8 years, 1 month ago.

Thank you Zoltan for your answer. It helped me to understand. I solved this problem. so: Between the lines

mbed-dev/targets/hal/TARGET_STM/TARGET_STM32F1/us_ticker.c

    counter = (uint32_t)(SlaveCounter << 16);
    counter += cnt_val;

some times was interrupt TIM4_IRQn and update the variable cnt_val. So I was banned it.

mbed-dev/targets/hal/TARGET_STM/TARGET_STM32F1/us_ticker.c

........
// Timer selection
#define TIM_MST TIM4
........
uint32_t us_ticker_read()
{
    uint32_t counter;
    if (!us_ticker_inited) us_ticker_init();

    //Current value of TIM_MST->CNT is stored in cnt_val and is
    //updated in interrupt context
  NVIC_DisableIRQ(TIM4_IRQn);  
    counter = (uint32_t)(SlaveCounter << 16);
    counter += cnt_val;
  NVIC_EnableIRQ(TIM4_IRQn);
    return counter;
}

seems that is there an official fix for this problem https://github.com/ARMmbed/mbed-os/pull/3076/files

posted by Testato Testato 20 Oct 2016

I think my solution is the simpler and better!

posted by Slava JM 20 Oct 2016

Hello Slava,
Thank you for your suggestion. It sounds logical and elegant, but when I tried to test it on a NUCLEO-F103RB with the following code:

main.cpp

#include "mbed.h"

int main()
{
    Serial      pc(USBTX, USBRX);
    DigitalOut  myled(LED1);
    Timer       timer;
    int         begin;
    int         end;
    
    timer.start();
    while(1) {
        myled = !myled;
        begin = timer.read_us();
        wait_us(1);
        end = timer.read_us();
        pc.printf("Delay = %d\r\n",(end - begin));
    }
}


I have got the following results:
Delay = 1001
Delay = 1001
Delay = 1001
Delay = 1001
Delay = 1001
Delay = 1001
Delay = 642
Delay = 1001
Delay = 1001
...

With the fix proposed officially, which is the code already used in mbed-dev revision #135, the results were as follows:
Delay = 8
Delay = 8
Delay = 8
Delay = 8
Delay = 7
Delay = 7
Delay = 8
Delay = 7
...

I am currently using the following implementation:

us_ticker.c

uint32_t us_ticker_read()
{
    uint32_t counter, counter2;
    if (!us_ticker_inited) us_ticker_init();

    // A situation might appear when Master overflows right after Slave is read and before the
    // new (overflowed) value of Master is read. Which would make the code below consider the
    // previous (incorrect) value of Slave and the new value of Master, which would return a
    // value in the past. Avoid this by computing consecutive values of the timer until they
    // are properly ordered.
    
    counter = (uint32_t)(SlaveCounter << 16); 
    counter += TIM_MST->CNT;  
    do {  
        counter2 = counter;  
        counter = (uint32_t)(SlaveCounter << 16);  
        counter += TIM_MST->CNT;  
    } while(counter < counter2); 
    
    return counter;
}


With the results as follows:
Delay = 6
Delay = 6
Delay = 6
Delay = 7
Delay = 6
Delay = 6
Delay = 7
Delay = 6
Delay = 6
...

Update #1:

Hello Slava,
After a minor change it seems that your elegant proposal works correctly:

us_ticker.c

uint32_t us_ticker_read()
{
    uint32_t counter;
    if (!us_ticker_inited) us_ticker_init();
  
    NVIC_DisableIRQ(TIM4_IRQn);  
    counter = (uint32_t)(SlaveCounter << 16);
    counter += TIM_MST->CNT;
    NVIC_EnableIRQ(TIM4_IRQn);
    
    return counter;
}


The results were as follows:
Delay = 6
Delay = 6
Delay = 6
Delay = 7
Delay = 6
Delay = 6
Delay = 7
Delay = 6
Delay = 6
...

Update #2:

Hello Slava,
Finally I have tested the following simple solution:

us_ticker.c

uint32_t us_ticker_read()
{
    uint32_t counter;
    if (!us_ticker_inited) us_ticker_init();
  
    counter = TIM_MST->CNT;
    counter += (uint32_t)(SlaveCounter << 16);
    
    return counter;
}


And guess what, it worked like a charm :)
Delay = 5
Delay = 6
Delay = 7
Delay = 5
Delay = 6
Delay = 7
...

Update #3:

Hello Slava,
After about an hour also the code above returned an inconsistent time stamp. So my proposal is as follows:

us_ticker.c

volatile uint32_t SlaveCounter = 0;
...
volatile uint8_t  tim_it_update;		// TIM_IT_UPDATE event flag
volatile uint32_t tim_it_counter = 0;	// time stamp to be updated by timer_irq_handler()

uint32_t us_ticker_read()
{
    uint32_t counter;

    if (!us_ticker_inited) us_ticker_init();

    tim_it_update = 0;			// clear TIM_IT_UPDATE event flag
    counter = TIM_MST->CNT + (uint32_t)(SlaveCounter << 16);	// calculate new time stamp
    if (tim_it_update == 1)
    	return tim_it_counter;	// in case of TIM_IT_UPDATE return the time stamp that was calculated in timer_irq_handler()
    else
    	return counter;			// otherwise return the time stamp calculated here
}



hal_tick.c

extern volatile uint32_t SlaveCounter;
...
extern volatile uint8_t  tim_it_update;
extern volatile uint32_t tim_it_counter;

void timer_irq_handler(void) {
    cnt_val = TIM_MST->CNT;

    TimMasterHandle.Instance = TIM_MST;

    // Clear Update interrupt flag
    if (__HAL_TIM_GET_FLAG(&TimMasterHandle, TIM_FLAG_UPDATE) == SET) {
        if (__HAL_TIM_GET_IT_SOURCE(&TimMasterHandle, TIM_IT_UPDATE) == SET) {
            __HAL_TIM_CLEAR_IT(&TimMasterHandle, TIM_IT_UPDATE);
            SlaveCounter++;
            tim_it_counter = cnt_val + (uint32_t)(SlaveCounter << 16);
            tim_it_update = 1;
        }
    }

    ...

}


The results are promising :)

posted by Zoltan Hudak 20 Oct 2016

Thank you Zoltan! Your solution very good! My old solution really does not work well with low wait. This is due to the fact that variables (cnt_val, SlaveCounter) are updated every 1 ms and when the timer overflow So my solution is the same as yours: To check for updates to the SlaveCounter. during the reading of the data variables. The difference: If SlaveCounter updated, I repeat the operation.

mbed-dev/targets/hal/TARGET_STM/TARGET_STM32F1/us_ticker.c

uint32_t us_ticker_read()
{
    uint32_t counter,counter2;
    if (!us_ticker_inited) us_ticker_init();
    
    counter = (uint32_t)(SlaveCounter << 16);
    uint16_t val = TIM_MST->CNT;
    
    counter2 = (uint32_t)(SlaveCounter << 16);
    if (counter!=counter2) //To check for updates to the SlaveCounter       
        counter= counter2+TIM_MST->CNT; //repeat the operation
     else 
        counter+=val; 
    return counter;
}
posted by Slava JM 26 Oct 2016
8 years, 2 months ago.

Hello Chris,
Looks like the bug you reported also on the Forum is still there. I think it's caused by late update of the SlaveCounter variable in interrupt context while used in the us_ticker_read() function. One solution could be to wait until the SlaveCounter gets updated by the timer_irq_handler() as follows:

mbed-dev/targets/hal/TARGET_STM/TARGET_STM32F1/us_ticker.c

// ...
const int MAX_LOOPS = 5
// ...
volatile uint32_t previousCounter = 0;
// ...
uint32_t us_ticker_read()
{
    uint32_t counter;
	int      i = 0;

    if (!us_ticker_inited) us_ticker_init();

    //Current value of TIM_MST->CNT is stored in cnt_val and is
    //updated in interrupt context
    do {
    	counter = (uint32_t)(SlaveCounter << 16);
        counter += cnt_val;
    	// Repeat until SlaveCounter gets updated in interrupt context
        // Also take into account roll over and prevent endless looping
	} while((counter < previousCounter) && (SlaveCounter != 0) && (i++ < MAX_LOOPS));
    previousCounter = counter;
    return counter;
}


Although that isn't a perfect solution it would considerably improve the performance of wait() function for NUCLEO-F103RB boards.

8 years, 2 months ago.

but is there an official issue opened on GIthub ?

Opened an official issue, confirmed the bug https://github.com/ARMmbed/mbed-os/issues/2910

posted by Testato Testato 07 Oct 2016

Hello Testato,
Thank you for opening an official issue. Let's hope it is going to be fixed soon.

posted by Zoltan Hudak 22 Oct 2016