FreeRTOS Implementation on Arduino for DIY LED Fan Controller

I am trying to make an RGB LED with Fan Controller for PCs. It is an Open Sourced project. But I want to implement RTOS using this implementation of it as a function in it, namely rollColor(), takes a lot of cycles.

The data the Arduino uses comes from the Serial. The Serial data has to wait until the function rollColor() completes. This function also has a delay and total runtime of this function is approximately 20 Seconds.

This is the original code, in which RTOS is not implemented: nonRTOS

Now I don’t want to give a consecutive 20 seconds of execution time to a single function, but time slices would be good.

Now this falls under (RT)OS category and I would like to utilize this library but I seem to be unable to do that as there is not much documentation to begin with. The code I have come up with is this:

#include <Arduino_FreeRTOS.h>
#include <LCD.h>
#include <LiquidCrystal_SR.h>

#define FAN1 3
#define FAN2 5

#define LEDR 6
#define LEDG 9
#define LEDB 10

#define LCD_DT 14
#define LCD_CK 15
#define LCD_EN 16

#define E_FAN1 0
#define E_FAN1 1

#define SAMPLE 100

#define E_RGB_START 2
#define E_RGB_END 502

LiquidCrystal_SR lcd(LCD_DT, LCD_CK, LCD_EN);

int led[2][6];
int led_type = 0;
int led_bright = 0;
int fan[2] = {50, 50};
int load[3];
int temp[2];
int gradient[SAMPLE][3];

bool fchange = false;
bool lchange = false;

void TaskPrintLCD( void *pvParameters );
void TaskRGBCycle( void *pvParameters );
void TaskFanSpeed( void *pvParameters );

void setup() {
  Serial.begin(57600);
  xTaskCreate(
    TaskPrintLCD
    ,  (const portCHAR *)"Print_To_LCD"  // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );
  xTaskCreate(
    TaskRGBCycle
    ,  (const portCHAR *)"Cycle_RGB_Colors"  // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  3  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );
  xTaskCreate(
    TaskFanSpeed
    ,  (const portCHAR *)"Set_Fan_Speed"  // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  1  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );
}

void loop() {

}

void TaskPrintLCD(void *pvParameters) {
  (void) pvParameters;
  Serial.println("LCD Settings...");
  lcd.begin(20, 4);
  for (;;) {
    printCPUUsage(load[0]);
    printGPUUsage(load[1]);
    printRAMUsage(load[2]);
    printTemps(temp[0], temp[1]);
  }
}

void TaskFanSpeed(void *pvParameters) {
  Serial.println("System Started...");
  Serial.println("Setting Pins...");
  pinMode(FAN1, OUTPUT);
  pinMode(FAN2, OUTPUT);
  (void) pvParameters;
  for (;;) {
    if (fchange) {
      Serial.println("SETTING FAN SPEED");
      int speed1 = ((fan[0] * 256) / 100 ) - 1;
      int speed2 = ((fan[1] * 256) / 100 ) - 1;
      fchange = false;
    }
  }
}

void TaskRGBCycle(void *pvParameters) {
  (void) pvParameters;
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
  for (;;) {
    if (led_type == 1) {
      //ROLLCOLOR() FUNCTION START
      Serial.println("ROLLING GRADIENT START...");
      for (int i = 0; i < SAMPLE; i++) {
        analogWrite(LEDR, gradient[i][0]);
        analogWrite(LEDG, gradient[i][1]);
        analogWrite(LEDB, gradient[i][2]);
        vTaskDelay(100 / portTICK_PERIOD_MS);
      }
      for (int i = SAMPLE - 1; i >= 0; i--) {
        analogWrite(LEDR, gradient[i][0]);
        analogWrite(LEDG, gradient[i][1]);
        analogWrite(LEDB, gradient[i][2]);
        vTaskDelay(100 / portTICK_PERIOD_MS);
      }
      //ROLL COLOR FUNCTION END
      Serial.println("ROLLING GRADIENT END...");
    } else {
      analogWrite(LEDR, led_bright);
    }
  }
}

void serialEvent() {
  Serial.readStringUntil('*');
  while (String main = Serial.readStringUntil('>')) {
    Serial.println("IN WHILE");
    Serial.println(main);
    if (main == "") {
      break;
    }
    String data;
    if (main == "leds") {
      lchange = true;
      int type = Serial.readStringUntil(':').toInt();
      led_type = type;
      if (type == 1) {
        Serial.println("LED TYPE: " + led_type);
        for (int i = 0; i < 6; i++) {
          data = Serial.readStringUntil(',');
          Serial.println(data);
          if (i < 3) {
            led[0][i] = data.toInt();
          } else {
            led[1][i - 3] = data.toInt();
          }
          data = "";
        }
        generateGradient();
      } else {
        led_bright = Serial.readStringUntil(',').toInt();
      }
      Serial.readStringUntil(';');
    } else if (main == "fans") {
      fchange = true;
      for (int i = 0; i < 2; i++) {
        data = Serial.readStringUntil(',');
        fan[i] = data.toInt();
        data = "";
      }
      Serial.readStringUntil(';');
    } else if (main == "stat") {
      for (int i = 0; i < 2; i++) {
        data = Serial.readStringUntil(':');
        if (data == "load") {
          for (int j = 0; j < 3; j++) {
            data = Serial.readStringUntil(',');
            load[j] = data.toInt();
            data = "";
          }
          Serial.readStringUntil(';');
        } else if (data == "temp") {
          for (int j = 0; j < 2; j++) {
            data = Serial.readStringUntil(',');
            temp[j] = data.toInt();
            data = "";
          }
          Serial.readStringUntil(';');
        }
      }
    }
  }
}

void printCPUUsage(int usage) {
  int loc;
  int i;
  lcd.setCursor(0, 0);
  lcd.print("CPU% ");
  for (i = 1; i <= ceil(usage / 10); i++) {
    lcd.write(255);
  }
  for (i; i <= 10; i++) {
    lcd.print(" ");
  }
  lcd.print(" ");
  if (usage > 99) {
    lcd.print(usage);
  } else {
    lcd.print(" ");
    lcd.print(usage);
  }
  lcd.print("%");
}

void printGPUUsage(int usage) {
  int loc;
  int i;
  lcd.setCursor(0, 1);
  lcd.print("GPU% ");
  for (i = 1; i <= ceil(usage / 10); i++) {
    lcd.write(255);
  }
  for (i; i <= 10; i++) {
    lcd.print(" ");
  }
  lcd.print(" ");
  if (usage > 99) {
    lcd.print(usage);
  } else {
    lcd.print(" ");
    lcd.print(usage);
  }
  lcd.print("%");
}

void printRAMUsage(int usage) {
  int loc;
  int i;
  lcd.setCursor(0, 2);
  lcd.print("RAM% ");
  for (i = 1; i <= ceil(usage / 10); i++) {
    lcd.write(255);
  }
  for (i; i <= 10; i++) {
    lcd.print(" ");
  }
  lcd.print(" ");
  if (usage > 99) {
    lcd.print(usage);
  } else {
    lcd.print(" ");
    lcd.print(usage);
  }
  lcd.print("%");
}

void printTemps(int cputemp, int gputemp) {
  lcd.setCursor(0, 3);
  lcd.print("TEMP ");
  lcd.print("CPU ");
  if (cputemp < 100) {
    lcd.print(" ");
  }
  lcd.print(cputemp);
  lcd.print(" GPU ");
  if (gputemp < 100) {
    lcd.print(" ");
  }
  lcd.print(gputemp);
}

void generateGradient() {
  Serial.println("GENERATING GRADIENT...");
  float m[3];
  int r, g, b;
  m[0] = (led[1][0] - led[0][0]) / SAMPLE;
  m[1] = (led[1][1] - led[0][1]) / SAMPLE;
  m[2] = (led[1][2] - led[0][2]) / SAMPLE;
  for (int i = 0; i < SAMPLE; i++) {
    r = ceil(m[0] * i) + led[0][0];
    g = ceil(m[1] * i) + led[0][1];
    b = ceil(m[2] * i) + led[0][2];
    gradient[i][0] = r;
    gradient[i][1] = g;
    gradient[i][2] = b;
  }
  lchange = false;
  Serial.println("GRADIENT GENERATED...");
}

To me this code seems complete, but there is nothing from Arduino end, no LCD print nothing. I may be doing something wrong, but I don’t know what, so please advice.

If you have any other better alternative please advise that too.

One thing I was thinking that could help me create timed slices would be to use external interrupts with regular intervals, say 30ms cycle and 50% high time. A 555 Timer can do that or an oscillator/resonator.
But then how would I use that interrupt to stop working on the current function and start from the beginning without resetting the counters in the for loops.

I cannot comment on Arduino RTOS, never used it myself. Instead I'm using task macros. These task macros implement cooperative multitasking, using common Arduino code patterns.

DrDiettrich:
I cannot comment on Arduino RTOS, never used it myself. Instead I'm using task macros. These task macros implement cooperative multitasking, using common Arduino code patterns.

OK. This seems promising. Let me try it out. Thanks!

Ok I now have this code. It works but if there is an interrupt while the TaskRGBCycle is running, it never goes back to completing what it was doing. How can I solve that.

#include <ALib0.h>
#include <LCD.h>
#include <LiquidCrystal_SR.h>

#define FAN1 3
#define FAN2 5

#define LEDR 6
#define LEDG 9
#define LEDB 10

#define LCD_DT 14
#define LCD_CK 15
#define LCD_EN 16

#define E_FAN1 0
#define E_FAN1 1

#define SAMPLE 100

#define E_RGB_START 2
#define E_RGB_END 502

LiquidCrystal_SR lcd(LCD_DT, LCD_CK, LCD_EN);

int led[2][6];
int led_type = 0;
int led_bright = 0;
int fan[2] = {50, 50};
int load[3];
int temp[2];
int gradient[SAMPLE][3];

bool fchange = false;
bool lchange = false;

void TaskPrintLCD();
void TaskRGBCycle();
void TaskFanSpeed();

void setup() {
  Serial.begin(57600);
  Serial.println("System Started...");
  Serial.println("Setting Pins...");
  Serial.println("LCD Settings...");
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
  pinMode(FAN1, OUTPUT);
  pinMode(FAN2, OUTPUT);
  lcd.begin(20, 4);
}

void loop() {
  TaskPrintLCD();
  TaskFanSpeed();
  TaskRGBCycle();
}

void TaskPrintLCD() {
  taskBegin();
  printCPUUsage(load[0]);
  printGPUUsage(load[1]);
  printRAMUsage(load[2]);
  printTemps(temp[0], temp[1]);
  taskEnd();
}

void TaskFanSpeed() {
  taskBegin();
  if (fchange) {
    Serial.println("SETTING FAN SPEED");
    int speed1 = ((fan[0] * 256) / 100 ) - 1;
    int speed2 = ((fan[1] * 256) / 100 ) - 1;
    analogWrite(FAN1, speed1);
    analogWrite(FAN2, speed2);
    fchange = false;
  }
  taskEnd();
}

void TaskRGBCycle() {
  taskBegin();
  int i;
  if (led_type == 1) {
    Serial.println("ROLLING GRADIENT START...");
    for (i = 0; i < SAMPLE; i++) {
      analogWrite(LEDR, gradient[i][0]);
      analogWrite(LEDG, gradient[i][1]);
      analogWrite(LEDB, gradient[i][2]);
      taskDelay(100);
    }
    for (i = SAMPLE - 1; i >= 0; i--) {
      analogWrite(LEDR, gradient[i][0]);
      analogWrite(LEDG, gradient[i][1]);
      analogWrite(LEDB, gradient[i][2]);
      taskDelay(100);
    }
    Serial.println("ROLLING GRADIENT END...");
  } else {
    analogWrite(LEDR, led_bright);
  }
  taskEnd();
}

void serialEvent() {
  Serial.readStringUntil('*');
  while (String main = Serial.readStringUntil('>')) {
    Serial.println("IN WHILE");
    Serial.println(main);
    if (main == "") {
      break;
    }
    String data;
    if (main == "leds") {
      lchange = true;
      int type = Serial.readStringUntil(':').toInt();
      led_type = type;
      if (type == 1) {
        Serial.println("LED TYPE: " + led_type);
        for (int i = 0; i < 6; i++) {
          data = Serial.readStringUntil(',');
          Serial.println(data);
          if (i < 3) {
            led[0][i] = data.toInt();
          } else {
            led[1][i - 3] = data.toInt();
          }
          data = "";
        }
        generateGradient();
      } else {
        led_bright = Serial.readStringUntil(',').toInt();
      }
      Serial.readStringUntil(';');
    } else if (main == "fans") {
      fchange = true;
      for (int i = 0; i < 2; i++) {
        data = Serial.readStringUntil(',');
        fan[i] = data.toInt();
        data = "";
      }
      Serial.readStringUntil(';');
    } else if (main == "stat") {
      for (int i = 0; i < 2; i++) {
        data = Serial.readStringUntil(':');
        if (data == "load") {
          for (int j = 0; j < 3; j++) {
            data = Serial.readStringUntil(',');
            load[j] = data.toInt();
            data = "";
          }
          Serial.readStringUntil(';');
        } else if (data == "temp") {
          for (int j = 0; j < 2; j++) {
            data = Serial.readStringUntil(',');
            temp[j] = data.toInt();
            data = "";
          }
          Serial.readStringUntil(';');
        }
      }
    }
  }
}

void printCPUUsage(int usage) {
  int loc;
  int i;
  lcd.setCursor(0, 0);
  lcd.print("CPU% ");
  for (i = 1; i <= ceil(usage / 10); i++) {
    lcd.write(255);
  }
  for (i; i <= 10; i++) {
    lcd.print(" ");
  }
  lcd.print(" ");
  if (usage > 99) {
    lcd.print(usage);
  } else {
    lcd.print(" ");
    lcd.print(usage);
  }
  lcd.print("%");
}

void printGPUUsage(int usage) {
  int loc;
  int i;
  lcd.setCursor(0, 1);
  lcd.print("GPU% ");
  for (i = 1; i <= ceil(usage / 10); i++) {
    lcd.write(255);
  }
  for (i; i <= 10; i++) {
    lcd.print(" ");
  }
  lcd.print(" ");
  if (usage > 99) {
    lcd.print(usage);
  } else {
    lcd.print(" ");
    lcd.print(usage);
  }
  lcd.print("%");
}

void printRAMUsage(int usage) {
  int loc;
  int i;
  lcd.setCursor(0, 2);
  lcd.print("RAM% ");
  for (i = 1; i <= ceil(usage / 10); i++) {
    lcd.write(255);
  }
  for (i; i <= 10; i++) {
    lcd.print(" ");
  }
  lcd.print(" ");
  if (usage > 99) {
    lcd.print(usage);
  } else {
    lcd.print(" ");
    lcd.print(usage);
  }
  lcd.print("%");
}

void printTemps(int cputemp, int gputemp) {
  lcd.setCursor(0, 3);
  lcd.print("TEMP ");
  lcd.print("CPU ");
  if (cputemp < 100) {
    lcd.print(" ");
  }
  lcd.print(cputemp);
  lcd.print(" GPU ");
  if (gputemp < 100) {
    lcd.print(" ");
  }
  lcd.print(gputemp);
}

void generateGradient() {
  taskBegin();
  Serial.println("GENERATING GRADIENT...");
  float m[3];
  int r, g, b;
  m[0] = (led[1][0] - led[0][0]) / SAMPLE;
  m[1] = (led[1][1] - led[0][1]) / SAMPLE;
  m[2] = (led[1][2] - led[0][2]) / SAMPLE;
  for (int i = 0; i < SAMPLE; i++) {
    r = ceil(m[0] * i) + led[0][0];
    g = ceil(m[1] * i) + led[0][1];
    b = ceil(m[2] * i) + led[0][2];
    gradient[i][0] = r;
    gradient[i][1] = g;
    gradient[i][2] = b;
  }
  lchange = false;
  Serial.println("GRADIENT GENERATED...");
  taskEnd();
}

//*stat>temp:39,38,;load:17,4,42,;fans>50,50,;
//*fans>10,10,;
//*leds>1:122,152,255,12,10,0;

Regards

With task macros you can not have local automatic variables. They will lose their values or are re-initialized with every iteration.

Use static variables instead. This restriction seems worth a documentation update.

DrDiettrich:
With task macros you can not have local automatic variables. They will lose their values or are re-initialized with every iteration.

Use static variables instead. This restriction seems worth a documentation update.

That did the trick! Thanks! :smiley:

DrDiettrich:
I cannot comment on Arduino RTOS, never used it myself. Instead I'm using task macros. These task macros implement cooperative multitasking, using common Arduino code patterns.

I'd not seen 'task macros' before - it's a great piece of work. As the 'readme' says, it

provides slick solutions for 90% of typical tasks in Arduino beginner sketches

.
So much easier to understand how write this type of code than the somewhat messy 'severalThings' demo that is referred to so often.

It's the educational Arduino touch, that people should learn basic programming, before they start using obscure macros :wink:

Using the task macros without such knowledge can be dangerous, as above problem has shown.

DrDiettrich:
It's the educational Arduino touch, that people should learn basic programming, before they start using obscure macros :wink:

Yes, it's good that people should learn basic programming, but the Arduino system provides (for example) a Serial class; we don't expect newbies to write their own serial output from scratch (as I have had to, in the past) so why not task management as well?

DrDiettrich:
Using the task macros without such knowledge can be dangerous, as above problem has shown.

Good point, but documentation is the key. Anyway, there are other task classes available which may not have this restriction.

Cooperative multi tasking is very different from traditional multi tasking. In traditional multi tasking a task can be stopped at any
time, and resumed later. This requires a dedicated stack for every task, what's impossible to implement on 8 bit Arduinos.

Would you like to write a tutorial, how to use task macros or a task class for cooperative multi tasking, from a newbie view?

Would you like to write a tutorial, how to use task macros or a task class for cooperative multi tasking, from a newbie view?

Well, I wrote a class for that a few days after I started looking in this forum. But I seem to have got rather slapped down for it.

If I can expect some support for it, I'd be very happy to expand the class and write a 'newbie' tutorial around it.

Am I right that your class only allows to schedule a function at certain intervals?

This would be what my every() macro does, except for the invocation counter.

DrDiettrich:
Am I right that your class only allows to schedule a function at certain intervals?

Yes, it was only intended to mimic the 'doing several things at the same time' sketch. I'm sure I could extend it to do a lot more....

Would you like to write a tutorial, how to use task macros or a task class for cooperative multi tasking, from a newbie view

Since you posted that I've been looking through the libraries and the 'playground' and found this which is quite a good start. I'm also a bit confused now because in the 'playground' there are hundreds of libraries which don't appear to have any peer-review - I wonder how a user should decide which to use?
I didn't find your excellent one anywhere, though.

You are right, peer review is widely missing nowadays, not only in the forum. One reason are the many possible solutions for each problem, and the wide range of problems resulting from combinations of such solutions.

The task macros originate from Donald Knuth, one of the first computation gurus, rediscovered by the forum member Combie, and cast into a library by me. I was wondering how such simple macros could solve so many problems, but convinced myself by writing a couple of examples. Now I'm waiting for problems or situations where the macros really fail, but never heard of any. Above issue resulted from improper use of the macros, which again came from a lack of documentation. Documentation updated, problem solved :slight_smile:

Perhaps presentations on twitter, facebook or youtube could help to spread the task macros, but I for myself are old school, not using such social media :-]