Relação Ametódica

Data Science, Machine Learning, Artificial Intelligence, Visualization, and Complex Systems.

Stop using sleep in your Arduino projects

Arduino sketches are single threaded. This means your Arduino won’t be doing two things simultaneously. The problem of this is that when you use the command sleep the micro-controller is blocked waiting for the time to pass and won’t be able to do anything else.

If you need the processor to work on other things you need to get rid of sleep and replace it with a timer based logic. Here’s an example:

You want to turn a LED on for 1 second, but you don’t want to sleep(1000) because during the period others things might happen. You might be reading data from a potentiometer in an unrelated piece of logic of your project. Or you might be sending data to other Arduino. Or the LED is just a warning light in a Robot and you want to turn it on and off every other second without stopping the robot from moving. Well, there are many examples where this is applicable. The solution to not using sleep is to use a timer.

example:

unsigned long timer;

When you turn the LED ON, you start the timer; note that we declare the timer as unsigned long because that is what the millis() function returns.

digitalWrite(LED_PIN, HIGH);
timer = millis();

Now, instead of sleeping for 1000ms we want to turn the LED OFF after 1000ms.

if (digitalRead(LED_PIN)==HIGH && millis()-timer>1000){
digitalWrite(LED_PIN, LOW);
}

There you have it. No blocking sleep. Notice that we checked to see if the LED was HIGH with a digitalRead that shortcuts the evaluation of the condition. This avoids repeating the digitalWrite continuously while the LED is OFF. Instead of using digitalRead you could use a boolean led_status variable that would make the code even faster.

Advanced C example

The principles above can be condensed in C by defining struct timers and passing functions to execute parts of code as parameters to a timer function. The following example illustrates a minimum working case. This runs on the Desktop and not on the Arduino, but the principle is the same. Study the code to learn; the main function simulates the calls to setup and loop functions of the Arduino. You should be able to compile the code with gcc.

Note that we are using two timers (1 and 2) that run at intervals of 1 second and 3 seconds respectively. Note also that here we are using thelibrary and the clock() function. When using the Arduino you’ll probably be using the millis() function instead.
The important mechanisms are in functions runTimer and activateTimer; The latter sets up the timers while the former is called repeatedly in the draw functions. print1 and print2 are the two functions that you want to invoke when the timers fire.

#include <‍stdio.h‍>
#include <‍time.h‍>

/* Timer structure. Make active 1 to activate, 0 to stop the timer */
struct Timer {
char active;
clock_t previous;
clock_t interval;
} timer1, timer2;

/* Checks if timer has fired and executes function if true */
void runTimer(struct Timer* timer, void (*function)()) {
if (timer->active && clock() - timer->previous >= timer->interval) {
function();
timer->previous = clock();
}
}
/* Initializes timer with a given time interval */
void activateTimer(struct Timer* timer, unsigned long interval) {
timer->active = 1;
timer->previous = clock();
timer->interval = interval * CLOCKS_PER_SEC;
}

void print1() {
printf("%ld\tRunning Code 1\n", clock() / CLOCKS_PER_SEC);
}

void print2() {
printf("%ld\tRunning Code 2\n", clock() / CLOCKS_PER_SEC);
}

void setup() {
activateTimer(&timer1, 1);
activateTimer(&timer2, 3);
}

void loop() {
runTimer(&timer1, print1);
runTimer(&timer2, print2);
}

int main(int argc, char const* argv[]) {
setup();
while (1) {
loop();
}
return 0;
}

Final notes

Take particular attention that without sleeps this code is running at full speed consuming more power. If your other input is a reading from a temperature sensor, probably you don’t want to be probing the sensor constantly and it might make perfect sense to use a sleep command. Use this approach to simulate a sort of fake multithreading where you really need not to block on sleeps.

This approach can be enhanced with other functions like stop, pause, toggle, check, run, etc, that would increase the functionality of the timer. In any case the two proposed functions are enough to have a running proof of concept.

What about Arduino C?

The conversion of the above C code to Arduino code can be straightforward. Everything is the same except the main function doesn’t exist and you won’t be printing anything and therefore the inclusion of the stdio.h header will probably be replaced with Serial.

The choice of char and clock_t for the types of Timer can be replaced in your Arduino sketches with bool and unsigned long respectively; then just treat the active as true|false and the previous will be using the millis() function. Also you won’t divide by CLOCKS_PER_SEC but by 1000.

This means that you will not use the time.h header and therefore the reference to it can be deleted.

Below is the code for Arduino. Compare it with the C version above and it will become clear that there is no structural difference.

Sleep free code for Arduino

/* Timer structure. Make active 1 to activate, 0 to stop the timer */
struct Timer {
boolean active;
unsigned long previous;
unsigned long interval;
} timer1, timer2;

/* Checks if timer has fired and executes function if true */
void runTimer(struct Timer* timer, void (*function)()) {
if (timer->active && millis() - timer->previous >= timer->interval) {
function();
timer->previous = millis();
}
}
/* Initializes timer with a given time interval */
void activateTimer(struct Timer* timer, unsigned long interval) {
timer->active = 1;
timer->previous = millis();
timer->interval = interval * 1000;
}

void print1() {
Serial.println(String(millis() / 1000)+"\tRunning Code 1" );
}

void print2() {
Serial.println(String(millis() / 1000)+"\tRunning Code 2" );
}

void setup() {
Serial.begin(9600);
activateTimer(&timer1, 1);
activateTimer(&timer2, 3);
}

void loop() {
runTimer(&timer1, print1);
runTimer(&timer2, print2);
}