Teensy 3.x multithreading library first release

Add
Code:
NVIC_SET_PRIORITY(IRQ_SOFTWARE, 16);
to setup(). 208 is the default.


If that does not work, can you please post a short example that I can copy&paste to my arduino? I want to find&fix the problem, if it is not the interrupt-priority.

@Paul we should rename IRQ_SOFTWARE... it's misleading.
or add a #define IRQ_AUDIO IRQ_SOFTWARE
 
At a priority of 16 it still clicks like before. I increased the priority all the way to 1,where it only clicks around two times per play.
But at a priority of 11 my SK6812 led strips startet to freak out (i use them with the ws2812Serial Library from Paul).

And sending is kind of a problem because the code has around 1800 lines... I could try to send it here or upload it to github.
 
But at a priority of 11 my SK6812 led strips startet to freak out (i use them with the ws2812Serial Library from Paul).
I assume you keep in mind that the priority level are in steps of 16 (0 to 15 are the same, followed by 16 to 31, etc.)
IOW, the lowest 4 bits of the priority number are ignored (or better reserved for future chips)
 
Code:
AudioInputUSB               usb1;
AudioInputI2S               i2s1;
AudioMixer4                 LEFTC;
AudioMixer4                 RIGHTC;
AudioPlaySdWav              SDwav;
AudioOutputI2S              i2sout;
AudioMixer4                 mixer1;
AudioMixer4                 mixer2;
AudioAnalyzePeak            peak_L;
AudioAnalyzePeak            peak_R;
AudioConnection          patchCord1(usb1,    0, LEFTC,  0);
AudioConnection          patchCord2(usb1,    1, RIGHTC, 0);
AudioConnection          patchCord3(i2s1,    0, LEFTC,  1);
AudioConnection          patchCord4(i2s1,    1, RIGHTC, 1);
AudioConnection          patchCord5(LEFTC,   0, mixer1, 0);
AudioConnection          patchCord6(RIGHTC,  0, mixer2, 0);
AudioConnection          patchCord7(SDwav,   0, mixer1, 1);
AudioConnection          patchCord8(SDwav,   1, mixer2, 1);
AudioConnection          patchCord9(mixer1,  0, i2sout, 0);
AudioConnection          patchCord10(mixer2,  0, i2sout, 1);
AudioConnection          patchCord11(LEFTC,   0, peak_L, 0);
AudioConnection          patchCord12(RIGHTC,  0, peak_R, 0);

AudioControlSGTL5000     CHIP;

IntervalTimer FadeSDin;
IntervalTimer FadeSDout;

//void DebugSermon() {}

class FADECLASS {
  public:
    void in(volatile int ch, volatile float Gain) {
      mixer1.gain(ch, Gain);
      mixer2.gain(ch, Gain);
    }
    void out(volatile int ch, volatile float Gain) {
      mixer1.gain(ch, 1 - Gain);
      mixer2.gain(ch, 1 - Gain);
    }
};
FADECLASS Fade;

void SDin() {
  noInterrupts();
  lockT();
  Fade.in(1, G);
  Fade.out(0, G);
  G += 0.1;
  if (G > 1.1) {
    FadeSDin.end();
  }
  unlockT();
  interrupts();
}

void SDout() {
  noInterrupts();
  lockT();
  Fade.in(0, G2);
  Fade.out(1, G2);
  G2 += 0.1;
  if (G2 > 1.1) {
    delayofchance = millis() - 50;
    delaybegun = true;
    FadeSDout.end();
    SDwav.stop();
  }
  unlockT();
  interrupts();
}

////////////////////////////////////////////////////////////////////////////
void HandleAudio() {
  //Serial.print("Audio: ");
  //Serial.println(threads.id());
  //while (1) {
  unsigned long curr = millis();
  if (SDwav.isPlaying() == false && plays == true && FDOUT == false) {
    noInterrupts();
    lockT();
    SDwav.play(currPlay);
    delay(5);
    plays = false;
    FDOUT = true;
    waitSonder = false;
    G = 0;
    pre = curr;
    FadeSDin.begin(SDin, 500000);
    unlockT();
    interrupts();
  }

  if (curr - pre > AudioTime + FadeOffset && FDOUT == true) {
    G2 = 0;
    FadeSDout.begin(SDout, 500000);
    FDOUT = false;
  }
  if (DebugBoot && Debugdelay >= Debugwait) {
    Debugdelay = 0;
    //DebugSermon();
  }
  threads.yield();
  //}
}

This is the code that handles the audio (There are quite a few unnecessary things but i wanted to try something with classes)
Maybe this helps....

This is for locking the threads:
Code:
void lockT() {
  for (int i = 0; i <= 4; i++) {
    if (i != threads.id()) {
      threads.suspend(i);
    }
  }
}

void unlockT() {
  for (int i = 0; i <= 7; i++) {
    if (i != threads.id()) {
      threads.restart(i);
    }
  }
}
 
Is there an other thing i could try beside changing the interrupt priority?

Restart from basics
get audio playback example to work, which, by design, will work without threads (All is interrupt driven)
Now, introduce threads, that must run on a lower interrupt priority (higher number)
Obviously the treads are not allowed to interfere with audio (e.g. disable interrupts, reading, writing to microSD, etc)
etc.etc
 
Hi,

what is the correct way of terminating a thread from inside the thread? Is a "return" adequate?
The doku says:
All threads start immediately and run until the function terminates (usually with a return).
Once a thread ends because the function returns, then the thread will be reused by a new function.
What does "reused by a new function" mean? Which new function? And what if there is no new function?

And my second question: Instead of rebooting the entire microcontroller I would like to restart one thread only. What is the best way of doing that?

Kind regards
Wolfgang
 
just make sure the thread exits the scope, return or not.
you know the thread needs a while loop to remain always running, the moment that while loop exits, the thread exits, and the thread stops as it exits scope. so if you wanted to restart a thread, exit it first then restart it again later on in your code

you can use a volatile variable to break the while loop as well, if your code decides to end the thread, you can always if ( var ) return; (or break;) the loop so the thread exits. then you can start it again later on (have the thread unset the volatile variable right before the while loop begins)
 
Class method thread

When I compile the following code (platformIO) I get the following error message:
Code:
lib/Test/test.cpp: In member function 'void Test::begin(HardwareSerial*)':
lib/Test/test.cpp:20:46: error: invalid use of non-static member function
   readThreadID = threads.addThread(readSerial);
                                              ^
lib/Test/test.cpp:21:46: error: invalid use of non-static member function
   processThreadID = threads.addThread(process);
                                              ^
Compiling .pio/build/teensy41/FrameworkArduino/yield.cpp.o
*** [.pio/build/teensy41/libeec/Test/test.cpp.o] Error 1

In post # 292 it appears to me that the methods BaseControl::readSerialData and BaseControl::processBaseCommand are not static methods they both access class variables.

Any insight would be helpful. I’m just starting to try and understand how to use threads

Code:
main.cpp

#include <Arduino.h>
#include <test.h>

#define UART Serial5
#define BAUD 9600

HardwareSerial myUART = HardwareSerial(UART);

Test myTest;

void setup() {
  delay(5000);
  Serial.begin(115200);
  while (!Serial); //Wait for user to open terminal
  Serial.println("Setup:");
  
  myUART.begin(BAUD);  // void return
  delay(1000);
  myTest.begin(&myUART);
  
}

void loop() {
  // put your main code here, to run repeatedly:
}

Code:
test.h

#pragma once

#include <Arduino.h>
#include "circular_buffer.h"


// Test -----------------------------------------------------------------------
class Test{
public:
	Test(void);
  
  // Methods ------------------------------------------------------------------
  void begin(HardwareSerial *hws);

private:
  void readSerial();
  void process();
  Circular_Buffer<uint8_t, 256> readCB;   // Size must be power of 2
  HardwareSerial *hwsPort;
 
  uint8_t readThreadID;
  uint8_t processThreadID;
  uint64_t nBytesRead = 0;  
};

Code:
test.cpp
#include <Arduino.h>

#include <test.h>
#include <TeensyThreads.h>

Threads::Mutex hwSerial5;

// ----------------------------------------------------------------------------
Test::Test(void){
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWriteFast(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWriteFast(LED_BUILTIN, LOW);
}

// Initialize the Serial port -------------------------------------------------
void Test::begin(HardwareSerial *serial){
  hwsPort  = serial; 
  readThreadID = threads.addThread(readSerial);
  processThreadID = threads.addThread(process);
  
} // begin(serial)


void Test::readSerial(){
  uint8_t chr;
  digitalWriteFast(LED_BUILTIN, LOW);
  
  while (true){  
    {Threads::Scope scope(hwSerial5);
    while (hwsPort->available() > 0){
      chr = hwsPort->read();
      readCB.write(chr);
      nBytesRead++;
      if (readCB.available() == 0)
        digitalWriteFast(LED_BUILTIN, HIGH);
    } // available
    } // scope 
  } // true  
} // readSerial


void Test::process(){
  uint8_t chr;
  
  {Threads::Scope scope(hwSerial5);  // protecting readCB
  while (readCB.available() > 0){
    chr = readCB.read();
    Serial.printf("%02X \n", chr);  // place holder for someting more intresting
  } // available
  } // scope   
} //process

thanks
dk23
 
Hi,

I ran into the same problem as dk23, is it possible to use addThread with a non static method?

Thanks
Wolfgang
 
Hi,

I ran into the same problem as dk23, is it possible to use addThread with a non static method?

Thanks
Wolfgang

There's a trick that's used in most threading systems. You pass the class instance as a parameter:

Code:
MyClass serobj;

void runThreadStatic(void *obj) {
  ((MyClass *)obj)->runThreadMethod()
}

void setup() {
  threads.addThread(runThreadStatic, &serobj);
}
 
Hi
In my case, I want to define the number of threads in my code and not statically in your library. It's because in my program I need 10 threads and each time Teensyduino is updated, I need to go back to your library and modify this value ( MAX_THREADS) and it's a bit boring.
Is there a trick to do that?
Thanks a lot for this library.
 
Hi
In my case, I want to define the number of threads in my code and not statically in your library. It's because in my program I need 10 threads and each time Teensyduino is updated, I need to go back to your library and modify this value ( MAX_THREADS) and it's a bit boring.
Is there a trick to do that?
Thanks a lot for this library.

I suppose the easiest thing would be to just increase the number in github. It should have very minimal impact. Do you guys think 16 is good for the maximum number of threads?
 
Yes, I think that 16 it's enough. There is a Teensyduino 1.55 update in preparation. Do you think that it will be ok for this update?
 
Yes, I think that 16 it's enough. There is a Teensyduino 1.55 update in preparation. Do you think that it will be ok for this update?

I committed the change to github along with a few other tweaks. I don't know if it will make it into Teensyduino this time around.
 
Why not use a static variable that the user can set before threads start?

Since execution is a round robin, it could also be a linked list that would just grow as needed. I decided not to do that to avoid dynamic memory allocation whenever possible, but the unfortunate side effect is a compile-time limit on the number of threads.
 
Templates seem like a natural solution. No dynamic memory allocation and easy to set by the user.
 
Loop through threads

Hi,

I would like to loop through all threads and output statistics about the stacks. Is there a better solution than this?

Kind regards

Code:
void loop() {
	static int maxPercent[threads.MAX_THREADS+1] = {};
	static uint32_t millis_old = millis()-10000;
	static bool print = false;

	if(millis() - millis_old >= 10000) {
		millis_old = millis();
		print = true;
	}

	for(int i=1; i<=threads.MAX_THREADS; i++) {
		int state = threads.getState(i);
		if(state >= 1 && state <= 4) {
			int used = threads.getStackUsed(i);
			int remaining = threads.getStackRemaining(i);
			int percent = used*100/(used+remaining);
			if(percent > maxPercent[i])
				maxPercent[i] = percent;
	
			if(print == true) {
				if(i == 1)
					DEBUG_PRINTLN(F(""));
				DEBUG_PRINTLN(F("Thread ID %d: State %d | stack used: %5d B ; %3d %% ; max %3d %% | stack total: %5d B"),i, threads.getState(i), used, used*100/(used+remaining), maxPercent[i], used+remaining);
			}
		}
	}
	print = false;
}
 
Last edited:
Back
Top