issues with USB mass storage function within TeensyThreads

strud

Well-known member
This project is for a Teensy4.1, code dev environment is PlatformIO in VScode.

Iam using the USB mass storage library to read and write to a USB memery stick, primarily to save data acquired from a DAQ capture.

It works fine for code architecture just with a single loop, but if I instatiate the USB objects in the main scope then make calls to these within a thread all bets are off.

Doing the same with the SD library seems to be fine.

Is this an expected behaviour?

I can go back to these calls being made from within the loop() if need be, just nice and convenient if I can use the TeensyThreads approach.
 
Yes, most libraries are not coded in a way that makes them safe to be called by multiple threads.
 
Not exactly the answer for your specific problem, but I do have my two cents related to the issue with TeensyThreads, which may or may not be helpful to you.

I have a very large program (30k + lines of code) which use dozens of different libraries, none of them directly thread safe. In the past, I have tried using TeensyThreads with short time slices for each thread and tried to use locks, semaphores and other protections to modify my code and libraries to be thread safe. I had many many issues following this approach, as I often came across bugs and problems which were very intermittent and often impossible to debug.

The fact is that almost everything that involves communication in Teensy (think UART, SD Card, USB host, SPI, I2C, Ethernet) is not thread safe by default. Even if all critical zones of the code are wrapped with mutexes or semaphores, most communication still has the problem of being heavily time-dependent, so the thread just cannot be interrupted during certain moments of the communication process. Fixing this is just too much work and suffering.

What I ended up doing is setting a very long time slice for each thread (around 20 seconds), so the thread will in reality never be interrupted. I just control the switching of the threads using threads.yield() and threads.delay(). Most of the delay() in my code was replaced by threads.delay(). I also place an threads.yield() in the end of main loop and each thread loop, so those areas where the context switching happens are all safe and does not interrupt any critical ongoing operation and communication. At least in my application, it is much easier to do and much less error-prone.
 
What I ended up doing is setting a very long time slice for each thread (around 20 seconds), so the thread will in reality never be interrupted. I just control the switching of the threads using threads.yield() and threads.delay(). Most of the delay() in my code was replaced by threads.delay(). I also place an threads.yield() in the end of main loop and each thread loop, so those areas where the context switching happens are all safe and does not interrupt any critical ongoing operation and communication. At least in my application, it is much easier to do and much less error-prone.

Yes, cooperative multi-tasking is much easier to do with Teensy (and Arduino generally) than preemptive (time-sliced) multi-tasking, and is usually sufficient. The basic idea is to override yield() to do a context switch, and any calls to yield() in the Teensy core or in any library will result in a context switch. To my understanding, this is the intended use of yield(). In general I have a main thread that executes setup() and loop(), and I only create additional threads for I/O tasks that call yield() while waiting for completion, as opposed to creating multiple threads for a set of actions that are logically sequential.
 
Hi Joe

Can you explain more about your setup() and loop() being within a single main thread?

does it look something like this?

mainthread (){


setup();

while (1){
loop()
threads.yield();
}
}


My code structure is currently still having setup() and loop() defined per the Arduino std method, then I'm creating other threads to perform tasks. Will my loop() effectively be turned into a thread by TeensyThreads?
 
does it look something like this?
Code:
mainthread () {
  setup();
  while (1) {
     loop()
     threads.yield();
  }
}

My code structure is currently still having setup() and loop() defined per the Arduino std method, then I'm creating other threads to perform tasks. Will my loop() effectively be turned into a thread by TeensyThreads?

No, it doesn't look like your code above. It's just the standard Arduino setup() and loop(), and when you start your multi-tasking executive, the existing context becomes the "main" thread, and you can define additional threads.
 
Ok, that is what I am doing then.

Will go look at the documentation, but do you know if that main thread has a default ID? ie so one can go and change the time slice etc?
 
The main thread is usually thread ID 0. You can check it by running threads.id() function. To create other threads, you will use threads.addThread(thread_function), which creates the thread and will return the thread ID of the created thread.

There are many ways to use TeensyThreads, as it depends on your use case. In my case, I use "cooperative" multi-tasking, like Joe explained, which means that the threads will cooperate with each other to share the CPU time. In other words, each thread should actively sleep and pass the CPU time to the next thread sometimes, in order not to stave the other threads.

In my use case, I do something like this:

Code:
void thread_func()
{
  while(1)
  {
    //do stuff
    threads.yield(); // Give back CPU time to the next thread
  }
}

void setup()
{
  threads.setDefaultTimeSlice(20000); // Each new thread will have 20s runtime
  threads.setTimeSlice(0, 20000); // Also gives current thread 20s runtime
  for (int i=1; i<=threadCount; i++) threads.addThread(thread_func); // Creates the threads  
}

void loop() 
{
  // do stuff
  threads.yield(); // Give back CPU time to the next thread
}

Check the TeensyThreads Github repo for more info.
Keep in mind that TeensyThreads library is not really maintained. There are many bugs, some of which are very easy to reproduce. There are also some memory related bugs when using malloc or new inside a thread, or when using the threadsInfo function, or getStackUsed or getStackRemaining.
 
Ahh ok, are you able to advise what you are using?

I'm using a cooperative OS of my own, originally developed for 68xxx, and adapted for ARM via the task start and context switch logic in Zilch by @duff on this forum (and github). I've tested it on all Teensy (LC, 3.x, 4.x, MM), and also on Adafruit Feather M4 (SAMD51). It's much simpler than TeensyThreads in terms of task management, and it has mailboxes and queues for task synchronization, and the ability to pend on those objects with a timeout. I'm trying to get it cleaned up so I can post it here.
 
Back
Top