This is a supplement to the helpful and popular thread, demonstration code for several things at the same time. That thread is definitely helpful for people who want to be super close to the metal, but at the same time, what if there's an easier way that usually gives us better performance?
I wanted to share how the exact same effects can be achieved using FreeRTOS, with less code and more clarity. In FreeRTOS, we create tasks, which are individual threads of execution with their own stacks. Tasks can communicate with primitives like queues and streams, although that is not needed in this demonstration because none of the tasks use any shared resources or need to convey information to one another.
Using FreeRTOS in a core that supports it is stupid-easy. Just download the library, #include its headers, and you're off to the races.
I have preserved the original code from this demonstration alongside the FreeRTOS version to serve as a basis for comparison.
// SeveralThingsAtTheSameTimeRevRTOS.ino
#include <FreeRTOS.h>
#include <Servo.h>
#include <semphr.h>
#include <task.h>
// ----CONSTANTS (won't change)
const int onBoardLedPin = LED_BUILTIN; // the pin numbers for the LEDs
const int led_A_Pin = 13;
const int led_B_Pin = 14;
const int buttonLed_Pin = 12;
const int buttonPin = 7; // the pin number for the button
const int servoPin = 15; // the pin number for the servo signal
const int onBoardLedInterval = 500; // number of millisecs between blinks
const int led_A_Interval = 2500;
const int led_B_Interval = 4500;
const int blinkDuration =
500; // number of millisecs that Led's are on - all three leds use this
const int buttonInterval = 300; // number of millisecs between button readings
const int servoMinDegrees = 20; // the limits to servo movement
const int servoMaxDegrees = 150;
//------- VARIABLES (will change)
byte onBoardLedState = LOW; // used to record whether the LEDs are on or off
byte led_A_State = LOW; // LOW = off
byte led_B_State = LOW;
byte buttonLed_State = LOW;
Servo myservo; // create servo object to control a servo
int servoPosition = 90; // the current angle of the servo - starting at 90.
int servoSlowInterval = 80; // millisecs between servo moves
int servoFastInterval = 10;
int servoInterval = servoSlowInterval; // initial millisecs between servo moves
int servoDegrees = 2; // amount servo moves at each step
// will be changed to negative value for movement in
// the other direction
unsigned long currentMillis =
0; // stores the value of millis() in each iteration of loop()
unsigned long previousOnBoardLedMillis =
0; // will store last time the LED was updated
unsigned long previousLed_A_Millis = 0;
unsigned long previousLed_B_Millis = 0;
unsigned long previousButtonMillis = 0; // time when button press last checked
unsigned long previousServoMillis =
0; // the time when the servo was last moved
//========
void OnboardLedTask(void* unused);
void LedATask(void* unused);
void LedBTask(void* unused);
void ButtonTask(void* unused);
void ServoTask(void*);
//========
void setup() {
Serial1.begin(9600);
Serial1.println("Starting SeveralThingsAtTheSameTimeRevRtos.ino");
pinMode(onBoardLedPin, OUTPUT);
pinMode(led_A_Pin, OUTPUT);
pinMode(led_B_Pin, OUTPUT);
pinMode(buttonLed_Pin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
myservo.write(servoPosition); // sets the initial position
myservo.attach(servoPin);
// Start RTOS tasks.
auto simple_task = [](auto function, auto name) {
xTaskCreate(function, name, 256, NULL, 1, NULL);
};
simple_task(OnboardLedTask, "OnboardLedTask");
simple_task(LedATask, "LedATask");
simple_task(LedBTask, "LedBTask");
simple_task(ButtonTask, "ButtonTask");
simple_task(ServoTask, "ServoTask");
}
//=======
void loop() {}
//========
void BlinkLedForDurationPerInterval(int pin, int32_t off_duration,
int32_t on_duration) {
while (true) {
delay(off_duration);
digitalWrite(pin, HIGH);
delay(on_duration);
digitalWrite(pin, LOW);
}
}
void OnboardLedTask(void* unused) {
BlinkLedForDurationPerInterval(onBoardLedPin, onBoardLedInterval,
blinkDuration);
}
void LedATask(void* unused) {
BlinkLedForDurationPerInterval(led_A_Pin, led_A_Interval, blinkDuration);
}
void LedBTask(void* unused) {
BlinkLedForDurationPerInterval(led_B_Pin, led_B_Interval, blinkDuration);
}
void ButtonTask(void* unused) {
while (true) {
if (digitalRead(buttonPin) == LOW) {
// Invert the LED state if the button is pressed when we check.
buttonLed_State = !buttonLed_State;
digitalWrite(buttonLed_Pin, buttonLed_State);
}
delay(buttonInterval);
}
}
void ServoTask(void*) {
while (true) {
servoPosition += servoDegrees;
if (servoPosition <= servoMinDegrees) {
if (servoInterval == servoSlowInterval) {
servoInterval = servoFastInterval;
} else {
servoInterval = servoSlowInterval;
}
}
if (servoPosition >= servoMaxDegrees || servoPosition <= servoMinDegrees) {
servoDegrees = -servoDegrees;
servoPosition = servoPosition + servoDegrees;
}
myservo.write(servoPosition);
delay(servoInterval);
}
}
#if 0
// Non-RTOS code
void loop() {
// Notice that none of the action happens in loop() apart from reading millis()
// it just calls the functions that have the action code
currentMillis = millis(); // capture the latest value of millis()
// this is equivalent to noting the time from a clock
// use the same time for all LED flashes to keep them synchronized
readButton(); // call the functions that do the work
updateOnBoardLedState();
updateLed_A_State();
updateLed_B_State();
switchLeds();
servoSweep();
}
void updateOnBoardLedState() {
if (onBoardLedState == LOW) {
if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
onBoardLedState = HIGH;
previousOnBoardLedMillis += onBoardLedInterval;
}
} else {
if (currentMillis - previousOnBoardLedMillis >= blinkDuration) {
onBoardLedState = LOW;
previousOnBoardLedMillis += blinkDuration;
}
}
}
//=======
void updateLed_A_State() {
if (led_A_State == LOW) {
if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
led_A_State = HIGH;
previousLed_A_Millis += led_A_Interval;
}
} else {
if (currentMillis - previousLed_A_Millis >= blinkDuration) {
led_A_State = LOW;
previousLed_A_Millis += blinkDuration;
}
}
}
//=======
void updateLed_B_State() {
if (led_B_State == LOW) {
if (currentMillis - previousLed_B_Millis >= led_B_Interval) {
led_B_State = HIGH;
previousLed_B_Millis += led_B_Interval;
}
} else {
if (currentMillis - previousLed_B_Millis >= blinkDuration) {
led_B_State = LOW;
previousLed_B_Millis += blinkDuration;
}
}
}
//========
void switchLeds() {
digitalWrite(onBoardLedPin, onBoardLedState);
digitalWrite(led_A_Pin, led_A_State);
digitalWrite(led_B_Pin, led_B_State);
digitalWrite(buttonLed_Pin, buttonLed_State);
}
//=======
void readButton() {
if (millis() - previousButtonMillis >= buttonInterval) {
if (digitalRead(buttonPin) == LOW) {
buttonLed_State =
!buttonLed_State;
previousButtonMillis += buttonInterval;
}
}
}
//========
void servoSweep() {
if (currentMillis - previousServoMillis >= servoInterval) {
previousServoMillis += servoInterval;
servoPosition =
servoPosition + servoDegrees;
if (servoPosition <= servoMinDegrees) {
if (servoInterval == servoSlowInterval) {
servoInterval = servoFastInterval;
} else {
servoInterval = servoSlowInterval;
}
}
if ((servoPosition >= servoMaxDegrees) ||
(servoPosition <= servoMinDegrees)) {
servoDegrees = -servoDegrees;
servoPosition = servoPosition + servoDegrees;
}
myservo.write(servoPosition);
}
}
#endif
In the interest of demonstrating that this code does the same thing as the original, here's a video:
About 75 lines of RTOS code have replaced about 150 lines of Arduino-loop-style code. The RTOS code will likely be faster, and, to my eye, is vastly clearer. Consider using an RTOS for your next project that involves concurrency!