Suggestions on creating a Logger class for printing stuff to the debug console

So I’m planning on having a Logger class that takes care of printing stuff to the debug console from multiple tasks.

Initial thoughts: it doesn’t quite make sense to have different Logger instances for each task in particular i.e source file? (SensorA, SensorB etc), yeah? I could just have one instance, and whoever wants to print stuff could rightaway write to the buffer of the Logger class. I would just need to ensure the write operation is protected via Mutex.

Initially, I had all the methods as non-static but I realized xTaskCreate() takes in a function pointer to the task entry function, and in C++, all member functions are also passed *this and I saw an error invalid use of non-static member function Logger::Process(void *arg)

void Logger::Logger()
{
  if (pdPASS != xTaskCreate(process, "PROCESS_LOG", 256, NULL, 5, &td))
    {
        // ...
    }
}
void Logger::Process(void *arg)
{
  xStreamBufferReceive(nrfLogTaskMsgBuffer, buffer, 40, portMAX_DELAY);
  // ...
}

Would it rather make sense to have Logger::Process as a static function instead since it doesn’t really depend on the the Logger instance itself? and if I make it static, then I could do something like:

void Logger::Logger()
{
  if (pdPASS != xTaskCreate(process, "PROCESS_LOG", 256, this, 5, &td))
    {
        // ...
    }
}

static void Logger::Process(void *arg)
{
   Logger *obj = reinterpret_cast<Logger*>(arg);
  xStreamBufferReceive(nrfLogTaskMsgBuffer, obj->getBuffer(), 40, portMAX_DELAY);
  // ...
}

My own C++ Task wrappers make the actual function passed to the Task Create function a static member, which then converts the void* pointer to a pointer to the class and then calls a member function in that class.

One question, why does Logger need a task? My serial drivers output drivers don’t use a task, just a queue or StreamBuffer (guarded with a mutex) connected to an ISR.

My own C++ Task wrappers make the actual function passed to the Task Create function a static member, which then converts the void* pointer to a pointer to the class and then calls a member function in that class.

So something like the last example that I wrote?

One question, why does Logger need a task? My serial drivers output drivers don’t use a task, just a queue or StreamBuffer (guarded with a mutex) connected to an ISR.

Yeah I thought about whether it’s really worth having a task or not. It’s just a task would be idling until there’s something written to the stream buffer. Doesn’t it sound practical?

If all it is doing is copying a StreamBuffer to a serial port, it seems that it isn’t worth a task, but directly send the data to the serial port.

Somewhat depends on how you implement your serial port. I make my serial ports a class, with an instance per physical port, with a member to write a message to it. It includes a Mutex that the writer can use to make a ‘message’ go out as an atomic whole, even if it takes multiple calls to formatters. Then there is no need for a concentrator like your logging class. It has a generic stream based base class that provides most the formatting options for all sorts of stream like devices.

I do have a logging class, but its purpose is to provide hooks to enable/disable classes of messages and add some uniform boiler plate to the messages. It doesn’t create a task, it is just an object that hold a reference to an output stream.

i’m using an nRF SDK so they have uart driver already written…though I’ve been inclined towards writing a stripped-down version of it mainly for my own learning. Also if you write it yourself, you know the nitty gritty details better of the scenes under the hood.

Your serial class basically is generic in the sense it takes in user input of any type which then gets printed out? Do you have any brief example to get the point across? If not, totally fine

I have found that most manufactures serial drivers don’t work well in an RTOS. Many seem to want the task to wait until one message is out before you can send another, and often you can’t change the buffer while it is sending. I use a Queue to send the characters into so that it doesn’t hold up the task side buffer, and I often don’t NEED a buffer, as I can format number directly to the stream without needed a task side buffer. It has a Mutex, so a task get reserve the stream to build the message, and the release it when it has all been enqueued, and another task can add to that queue even while that message is going out.

I do have plans to rewrite it to use a StreamBuffer, which should be a bit better and a bit less overhead.

So say there’s a UART class with a fifo buffer as its member. Interrupt occurs (i.e a char is received from the terminal), respective IRQ handler gets fired where you populate the fifo (perhaps the stream buffer as well) and and in case there’s any task waiting on the fifo getting populated gets unblocked.

Do you see anything wrong with this generic approach?

Thats sort of what I do, my Serial Port class has two fifes (Queue or StreamBuffers). One get filled by the Rx interrupt, one gets drained by the Tx interrupt.

Generally, there will be only 1 task draining the Rx queue, though it might change over time for some devices if you send a message to it and get a response.

The Tx Queue will be guarded with a recursive Mutex, so a task can get a temporary lock on the port to send a full message

Tx queue will be guarded and not Rx queue? in case of a FIFO at least the way I’d implement, reading will increment a read index and writing would increment a write index. Wouldn’t it cause a problem if multiple tasks try to write to a FIFO?

also, do you implement your FIFO such that it’s fixed? so if it gets full, the incoming data would get written to index 0 i.e wraparound?

I don’t put a mutex guard on the receive side of the serial port, as I find I don’t need it. Either the port will be used by just one task dedicated to processing it, or it is for a serial device that the task wanting to us it first grabs the TX mutex, sends the command, waits for the response, then releases the TX mutex. No need for a separate RX Mutex.

For overflow, if the RX interrupt detects a full queue, then it sets an ‘Overrun’ flag, that will discard all characters received after that until it is cleared. When the reading task empties the queue it then gets the overrun error, so it knows it fell behind, clears the error and then carries on.

Note, I am talking about a serial port having TWO independent queue. One to hold the character stream being buffered to send out the serial port (so the tasks doesn’t have to wait for them to be sent) and a second queue to hold the characters coming in from the serial port to be processed.

Do you mean the “sending/responsing” session taskes a long time, so you use TX mutex for task priority inherit?

I also use RX/TX dual queues, but ignore the OVERRUN flag, just throw away the new incomings when RX queue is full. That will be easier to port serial drivers.

‘Task Priority’ isn’t really a factor if you are waiting for a response.

If multiple task might send a query to a device and wait for a comms response, they take the mutex (which guard the Tx) send their query and hold the mutex until they get their response. Most devices don’t want a second request before the first is done.

If you get Comm Port OVERRUNs, your Interrupt Setup is bad, as you shouldn’t get locked out of an ISR for that long.

If you get a character when the FIFO is full, you need to do more than just throw away THAT character, or you will get apparent just random dropping of characters through the message. I put my receiver into an ‘overflow’ mode what ALL characters received after that are dropped until the receive task has emptied the FIFO and been told of the error. Then you have a clear marking in the data stream of where the error occurred. This maximizes the amount of ‘good’ data received under conditions when the system is lagging enough to fill the receive fifo.