Bare minimum FreeRTOS
I actually wrote a very basic (minimalistic for sure) scheduler a few years back that has no reliance on an OS, just needs a timer interrupt to mark the passage of one millisecond at a time. Here's an article I wrote about it: https://shop.wickeddevice.com/2014/12/22/wildfire-lightweight-periodic-task-scheduler/. It would be super easy (trivial even) to port it to an ARM (from AVR). Tasks are defined apriori in an array of structs like this:

typedef struct {
  volatile uint16_t task_timer;
  uint16_t task_period;
  void (*task)(void);
} task_t;


The member variable task is the function you want called. The member variable task_period is how often you want to call it in milliseconds, and the task_timer variable is counted down to zero in order to determine when next to call task.

The basic 'Arduino-style' pattern looks something like this:

void setup(){
  initializeScheduler();
}

void loop(){
  executeTasks();
}


For posterity, here is the code from the linked article:

#include &lt;avr/interrupt.h&gt;
#include &lt;util/atomic.h&gt;

typedef struct {
  volatile uint16_t task_timer;
  uint16_t task_period;
  void (*task)(void);
} task_t;

////////////////////////////////////////////////////////////////////
//                                                                //
// define NUM_TASKS to be &gt;= the actual number of tasks defined!  //
//                                                                //
////////////////////////////////////////////////////////////////////

#define NUM_TASKS (4)
task_t tasks[NUM_TASKS] = {0};

void initializeScheduler(){
  /////////////////////////////////////////////////////////////////////////////
  //                                                                         //
  // statically set up the tasks here, e.g.:                                 //
  // tasks[0].task_period = 500;   // execute once every 500ms (technically  //
  //                               // "no more frequantly than every 500ms") //
  // tasks[0].task = &amp;exampleTask; // call the function exampleTask          //
  //                                                                         //
  /////////////////////////////////////////////////////////////////////////////

  TCCR3B = _BV(WGM32) | _BV(CS31) | _BV(CS30); // prescaler=64, enable CTC mode
  OCR3A = 250;                                 // compare match every 250 ticks
  TIMSK3 = _BV(OCIE3A);                        // enable compare match ISR
}

// initializeScheduler sets this ISR up to fire once per millisecond
ISR(TIMER3_COMPA_vect){
  for(uint8_t ii = 0; ii &lt; NUM_TASKS; ii++){
    if(tasks[ii].task_timer &gt; 0) tasks[ii].task_timer--;
  }
}

// call this from loop()
void executeTasks(){
  for(uint8_t ii = 0; ii &lt; NUM_TASKS; ii++){
    if(tasks[ii].task != 0){
      if(tasks[ii].task_timer == 0){
        tasks[ii].task();
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
          tasks[ii].task_timer = tasks[ii].task_period;
        }
      }
    }
  }
}

// task functions must take no arguments, e.g.
// void exampleTask(){
//  static uint8_t x = 0;
//  x ^= 1;
//  digitalWrite(A6, x);
// }
```