Creation of an OS / Scheduler

Hi Guys,

I am trying to implement an OS-functionality to my project. The main aim is that Task1 runs every 100 microseconds, Task2 runs every 1 millisecond, Task3 runs every 10 milliseconds and atlast Task4 runs every 100 ms.

I have read through a lot of topics such as Interrupts etc., however me being a newbie, I am quite not sure on how to implement it.

Does anyone have an idea on how this functionality could be implemented?

Any help would be greatly appreciated!

Best Regards,
h_se

See Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

1 Like

Hi @UKHeliBob ,

I have already tried using millis(), however I am not getting any result if my task duration is 100 ms or lesser. With millis(), I am only able to run my task every 1 second.

You can easily get sub 100 ms timing using millis() and there is always micros() available if you need it

Please post a full example of the sketch where only 1 second timing is possible. So far we know nothing about what your tasks are doing or even what Arduino board you are using

More details please

1 Like

Hello h_se

Post your current sketch in code tags to see how we can help.

Do you have experience in OOP?

If you don't want to implement this functionality yourself you could look at RTOS on the ESP32. However, what you want could, as has already been pointed out, be achieved using millis() or micros(). It gets complicated if you want to protect against one task running into the time slice belonging to the next task.

#include <Arduino_MachineControl.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);
using namespace machinecontrol;

int iValue=0;
int counter = 0;
int counter_lcd = 0;

int current_time;
int uptime;
int duration = 100;   

/********************************************/
void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();

  start();
  delay(5000);
  lcd.clear();

  current_time = millis();
}
void loop()
{
  uptime = millis();

  if((uptime - current_time) == duration)
  { 
    duration = duration +100;
    counter = counter + 1;
    
  }

  if(counter == 10)
  {
    clearLCDLine(iValue);
    counter = 0;
    counter_lcd = counter_lcd + 1;
    updatemenu();
    
    
    if(counter_lcd == 1)
    {
      counter_lcd  = 0;
      cursor();
      
    }

  }
}
/********************************************/

void cursor()
{
  
  iValue = encoders[0].getPulses();

  if((iValue < 0) || (iValue > 3))
  {
    iValue = 0;
    encoders[0].reset();
  }
  
  lcd.setCursor(0, iValue);
  lcd.print(">");
  
}

void updatemenu()
{
  main_level();
}

void start()
{
  lcd.setCursor(6,0);
  lcd.print("Line1");
  lcd.setCursor(3,1);
  lcd.print("Line2");
  lcd.setCursor(0,2);
  lcd.print("");
  lcd.setCursor(0,3);
  lcd.print("Line4");
}

void main_level()
{
  
  lcd.setCursor(1,0);
  lcd.print("Mode1");
  lcd.setCursor(1,1);
  lcd.print("Mode2");
  lcd.setCursor(1,2);
  lcd.print("Mode3");
  lcd.setCursor(1,3);
  lcd.print("Performance"); 
}

void clearLCDLine(int line)
{               
        lcd.setCursor(0,line);
        for (int n = 0; n<1; n++)
        {
                lcd.print(" ");
        }
}

This the code that I have written for 2 tasks, Ideally the cursor() should run every 1 ms and updatemenu() should run every 10 ms. But at this moment, both run every 1 second.

adjust MsecPeriod for your needs

void task1 () { Serial.println (__func__); }
void task2 () { Serial.println (__func__); }
void task3 () { Serial.println (__func__); }

struct Sched {
    const unsigned long MsecPeriod;
    void (*func) (void);

    unsigned long msec;
};

Sched sched [] = {
    { 1000, task1 },
    { 2500, task2 },
    { 5000, task3 },
};

const int Nsched = sizeof(sched)/sizeof(Sched);

// -----------------------------------------------------------------------------
void
loop (void)
{
    unsigned long msec = millis ();

    for (int n = 0; n < Nsched; n++)  {
        if (msec - sched [n].msec >= sched [n].MsecPeriod) {
            sched [n].msec = msec;
            sched [n].func ();
        }
    }
}

void
setup (void)
{
    Serial.begin (9600);

    unsigned long msec = millis ();
    for (int n = 0; n < Nsched; n++)
        sched [n].msec = msec;
}

2 Likes

Hi @Delta_G,

You do make a valid point. I did not notice that i was using the wrong comparision operator.

Regarding the second point, That was a test where I was trying to run it every second.

This piece of code does indeed work good.

Hello

Consider this scheduler example.

You have to adjust the timing to your needs.

//https://forum.arduino.cc/t/creation-of-an-os-scheduler/1228636
//https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
#define ProjectName "Creation of an OS / Scheduler"
#define NotesOnRelease "Arduino MEGA tested"
// make names
enum TimerEvent {NotExpired, Expired};
enum TimerControl {Halt, Run};
enum TaskNames {Task1, Task2, Task3, Task4};
// make includes
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// make objects
LiquidCrystal_I2C lcd(0x27, 20, 4);
// make variables
uint32_t currentMicros = micros();
void (*task[])(void) {task1, task2, task3, task4};
uint32_t tickTimes[] {1000000,1000000,1000000,1000000};
// make structures
struct TIMER
{
  uint32_t interval;
  uint8_t control;
  uint32_t now;
  uint8_t expired()
  {
    uint8_t timerEvent = currentMicros - now >= interval and control;
    if (timerEvent == Expired) now = currentMicros;
    return timerEvent;
  }
};
struct SCHEDULE
{
  uint8_t name;
  TIMER tickTimer;
  void run()
  {
    if (tickTimer.expired() == Expired) task[name]();
  }
};
SCHEDULE schedulers[]
{
  {Task1, tickTimes[Task1], Run, 0},
  {Task2, tickTimes[Task2], Run, 0},
  {Task3, tickTimes[Task3], Run, 0},
  {Task4, tickTimes[Task4], Run, 0},
};
// -------------- user tasks ----------------------
//
void task1()
{
  Serial.println(__func__);
}
void task2()
{
  Serial.println(__func__);
}
void task3()
{
  Serial.println(__func__);
}
void task4()
{
  Serial.println(__func__);
}
//
// ----------------------------------------------------
// make application
void setup()
{
  Serial.begin(115200);
  Serial.print("Source: "), Serial.println(__FILE__);
  Serial.print(ProjectName), Serial.print(" - "), Serial.println(NotesOnRelease);
  // initialize the LCD
  lcd.init();
  // Turn on the blacklight and print a message.
  lcd.backlight();
  lcd.print(ProjectName);
  Serial.println(" =-> and off we go\n");
}
void loop()
{
  currentMicros = micros();
  for (auto &scheduler : schedulers) scheduler.run();
}

Have a nice day and enjoy coding in C++.

2 Likes

Here's a scheme that can run every second, starting at 0:

void report(void) {
  uint32_t now = millis();
  const uint32_t interval = 1000;
  static uint32_t last = -interval;
  if (now - last >= interval){ 
    last += interval;
    Serial.print(now);
    Serial.print(" ");
  }
}

Extended to the rest of your schedule:

void setup() {
  Serial.begin(115200);
}

void loop() {
  report();
  task1();
  task2();
  task3();
  task4();
}

void report(void) {
  uint32_t now = millis();
  const uint32_t interval = 1000;
  static uint32_t last = -interval;
  if (now - last >= interval){ 
    last += interval;
    Serial.print("\n");
    Serial.print(now);
    Serial.print(": ");
  }
}


void task1(void) {
  uint32_t now = millis();
  const uint32_t interval = 100;
  static uint32_t last = -interval;
  if (now - last >= interval){ 
    last += interval;
    Serial.print(now);
    Serial.print("T1 ");
  }
}

void task2(void) {
  uint32_t now = millis();
  const uint32_t interval = 1;
  static uint32_t last = -interval;
  if (now - last >= interval){ 
    last += interval;
    Serial.print(".");
  }
}

void task3(void) {
  uint32_t now = millis();
  const uint32_t interval = 10;
  static uint32_t last = -interval;
  if (now - last >= interval){ 
    last += interval;
    Serial.print(now);
    Serial.print("T3 ");
  }
}

void task4(void) {
  uint32_t now = millis();
  const uint32_t interval = 100;
  static uint32_t last = -interval;
  if (now - last >= interval){ 
    last += interval;
    Serial.print(now);
    Serial.print("T4 ");
  }
}
1 Like

"Every 100us" is pretty aggressive for most microcontrollers. a 16MHz AVR will take ~10us just to do the context switch between two processes (save all the register state of one, restore the state of the other.)

There are several existing multitasking libraries for the assorted Arduinos, ranging from quick hacks to full-fledged RTOS implementations; have you looked at any of those?

1 Like

Yes. The code that I uploaded does it every second. But my aim was to reach the levels of 100 microseconds, 1 ms etc.

That is interesting! However, I am working on a Portenta Machine Control board with an STM32 processor. So, I do believe that I can achieve such timings.

However, could you suggest me some libraries for the Portenta Machine Control board, that could help me with the Task-scheduling or even implementing RTOS?

Did you ever check my scheduler proposal?

I have already implemented the code(it works with lower timer frames), however my main question was on how to create a much effective Task-scheduling system/OS. The code I have uploaded(with some minor tweaks) does fulfill the functionality, but I am quite skeptical of its performance when I would try to add further components.

So, to rephrase my question, What is the best possible/effective way to implement this functionality?

I am trying to implement an OS-functionality to my project. The main aim is that Task1 runs every 100 microseconds, Task2 runs every 1 millisecond, Task3 runs every 10 milliseconds and atlast Task4 runs every 100 ms.

Hey @paulpaulson,

I just now saw your scheduling code. I have not tried it as of now, but I am quite optimistic that it would work.

Those two concepts are mutually exclusive.

As already suggested, use the time-honored Arduino millis() approach. You may adapt that as necessary to use micros().

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.