3
\$\begingroup\$

Background

I understand, maybe I'm wrong, an asynchronous delay would be one that does not completely take over the CPU.

On the other hand, the synchronous delay remains in a loop, counting clock cycles, until the desired time is reached.

A type of synchronous delay is the instructions called delay, depending on the compilation language, for example, in C they are something like this:

delay_ms(30); // wait 30 millisecons

An asynchronous delay is a bit more complex to use, it requires a timer that is running freely and auxiliary variables.

At a given time, the auxiliary variable captures the value of the timer, again a rough example in C would be like this:

instantaneousValueTimer = SysTick->VAL;

Then, to create a delay, I must perform the subtraction between the timer value and the captured value; if the result of this subtraction is greater than or equal to the number of cycles that equal the delay, we can say that the desired time has been achieved.

Again in C, an example:

if (((SysTick->VAL) - instantaneousValueTimer) > _500ms)
{
    gpio_toggle_output_bit(LED_GREEN_GPIO_PORT, LED_GREEN_PIN);
    instantaneousValueTimer = SysTick->VAL;//I again capture the timer value to generate another delay
}

Where _500ms, can be the value that the number of cycles counted is equivalent to 500 ms.

This way, the program counter is not so to speak "stuck" in a loop, the CPU can do other tasks, and then return to the asynchronous delay to determine if the time has elapsed.

What are the disadvantages of this?

  1. A free-running timer, usually 24 bits wide or better yet 32 ​​bits wide, since if you want large delay values, you may not be able to achieve this with a 16-bit or smaller timer and require auxiliary variables, making it somewhat shabby.
  2. The difference between two variables, even if they are unsigned, can give a negative value, which implies using a function or other method to calculate the absolute value of said difference. In some compilers, I have noticed that when subtracting a large number from a small number (both being unsigned integers), the absolute value is obtained, without having to resort to what was explained above.
  3. It's not exact, this is because if the delay hasn't been met, and the CPU is going to do other tasks (which should also be asynchronous), the more things it does before re-evaluating if the delay has been met, the more likely it is that when it checks again, the elapsed time will be slightly longer than expected (hence the "greater than" > condition). If someone wants something precise, they'll have to use interrupts or sacrifice system performance by using synchronous delays. So an asynchronous delay would be useful for things where time precision isn't critical, for example turning on and off an alarm LED.

Question

I have seen another way of doing it, they use the mentioned timer to generate an interrupt, let's say every 1 ms.

When the interrupt occurs, a variable is incremented, and with that variable, similar to what I do, that is, capturing any value and doing the mentioned subtraction, an asynchronous delay is achieved.

But my doubt is that this method introduces a latency to the system, an interrupt, in this case every 1 ms.

In some projects it may not be important, in others, it can be a nuisance for other processes that are interrupted every moment.

My question is: Are there other ways to achieve the asynchronous delay that I propose?

Any comments or suggestions are welcome

Update

Thanks to Scott Seidman's comment, and as I said before, maybe I'm wrong, i.e. using the words synchronous and asynchronous.

I've taken the concept from Harmony 3 for 32-bit microcontrollers.

Initially they (MCHP) used the concept of "blocking" and "non-blocking", but now it's synchronous and asynchronous.

enter image description here

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Your example isn't truly asynchronous: it can only fire as often as the check is performed, which is gated by execution of other functions in the thread's loop. (Are you familiar with threading concepts, by the way?) How familiar are you with embedded peripherals? Typically timers are cycle accurate (though to which clock, may vary: CPU, peripheral, external...), and interrupt overhead can be controlled (if not eliminated). \$\endgroup\$ Commented Aug 30, 2024 at 18:02

4 Answers 4

2
\$\begingroup\$

There are many different ways of handling this, and there is no way to know what's "the best" way as it depends what you are doing, how, and are all the options you suggested poor and require multiple timers and interrupts.

  1. Timers of 24-bit or 32-bit are rare even for 32-bit MCUs. You might have a dozen of timers but maybe only 2 or 3 larger than 16 bits. But one 16-bit might be only one you need. You need auxiliary variables anyway, if you use the same timer for multiple purposes - each separate delay needs a separate variable, such as an array of dozen software timers. A 16-bit timer may work just fine depending on what kind of delays you need and at what rate you make the timer count.

  2. Generally you can assume that on an embedded system you can subtract say two 16-bit timer values and end up with a 16-bit result, you don't generally need to separately examine if you got a negative result, the binary bit patterns are identical. For example 0xFFFF equals -1.

  3. Exactly, even if you do other tasks and detect a 500ms count has elapsed few milliseconds late, you can use the original time of trigger to restart a 500ms time count instead of using the current timestamp which is late, this allows to keep the average rate spot on. And you can always run a many hardware timers or multiple compare channels for one hardware timer to raise an interrupt right when count elapses so you can toggle a LED when you need it, instead of polling if time has elapsed in the main loop.

\$\endgroup\$
2
\$\begingroup\$

It feels like you're using non-standard language for blocking vs non-blocking delays.

Blocking delays are delays during which no other code can run, and non-blocking means other code can run during them. A blocking delay might be implemented by things like a loop with a test in it like a do..while, or some such. Non blocking delays can be handled using methods like a peripheral timer used to trigger an interrupt.

When you start using terms like "synchronous" and "asynchronous" the question "with respect to what?" immediately springs up.

\$\endgroup\$
7
  • \$\begingroup\$ I have taken the concept, perhaps wrongly, from Harmony 3 for 32-bit microcontrollers. And it would be in relation to the microcontroller's CPU. Initially they (MCHP) used the concept of "blocking" and "non-blocking", as you describe it, but now it is synchronous and asynchronous. ww1.microchip.com/downloads/en/DeviceDoc/… \$\endgroup\$ Commented Aug 30, 2024 at 18:56
  • \$\begingroup\$ @FabiánRomo -- notice that they use "blocking" and "non-blocking" when they actually describe what synchronous and asynchronous mean in that document. \$\endgroup\$ Commented Aug 30, 2024 at 19:20
  • \$\begingroup\$ I agree, but the question is as to whether there are other ways to make a "non-blocking" delay as I have described. \$\endgroup\$ Commented Aug 30, 2024 at 19:23
  • \$\begingroup\$ @FabiánRomo that's hard to answer without much more information about your specific application, and the tolerance you demand on your delay. "I need a non-blocking delay of 50ms +/- 5us" is a very different requirement than 50ms +/- 10ms. It also has to do with what you need to achieve with your code --- if the code that you need to run is amenable to going in an interrupt (i.e., fast and simple), that's much easier to achieve than code that needs to go inside a big, slow, complicated main. \$\endgroup\$ Commented Aug 30, 2024 at 19:33
  • \$\begingroup\$ You can do this in seconds in a nonblocking way if you're trying to use the delay to toggle an LED, for example, but if you're trying to start execution of a block of code in a very complicated main that takes 50ms to loop through, your effective delay will have to do with where you were in main when the interrupt got triggered. \$\endgroup\$ Commented Aug 30, 2024 at 19:37
2
\$\begingroup\$

As you’ve noted, there a many different techniques for timing. Having a regular timer tick is very common. Yes, it does chew up some cpu cycles, but usually that is a small price to pay. With most modern microcontrollers you have interrupt priorities, so higher priority interrupts aren’t delayed by your timer tick if you make it a lower priority.

The average RTOS will use this to manage tasks.

If you want to be a bit smarter, you can have a timer management function that manages lists of virtual timers and calculates when the next time event is required and then loads a hardware timer with the time to interrupt. When the time elapses, it can do a callback or maybe push an event to an event queue for an event driven system. I think FreeRTOS refers to this method as a ‘tickless timer’.

If you need precision timing, then the timer peripherals with capture and compare can be used.

As mentioned in the comments, you should determine upfront what time resolution and the precision required. Generally anything human interface related is in term of 10’s of milliseconds and a few milliseconds either way is usually undetectable.

My first choice would be a timer tick. The arm cortex series have a ‘systick’ timer for the very purpose. Common tick periods might be 1,10,50 milliseconds - there’s no rule that says you must use these times, so choose a period that suits your application.

One piece of advice is not to super optimise everything upfront. Keep things simple. If you find you have performance problems, then you can look deeper and maybe use a different implementation. As you gain more experience you’ll get a feel for what is required.

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

Timer bit depth

If you need a 16/24 bit timer depends on the required resolution of the time. From the looks of it you are counting milliseconds, so it does not matter that much.

When the interrupt occurs, a variable is incremented, and with that variable, similar to what I do, that is, capturing any value and doing the mentioned subtraction, an asynchronous delay is achieved.
But my doubt is that this method introduces a latency to the system, an interrupt, in this case every 1 ms.

Does it though? There will only be no latency when you are waiting for it,
eg: while(SysTick->VAL...), that's a waste

The Systick is often used because it's available in all cortex cores, and easily portable. The interrupt is often also used for other tasks, such as scheduler or stack bound checks.
And you are now able to use __WFI() to save power.
The overhead lost in the ISR is tiny. Eg say takes 50 cycles. 1000 times a second is 50,000 cycles. Even on 8 MHz this is below 1%.
Are you that tight already?

Time comparisons

Correct use of widths and arithmetic will wrap the subtract and provide a correct result at the typical 49.5 day boundary.
However, the timer must overflow on an arithmetic width (8/16/32/64). Otherwise you need to do 24-bit subtracts or left align the timer first.
if ( (Tick() - start) > delay)

This is a very common source of bugs and I cannot guarantee all the code I've ever written is free from it.

Not exact?

Yes, this method for scheduling has significant jitter. But that is not a problem, this is often used in the low priority main() thread while the real-time stuff happens in the various preempting interrupt handlers of timers and peripherals.
To prevent accumulating jitter error you do not restart the timer, but instead increase the start moment.

An example of the use case for this type of multitasking:
(common stuff like polling buttons, updating the display)

timer = 500ms
task1_start = Tick()
task2_start = Tick()
while(1){
  __WFI()
  if ( (Tick() - task1_start) > task1_delay){
    task1_start += task1_delay
    // task
  }
  if ( (Tick() - task2_start) > task2_delay){
    task2_start += task2_delay
    // task
  }
}

void timer_isr(){
 // exact real time stuff
}
\$\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.