Unable to call private SerialBase functions

I’m trying to call int read (uint8_t *buffer, int length, const event_callback_t &callback, int event=SERIAL_EVENT_RX_COMPLETE, unsigned char char_match=SERIAL_RESERVED_CHAR_MATCH) from a BufferedSerial object, but I get the error that ../main.cpp:124:60: error: no matching function for call to 'mbed::BufferedSerial::read(uint8_t [10], int, mbed::Callback<void()>, int&, char)'.

It seems like BufferedSerial objects are unable to operate on SerialBase functions like this read() function. Only the public ssize_t read(void *buffer, size_t length) is available.

My relevant code:

#include "mbed.h"

#define BAUDRATE 115200

BufferedSerial serial(USBTX, USBRX, BAUDRATE);

void rxCallback(int arg)
{
    pc.printf("%i", arg);
}

int main()
{
    uint8_t buf[10];
    int event = SERIAL_EVENT_RX_COMPLETE | SERIAL_EVENT_RX_OVERFLOW;

    serial.read(buf, 10, callback(&rxCallback), event, '\n');
}

Hello,

That is what private methods do. So that behavior is right, I think.

BR, Jan

The issue was actually that read(...) requires DEVICE_SERIAL_ASYNCH to be set true, since it’s actually a public function in SerialBase that is privately inherited.

Now, I’m having a strange issue with BufferedSerial::sigio: even though sigio is just called from my main function and I don’t have any interrupts, I get the error

++ MbedOS Error Info ++
Error Status: 0x8001012F Code: 303 Module: 1
Error Message: Error - writing to a file in an ISR or critical section

Minimal code rep:

void clbk(void)
{
}
int main()
{
    serial.sigio(callback(&clbk));
}

It used to be a Serial API class which allowed us to attach interrupt handlers. But that was deprecated and replaced by thread safe and POSIX compliant BufferedSerial and UnbufferedSerial APIs. Attaching interrupt handler to a slow serial link imposes a risk of blocking the system (the other tasks must wait until the interrupt handler finishes its job).

Isn’t that the point of using a callback function though? We only execute the callback when the system state has changed. I’m confused why I can’t use BufferedSerial::sigio() in the main function like how I’m doing.

I was not able to reproduce the reported runtime error when running the code below (built wit Mbed OS 6.4.0) on a NUCLEO_F446RE:

#include "mbed.h"

DigitalOut      led(LED1);
BufferedSerial  serial(USBTX, USBRX);
char            buf[64];
Thread          t;
EventQueue      eventQueue;

void onSerialReceived(void)
{
    char* p_buf = buf;

    memset(buf, 0, sizeof(buf));
    while (serial.readable()) {
        p_buf += serial.read(p_buf, sizeof(buf) - (p_buf - buf));
    }
    if (p_buf > buf) {
        printf("Received: %s\r\n", buf);
    }
}

void onSigio(void)
{
    eventQueue.call(onSerialReceived);
}

int main()
{
    printf("Starting...\r\n");
    t.start(callback(&eventQueue, &EventQueue::dispatch_forever));
    serial.sigio(callback(onSigio));
}

Maybe you have a mbed_app.json configuration file in your project affecting the build?

Ah yeah, it looks like my minimal code example actually did work, I just had a printf in my callback which was blocking. Thanks so much for your help!

It seems like this works well if all I want to do is read a character, but if I try to do other stuff in the background, I get a HardFault error.

Testing this code

#include "mbed.h"

#define BAUDRATE 115200

BufferedSerial serial(USBTX, USBRX, BAUDRATE);
char buf[64];
Thread t;
EventQueue eventQueue;

void onSerialReceived(void)
{
    char *p_buf = buf;

    memset(buf, 0, sizeof(buf));
    while(serial.readable())
    {
        p_buf += serial.read(p_buf, sizeof(buf) - (p_buf - buf));
    }
    if(p_buf > buf)
    {
        printf("Received: %s\r\n", buf);
    }
}

void onSigio(void)
{
    eventQueue.call(onSerialReceived);
}

int main()
{
    printf("Starting...\r\n");
    t.start(callback(&eventQueue, &EventQueue::dispatch_forever));
    serial.sigio(callback(onSigio));

    int i = 0;
    while(1)
    {
        printf("%i\r\n", i++);
        ThisThread::sleep_for(1s);
    }
}

results in this error:

Starting…
0
[long error debug info ending in:]
++ MbedOS Error Info ++
Error Status: 0x80FF013D Code: 317 Module: 255
Error Message: Fault exception
Location: 0x3988
Error Value: 0x10002A44
Current Thread: rtx_idle Id: 0x10001180 Entry: 0x26D5 StackSize: 0x200 StackMem: 0x100014C8 SP: 0x10001680
For more info, visit: https://mbed.com/s/error?error=0x80FF013D&tgt=LPC1768
– MbedOS Error Info –

Is this an LPC1768 bug or sometime I’m missing?

No runtime error when copy & pasted and built with ARM GCC + Mbed OS 6.5.0 and run on mbed LPC1768. Check your Mbed revision and mbed_app.json file.

Without changing any code this seems to work now, I think there’s something weird with my LPC. Thanks again for the help!

If you don’t like sigio replace BufferedSerial with UnbufferedSerial and use Rx interrupt:

#include "mbed.h"

DigitalOut          led(LED1);
UnbufferedSerial    serial(USBTX, USBRX);
char                buf[64];
//Thread              t;
//EventQueue          eventQueue;

//void onSerialReceived(void)
//{
//    char*   p_buf = buf;

//    memset(buf, 0, sizeof(buf));
//    while (serial.readable()) {
//        p_buf += serial.read(p_buf, sizeof(buf) - (p_buf - buf));
//    }

//    if (p_buf > buf) {
//        printf("Received: %s\r\n", buf);
//    }
//}

//void onSigio(void)
//{
//    eventQueue.call(onSerialReceived);
//}

void onRxInterrupt()
{
    char*   p_buf = buf;

    memset(buf, 0, sizeof(buf));
    while (serial.readable()) {
        p_buf += serial.read(p_buf, sizeof(buf) - (p_buf - buf));
    }
}

int main()
{
    printf("Starting...\r\n");
//    t.start(callback(&eventQueue, &EventQueue::dispatch_forever));
//   serial.sigio(callback(onSigio));
    serial.attach(&onRxInterrupt, SerialBase::RxIrq);

    int i = 0;
    while (1) {
        printf("%i\r\n", i++);
        ThisThread::sleep_for(1s);
    }
}

But you cannot call printf in the ISR.

Extending this topic.

First,
If I have a device that sends a data packet like s1234567# per second.
On the received side, I want to remove s and # to get 123456 then put in a char array or string.
Before I can use Serial and getc() to do that successfully.
However, I can’t do it using both BufferedSerial and UnbufferedSerial.

Dear Zoltan, I can run your BufferedSerial example successfully.
However, the packet data come from a different thread, I get following
Received:s
Received:1234567#
how can I get 123456 in a different thread?
Any recommendation methods?

Second,
why we can’t use “attach” in BufferedSerial instead of using “sigio”?

Third, I am not sure the timing to choose BufferedSerial or UnbufferedSerial even I read the API.

Thanks a lot.

Hello Tzu-Hsuan,

How can I get 123456 in a different thread?

The following should work:

#include "mbed.h"

#define BUFF_LEN    32
#define MSG_LEN     64
#define DATA_LEN    MSG_LEN - 2

BufferedSerial      serial(STDIO_UART_TX, STDIO_UART_RX);
Thread              thread1;
Thread              thread2;
EventQueue          eventQueue;
Mutex               mutex;
ConditionVariable   cond(mutex);
char                recvBuff[BUFF_LEN] = { 0 };
size_t              recvLen;
char                message[MSG_LEN] = { 0 };

// The following variable is protected by locking the mutex
char                data[DATA_LEN] = { 0 };

void taskPrintData()
{
    mutex.lock();

    while (1) {
        // Wait for a condition to change
        cond.wait();

        // Now it is safe to access data in this thread
        printf("Data received: %s\r\n", data);
        memset(data, 0, DATA_LEN);  // empty data to make space for new data
    }
}

void onSerialReceived(void)
{
    while (serial.readable()) {
        // Read serial
        recvLen = serial.read(recvBuff, BUFF_LEN);

        if ((strlen(message) + recvLen) > MSG_LEN) {
            // too much data -> something went wrong
            memset(message, 0, MSG_LEN);
            break;
        }

        strcat(message, recvBuff);          // append received chars to message

        if (message[0] != 's') {
            // garbage message received
            memset(message, 0, MSG_LEN);
            break;
        }

        if (message[strlen(message) - 1] == '#') {
            // message complete
            mutex.lock();

            // copy the chars from the message to the data storage
            strcat(data, &message[1]);      // omit first char (which is 's')
            data[strlen(data) - 1] = '\0';  // delete last char (which is '#')
            memset(message, 0, MSG_LEN);

            // Signal for other threads that data has been received
            cond.notify_all();
            mutex.unlock();
            break;
        }
    }
    memset(recvBuff, 0, BUFF_LEN);
}

void onSigio(void)
{
    eventQueue.call(onSerialReceived);
}

int main()
{
    printf("Starting..\r\n");
    thread1.start(taskPrintData);
    thread2.start(callback(&eventQueue, &EventQueue::dispatch_forever));
    serial.sigio(callback(onSigio));

    while (1) {
        ThisThread::sleep_for(10ms);
    }
}

Make sure you don’t append anything to the s123456# message (no new line or carriage return etc.).
For more info about ConditionVariable read here.

Why we can’t use “attach” in BufferedSerial instead of using “sigio”?

Because the mbed team designed the BufferedSerial that way (no attach function is available).

I am not sure the timing to choose BufferedSerial or UnbufferedSerial even I read the API.

Unlike the BufferedSerial class, the UnbufferedSerial class does not use intermediary buffers to store bytes to transmit to or read from the hardware. The user application is responsible for processing each byte as it is received. The method to read data returns only one byte for every call. Therefore, we recommend you use this class when you need more control and for use in interrupt handlers with the RTOS. You can also use this class to write multiple bytes at once. Because it does not acquire a mutex lock, you must ensure only one instance uses the serial port.

For normal blocking applications that require a serial channel for something other than the console, BufferedSerial performs better than UnbufferedSerial and causes less CPU load and fewer latency issues. Only applications that are short of RAM and cannot afford buffering or that need more control of the serial port and use it from IRQ should use UnbufferedSerial.

Best regards, Zoltan

Dear Zoltan Very useful example for everybody.
Thanks.

About you mention " Make sure you don’t append anything to the s123456# message (no new line or carriage return etc.)."

In my sending node, I send “s12345#\r\n”
But your example still works.
So why you mention that don’t append anything to the message?

Thanks

Lin

Hello Lin,

I’m glad it helped. A message is received by several packets. Packets are parsed in the onSerialReceived function which tries to detect the end of a message using the following if condition:

if (message[strlen(message) - 1] == '#')

When you send “s12345#\r\n” it could work too, if you are lucky. However, not always. For example, if the last packet of a message arrives as “5#\r\n” then the end of the message is not detected because message[strlen(message) - 1] = '\n'.
So in case your messages always end with the '\n' (new line) character and the body of a message never contains '\n' then you can use it as the end-of-message delimiter and modify your code accordingly:

if (message[strlen(message) - 1] == '\n')

Best regards, Zoltan