Teensy 3.x multithreading library first release

@fritas. Would this also apply to the main thread, ie, threads.addThread(0)?

As it stands, it will suspend thread 0 if waiting for a lock. I think that will be OK because even though interrupts run on thread 0, they are started by the CPU and the context switcher will not switch while an interrupt is active, so interrupts should still run. Are there other things I should worry about?
 
I would use a better wrapper for Serial.print. C++ has excellent support for variadic functions. 'print()' / 'println()' is atomic for printing its entire parameter list.

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

// println implementation - can be placed in a header file
namespace arduino_preprocessor_is_buggy {
    // helper for parameter pack expansion, 
    // due to GCC bug 51253 we use an array
    using expand = int[];
    // used for suppressing compiler warnings
    template<class T> void silence(T&&) {};

    inline Threads::Mutex& getSerialLock() {
        static Threads::Mutex serial_lock;
        return serial_lock;
    }
  
    template<class T> void print_fwd(const T& arg) {
        Serial.print(arg);
    }

    template<class T1, class T2> void print_fwd(const std::pair<T1, T2>& arg) {
        Serial.print(arg.first, arg.second);
    }

    template<class... args_t> void print(args_t... params) {
        Threads::Scope locker(getSerialLock());
        silence(expand{ (print_fwd(params), 42)... });
    }
  
    template<class... args_t> void println(args_t... params) {
        Threads::Scope locker(getSerialLock());
        silence(expand{ (print_fwd(params), 42)... });
        Serial.println();
    }
}

using arduino_preprocessor_is_buggy::print;
using arduino_preprocessor_is_buggy::println;
using std::make_pair;
// end println implementation


void setup() {
    Serial.begin(9600);
    delay(2000);
}

int counter = 0;

void loop() {
    counter++;
    println("I ", "support ", "any ", "number ", "of ", "parameters");
    println("The counter: ", counter);
    println("The counter as HEX: ", make_pair(counter, HEX));
    delay(100);
}
 
@ftrias. Ran my crazy thread program using sonar, the i2c BNO055 IMU, and sharp IR sensors.

Test 1 with no serial locking, and all serials in the loop() returned data was good:
Code:
0=26cm 1=20cm 2=24cm 3=37cm   --- Sonar data
IR Distances: 17 -- 7
-0.56,2.06,2.56------------------------------------ roll, pitch yaw from BNO055
0=26cm 1=20cm 2=24cm 3=37cm 
IR Distances: 17 -- 7
-0.56,2.06,2.56
0=26cm 1=20cm 2=24cm 3=37cm 
IR Distances: 17 -- 7
-0.56,2.06,2.56
0=26cm 1=20cm 2=24cm 3=37cm 
IR Distances: 17 -- 7

Test 2. Just using your wrapper (no serial prints in threads). It takes a very long time for threads to start.
Code:
0=28cm 1=26cm 2=24cm 3=0cm 
IR Distances: 15 -- 0
-1.00,3.25,32.50
0=28cm 1=26cm 2=24cm 3=0cm 
IR Distances: 15 -- 0
-1.00,3.25,32.50
0=28cm 1=26cm 2=24cm 3=0cm 
IR Distances: 15 -- 0
-1.00,3.25,32.50

Looks like I am starting to loose data values. Probably could be fixed with adjustments to timeslice or add delays.

Test 3. Prints in the functions. Only get sonar data and its all zeros.

Test 4. Arbitrarily added a threads.delay(10); in before the print in the sonar function and it appears to work.
Code:
0=26cm 1=24cm 2=19cm 3=36cm 
0=26cm 1=24cm 2=19cm 3=36cm 
0=26cm 1=24cm 2=19cm 3=36cm 
0=26cm 1=24cm 2=19cm 3=36cm 
IR Distances: 13 -- 28
-0.44,2.56,43.56
0=27cm 1=24cm 2=19cm 3=36cm 
0=27cm 1=24cm 2=19cm 3=36cm 
-0.44,2.56,43.56
0=27cm 1=24cm 2=19cm 3=36cm 
0=27cm 1=24cm 2=19cm 3=37cm 
0=27cm 1=24cm 2=19cm 3=37cm 
0=27cm 1=24cm 2=19cm 3=37cm 
-0.44,2.56,43.56
0=27cm 1=24cm 2=19cm 3=37cm 
0=27cm 1=24cm 2=19cm 3=36cm 
-0.44,2.56,43.56

Test 5. Mixed mode with delay in sonar and prints in sonar. Would say test failed.
Code:
Sonar thread started
IR thread started
BNO055 thread started
IR Distances: 0 -- 0
0.00,0.00,0.00
IR Distances: 0 -- 0
0.00,0.00,0.00
IR Distances: 0 -- 0
0.00,0.00,0.00
IR Distances: 0 -- 0
0.00,0.00,0.00
IR Distances: 0 -- 0
Hangs at this point.

@tonton81 will probably give you a better test of the wire wrapper.

Heres the sketch if you are interested.
Code:
#include <Arduino.h>
#include "TeensyThreads.h"
#include <NewPing.h>
#include <Streaming.h>
#include <vector>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>


/* Set the delay between fresh samples for BNO055*/
#define BNO055_SAMPLERATE_DELAY_MS (100)
Adafruit_BNO055 bno = Adafruit_BNO055(55);

#define rad2deg 57.2957795131
#define deg2rad 0.01745329251
#define m2ft 3.280839895

float yar_heading, pitch, roll;
float gyroz, accelx, accely;

#define telem Serial
unsigned int pingSpeed = 50; // How frequently are we going to send out a ping (in milliseconds). 50ms would be 20 times a second.

#define SONAR_NUM     4 // Number or sensors.
#define MAX_DISTANCE 200 // Maximum distance (in cm) to ping.
#define PING_INTERVAL 45 // Milliseconds between sensor pings (29ms is about the min to avoid cross-sensor echo).

unsigned int cm[SONAR_NUM];         // Where the ping distances are stored.
uint8_t currentSensor = 0;          // Keeps track of which sensor is active.
unsigned long pingTimer[SONAR_NUM]; // Holds the times when the next ping should happen for each sensor.

NewPing sonar[SONAR_NUM] = {
  NewPing(27, 26, MAX_DISTANCE), //lower left sensor
  NewPing(36, 35, MAX_DISTANCE), //front middle
  NewPing(30, 29, MAX_DISTANCE), //lower rigth sensro
  NewPing(34, 33, MAX_DISTANCE)  // Each sensor's trigger pin, echo pin, and max distance to ping.
};

//IR Sensor Pins
const int leftIRsensor = A22;   //Front
const int rightIRsensor = A2;   //Rear
int frtIRdistance, rearIRdistance;
int max_IR_distance = 200;

int id1, id2, id3;

ThreadWrap(Serial, SerialX);
#define Serial ThreadClone(SerialX)

void my_priv_func1(){
  while(1){
   for (uint8_t i = 0; i < SONAR_NUM; i++) { // Loop through all the sensors.
    if (millis() >= pingTimer[i]) {         // Is it this sensor's time to ping?
      pingTimer[i] += PING_INTERVAL * SONAR_NUM;  // Set next time this sensor will be pinged.
      if (i == 0 && currentSensor == SONAR_NUM - 1) oneSensorCycle(); // Sensor ping cycle complete, do something with the results.
      sonar[currentSensor].timer_stop();          // Make sure previous timer is canceled before starting a new ping (insurance).
      currentSensor = i;                          // Sensor being accessed.
      cm[currentSensor] = 0;                      // Make distance zero in case there's no ping echo for this sensor.
      sonar[currentSensor].ping_timer(echoCheck); // Do the ping (processing continues, interrupt will call echoCheck to look for echo).
    }
    threads.delay(10);
  for (uint8_t i = 0; i < SONAR_NUM; i++) {
    Serial.print(i);
    Serial.print("=");
    Serial.print(cm[i]);
    Serial.print("cm ");
  }
  Serial.println();
   }
  }
}


void my_priv_func2(){
  while(1){  
  int sum = 0;
  for (int i=0; i<3; i++) {
    int sensor_value = analogRead(leftIRsensor);  //read the sensor value
    if(sensor_value < 100){
      sensor_value = 100; 
    }
    sum = sum + sensor_value;
    threads.delay(5);
    }
    frtIRdistance = 27495 * pow(sum/3,-1.36); //convert readings to distance(cm)

  sum = 0;
  for (int i=0; i<3; i++) {
    int sensor_value = analogRead(rightIRsensor);  //read the sensor value
    if(sensor_value < 100){
      sensor_value = 100; 
    }
    sum = sum + sensor_value;
    threads.delay(5);
  }
  rearIRdistance = 25445 * pow(sum/3,-1.362); //convert readings to distance(cm)
  telem << "IR Distances: " << frtIRdistance << " -- " << rearIRdistance << endl;
  }
}


void my_priv_func3(){
  while(1){
    sensors_event_t sensor;
    bno.getEvent(&sensor);
    imu::Vector<3> euler = bno.getVector(Adafruit_BNO055::VECTOR_EULER);
    imu::Vector<3> accel_linear = bno.getVector(Adafruit_BNO055::VECTOR_LINEARACCEL);
    imu::Vector<3> rot_rate = bno.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE);
    
    roll = (float)sensor.orientation.y;
    pitch = (float)sensor.orientation.z;
    yar_heading = (float)sensor.orientation.x;

    gyroz = (float) rot_rate.z() * rad2deg;
    accelx = (float) accel_linear.x() * m2ft;
    accely = (float) accel_linear.y() * m2ft;

    
    //telem << roll << "\t" << pitch << "\t" << yar_heading << endl;
    //telem << "Changed heading: " << yar_heading << endl;
    threads.delay(BNO055_SAMPLERATE_DELAY_MS);
    telem << -roll << "," << -pitch << "," << yar_heading << endl;
  }
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Wire.begin(400000);
  //ctx_switch_timer.begin(ctx_switch_timer_isr, 100);
  //context_timer.priority(254);
  //context_timer.begin(context_switch_isr, 100);
  
  BNO055_Init();

  delay(5000);
  //Serial.print("Thread start ");
  //if (threads.setMicroTimer(5)==0) { // ticks every 10 microseconds!
  //  Serial.println("Failed to set timer!");
  //}
  pingTimer[0] = millis() + 75;           // First ping starts at 75ms, gives time for the Arduino to chill before starting.
  for (uint8_t i = 1; i < SONAR_NUM; i++) // Set the starting time for each sensor.
    pingTimer[i] = pingTimer[i - 1] + PING_INTERVAL;

  id1 = threads.addThread(my_priv_func1);
  id2 = threads.addThread(my_priv_func2);
  id3 = threads.addThread(my_priv_func3);
  threads.setTimeSlice(0, 1);
  threads.setTimeSlice(id1, 20);
  threads.setTimeSlice(id2, 1);
  threads.setTimeSlice(id3, 50);
  if (threads.getState(id1) == Threads::RUNNING) Serial.println("Sonar thread started");
  if (threads.getState(id2) == Threads::RUNNING) Serial.println("IR thread started");
  if (threads.getState(id3) == Threads::RUNNING) Serial.println("BNO055 thread started");
}


void oneSensorCycle() { // Sensor ping cycle complete, do something with the results.
/*  for (uint8_t i = 0; i < SONAR_NUM; i++) {
    Serial.print(i);
    Serial.print("=");
    Serial.print(cm[i]);
    Serial.print("cm ");
  }
  Serial.println();
  telem << "IR Distances: " << frtIRdistance << " -- " << rearIRdistance << endl;
  telem << -roll << "," << -pitch << "," << yar_heading << endl;
*/
}

void loop() {
    oneSensorCycle();
}

void echoCheck() { // If ping received, set the sensor distance to array.
  if (sonar[currentSensor].check_timer())
    cm[currentSensor] = sonar[currentSensor].ping_result / US_ROUNDTRIP_CM;
}
 
i tried putting:

Code:
ThreadWrap(Serial, SerialX);
ThreadWrap(Wire, WireX);
#define Serial ThreadClone(SerialX)
#define Wire ThreadClone(WireX)

but only thread 0 (loop) is being processed

i noticed you removed Threads::Scope from the function in your example. I presume its not needed anymore since the wrapper takes care of it? following the example only thread 0 is active, other thread doesnt do anything

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

ThreadWrap(Serial, SerialX);
ThreadWrap(Wire, WireX);
#define Serial ThreadClone(SerialX)
#define Wire ThreadClone(WireX)

int count = 0;
const int LED = 13;

//Threads::Mutex i2c_lock;
//Threads::Mutex serial_lock;

void setup() {
  Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, 100000);
  Wire.setDefaultTimeout(200000); // 200ms
  pinMode(LED, OUTPUT);

  auto blinky = threads.addThread(blinkthread);
//  threads.setTimeSlice(0, 1);
//  threads.setTimeSlice(1, 1);
   threads.setMicroTimer(100);
  //  threads.setTimeSlice(blinky, 1000);
}


void loop() {
  byte x, y;
//return;
  Wire.beginTransmission(32); Wire.write(0x0C); Wire.write(0xFF); Wire.write(0xFF); Wire.endTransmission();
  Wire.beginTransmission(32); Wire.write(0x12); Wire.endTransmission(); Wire.requestFrom(32, 2);
  x = Wire.read();
  y = Wire.read();
  {
    if ( x == 191 && y == 127 ) {
      Serial.print("Wire Register Read Thread1: ");
      Serial.print(x);
      Serial.print(" : ");
      Serial.print(y);
      Serial.println(" GOOD!");
    }
    else {
      Serial.print("Wire Register Read Thread1: ");
      Serial.print(x);
      Serial.print(" : ");
      Serial.print(y);
      Serial.println(" FAIL!");
    }
  }

}

void blinkthread() {
  while (1) {
    byte x, y;
    Wire.beginTransmission(32); Wire.write(0x0C); Wire.write(0xFF); Wire.write(0xFF); Wire.endTransmission();
    Wire.beginTransmission(32); Wire.write(0x13); Wire.endTransmission(); Wire.requestFrom(32, 1);
    x = Wire.read();
    Wire.beginTransmission(32); Wire.write(0x12); Wire.endTransmission(); Wire.requestFrom(32, 1);
    y = Wire.read();
  { if ( x == 127 && y == 191 ) {
        Serial.print("Wire Register Read Thread2: ");
        Serial.print(x);
        Serial.print(" : ");
        Serial.print(y);
        Serial.println(" GOOD!");
      }
      else {
        Serial.print("Wire Register Read Thread2: ");
        Serial.print(x);
        Serial.print(" : ");
        Serial.print(y);
        Serial.println(" FAIL!");
      }
    }

  }
}
 
I am embarrassed to say that I think the problem @mjs513 and @tonton81 are having with the latest code is due to a bug in the svcall_isr() function, which is used in yield(). It wasn't getting the right stack and thus wasn't always running correctly. I revised the Git repository with a fix. I also altered the unlock() function to automatically yield if there is a waiting thread. If this fixes the problem, I'll put in a more complete fix.
 
i just tried the repo update, same as last post

Looking at your code again, I think there are other problems. The ThreadWrap() macro was only intended for simple libraries that only need to lock each function call. That would be Serial, MIDI, etc. Wire needs to lock one or more transactions and multiple function calls. You have to do those manually as below. I can't think of any way to avoid it.

Code:
{
    Threads::Scope scope(i2c_lock);
    Wire.beginTransmission(32); Wire.write(0x0C); Wire.write(0xFF); Wire.write(0xFF); Wire.endTransmission();
    Wire.beginTransmission(32); Wire.write(0x13); Wire.endTransmission(); Wire.requestFrom(32, 1);
    x = Wire.read();
    Wire.beginTransmission(32); Wire.write(0x12); Wire.endTransmission(); Wire.requestFrom(32, 1);
    y = Wire.read();
}
If not, one thread could write one set of values while another writes a different set and they clobber each other.
 
Code:
#include <Arduino.h>
#include "TeensyThreads.h"
#include <i2c_t3.h>

//ThreadWrap(Serial, SerialX);
//ThreadWrap(Wire, WireX);
//#define Serial ThreadClone(SerialX)
//#define Wire ThreadClone(WireX)

volatile uint32_t myCounter = 0;

int count = 0;
const int LED = 13;

Threads::Mutex i2c_lock;
Threads::Mutex serial_lock;

void setup() {
  Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, 100000);
  Wire.setDefaultTimeout(200000); // 200ms
  pinMode(LED, OUTPUT);

  auto blinky = threads.addThread(blinkthread);
  threads.setTimeSlice(0, 1);
  threads.setTimeSlice(1, 1);
  //  threads.setMicroTimer(10);
  //  threads.setTimeSlice(blinky, 1000);
}


void loop() {
  byte x, y;
  {
    Threads::Scope scope(i2c_lock);
    Wire.beginTransmission(32); Wire.write(0x0C); Wire.write(0xFF); Wire.write(0xFF); Wire.endTransmission();
    Wire.beginTransmission(32); Wire.write(0x12); Wire.endTransmission(); Wire.requestFrom(32, 2);
    x = Wire.read();
    y = Wire.read();
  }
  {
    Threads::Scope locker(serial_lock);
    if ( x == 191 && y == 127 ) {
      Serial.print("Wire Register Read Thread1: ");
      Serial.print(x);
      Serial.print(" : ");
      Serial.print(y);
      Serial.println(" GOOD!");
    }
    else {
      Serial.print("Wire Register Read Thread1: ");
      Serial.print(x);
      Serial.print(" : ");
      Serial.print(y);
      Serial.println(" FAIL!");
    }
    myCounter++;
    Serial.println(myCounter);
  }
  threads.yield();
}

void blinkthread() {
  while (1) {
    byte x, y;
    {
      Threads::Scope scope(i2c_lock);
      Wire.beginTransmission(32); Wire.write(0x0C); Wire.write(0xFF); Wire.write(0xFF); Wire.endTransmission();
      Wire.beginTransmission(32); Wire.write(0x13); Wire.endTransmission(); Wire.requestFrom(32, 1);
      x = Wire.read();
      Wire.beginTransmission(32); Wire.write(0x12); Wire.endTransmission(); Wire.requestFrom(32, 1);
      y = Wire.read();
    }
    {
      Threads::Scope locker(serial_lock);
      if ( x == 127 && y == 191 ) {
        Serial.print("Wire Register Read Thread2: ");
        Serial.print(x);
        Serial.print(" : ");
        Serial.print(y);
        Serial.println(" GOOD!");
      }
      else {
        Serial.print("Wire Register Read Thread2: ");
        Serial.print(x);
        Serial.print(" : ");
        Serial.print(y);
        Serial.println(" FAIL!");
      }
    }
    myCounter--;
    Serial.println(myCounter);
    threads.yield();
  }
}

okay, reverting back to the old code, i added a counter, 1 thread to count up, another to count down. I have a question as I'm not sure this is loosing an event in another thread or not (i2c/serial). If a mutex is locked, the other thread skips what it suppose to do right? and cause that event to be lost? I hope I said it understandably hehe

I have this output with the counter:

Code:
Wire Register Read Thread2: 127 : 191 GOOD!
7684
Wire Register Read Thread1: 191 : 127 GOOD!
7685
Wire Register Read Thread2: 127 : 191 GOOD!
7684
Wire Register Read Thread1: 191 : 127 GOOD!
7685
Wire Register Read Thread2: 127 : 191 GOOD!
7684
Wire Register Read Thread1: 191 : 127 GOOD!
7685
Wire Register Read Thread2: 127 : 191 GOOD!
7684
Wire Register Read Thread1: 191 : 127 GOOD!
7685
Wire Register Read Thread2: 127 : 191 GOOD!
7684
Wire Register Read Thread1: 191 : 127 GOOD!
7685
Wire Register Read Thread1: 191 : 127 GOOD!
7686
Wire Register Read Thread2: 127 : 191 GOOD!
7685
Wire Register Read Thread1: 191 : 127 GOOD!
7686
Wire Register Read Thread2: 127 : 191 GOOD!
7685
Wire Register Read Thread1: 191 : 127 GOOD!
7686
Wire Register Read Thread2: 127 : 191 GOOD!
7685
Wire Register Read Thread1: 191 : 127 GOOD!
7686
Wire Register Read Thread2: 127 : 191 GOOD!
7685
Wire Register Read Thread1: 191 : 127 GOOD!
7686
Wire Register Read Thread2: 127 : 191 GOOD!
7685
Wire Register Read Thread1: 191 : 127 GOOD!
7686
Wire Register Read Thread1: 191 : 127 GOOD!
7687
Wire Register Read Thread2: 127 : 191 GOOD!
7686
Wire Register Read Thread1: 191 : 127 GOOD!
7687

does that mean the loop (thread 0) which ran twice skipped an update from thread1 which would be lost?
OR
does that mean even though thread 0 ran twice before thread1, that thread1 never ran because it was "suspended" as in put on hold aka blocking until it was able to continue?

also previously threads.setMicroTimer(1); worked, now anything less than threads.setMicroTimer(515); doesnt output anything on serial monitor

using threads.setMicroTimer(515); without settimeslice shows 0 and 1 0 and 1 etc in order
Code:
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1
Wire Register Read Thread2: 127 : 191 GOOD!
0
Wire Register Read Thread1: 191 : 127 GOOD!
1

for the above, adding a delay(1000) to thread0(loop) scope that is locked, i get this result:


Code:
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread2: 127 : 191 GOOD!
13
Wire Register Read Thread1: 191 : 127 GOOD!
14
Wire Register Read Thread1: 191 : 127 GOOD!

if i understand that correctly, it does mean when the mutex is locked the threads needing access will wait for the unlock before continuing. this looks good this way. I dont know what happened with setMicroTimer though, something broke it when value is below 515 range, and with the settimeslices values set as 1 for both, there seems to be more activity from thread0 than thread1, and without, setmicrotimer runs them sequentially fine

the mutex locking & suspending threads is actually working pretty well. ive tested with delay(2000) as well as threads.delay(2000) (inside the mutex scope lock) for visual observation in serial monitor and it doesn't seem to be missing any events, this is very good. this might be good now in a multiple i2c device scenario. the only thing that _seems_ broken is the timeslices/microtimer. will this work on spi as well?

Thanks
Tony
 
Last edited:
note, this actually might be VERY good, it seems I just tried now, to ONLY enable scope lock on thread1, since that prevented thread0 loop from running (which has no scope locks), this was also a confirmation that the thread0(loop) was paused until the thread1 scope exited. so far no errors on the bus. hows that for an update? :)

result with thread1 on scope lock, thread0 without scope locks:

Code:
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
 
Last edited:
I made another mistake. I did not sync my changes with Github properly so my patch was not in the repository. I've synced now.
 
output with delay(2000) in i2c_lock thread0 scope:

Code:
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 1
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 0
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967295
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967294
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967293
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967292
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967291
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967290
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967289
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967288
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967287
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967286
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967285
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967284
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967283
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967281
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967282
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967283
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967282
Wire Register Read Thread1: 191 : 127 GOOD! : Counter: 4294967283
Wire Register Read Thread2: 127 : 191 GOOD! : Counter: 4294967282

doesnt seem to be missing anything, prioritizes pretty well.
setMicroTimer as 1 uSec works as well, however, there is no output to serial monitor if used WITH settimeslice, using one, or the other works. (setMicroTimer(1)) or (setTimeSlice(0,1) && setTimeSlice(1,1)), but not together
 
@mjs513:
You are sharing state between threads without synchronization (e.g. using a mutex, C++ atomics or volatile). That is undefined behavior, meaning anything can happen. A thread may or may not see updates of the shared state (the values may be kept in registers). A thread may or may not see spurious changes to the shared state (the compiler may use the memory to store intermediate computations).

If code that doesn't do the correct synchronization is working correctly, it's by pure coincidence and may change depending on the compiler flags, making a tiny change anywhere in the code or the phase of the moon.
 
@ftrias:
Your mutex implementation lacks proper compiler/memory barriers. It will work without LTO, but the latest Teensyduino release makes using LTO easy. If the mutex code gets inlined, things won't work correctly. Adding "__attribute__ ((noinline))" to lock() / unlock() is probably good enough (on Cortex M4).
 
@tni. Thanks for the link going through it now. If I use volatile for the variables that I am sharing between threads would that suffice or should I declare the atomics which I never used before. Man, l actually love this thread - no pun intended.

Mike
 
Question for:
Code:
Threads::Scope scope(i2c_lock);
is i2c_lock derived from hardware? or can it be any name? scope(myI2CLock)

I would presume it locks any type of access when used no?
ex. will this work with i2c&serial for example?

Code:
  {
    Threads::Scope scope(lockThreads);
    Wire.beginTransmission(32); Wire.write(0x0C); Wire.write(0xFF); Wire.write(0xFF); Wire.endTransmission();
    Wire.beginTransmission(32); Wire.write(0x12); Wire.endTransmission(); Wire.requestFrom(32, 2);
    Serial.println("OK DONE");
  }
 
@tni. Thanks for the link going through it now. If I use volatile for the variables that I am sharing between threads would that suffice or should I declare the atomics which I never used before. Man, l actually love this thread - no pun intended.

Mike

Not necessarily. A lot depends on the location of the variables, the internal machine, the code generated. For example, if a variable is unaligned and it crosses a cache boundary, you might get into a state where part of the variable in memory was modified, and part was not, depending on exactly when the interrupt occurred. Or if the variable is bigger than what a single load/store can access in one instruction.

Your best bet is to use atomic locks at the thread level to protect yourself from other threads. You would use volatile of appropriate types for communication with code run within interrupts, but you should never access a lock from within interrupt context. If you are doing DMA, you really need to do a deep dive of the appropriate data sheet to know what the safe practices are.

Threads which are based on interrupts are hard, and you need to understand what is going on at the instruction and micro-architecture level to write appropriate code.
 
@ftrias:
Your mutex implementation lacks proper compiler/memory barriers. It will work without LTO, but the latest Teensyduino release makes using LTO easy. If the mutex code gets inlined, things won't work correctly. Adding "__attribute__ ((noinline))" to lock() / unlock() is probably good enough (on Cortex M4).

Thanks for pointing this out. This is an important consideration that I neglected to account for. "noinline" is a great idea. I think I will also sprinkle a few DMB instructions to make sure the lock is complete before returning.
 
Michael

Thanks for the explanation. Need some time to absorb it all. New to this level of programming. Nothing is every easy.

Mike
 
@tni. Thanks for the link going through it now. If I use volatile for the variables that I am sharing between threads would that suffice or should I declare the atomics which I never used before. Man, l actually love this thread - no pun intended.
If you want portable code, you shouldn't use volatile. Even though it protects you from compiler optimizations, the underlying hardware may have very weak memory ordering guarantees and reads/writes can be completely out of order.

On Teensy, volatile is quite well-behaved and you can expect atomic reads / writes for 8bit / 16bit / 32bit variables that are properly aligned. The hardware doesn't do nasty memory reordering (for CPU memory access).

If you want to update multiple variables, you should usually use a mutex. E.g. for your accelerometer updates, your thread will otherwise potentially see a mix of values from previous / current update cycle.
 
@tni. Thanks for the advice. Actually not looking at making this portable. Only planning on using it on Teensy 3.5. The test code I use is part of a much larger program for an autonomous rover using the teensy.
 
ftrias, good evening

i was wondering if you could confirm post #141, does the name on the mutex lock really matter?
and can code be combined inside the same scope (serial&i2c/spi)

im trying to understand if a mutex is a global lock, or just a specific lock, or if the naming is derived from hardware (i2c_lock)? if a mutex scope is locked, shouldnt all hardware access regardless of bus work within that scope?

is it also recursive? what happens if you call another function from within the scope, and the other function also has a scope lock, curious

thank you
Tony
 
Last edited:
@tonton81:
No, the name of the mutex doesn't matter. It's just a normal C++ object. The TeensyThreads mutex is not recursive. If you call lock() again, the thread will hang.

You don't want to use a combined lock for USB Serial and I2C. USB Serial and I2C are independent and can be used at the same time. If you use a single mutex for both, you won't be able to use USB Serial in a second thread, while you are using I2C in the first one.
 
so if i have a thread that locked the mutex to do it's own thing and it calls another function to finish the job, and that function already contains a scope lock in it as well, the entire micro locks up indefinately? wow...

for the most part there will be 24/7 spi & i2c read/write polling, the usb serial is just for debugging purposes, but serial1 and serial2 will always have 2-way flowing syncronized data
In my case the threads are just for priority loads, and most if not all functions talk to a multiple shared bus (11 spi & 12 i2c), thats why i ask
 
so if i have a thread that locked the mutex to do it's own thing and it calls another function to finish the job, and that function already contains a scope lock in it as well, the entire micro locks up indefinately? wow...

for the most part there will be 24/7 spi & i2c read/write polling, the usb serial is just for debugging purposes, but serial1 and serial2 will always have 2-way flowing syncronized data
In my case the threads are just for priority loads, and most if not all functions talk to a multiple shared bus (11 spi & 12 i2c), thats why i ask

That's right, as @tni pointed out, mutex locks generally are not "recursive".

To use the bathroom analogy, a mutex is like the lock on a bathroom door. Once you go in, you lock the door. Then no one else can go in and they have to either wait in line or go do something else until you come out.

Each bathroom has it's own lock. So in your case I2C has a mutex lock; SPI has a mutex lock; Serial has a mutex lock.

If you have a heart attack in the bathroom (so if your thread crashes or goes into an infinite loop), then you've got a big problem. This is why you have timeouts and contingency code.

If you decide to go out the window, no one will be able to get back in to the bathroom. This is why you use Scope to make sure that no matter how/where you exit the function, your lock will get unlocked. This is especially problematic in desktop applications with exceptions. Teensy doesn't have this, but it's always possible you could inadvertently put a "return" in there somewhere and forget to unlock beforehand.

If you call out to your friend and ask him to go into your bathroom and get something, he won't be able to get in. This is your scenario. Don't call other functions when you have a lock because you never know what they'll do.

If you ask your friend to get extra toilet paper from another bathroom and that other bathroom is also locked, then your friend has to wait, and so do you. If while he is waiting there to get you your toilet paper, the guy inside that bathroom asks someone else to get toilet paper from your bathroom, then you've got a deadlock. You're waiting for the other guy's door to unlock. The other guy is waiting for your door to unlock. Everything stops. This is a common problem. For a Teensy example, you could have a lock on Serial over USB, and then you add locking in USB, and then you decide to add a few debugging lines in the USB code that call Serial. This is a recipe for disaster.

So the best thing is to keep locks in controlled parts of the code that don't call any other code that could possibly lock anything.
 
so if a locked scope processes another function call that also has a lock on it, thats a guarenteed deadlock? why not check if the ownership of the current mutex lock is of same ownership of the one being called in the other called function, letting it pass through like a normal single threaded sketch? i would assume that as being recursive, correct? why wouldnt this be useful?

Lets say I start a lock, I would then assume responsibility of all current and future calls, of other functions, regardless if they also include locks of their own, as long as I currently hold the MAIN lock status flag, I should be able to bypass other lock sections to process their code as well. Recursive would be more ideal then. The owner could basically be the thread id (id1) for example. And if thread(id2) tried to call a function with a lock, his access, recursive or not, will be queued until thread(id1) lock (recursive or not) is removed.

thread1: im locked, i own all current and future code, any locks in sub functions will be ignored as I will run their code anyways! (Basically same concept as an everyday single threaded arduino sketch.
thread2: arg, thread1 owns the main lock, I will just wait since I need to aquire a lock.
thread3: gibbs on next queue! I'll be right after you!

if thread(id1) is locked, and a subfunction calls to lock a mutex, I could imagine HIS scope would exit the lock, but, wouldn't the library function just say:

thread(id1) is locked, i need to call foo();, oh! there is a scope lock in that function, im told to not set it as I already own the main lock, that way when it exits I dont remove the main lock, oh great, i returned to my scope, and ended the brace, release the main lock. (this would be sort of like a master lock in single thread mode).
isnt this safe?
 
Last edited:
Back
Top