How can I use RTOS in Teensy 4.0?

I need to use an RTOS in my Teensy 4.0 that has some scheduling algorithm such as Free-RTOS,
one of the most famous that can be implemented in the arduino IDE.

I have been using these libraries without any success:

https://github.com/discord-intech/FreeRTOS-Teensy4
https://github.com/greiman/FreeRTOS-Arduino
https://github.com/tsandmann/freertos-teensy

I read in some threads like this:
https://forum.pjrc.com/threads/41504-Teensy-3-x-multithreading-library-first-release

I can use this library to do parallel processes, however I see that since it does not have a scheduling algorithm,
I cannot synchronize the tasks properly as a mutex or a semaphore would do.

Code:
#include <Arduino.h>
#include "TeensyThreads.h"

const int LED_RED = 15;
const int LED_GREEN = 19;
const int PUSH = 23;
int count = 0;

void blinkRed(){
  while (1) {
    digitalWrite(LED_RED, HIGH);
    threads.delay(250);
    digitalWrite(LED_RED, LOW);
    threads.delay(250);
    threads.yield();
  }
}
void blinkGreen() {
  while (1) {
    digitalWrite(LED_GREEN, HIGH);
    threads.delay(500);
    digitalWrite(LED_GREEN, LOW);
    threads.delay(500);
    threads.yield();
  }
}
void push() {
  while (1) {
    if(digitalRead(PUSH) == 0){
      while(digitalRead(PUSH) == 0){
          count++;
          Serial.print("TOCO ");
          Serial.println(count);
          threads.delay(250); // efecto rebote
      }
    }
    threads.yield();
  }
}
void setup() {
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(PUSH, INPUT_PULLUP);
  threads.addThread(blinkRed);
  threads.addThread(blinkGreen);
  threads.addThread(push);
}

void loop() {
  // nada
}

As can be seen in the code, there are 2 trains of square pulses, one has twice the frequency of the other, and when reviewing it through an oscilloscope, I observe that one of the pulses with form passes over time and moves with respect to the other , that is, it gets out of sync after a while.
I need the pulse trains to be synchronized or in phase always and working in parallel with other tasks.
In my opinion, if I am able to use a scheduler like the one offered by Free-RTOS, I will be able to solve my problem.
How can I implement Free-RTOS or other RTOS on Teensy 4.0?

Thank you very much in advance for your attention.
 
teensythreads has mutexes

Hello, thank you very much for answering.
What worries me is that the square signals in my described code get out of sync, so what I would like to know is if TeensyThreads is a sufficient tool to achieve real-time processing or am I still looking to use Free-RTOS.
On the other hand, does this mean that there is no library to use Free-RTOS?
Do you know any that can be used to implement Real Time?

My ultimate goal is to be able to implement tasks in real time.
 
Have a look at this.

I just did as search for "teensy rtos" and that was one of the few things thrown up.

Hey, thanks. I will try that, as soon as I have results I will comment on them.
It is worth mentioning that I had seen this library before:
https://github.com/greiman/ChibiOS-Arduino
It was announced in this thread:
https://forum.pjrc.com/threads/53662-Teensy-4-RTOS?highlight=RTOS
However, since I saw that only AVR, Due, and Teensy 3.x were supported, I didn't try it.


As you mentioned, I will try to use this library for Teensy 4.0 by same author:
https://github.com/greiman/ChRt
which is the one that states the thread that you share with me.
 
Have a look at this.

I just did as search for "teensy rtos" and that was one of the few things thrown up.

Sorry to answer so late. However, I was learning how to use the ChibiOS library, since finally that was the library that helped me solve my problem.
https://github.com/greiman/ChRt
I will append some codes to help someone who may be looking for the same answers as me.

First of all, I want to clarify that the use of mutex with the TeensyThreads library did not help me to do parallel processes since the mutex blocks the used resource to prevent other threads in execution from interfering.

Code:
#include <Arduino.h>
#include "TeensyThreads.h"

const int LED_RED = 15;
const int LED_GREEN = 19;
const int PUSH = 23;
int count = 0;
Threads::Mutex mutExample;
void blinkRed(){
  while (1) {
    mutExample.lock();
    digitalWrite(LED_RED, HIGH);
    //mutExample.unlock();
    //threads.delay(250);
    delay(250);
    //mutExample.lock();
    digitalWrite(LED_RED, LOW);
    mutExample.unlock();
    threads.delay(250); 
    //delay(250);
    //mutExample.unlock();
    threads.yield();
  }
}
void blinkGreen() {
  while (1) {
    mutExample.lock();
    digitalWrite(LED_GREEN, HIGH);
    //mutExample.unlock();
    //threads.delay(500);
    delay(500);
    //mutExample.lock();
    digitalWrite(LED_GREEN, LOW);
    mutExample.unlock();
    threads.delay(500);
    //delay(500);
    //mutExample.unlock();
    threads.yield();
  }
}
void push() {
  while (1) {
    if(digitalRead(PUSH) == 0){
      while(digitalRead(PUSH) == 0){
          count++;
          Serial.print("TOCO ");
          Serial.println(count);
          threads.delay(250); // efecto rebote
      }
    }
    threads.yield();
  }
}
void setup() {
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_RED, OUTPUT);
  pinMode(PUSH, INPUT_PULLUP);
  threads.addThread(blinkRed);
  threads.addThread(blinkGreen);
  threads.addThread(push);
}

void loop() {
  // nada
}

The result was that although now the pulses are mutually synchronized, the processes do not interfere with each other, that is, as if it were a kind of algorithm without preemtive.
TeensyThreadsScope.jpg
However, it is not what I wish to achieve.

Finally, I tried the ChibiOS library which I attached both by zip file, an example with the same TeensyThreads application and by the link of its original creator on GitHub.

Code:
#include "ChRt.h"

const int PUSH = 23;
int count = 0;

static THD_WORKING_AREA(waTh1, 100); //100 Bytes
static THD_WORKING_AREA(waTh2, 100);

/*struct threadData{
  int _blinkTime;
  int _lightPin;
  int _fadeTime;
};*/




static THD_FUNCTION (blinkerThread, arg) {
  //setup thread vars
  /*threadData *thisData = (threadData*)arg;
  int lightPin = thisData->_lightPin;
  int blinkTime = thisData->_blinkTime;*/
  (void)arg;
  //set the LED pinMode
  //pinMode(lightPin, OUTPUT);
  pinMode(15, OUTPUT);
  while(1){

    //blink
    /*digitalWrite(lightPin, HIGH);
    chThdSleepMilliseconds(blinkTime);
    digitalWrite(lightPin, LOW);
    chThdSleepMilliseconds(blinkTime);*/
    digitalWrite(15, HIGH);
    chThdSleepMilliseconds(500);
    digitalWrite(15, LOW);
    chThdSleepMilliseconds(500);
  }
}



static THD_FUNCTION (fadeThread, arg){
  /*threadData *thisData = (threadData*)arg;
  int lightPin = thisData->_lightPin;
  int fadeTime = thisData->_fadeTime;*/
  (void)arg;

  //pinMode(lightPin, OUTPUT);
  pinMode(19, OUTPUT);
  while(1){
    digitalWrite(19, HIGH);
    chThdSleepMilliseconds(250);
    digitalWrite(19, LOW);
    chThdSleepMilliseconds(250);
    /*for(int i = 0; i < 255; i+=5){
      analogWrite(lightPin, i);
      chThdSleepMilliseconds(fadeTime);
    }
    for(int i = 255; i > 0; i-=5){
      analogWrite(lightPin, i);
      chThdSleepMilliseconds(fadeTime);
    }*/
  }
}










//------------------------------------------------------------------------------
void setup() {
  // inicializa el SerialPort
  Serial.begin(9600);
  // Wait for USB Serial.
  while (!Serial) {}
  // push buttom pin
  pinMode(PUSH, INPUT_PULLUP);
  // initialize and start ChibiOS
  chBegin(chSetup);
  
  // should not return
  while(1);
}


//------------------------------------------------------------------------------
void chSetup() {
  /*threadData set1;
  set1._lightPin = 15;
  set1._blinkTime = 300;*/

  /*threadData set2;
  set2._lightPin = 19;
  set2._fadeTime = 10;*/


  //schedule thread 2 
  //chThdCreateStatic(waTh1, sizeof(waTh1), NORMALPRIO, blinkerThread, (void*)&set1);
  chThdCreateStatic(waTh1, sizeof(waTh1), NORMALPRIO + 1, blinkerThread, NULL);//Mas prioridad

  //schedule thread 3 (fading)
  //chThdCreateStatic(waTh2, sizeof(waTh2), NORMALPRIO, fadeThread, (void*)&set2);
  chThdCreateStatic(waTh2, sizeof(waTh2), NORMALPRIO+1, fadeThread, NULL);//Menos prioridad



  //while(1){ // Se ejecuta con una prioridad normal NORMALPRIO
  //  chThdSleepMilliseconds(10000); // lo que sea
//    if(digitalRead(PUSH) == 0){
//      while(digitalRead(PUSH) == 0){
//          count++;
//          Serial.print("TOCO ");
//          Serial.println(count);
//          chThdSleepMilliseconds(250); // efecto rebote
//      }
//    }
  //}
}
//------------------------------------------------------------------------------
void loop() {// Se ejecuta con una prioridad normal NORMALPRIO
  if(digitalRead(PUSH) == 0){
    while(digitalRead(PUSH) == 0){
        count++;
        Serial.print("TOCO ");
        Serial.println(count);
        chThdSleepMilliseconds(250); // efecto rebote
    }
  }
}

Finally, the result achieved was satisfactory. The context change is immediate, the processes are synchronized running in parallel and you can give it different execution priorities.
ChibiOSScope.jpg

Thanks to everyone who took the time to help me.
I hope this information can be useful to someone on the web.
 

Attachments

  • ChRt-master.zip
    1.2 MB · Views: 66
if you put a delay in a mutex block and share that block accross other threads, of course the mutex would block and not run in parallel, and that output would be expected
 
if you put a delay in a mutex block and share that block accross other threads, of course the mutex would block and not run in parallel, and that output would be expected

That's right, I forgot to specify that I didn't spend a lot of time on that algorithm, if you can reproduce the same output that I got with ChibiOS using TeensyThreads I'd really appreciate it if you shared it. :D
 
I dont have a scope just mentioning it :)

it probably should look better without the mutex, because the way you had it it would wait for a release before toggling. I don't see you do this in chibios and since your using 2 different digital pins, you dont really need a mutex for that (unless you use gpio port accesses) but no delay calls within the mutex regardless, you dont need the mutex here
 
ESP32 fanboy here. Having said that I am used to the freeRTOS it uses.
Recently I had a project that required USB host for attaching a keyboard, but sadly USB host is new for them and I had to find a solution. Picked up a Teensy 4.1 and worked just fine.
I like the product, but not having a "standard" RTOS blows my mind for a product (600Mhz part) as powerful as this.
TO hopefully better explain its like having the power of a windows machine but can only run one program at a time. You got the CPU speed its just begging for threading. I know there are some others out there like mentioned above, but why isn't there some Arduino RTOS that I can know it will be updated and kept current.

I am hoping someone could answer this burning question I have about teensy 4.1 not having a RTOS after all this time.
 
Use TeensyThreads, even though RTOS on ESP32 uses 2 cores mostly it uses tasks on core 1 for arduino, TeensyThreads is very good at switching tasks(threads), although there are no sepamores you still would deal with mutexes in it just like RTOS. I used both and both perform well.
 
Use TeensyThreads

Thanks, I have, but there is a flaw in it, and there is are no priorities.

I created 2 tasks, both the same.

Code:
void thread_func1(int data)
{
  int count = 0;

  while(1)
  {
    count += data;
    Serial.printf("Thread 1: %u\r\n",count);
    delay(150);
  }

}
void thread_func2(int data)
{
  int count = 0;

  while(1)
  {
    count += data;
    Serial.printf("Thread 2: %u\r\n",count);
    delay(150);
  }

}


    threads.addThread(thread_func1, 1);
    threads.addThread(thread_func2, 1);

All it does is print from thread 1, Thread 2 never prints. If i decrease the delay on thread 2 then thread 2 prints and not 1. So ya.... It fails my test as an RTOS.
 
Yes, threads.delay() should be used.

Also using the same resource (Serial) in both threads seems less than ideal. At least when abusing an ISR() to print it seems output order can be mixed.
 
Yes, threads.delay() should be used.

Also using the same resource (Serial) in both threads seems less than ideal. At least when abusing an ISR() to print it seems output order can be mixed.

" threads.delay() should be used. " That's strange because I did look it up and it said it was ok to use delay because it calls yield. I looked at the code for delay() and it does use yield.
Well I tried threads.delay(), it worked. Still wondering why a more robust ROTS has not been implemented.

" Also using the same resource (Serial) in both threads seems less than ideal. "
For sure, you never want to print in an ISR. Though like you said it's good practice to avoid it none the less. But it was a super quickie to check it out. The worst that could have happened is that I would have gotten out of alignment characters from both threads.
 
a scoped mutex would be sufficient there
Not that the posted example was anything more than a trivial example it seems to have been accounted for ... experimentally ...

Looking at the readme ... github.com/ftrias/TeensyThreads:
Experimental: For widely used libraries like Serial, adding locks may require vast changes to the code. Because of this, the library provides a helper class and set of macros to encapsulate all method calls with a lock. An example illustrates its use:

Code:
// this method is experimental
ThreadWrap(Serial, SerialXtra);
#define Serial ThreadClone(SerialXtra)

int thread_func()
{
    Serial.println("begin");
}

In the code above, every time Serial is used, it will first lock a mutex, then call the desired method, then unlock the mutex. This shortcut will only work on all the code located below the #define line. More information about the mechanics can be found by looking at the source code.
 
" threads.delay() should be used. " That's strange because I did look it up and it said it was ok to use delay because it calls yield. I looked at the code for delay() and it does use yield.
Well I tried threads.delay(), it worked. Still wondering why a more robust ROTS has not been implemented.

" Also using the same resource (Serial) in both threads seems less than ideal. "
For sure, you never want to print in an ISR. Though like you said it's good practice to avoid it none the less. But it was a super quickie to check it out. The worst that could have happened is that I would have gotten out of alignment characters from both threads.

Since the readme was open I searched delay(). The only occurenece is under the DOC for the "threads" class:
Code:
[B][U]The following members of class Threads control threads. [/U][/B]
...
void yield()	Yield current thread's remaining time slice to the next thread, causing immedidate context switch
void delay(int millisecond)	Wait for milliseconds using yield(), giving other slices your wait time
 
Guess I'll give it a shot. Thanks

Would be interesting to know it is there as an option.

So far TeensyThreads has gotten some attention. It would be cool to see how much size difference there is for similar samples code ... what is working now and as redone with RTOS just to know how much it brings in.
 
Back
Top