Particle Photon multi blink sample using threads

Here is a simple example of Threads in Photon. As you all know since 0.4.6, threads are supported in beta. Looking at the firmware code, I found this file which defines the threads. I just played with it by creating multiple threads that simply prints some debug strings. I thought it would be great to share it here. I modified my sample to blink 3 LEDs at different rates (replica of this VIPER sample).

Here is the source code of the sample, hope this sneak peek is helpful for you.

#include "application.h"

// LED Param, defines pin and delay between blinks
typedef struct {
    int pin;
    int delay;
} LED_PARAM;

// LED pins
int led1 = D5;
int led2 = D3;
int led3 = D1;

// Defines thread parameters
LED_PARAM ledParams[3] = {
    {led1, 500},
    {led2, 1000},
    {led3, 2000}
};

Thread* led1Thread;
Thread* led2Thread;
Thread* led3Thread;

os_thread_return_t ledBlink(void* param){
    LED_PARAM *p = (LED_PARAM*)param;
    
    // Start never ending loop
    for(;;) {
        Serial.print("Thread Parameters: ");
        Serial.print(p->pin);
        Serial.print(", ");
        Serial.println(p->delay);
        
        // Blink led
        digitalWrite(p->pin, HIGH);
        delay(p->delay);
        digitalWrite(p->pin, LOW);
        delay(p->delay);
    }
}

void setup() {
    Serial.begin(115200);
    
    // Set pin mode
    pinMode(led1, OUTPUT);
    pinMode(led2, OUTPUT);
    pinMode(led3, OUTPUT);
    
    // Start new threads
    led1Thread = new Thread("sample", ledBlink, &ledParams[0]);
    led2Thread = new Thread("sample", ledBlink, &ledParams[1]);
    led3Thread = new Thread("sample", ledBlink, &ledParams[2]);
}

void loop() {
    // Nothing here.
}

Here is a demo video of the sample

Happy Threading :slight_smile:

8 Likes

This a nice simple example for introducing threading.

Unfortunately it also introduces one if the pitfalls of multithreaded code. I imagine if you look at the serial output, you may see missing output, interleaved output or other problems, and not a nice interleaving of the 3 threads as you might expect.

In a future release, the photon will also support mutexes, which are one way to solve this problem.

2 Likes

yes, the interleaving is more visible when the delay is same for all threads or the difference is very small. When I looked at the code I didn’t see any synchronization objects. But found Critical Section. For the time being this should help

#include "application.h"

typedef struct {
    int pin;
    int delay;
} LED_PARAM;

int led1 = D5;
int led2 = D3;
int led3 = D1;

LED_PARAM ledParams[3] = {
    {led1, 500},
    {led2, 500},
    {led3, 500}
};

Thread* led1Thread;
Thread* led2Thread;
Thread* led3Thread;

void debugPrint(LED_PARAM* p);

void debugPrint(LED_PARAM* p){
    CriticalSection cs;
    
    Serial.print("Thread Parameters: ");
    Serial.print(p->pin);
    Serial.print(", ");
    Serial.println(p->delay);
}

os_thread_return_t ledBlink(void* param){
    LED_PARAM *p = (LED_PARAM*)param;
    
    for(;;) {
        debugPrint(p);
        
        digitalWrite(p->pin, HIGH);
        delay(p->delay);
        digitalWrite(p->pin, LOW);
        delay(p->delay);
    }
}

void setup() {
    Serial.begin(115200);
    
    pinMode(led1, OUTPUT);
    pinMode(led2, OUTPUT);
    pinMode(led3, OUTPUT);
    
    led1Thread = new Thread("D1", ledBlink, &ledParams[0]);
    led2Thread = new Thread("D2", ledBlink, &ledParams[1]);
    led3Thread = new Thread("D3", ledBlink, &ledParams[2]);
}

void loop() {
    
}

Hi, running your code with a finite loop causes the program to crash after the loop is over…

Is there a way around this?

Thanks,
Jerry

@Jairbearrr, what do you mean “with a finite loop”? In the threads or in loop()?

If it is in the threads it is because by definition, a thread cannot “end” so it needs an infinite loop. You must destroy the thread to stop it.

2 Likes

Hi Peekay, how do I destroy a thread?
Where can I find some information?

Thanks

1 Like

FreeRTOS does not allow you to destroy threads, so once a thread is created it has to exist indefinitely. :slight_smile:

This seems excessively useful and I can’t wait until the day arrives when I successfully figure this out.

So I have a threads question and this feels like a good place to ask it. @mdma mentioned earlier that mutexes are coming, and I was under the impression that the application CPU on Particle devices was single threaded, so doesn’t that mean that there can’t ever be a race condition between threads? If there’s only one actual hardware thread, a variable can’t be changed while being used by another, on a Photon or Electron, right?

I’m thinking I’m wrong about that because mutexes are coming, and if they’re coming that means they deliver something that is not present currently.

So, my logic is arguing with itself, here. Which is right?

@naikrovek, single threaded doesn’t guarantee mutual exclusivity. FreeRTOS is preemptive meaning it can interrupt code at any time. Variables might be in the process of being changed when the FreeRTOS scheduler changes threads. Add to that the fact that hardware resources (SPI, I2C, Serial) can’t be grabbed at any time. There are wonderful examples of this classic mistake on this forum where folks were confused that data going to a serial port on two different threads was not in the order they expected! Adding mutexes is not only for variables but also for hardware resources.

1 Like

OK, so is there a way to guarantee atomicity (I think that’s the correct term) for variables or devices, currently?

@naikrovek, not really. There isn’t an elegant mutex solution as @mdma may have wanted to implement. I suggest your read the section of the docs referring to [system threading (https://docs.particle.io/reference/firmware/photon/#system-thread) for different mechanisms you could use.

1 Like

This is excellent information, thank you.

1 Like

Hello.

I don’t know if this is the right place to post. Is there a dedicated sub forum for freertos?
If yes, please move this.

So, I have been reading the photon is using freeRTOS (am I mistaken?) but the functions I find here are not the ones I am using when working on freeRTOS.

I am looking for a way to send messages in queues, just like in freeRTOS, can anyone inform me on how to do this?

In freeRTOS, I would create a queue with the xQueueCreate function and send messages with the xQueueSend function.

Thanks a lot!
Vasilis

Actually, I just tried this code and it doesn’t work.
I changed the led for D7 and I also added SYSTEM_THREAD(ENABLED); at the top of the code.

Has anything changed?
Any ideas what should I do?

Thanks,
Vasilis.

edit: it seems that no other program runs now. I tested the BLINK AN LED program and it doesn’t work. All it does is breathing GREEN.

Did I break it? Any advice?

second edit: Safe mode and the blink mode is working.

So, any ideas about the threads?

Hi, the example at the top of the page is now working.
However I found these two files from another post here:


But when I even try to include any freertos file, the compiler complains that it cannot find these files.

Is there any example that is using the functions from these files?
Just for a kickstart, after that I think I can work it out. I have been using freeRTOS for a couple of years now and I like it a lot.

These files are included in the framework and hence no #include statement (other than Particle.h) is required for the public APIs (esp. spark_wiring_thread.h).
But for concurrent_hal.cpp you may not have direct access to these functions as they might not be public (would need to check that tho’)

Hello and thanks for the answer!

Yes I found out about the concurrent_hal API after a while. I used the queue functions, I created one, sent a message and received it and it is working fine!

Now I am looking at the TCP functions, I want to use the classic socket approach with accept etc :smile:

By the way, why did you choose not to just expose the FreeRTOS functions to the user?

That's a question for Particle direct - forum mods (Elite) are not employed by Particle.

@KyleG, can you get an engineer to answer this?

1 Like

There are several reasons why we have not exposed FreeRTOS directly:

  • it’s not available on all platforms, and future platforms are free to use a different RTOS. Portability between different platforms would be lost.

  • each function has to be exposed by hand, and so we wanted to expose an API that we can port across architectures.

  • Mapping FreeRTOS to C++11 threading primitives provides similar functionality with portability.

Those are the primary reasons. That said, this is not written in stone and can be changed where it creates value.

What is the use case for needing to access FreeRTOS directly vs using the C++11 threading primitives like thread, mutex etc?

1 Like