2
\$\begingroup\$

I'm running three tasks using STM32 RTOS v2. I want to know the time difference between button presses, so I've configured it as follows:

mic1TaskHandle = osThreadNew(StartMic1Task, NULL, &mic1Task_attributes);
mic2TaskHandle = osThreadNew(StartMic2Task, NULL, &mic2Task_attributes);
mic3TaskHandle = osThreadNew(StartMic3Task, NULL, &mic3Task_attributes);

But the values ​​will vary depending on the task execution order above. If done as above, the values ​​will appear in the order 1176871634, 1176876058, and 1176879754. The execution order will be:

mic3TaskHandle = osThreadNew(StartMic3Task, NULL, &mic3Task_attributes);
mic2TaskHandle = osThreadNew(StartMic2Task, NULL, &mic2Task_attributes);
mic1TaskHandle = osThreadNew(StartMic1Task, NULL, &mic1Task_attributes);

If you change it like this, it will change to 1462456652, 1462452922, 1462460236. In other words, it seems like the task that was executed first is called slightly earlier.

I use RTOS because I want it to run concurrently. Is there a way to compensate for this?

volatile uint8_t isButtonClicked1 = 0;
volatile uint8_t isButtonClicked2 = 0;
volatile uint8_t isButtonClicked3 = 0;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == BTN_INT_Pin)
    {
        isButtonClicked1 = 1;
        isButtonClicked2 = 1;
        isButtonClicked3 = 1;
    }
}

void StartMic1Task(void *argument) {

    while (1) {
        if (isMic1Get == 0 && isButtonClicked1 == 1) {
            mic1GetTick = DWT->CYCCNT;
            mic1Offset -= mic1GetTick;
            isMic1Get = 1;
            break;
        }
    }
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ humni, It looks to me as though you are only testing one pin (setting all three "clicked" flags in the same callback.) What you are finding is that only one thread is being executed at a time. Which should be no shock to anyone. Your code seems like some kind of experiment and the results you got should have been expected. I've written perhaps 20 different operating systems, some cooperative and some pre-emptive, as well as working on existing kernel code for Unix, Linux, FreeBSD, and even earlier editions of Windows. What do you need and why do you need it? What's your requirements here? \$\endgroup\$ Commented Nov 10 at 5:48
  • 1
    \$\begingroup\$ humni, (The STM32H745 itself appears to have two cores. But I've not looked into how its memory system works together with the dual cores. So even hoping about getting the same time-stamp, even in that particular case, would require study and if that resulted in a possible approach then carefully crafted code, too. But I've my doubts even in that case.) \$\endgroup\$ Commented Nov 10 at 5:54
  • 1
    \$\begingroup\$ If accurate timing measurement is important for your application, then you need to learn to use the hardware Timer peripherals in Capture mode rather than relying on reading from the DWT in software. If you did this then it wouldn't matter whether you use an RTOS or not. \$\endgroup\$ Commented Nov 10 at 13:30

2 Answers 2

5
\$\begingroup\$

One CPU core only executes one task at any given time.

The illusion of "doing several things at the same time" is created by the OS or interrupts switching quickly between which tasks is currently being executed on that CPU core.

When an interrupt occurs, the CPU will shelve its current context (registers, program counter, etc) into memory to be able to resume later from where it was, then switch to the interrupt code, execute it, then once finished resume from where it was.

A multitasking OS expands on that, by having interrupts that can resume into another waiting task. So, for example if data is received on the serial port, an interrupt is raised, the OS reads the data, then checks if there was a task waiting for that. If so, it can give control to that task, otherwise to any other task waiting to execute.

If the current task does not yield, the OS will let it execute for a certain amount of time until a timer interrupt occurs, then switch to another waiting task, attempting to share available CPU time in a useful manner.

This introduces the notion of latency: when something happens (like a button press) what is the delay until your code executes to process the event? Sometimes you want that delay to be as low as possible, sometimes a guaranteed upper bound is alright, or you may want the delay to be as constant as possible. This latency also depends on how many other things may happen at the same time which also require processing.

Various ways to acquire a timestamp for a level change on a pin:

  • Polling the pin with code

This will only notice the change when the code is executing, so when no other task or interrupt is executing instead. Thus the latency will depend on task load, other tasks priority, etc. This has the highest maximum latency, potentially milliseconds or more, and it wastes a lot of CPU time polling the pin.

  • Pin change interrupt

The MCU is configured to raise an interrupt on level change on the pin. When that occurs, there is a short wait (few cycles to few tens of cycles depending on MCU) as the CPU stores its current execution context and switches to your interrupt code. However, if several other interrupts arrive at the same time, or if it was already processing a higher priority interrupt, then it'll have to finish that before executing your interrupt code checking the pin, which adds latency. If several pins change at the same time, who knows in what order the interrupts will execute.

Interrupt code should only do what needs to be done urgently, like storing the timestamp or input data in a FIFO. Then, later, a task can process it.

  • Timer/counter in capture mode

This sidesteps the CPU, and this time, if you have several timers or capture units, they do run in parallel. When the level change occurs, the capture unit will store the current value of the counter (acting as timestamp) into a register, then raise an interrupt. This has latency, so the event will be processed later, but the event timestamp that was stored by the capture unit is as accurate as possible.

In this case the latency for capturing the timestamp is fixed, very low, and known. If several events happen at the same time, they can be captured at the same time, with an error of +/- 1 cycle depending when they happen relative to the time the counter increments.

This still does not guarantee interrupts will execute in the order of the incoming level change events, but the timestamps will be correct.

\$\endgroup\$
1
\$\begingroup\$

As your tasks appear to have no "yield" mechanism, each task runs as long as specified somewhere until a system timer tick interrupt runs which changes the context to run another task that is able to run.

There is a single CPU core executing one task at a time. Running things concurently does not mean they are run simultaneously or interleaved per assembly opcode or even per line of C code, the tasks run for a given time slice unless the task execution cannot continue (such as waiting for mutex) or the task informs the RTOS that it has nothing left to do for the rest of the time slice so another task can be run instead of one task idling and consuming execution time.

If you cannot tolerate having three button tasks that run one after another, and showing different timestamps, have one task that checks all three buttons. But in that case, all other tasks that are running will delay checking all three buttons.

But your button flags are set in an interrupt so you could read the button timestamp in the interrupt, not in the task.

There's likely multiple ways to achieve what you want, but in the end, do consider how important it is to use interrupts for reading some user buttons, and if it is, consider where to read the timestamp in relation to printing them.

Also the MCU has hardware timers that can capture the timestamp on an external event and latch the timer value and raise interrupts from these capture events, or even DMA could be utilized to store series of trigger timestamps into memory from where the CPU can read them when it has time.

Of course the finer details how that specific RTOS works must be documented in some manual that should be available for reading, but it's unclear if you are running FreeRTOS or some other RTOS.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.