Multithreading on Arduino/esp

Hi,

I want to know if it's possible to run two or more different program loops concurrently on an arduino?

I've implemented an event-based system that deals with several different tasks using specific durations for each event. The system works fine but one issue I have is that certain functions, specifically functions that connect to the wifi and to an mqtt server require the use of delay() while they're waiting for a connection and thus the program blocks in certain areas.

This is why I thought of having multiple threads...

Another solution would be to monitor the connection status without the use of a delay.

I already do this when using the ESP8266WiFiClass' scanNetwork() function which can run asynchronously.

WiFi.scanNetworks(true);

and I keep checking for the connection status with each tick.

Is it possible to do the same thing with the WiFi.begin() and the PubSubClient's connect() methods?

Thank you,
Mehdi

Indeed The solution is not to use delay. Go do something else and come back later to see if the connection is up.

Begin is not the blocking part, most of the examples do


  WiFi.begin("network-name", "pass-to-network");

  Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());

So you just don’t want the active wait for the WL_CONNECTED answer (calling status() does not block)

1 Like

On any single-core microcontroller the way to have mutliple loops running is to have only one loop which is
void loop() itself
and whenever some part of the code shall not run a flag-variable is set to false

boolean initialWiFiBeginDone = false;
boolean check_If_WL_CONNECTED = false;

if ( !initialWiFiBeginDone) {
  WiFi.begin("network-name", "pass-to-network");
  check_If_WL_CONNECTED = true;
  initialWiFiBeginDone = true;
}

if (check_If_WL_CONNECTED == true) {
  // lines of code that check for wifi beeing connected or not
  // and code that makes visible that code tries to connect
  // and code for a timeout and if timeout occured 
  // inicating "no WiFi-connection"
}

The timeout has to be done non-blocking by avoiding a while-loop
remember if you want quick-responsive multi-tasking the only loop that is looping is

void loop()

itself

1 Like

Why not do something like this in your loop function if you don't want any code blocking

currentMillis = millis();
if (setonce == 0)
{
  setonce = 1;
  previousMillis = currentMillis;

}

if (stopchecking == 0{
if (currentMillis - previousMillis >= interval) {
    Serial.print("Connected, IP address: ");
    Serial.println(WiFi.localIP());
    stopchecking = 1;
  }
  else {
    Serial.print(".");
  }
}

If using a ESP32 just use freeRTOS for a solution. Just remember to keep loop() empty.

1 Like

Indeed

from reading's OP's initial message I think he is using a 8266

1 Like

If the ESP8266 has the OS version of firmware freeRTOS is included as well.

1 Like

Are you going to write an interrupt routine that manages the threads?

I spent some time this morning trying to code this:

#include <cstdint>  // uint32_t
#include <cstring>  // memcpy

unsigned constexpr max_threads = 6u;  // No more than 6 concurrent threads

void *const start_address_of_stack = reinterpret_cast<void*>(0x20088000u);

struct Thumb_Register_Set {
    std::uint32_t r[8u];             // eight general purpose registers
    std::uint32_t pc, sp, lr, cpsr;  // program counter, stack pointer, etc.
};

std::size_t constexpr max_stack_size = 64u;

struct Thread {
    bool is_active;
    Thumb_Register_Set registers;
    char unsigned stack[max_stack_size];
};

Thread threads[max_threads] = {};  // maximum six threads

void interrupt(void) // Fires every 15 milliseconds
{
    // Don't use stack variables in here!

    static unsigned current_thread = 0u;

    // First we save the current thread's stack and registers

    // Save all the registers
    __asm__ ("movs %0, r0": : "r"(&threads[current_thread].registers.r[0]));
    __asm__ ("movs %0, r1": : "r"(&threads[current_thread].registers.r[1]));
    __asm__ ("movs %0, r2": : "r"(&threads[current_thread].registers.r[2]));
    __asm__ ("movs %0, r3": : "r"(&threads[current_thread].registers.r[3]));
    __asm__ ("movs %0, r4": : "r"(&threads[current_thread].registers.r[4]));
    __asm__ ("movs %0, r5": : "r"(&threads[current_thread].registers.r[5]));
    __asm__ ("movs %0, r6": : "r"(&threads[current_thread].registers.r[6]));
    __asm__ ("movs %0, r7": : "r"(&threads[current_thread].registers.r[7]));

    // Save the entire stack
    std::memcpy(threads[current_thread].stack, start_address_of_stack, max_stack_size);

    // The following loop increments the thread number
    // until it finds one that is active
    do
    {
        ++current_thread;
        current_thread %= max_threads;
    } while ( false == threads[current_thread].is_active );

    // Restore the entire stack
    std::memcpy(start_address_of_stack, threads[current_thread].stack, max_stack_size);

    // Restore all the registers
    __asm__ ("movs r0, %0": : "r"(threads[current_thread].registers.r[0]));
    __asm__ ("movs r1, %0": : "r"(threads[current_thread].registers.r[1]));
    __asm__ ("movs r2, %0": : "r"(threads[current_thread].registers.r[2]));
    __asm__ ("movs r3, %0": : "r"(threads[current_thread].registers.r[3]));
    __asm__ ("movs r4, %0": : "r"(threads[current_thread].registers.r[4]));
    __asm__ ("movs r5, %0": : "r"(threads[current_thread].registers.r[5]));
    __asm__ ("movs r6, %0": : "r"(threads[current_thread].registers.r[6]));
    __asm__ ("movs r7, %0": : "r"(threads[current_thread].registers.r[7]));

    // Now somehow restore the program counter, stack pointer, cspr and ldr,
    // and jump back out of this interrupt routine... somehow!
}

Here it is up on Godbolt:

1 Like

Well I haven't even began to think about how to implement software multithreading, but goodluck on your endeavor, it looks promising.

Thank you, this answers my question. I was under the impression that we couldn't perform any actions until the connection was made. I'll just keep checking the status until it connects.

Never heard of it. Thank you for the suggestion, I'll check it out.

Great - glad it helped

I was going to continue writing more code for thread management on an Arduino micrcontroller, but then today I realised that most of it would already be written for me in the implementations of the functions, "setjmp" and "longjmp".

If I go on the Github account for 'glibc', I can navigate to:
sysdeps/arm/setjmp.S
sysdeps/arm/__longjmp.S

Inside these two Assembler files is everything I need to restore all the registers, restore the stack pointer and jump to an address in code -- but I can't find an implementation written in Thumb instructions.

With microcontrollers such as the sam3x83 or d21, can I temporarily switch from Thumb to ARM? So the thread manager could run in ARM instructions, and then the threads could all be Thumb. I'm reading one website here that says you use the BLX instruction to switch from Thumb to ARM, is that right? And then I can switch back to Thumb by executing "MOV PC,0x00".

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.