In an Arduino sketch, is there a way to make asynchronous function calls within the loop? Like listening to requests through http server and process them in a non-blocking way.
-
3If you're not a C++ beginner, then you could take a look at Cosa library (github.com/mikaelpatel/Cosa). It is a complete rewrite of Arduino library to make it better: OOP and generally interrupt-driven. I think they support HTTP, but I haven't checked that part yet.– jfpoilpretCommented Jun 3, 2014 at 5:06
3 Answers
Yes and no. You're kinda vague on what you want to do. I've made this into a few different sections (mainly focused on reading sensor data... it applies to everything but that's the context I'm using):
Threads
AFAIK all Arduinos only have one core (they can do one thing at once). For most Arduino boards, hardware multithreading isn't supported. However, there are ways to implement software multithreading. The approach by AsheeshR wouldn't work well for functions that take a long time to complete (i.e. something in a library that takes a while, or a delay) because it would get jammed up by those instructions, but it would work well for short functions like pinMode()
. The Protothreads library listed there might be better at this, but I don't really know.
It'd be hard to orchestrate this with HTTP, especially since you have to make
Delays
A common way for a sketch to stall is the use of a delay. This can be solved by using, in the main loop, a if statement and the millis()
function that returns a time (not a clock time, but rather the time since the Arduino started). You could also do a loop within the loop to poll sensor data.
The millis()
approach wouldn't work well with things that cause the whole program to stall (i.e. more delays or loops that last a noticeable amount of time). Note that 100ms IIRC is the general maximum time to make a UI not seem laggy.
Interrupts
Interrupts are a great way to keep things almost asynchronous. They run a short piece of code (that you specify) every time a pin state changes. It breaks from the loop()
, and goes back where it left off once the "ISR" has been ran. I don't have much time to explain how to do this, but a quick Google search will yield a lot of results.
As far as you example, the threading section would be the most applicable. This topic is pretty vague, so you'll have to experiment with a few things and find something that works.
I just posted some code for a task scheduler on the playground the other day, you may want to take a peek, maybe you can adapt it to your needs.
The Arduino's processor is inherently single-threaded and cannot multi-task. As has been mentioned there are however ways to create the illusion of multi-tasking. Annonomus Penguin hit on those pretty well.
Also check out TimerOne (It's probably better ;)
-
1TimerOne is a bad idea because it works with avr-based arduinos only. For example my Wemos D1 R1 doesn't work Commented Feb 23, 2016 at 8:11
You can definitely make asynchronous code on devices with only one thread. This is what the "static" keyword is for in C. It helps you to setup functions as a "state machine" so that they can run concurrently with other functions.
Let's take a simple function for example. These are just made up functions and not meant to be compiled in any IDE, just to show you the general principles.
char read_bit_sync()
{
while ( digitalRead(PIN_CLOCK) ) {}
while ( !digitalRead(PIN_CLOCK) ) {}
return digitalRead(PIN_DATA);
}
void loop()
{
char c = read_bit_sync();
Serial.println(c);
}
In the example code above, we wait for the clock pin to go high and then low again, and then on the falling edge we read the bit and print it to the Serial output in a loop.
The problem is, what if we want to read this pin asynchronously? These "while" loops cause the whole program to freeze until it receives a bit.
The trick is to reimagine our algorithm as one that can be done in many small steps that each can be completed in a very short amount of time, so that the function will need to be called repeatedly until those steps are completed.
int read_bit_async()
{
static int state = 0;
int ret;
switch (state)
{
case 0:
if ( digitalRead(PIN_CLOCK) )
{
return -1;
}
state++;
return -1;
case 1:
if ( !digitalRead(PIN_CLOCK) )
{
return -1;
}
state++;
return -1;
default:
ret = digitalRead(PIN_DATA);
state = 0;
return ret;
}
}
void loop()
{
int c = read_bit_async();
if (c != -1)
{
Serial.println(c);
}
}
In the example above, the function starts in a "0" state which it will remain in that state until it reads that the clock pin is high. It then transitions into a "1" state which it then will remain in until the clock pin is low. It then transitions to a "2" state which the next time you read it, it will return the bit read, and then reset its internal state to the "0" state.
Now in the main loop, we can call the read_bit_async() every iteration of the loop and it will return -1 when it's still "awaiting" the bit but when it received the bit then we get the actual bit value. The main loop can continue to run and process other code while waiting for the bit to be received.
I've used in this past to, for example, write a library that can read keyboard input from a PS/2 keyboard asynchronously alongside the main program. I've also used it to write a communication protocol that could send bytes of data down a single wire asynchronously, so if you have two wires for upstream and downstream data then the two devices could communicate without blocking the main program loop.
For what you are personally trying to do, let's say you receive an HTTP request and you want to respond to it with an HTTP file, but sending out the whole file is too time consuming. You could have each iteration send out only one byte.
You could also have a "listening", "processing", and "responding" state where at first it is listening for a request, and then if it receives one it switches to the "processing" state where it interprets the request, and then after that it switches to the "responding" state where it produces the output.
Also, the second block of code I wrote is not necessarily the most optimal, for example the last two states can be merged into one, but I prefer to write them this way because I find it to be more clear and easier to follow along with.
If you want to create a non-blocking asynchronous function on a single thread, the easiest way to do it is to reimagine the algorithm you're trying to write as a state machine that can be broken up into many many different tiny parts, where enough iterations of these parts can eventually complete task, and then to implement this.
When I say "tiny" I mean mostly in terms of speed. If a single iteration of the function takes too long, it can noticeably interfere with other asynchronous functions, so you want to make sure each iteration can be done in very little time. If a single iteration takes too long, then you should try to figure out how to break it up into even more states.