Teensy 3.x multithreading library first release

Sadly, I haven't really managed to keep that list the way I did before Teensy 3.0.

One of my major goals for this year is vastly improving the website, including the libraries documentation....
 
Yesterday I ran the test of 2 IMUs where they were on the same breadboard so the values were very similar even if I rotated the board. This morning I put them on separate breadboards so I could rotate each individually. YPR values were not behaving nicely so I switched over to reading the raw values and noticed that I was loosing values of the magnetometer so errors would abound since I am using that for yaw. I got that fixed by adding a small delay between readings in the library to make sure the mag was read properly. Probably had to do this anyway. Once I did that readings behaved nicely.

The other thing I noticed is that in the threaded version the update rate of the threads is actually too fast so that I get repeat readings for a several milis before the next update, see the output below:

Code:
ensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130166,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130166,

Sensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130166,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130167,

Sensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130167,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130167,

Sensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130167,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130167,

Sensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130167,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130167,

Sensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130168,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130168,

Sensor 0: 48,-140,17096,-3,15,9,287,71,11,2848,,130168,
Sensor 1: 228,504,16848,5,4,-11,82,-54,-164,2720,130168,

Last column is millis. First three are accel, gyro, mag, temp counts and then millis.

Ok. As I was typing this I left the program running and just a system kernel diags from the kinetsis. I attached. Any ideas?
 

Attachments

  • SynKernelDiag2017-04-15_11-12-49.zip
    225.1 KB · Views: 105
You will get same repeat responses from DHT22 as it only updates every 2 seconds, so it repeats same values for 2 seconds period
how are the functions setup?

Tony
 
Usually its fine with a 2ms update rate. Not sure what you mean by how the functions set up? Basically the raw values functions call a getMotion10Counts function that reads the registers containing the raw values. Based on your question I started thinking a little more and it may be a issue of the update rate of the sensors. Have to do a double check. May have to change the rates. It is not a big issue, was just meant to be an observation. When run as a single the raw values will update at a 2ms rate.

Mike
 
Did a little more testing. Changed millis to micros and it is incrementing. Should have done that to begin with. Also, as soon as I move it the values change immediately.
 
mjs513: are you using setTimeSlice or setMicros for your last post?

im using setTimeSlice(x,1) for my threads using millis() in code
 
tonton81: I'm using setTimeSlice(0,1) for the main thread and setTimeSlice(x,50) for the other threads. Why i used 50 i don't remember. Have to give it a try with just x,1 to see the effect on the data. I had noticed in the other sensor tests if i didn't give it enough time it would return erroreous data. I also use thread.delay so it releases time to the other thread.
 
Is it possible to create a pointer to std::thread and then using the new operator in a switch statement to create a new thread based on dynamic input?

From otherClass:
Code:
void doWork(uint16_t workItem)
{
uint8_t workItem1, WorkItem2;
uint8_t streamData[5];
workItem1 = lowByte(workItem >> 8);
workitem2 = lowByte(workItem);
streamData[0] = 0;
streamData[1] = workItem1;
streamData[2] = workItem2;
streamItem[3] = (streamItem[0] + streamItem[1] + streamItem[3]) & 0xFF; //I know the mask isn't necessary here due to the uint8_t declaration.

Serial.write(streamData, 5);
return;
}

From main class:
Code:
#include "otherClass.h"
#include "TeensyThreads.h"

std::thread* th1;
otherClass* workClass;
uint16_t workItem;

void setup() {
Serial.begin(115200);
th1 = new std::thread();
workClass = new otherClass();
}

void loop() {
workItem = 0;
switch(workItem){
case 1:
//Do Nothing
break;
case 2:
//Call the Thread library to start a new thread.
th1.addThread(workClass->doWork, this->workItem, -1, 0);
break;
default:
break;
}
workItem++;
}

I have a semi-large and very dynamic program that is using stream data to take data from one bus, convert it to a standard format, then stream it out via Serial. I would love to multi-thread this (so I can write to SD as well as stream to the serial data stream), as I believe it would be a more elegant solution than a single thread doing all the work.
 
Is it possible to create a pointer to std::thread and then using the new operator in a switch statement to create a new thread based on dynamic input?

This should be possible. You can create std::thread objects using the "new" operator.

However, there are a few problems with your code. First, the function you call has to be static. That means it either has to be outside a class, or it has to have the "static" keyword before the "void". One side effect is that this will prevent it from accessing the object's members. In your case, that probably won't be a problem. Just move it outside otherClass.

Code:
...
case2:
th1 = new std::thread(doWork, this->workItem)
...

One problem with this approach is when do you "delete" the "th1" object you just created?

If you just want to spawn a new worker thread for every "case 2", you might be better off just using the available "threads" object as in

Code:
case 2:
threads.addThread(doWork, this->workItem);

there is no need for the -1, 0 parameters because those are the default. This starts execution, passing the parameter.

You may face a problem if you have two threads running at the same time and they both call "Serial.write" at exactly the same time. To avoid this, you have to create a lock as in:
Code:
// put this in the global scope outside of any function
Threads::Mutex serial_lock;

Code:
{
Threads::Scope s(serial_lock);
Serial.write(streamData, 5);
}
[code]
 
Thanks!

That was just some quick code I whipped up as an example. The actual project is far more complex. Maybe 100K lines of code. I can add it here, but I thought it would be entirely too large.

Here, I'll post some snippets:

CANBUS.cpp
Code:
void CANBUSClass::gotFrame(CAN_message_t &frame, int mailbox)
{
	//int Threads::addThread(ThreadFunction p, void * arg, int stack_size, void *stack)
	switch (frame.id)
	{
	case 0x108:
		this->rpm = utils::getValueFromTwoBytes(frame.buf[1], frame.buf[0]);
		//threads.addThread(decoder->writeRPM, this->rpm, -1, 0);
		decoder->writeRPM(rpm);
	default:
		break;
	}
}

decode.h (This is declared as a member of CANBUS as a pointer, then instantiated with decoder = new decodeClass();)
Code:
void decodeClass::writeSpeedInMsec(uint32_t speedIn) //SpeedIn comes in CM/sec from the event handler CANBUS::GotFrame()
{
	//Speed (m/s) = (Data1 * 2^24 + Data2 * 2^16 + Data3 * 2^8 + Data4)* 0.01

	uint8_t outgoingSerialMessage[10];
	uint8_t checksum = 0, byte1 = 0, byte2 = 0, byte3 = 0, byte4 = 0;


	//Copy the bits into the byte array for serial stream formatting
	byte1 = lowByte(speedIn >> 24);
	byte2 = lowByte(speedIn >> 16);
	byte3 = lowByte(speedIn >> 8);
	byte4 = lowByte(speedIn);

	//Build the Serial Message;
	outgoingSerialMessage[0] = 11;
	outgoingSerialMessage[1] = byte1;
	outgoingSerialMessage[2] = byte2;
	outgoingSerialMessage[3] = byte3;
	outgoingSerialMessage[4] = byte4;
	outgoingSerialMessage[5] = 0;
	outgoingSerialMessage[6] = 0;
	outgoingSerialMessage[7] = 0;
	outgoingSerialMessage[8] = 0;

	for (int i = 0; i < 9; i++)
	{
		checksum += outgoingSerialMessage[i];
	}
	outgoingSerialMessage[9] = checksum;

	//Send it. This will be changed into the serialHelper class and sent as a pointer-to array and a length so the serialWriter thread can handle all serial writes.
	Serial1.write(outgoingSerialMessage, 10);
	return;
}

The idea is that the temp vars will be one thread, the pressure vars will be another thread, RPM its own, Speed yet another thread, one thread to handle logging to the SD card, and finally a SerialWriter thread that handles all serial writes.

This is my first foray into Teensy and using C++. I am an application developer used to writing for windows using Embarcadero.
 
Last edited:
why not just have the canbus on one thread instead of many? it makes no sense since your accessing the same module, ive put my 2 uart displays on thread1, an android root console on uart6 on thread2, and port expanders/vars on thread0

if your using multiple threads for 1 piece of hardware, i dont see a point in multithreading a single file access (single file, as in the line queue)
 
if your using only one canbus module, putting RPM and SPEED in different threads will be the same as if they are in the same thread, not only will you need to use mutex locks, but you will still have them both trying to access the same device, never together, but TAKING TURNS. SD card in a different thread is fine, so is Serial, you only need mutex locks if you use the same serial/i2c/spi/whatever among different threads
 
I am okay with locking.

It works as it's written now (single-threaded) .

I was thinking that multi-threading the application would alleviate some of the issues I was having with the 1Mb/sec stream being decoded.
 
Last edited:
to aleviatte the issue you could setup 2 can controllers, on TWO DIFFERENT spi busses, then you can run RPM & VSS in parallel.

question, the rpm and vss can be requested and received in the same canbus packet, so only one send, and one receive.
for performance reasons i request 2-3 items and read them all from 1 packet, i dont understand why you want to segregate and then parallel complicating things

if you setup filters you will not have to worry about the 1Mb/sec traffic, like i did for the ecu traffic, i filtered everything else out and boom, no lost frames, and if your feeling lucky, you could dynamically change the filters depending on your setup
 
Last edited:
do you think i can read data from multiple microphones simultaneously using multi-threading on teensy 3.6?
 
yes if they have their own bus, OR, the thread prioritizes them in case your other code is causing delays
example: i can parse gps live in one thread while another thread runs delay(50000);
this will not stop the gps from working

if you put 2 gps as an example, in their own thread, each having their own uart, they will both run (to the human) in parallel.
 
to aleviatte the issue you could setup 2 can controllers, on TWO DIFFERENT spi busses, then you can run RPM & VSS in parallel.

question, the rpm and vss can be requested and received in the same canbus packet, so only one send, and one receive.
for performance reasons i request 2-3 items and read them all from 1 packet, i dont understand why you want to segregate and then parallel complicating things

if you setup filters you will not have to worry about the 1Mb/sec traffic, like i did for the ecu traffic, i filtered everything else out and boom, no lost frames, and if your feeling lucky, you could dynamically change the filters depending on your setup

It's not an OBD-compliant ECU. It's an aftermarket ECU that just blasts the data across the bus. The switch/case is how I handle each ID I actually need. There are no IDs sent that are unnecessary.

It's functioning now. I just wanted to make it a bit more elegant and this multi-thread library seemed the perfect opportunity to do that.
 
sorry but as long as you have one can device your stuck with that performance and multithreading will not improvee upon it.
 
I would like to move the serial writes into their own thread. When data comes in via the CAN bus, I'd like to buffer N frames, then start a thread to write the data in the required format. Each serial channel has its own format however, so it's a bit tedious. That is, each channel has its own length. The only commonality is that pressure and temperature are a fixed size, where rpm, speed, a/fr, are their own.

speed is a 10 byte array
Pressure Vars are 6 bytes
Temp vars are 5 bytes
A/Fr is 5 bytes.

Ideally, I would like to do the calculations in the thread handling the event handler CANBUSClass::gotFrame(CAN_message_t &frame, int mailbox);

I am not sure how to implement this. Can I send a pointer to an array that's non-static and non-global (but public) into the threads.addThread() method?

The issue is that the updates come so quickly from the ECU and it's completely out of my control. They are blasted out all willy-nilly at a million bits per second. If I try to store them for 500ms, then send, I'll end up overflowing. If i disregard frames, I'll lose data.

I am sure there is an elegant solution. I just can't get my head around this threading library.
 
@Detroit_Aristo:

I doubt threads would make your life easier. Have you actually done multi-threaded programming? I'm getting the impression, you are trying to use the thread library in an entirely wrong way.

There is a ton of pitfalls - not even 'new' / 'malloc' are thread safe (as in you will get memory corruption).

Is your CANBUSClass::gotFrame() called from CANListener::frameHandler() ? If so, it's running as ISR and you have some gotchas communicating to your threads (you can't use a Mutex for locking).

I don't see a benefit in having a serial writer thread. Just make the serial TX buffer large enough.

The SdFat library is very good about calling 'yield()' (with my SD cards, the max latency is 75us on Teensy 3.6), so I would make everything polling-based. That will dramatically increase chances that you won't end up with something that occasionally hangs or crashes in mysterious ways.

I wouldn't use the CANListener::frameHandler() and just poll (you get the benefit of FlexCAN doing buffering for you).

E.g.:

Code:
setup() {
    // ...
}

void loop() {
    if(log_buffer_has_enough_entries) {
        // SdFat will call yield very frequently, CAN messages will still be processed
        // sdfat.write(...)
    }
    // yield is automatically called after 'loop()' 
}

void yield() {
    // check available TX buffer size so that serial doesn't block when we write
    if(serial.availableForWrite() >= largest_message_size) {
        if(canbus.available()) {
            // decode CAN message, write to serial
            // add entry to logbuffer
        }
    }
    // do whatever else...
}

You can adjust the FlexCAN receive buffer: SIZE_RX_BUFFER in 'FlexCAN.h' and the Serial1 transmit buffer: SERIAL1_TX_BUFFER_SIZE in 'arduino/hardware/teensy/avr/cores/teensy3/serial1.c'.
 
@Detroit_Aristo:

I doubt threads would make your life easier. Have you actually done multi-threaded programming? I'm getting the impression, you are trying to use the thread library in an entirely wrong way.


You can adjust the FlexCAN receive buffer: SIZE_RX_BUFFER in 'FlexCAN.h' and the Serial1 transmit buffer: SERIAL1_TX_BUFFER_SIZE in 'arduino/hardware/teensy/avr/cores/teensy3/serial1.c'.

I have however, I have never done it on a Teensy and I have only multi-threaded applications intended for CISC processors on the x86 and x64 platform using the MS CLR.

It's ColinK's FlexCAN library, so it's CANListener::gotFrame.
 
Increasing the buffer size and using the Serial1.availableForWrite() function seems to have resolved my issue.

Thank you for the pointers. This was purely my inexperience with the libraries and their use. Apologies. It turns out I won't need to multi-thread this unless I add another display on the SPI or !2C bus.
 
Back
Top