This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

How to avoid busy waiting on async operations and gracefully preserve code flow

In my code I am fully embracing the async way when using peripherals, but the problem is that my code is becoming more and more complex due to having to break down all operations in multiple parts with cascading callbacks. Suppose I have to implement a protocol on a peripheral that requires something like:

sendA()

sendB()

sendC()

... in sequence, maybe also handling intermediate errors. This is becoming a mess like:

sendA_async(sendA_callback)

[in sendA_callback] sendB_async(sendB_callback)

[in sendB_callback] sendC_async(sendC_callback)

The lazy obvious solution is to forget about async events and write code just like in some examples:

sendA()

while(!completed_A) {}

sendB()

...

But this increases power consumption, causes problems in IRQs. I've also seen something better like this:

sendA()

while(!completed_A) { __WFE() }

sendB()

...

But what if I am using SoftDevice, app timers, scheduler, logs, etc? In my main loop the "sleep loop" is:

for (;;) {

// Run application scheduler

app_sched_execute();

// Process logs if required

if (NRF_LOG_PROCESS() == false) {

// Manage power, sleep if required

ua_power_manage();

}

}

Can I put this code in a function and use it for waiting? I'm not so sure...

Also, going back to the original async code, using a state machine to keep track of what's going on does not make the code much more easy to handle if it's more complex than a simple example.

What's the preferred/suggested pattern for handling with async code, preserving state, avoiding resource conflicts when using multi step async code?

Thanks!

Parents
  • Hi,

    I am not quite sure what you mean by "fully embracing the async way". Are you basing your project on one of our SDK examples, or do you use a third party framework of some kind?

    Our BLE stack (the SoftDevice) and SDK examples are event based, which means interrupts from the BLE stack or peripherals trigger events, that are handled by event callbacks. That means you most likely will need some sort of state machine for each part of the system, and as you write it can sometimes become complex (depending on what you do and how you do it). In any case I suggest that you have a look at the SDK and see how for instance BLE services are implemented, to see how an event based system is built. Note that different parts of the system are separated by each instance handling only the events that are relevant to it.

    Regards,
    Terje

  • With "embracing async way" of doing things simply means using callbacks where available instead of writing code to burn CPU cycles just for waiting and keeping the context. There are a lot of samples out there just doing things in a simplistic way, using busy wait loops everywhere. While this is ok to keep things small and understandable, they rarely are a good pattern for production code.

    So, starting to use callbacks for multi-step protocols quickly become complicated. That's why some languages implement async/await patterns. I wasn't expecting something so complex, but at least some guidelines on how people out there handle it.

    Thanks!

Reply
  • With "embracing async way" of doing things simply means using callbacks where available instead of writing code to burn CPU cycles just for waiting and keeping the context. There are a lot of samples out there just doing things in a simplistic way, using busy wait loops everywhere. While this is ok to keep things small and understandable, they rarely are a good pattern for production code.

    So, starting to use callbacks for multi-step protocols quickly become complicated. That's why some languages implement async/await patterns. I wasn't expecting something so complex, but at least some guidelines on how people out there handle it.

    Thanks!

Children
  • Hi,

    Our SDK examples uses a main loop that "sends CPU to sleep" instead of "burning cycles", so that the CPU is active only when needed (i.e. when interrupts (events) are processed.) We do not use an approach similar to the "async" functionality in e.g. JavaScript.

    Worth mentioning is that our SoCs have Programmable Peripheral Interconnect (PPI), which means peripherals generate "events" that triggers "tasks" on other peripherals without CPU intervention. This means for many tasks you do not need to invoke CPU at all (so it can stay sleeping). For instance we use that extensively in the SoftDevice, for improved power consumption. (E.g. internally in the RADIO peripheral and for connecting the RADIO and TIMER peripherals.) See also the PPI example.

    Regards,
    Terje

Related