1 year, 8 months ago.

running a thread from inside a class (mutex lock error) ?

I am trying to have a class for controlling some communications, using MBED OS 5. It compiles successfully, however when running it on our device, a NUCLEO-L432KC we encounter a mutex error:

MbedOS_Error

                                          -- MbedOS Error Info --
      ++ MbedOS Error Info ++
       Error Status: 0x80020115 Code: 277 Module: 2
       Error Message: Mutex lock failed
       Location: 0x8007C1B
       Error Value: 0xFFFFFFFC
       Current Thread: main  Id: 0x20002188 Entry: 0x8008041 StackSize: 0x1000 StackMem: 0x20001188 SP: 0x20001EC0
       For more info, visit: https://armmbed.github.io/mbedos-error/?error=0x80020115
                                          -- MbedOS Error Info --

Our debugging has lead us to the believe that the fact of 'starting' the thread with the function causes the mutex error. In the simplest working example below, we have highlighted where the code can be commented out to prevent the mutex error.

minimal working example

#include "mbed.h"
#include "rtos.h"

class Comm{
    
    public:

        Thread *p_comm_in;
        
        bool _RUN;

        RawSerial pc;
        Queue<void, 8> inCharQ;  // Input Character Queue
        
        void serialISR(){ 
            uint8_t newChar = pc.getc(); 
            inCharQ.put((void*)newChar); 
        } 

        void commInFn() { // should put everything you write onto the terminal
            while (_RUN) {
                osEvent newEvent = inCharQ.get();
                uint8_t newChar = ((uint8_t)(&newEvent.value.p));
                pc.putc(newChar);
            }
        }

        Comm(): pc(SERIAL_TX, SERIAL_RX){ // inherit from the RawSerial constructor

            Thread comm_in(osPriorityAboveNormal, 1024);

            p_comm_in = &comm_in;

            pc.printf("%s\n\r", "Welcome, this won't work, but that's okay" );
        }
        
       void start_comm(){
            _RUN = true;
            pc.attach(callback(this, &Comm::serialISR));

            // COMMENT THIS OUT TO PREVENT MUTEX ERROR
            p_comm_in->start(callback(this, &Comm::commInFn));
            
        }
};


//Main
int main() {

    Comm comm_plz; 
    
    comm_plz.start_comm();

    while (1) {
    }
  
}

It would be nice to know how to " try building a non-release version with MBED_CONF_PLATFORM_ERROR_FILENAME_CAPTURE_ENABLED configuration enabled" using the online IDE, as it seems it might be able to tell us what line of the code is causing this error.

2 Answers

1 year, 8 months ago.

I'm going to use a mailbox which includes the storage. You're passing pointer to an automatic stack variable into the queue. That memory location is invalid after ISR exits. Other problem with having only one global char variable is that you can't put pointer into the queue multiple times and expect to get different chars out. The mbed examples tend to use heap memory with queues.

I've also had bad luck using serial.attach outside of ctor, I don't know why.

Usually queuing up each char is not what you want to do. Usually in ISR you stuff chars into a buffer looking for end of message char like '\n', then put the whole string into the mailbox to parse. And you want to clean out all chars from serial while in the ISR in case there has been more than one.

This seems to work for me on an STM32L4.

#include "mbed.h"

class Comm{
public:

    Comm() :
        thread_(osPriorityNormal, 1024),
        pc_(SERIAL_TX, SERIAL_RX) { 

        pc_.attach(callback(this, &Comm::receiveISR));
        pc_.printf("\n\n\nStart...\n");
    }

    void start() {
        thread_.start(callback(this, &Comm::commThread));
    }

private:
    void receiveISR() { 
        while (pc_.readable()) {
            char new_char = pc_.getc();
            putMail(new_char);
        }
    } 

    void commThread() { 
        while (true) {
            osEvent evt = mailbox_.get();

            if (evt.status == osEventMail) {
                mail_t *mail = (mail_t *)evt.value.p;
                pc_.putc(mail->new_char);
                // Add extra char to confirm we are seeing output and not terminal echo print
                pc_.putc('.');
                mailbox_.free(mail);
            }            
        }
    }

    void putMail(char new_char) {
        mail_t *mail = mailbox_.alloc();

        if (mail == NULL) {
            // I belieive RawSerial printf is ISR safe
            pc_.printf("Mailbox Full\n");
        }
        else {
            mail->new_char = new_char;
            mailbox_.put(mail);
        }
    }

    /* ----- Objects ----- */
    Thread    thread_;
    RawSerial pc_;

    // Define mailbox data type
    typedef struct {
        char new_char;
    } mail_t;

    Mail<mail_t, 20>mailbox_;
};
 
 
int main() { 
    Comm comm_plz;     
    comm_plz.start();
 
    while (1) {
        // do nothing
        Thread:wait(10000);
    }  
}

Accepted Answer
1 year, 8 months ago.

Hello Adel,

Great answer from Graham! The Mail class seems be very useful but as he said you can do it without queuing too:

#include "mbed.h"
 
class Comm : public Thread
{
    RawSerial         _pc;
    bool              _RUN;
    volatile bool     _commIn;
 
    void serialISR()
    {
        _pc.attach(NULL);   // detaching ISR prevents being called again and again
        _commIn = true; 
    }
 
    void commInFn()
    {
        while (_RUN) {
            if (_commIn) {
                _commIn = false;
                while (_pc.readable())
                    _pc.putc(_pc.getc());
                _pc.attach(callback(this, &Comm::serialISR));   // re-attach ISR
            }
        }
    }
 
public:
    Comm() : _pc(USBTX, USBRX), _RUN(0), _commIn(false), Thread(osPriorityAboveNormal, 1024)
    {
        _pc.printf("Welcome.\r\n");
    }
 
    void start_comm()
    {
        _RUN = true;
        _pc.attach(callback(this, &Comm::serialISR));   // attach ISR
        start(callback(this, &Comm::commInFn));
    }
};
 
//Main
int main()
{
    Comm comm_plz;
    
    comm_plz.start_comm();
 
    while (1) { }
}