Usage of FreeRTOS in a simple data logging communication system

I’m fairly new to RTOS architecture and been reading up on articles on FreeRTOS.org, but as it’s commonly agreed, practice clears things up even more.

I have a basic project idea which I’d ideally want to be RTOS-based as I’ll explain below along with some questions:

My understanding is RTOS comes in handy when you’re computing multiple tasks concurrently. Based on that, I came up with the idea which involves a BLE-supported MCU, sensors, BLE-supported app, and a display component.

Working:
A request is sent over BLE from an app (acting as a central), which is processed by the MCU (acting as a peripheral), and does the following concurrently:

  • sending the data from the desired sensor back to the app over BLE
  • updating the display with the value on an LCD

In an RTOS context, I’d then have:

  • TaskA - constantly reads the inputs coming over BLE from an app using a blocking call xQueueReceive()

  • Process the request in either TaskA or a different task (?), and obtain the desired data and put it in queueB.

  • TaskB - as soon as queueB is no longer empty, send the data out to the app over BLE

  • TaskC - as soon as queueB is no longer empty, update the display with the data

I think with this approach, there isn’t any shared data that I need to worry about locking mechanisms…unless I’m missing something. Does the idea sound okay? I’m afraid I’m RTOS isn’t justified and if so, what could else be done?

A usecase:

I have a BLE-compatible phone (acting as a central) with an app that’s connected to, say, the BLE of nRF52 (acting as a peripheral). After the connection, the user wants to retrieve some information from sensorA, so they send out “A”, which is received by the peripheral device, processes it, and responds back to the central with the desired data which was requested, and at the same time, the LCD display is updated with the data as well

I believe you can collate TaskB and TaskC into one task (updating display data normally is nothing but a memory write).

When TaskB reads from the queueB then the data is lost from the queue and that data isn’t longer available for the TaskC. It means, you can’t dequeue twice the same data from the queue (Queues 101), unless you use a Peek() alike function.

What type of display are you using: 16x2 LCD, TFT LCD, 7 segments, parallel, serial, …? Its type might define whether to use a RTOS worths. I’ve been facing problems with a serial 16x2 LCD (through PCF8574 I2C serial port expander) in a PLC like project (based upon the ATmega328 chip): the display doesn’t update correctly when its corresponding task has lower priority; but if I rise it, then my PLC applications doesn’t work as expected. In fact I had to use the round robin schema instead of preemption. Maybe it’s a design flawed of my own.

You’re spot on about unable to dequeuing more than once. I think the actual idea would be to store the sensor data value in a local ring buffer perhaps, which would then be used to populate the queue.

And this is the SPI-based LCD I’m looking at. Not too sure what category does it fall under based on what you mentioned though. On what basis are you identifying whether RTOS is worth it for a specific display?

And I’m not too sure as to why a lower priority task would cause the LCD to not display. It could possibly be getting pre-empted like you mentioned

The usual cause for this is that some task isn’t blocking and starving all the tasks of a priority below it. Maybe it has a yield, but that only allows tasks of the SAME priority to run, not lower priorities.

Careful, SPI LCD updates are notoriously slow! So you want to keep the display update separate from other stuff that may need to be more prompt, and more importantly, you may not want to use a queue for the display as in some cases it might not keep up (queue overflows). Sometimes a “redisplay screen” flag is more appropriate (depending on what’s on the screen).

I recently had to fix an application that lost data because of slow LCD updates…

Hope that helps!

My experience is that most SPI LCD’s include a memory on them that holds the display image, so you only need to send data to the display when you need to change it. It often is best to keep a copy of what you want on the display in your processor ram, (and possible a list of areas that have changed) and send screen updates as they happen, or on refresh boundries to avoid image tear. With a copy of the image in local ram, you can’t ‘lose’ updates. If you can’t keep a copy of the display in ram, then you need to either regenerate the image from local data, or wait when data changes happed so they can get sent to the display.

Absolutely, cache the screen image when possible. However, remember lots of smaller uC have less memory than required to cache a color bitmap image (as in the application I recently had to repair)… The LCD the OP linked needs 57kB assuming one-byte/pixel (which isn’t much color depth). Caching the data from which the image is derived can be less memory intensive but adds application complexity…

ok, point taken, thanks!

The issue I was getting at was more that in the TOs architecture, both taskB and taskC basically trigger on the same condition (" as soon as queueB is no longer empty"). Aside from what Xavier wrote, it’s also that the control flow has redundancies. If the job of taskC (updating the LCD) indeed is time critical, another solution would be for taskB to unqueue the outstanding events in turn and both send out the data (depending on whether those are synchronous or asynchronous sends, that may happen inline or through yet another IPC to a sender task) as well as queue the data for output (again, inline or asyncronous depending on how time critical the task is. I obviously underestimated that - I never work with displays…). In any case, it’s probably the same message as Xavier, only he nailed it better. Sorry for the noise!

Yes, if the display have more memory that your processor can cache, you can’t keep a local copy, which means you need to keep a local cache of the raw information needed to build the display.

In that case, I would probably put that data in mailboxes and use something like an EventGroup to mark what was new, or a Direct-To-Task as array of bits to each task to mark updates (you need the later if they might be processing at different speeds, so each task needs separate update flags.

Sounds good. Think of a structure enumerating data fields to be updated on screen, sent from source task to display task via a queue of depth one.

Source task always tries to read from queue prior enqueuing something; if it dequeues a result that means display task is behind and hasn’t got to it yet. In this case logically OR any new display update requirements and enqueue the result.

Remember to protect actual data to display for safe multi-task access (or include it in the queued structure).

Hope that helps @MasterSil !

The issue with the queue implementation is that the queue only provides data to ONE task, so since there were TWO consumers, either each needs their own set of queues, or you go to mailboxes + two sets of flags.

The biggest difference is that with two sets of queues every source sees all the changes, and the source needs to decide what to do if things fall behind. With mailboxes and flags, everyone will (eventually) get the latest set of data, but if things fall behind individual readings might get lost.

Hi,

In your exposition you wrote:

“> TaskB - as soon as queueB is no longer empty, send the data out to the app over BLE. TaskC - as soon as queueB is no longer empty, update the display with the data”

It’s implied that you want to read the same data from the same queue. Once TaskB dequeues the data then it’s not longer available, unless TaskB uses the Peek() function instead.

And this is the SPI-based LCD I’m looking at. Not too sure what category does it fall under based on what you mentioned though. On what basis are you identifying whether RTOS is worth it for a specific display?

Your LCD is under the serial category. Your application is not complex enough to use a RTOS, unless the way you update the LCD. If you only need to send a serial stream of bytes over SPI, you might not need a RTOS at all because the updating is performed whenever is needed. But think about other kind of displays, like 7-segments that must update all digits, on a single digit basis, every 1-2 ms.

When having complex parallel timing processes in your system then using a RTOS is almost mandatory, but your application seems to be flat serial: read-update-read-update.

And I’m not too sure as to why a lower priority task would cause the LCD to not display. It could possibly be getting pre-empted like you mentioned.

In a pre-empted enabled RTOS the LCD task has low priority in regards to the PLC main process. It takes toooooo long for the 16x2 LCD to update, so pre-emption takes it away the CPU and somehow the bytes stream sent to the LCD over I2C is broken, or the PCF8574 doesn’t receive the signals START, STOP or ACK in time. Fortunately for you, the SPI transfer rate (24 MHZ for your LCD) is too high and it doesn’t have such complex protocol of I2C, so you might not face any problem.

Of course you can write your application based upon FreeRTOS because it’s too funny and enlightful whether it needs a RTOS or not :slight_smile:

The I2C protocol and the byte stream is broken because of pre-emption. I need to find out a better way to update the LCD.

Apologies if I wasn’t clear; I was suggesting a dedicated queue for the display task. No sharing of queues!

“> TaskB - as soon as queueB is no longer empty, send the data out to the app over BLE. TaskC - as soon as queueB is no longer empty, update the display with the data”

It’s implied that you want to read the same data from the same queue. Once TaskB dequeues the data then it’s not longer available, unless TaskB uses the Peek() function instead.

I most likely mistyped it. Or I realized later that what I initially said didn’t make sense since you can only deque once.

Your application is not complex enough to use a RTOS, unless the way you update the LCD

Mind elaborating on update part here?

If you only need to send a serial stream of bytes over SPI, you might not need a RTOS at all because the updating is performed whenever is needed. But think about other kind of displays, like 7-segments that must update all digits, on a single digit basis, every 1-2 ms.

Sorry, I haven’t worked with LCDs on a low level before and I’m trying to understand how is 7-segments much different from SPI-based? Is it that in SPI-based, you send out a byte stream which eventually gets displayed into the screen whereas, with 7-segment, you have to deal with each segment separately? And same with 16x2 LCD?

I’m still in the early phase and I wouldn’t mind getting a 7-segment if that’s what makes things more interesting and for justifying RTOS’ use.

And thanks for clarifying about LCD task being pre-empted

I haven’t worked with LCDs on a low level before so still exploring/understanding my options here.

What really makes a SPI LCD slow compared to others (perhaps 16x2)? Is the idea that 16x2 gets the data over data lines based on whatever’s written on those lines whereas SPI LCD follows SPI topology? Not sure if i’m making any sense but i’m trying to understand.

you may not want to use a queue for the display as in some cases it might not keep up (queue overflows)

are you implying that SPI LCDs are that slow that it may not even update the LCD before we even get the new data in the queue i.e the old data getting lost?

How’d you go about fixing the speed issue? perhaps storing the data into a buffer, and then sending the data at each index to the LCD once it’s ready?

I should have been more precise: I’m talking specifically about high-res color displays.

Such displays have a lot of data. If you use a uC with a framebuffer and hardware drive (MIPI, TTL, or whatever), refreshing the display eats up a chunk of the available RAM bandwidth (unless it has fancy dual-port RAM frame buffer), but otherwise does not slow your program. If you use an SPI high-res display and it takes a bunch of cycles for each pixel, screen draws start to take a long time. In either case it still takes a lot of time to prepare the image - lots of pixels.

An RTOS can help by moving the slow redraw process into a background task.
You’ll need to figure this out before committing to an architecture.
Hope I was more clear this time!

SPI-based displays (as well as I2C based) includes a chip that drives and performs everything that is needed to display somenthing into the display. You only write what you want to be displayed whenever you want. Once the data is into the display’s RAM your program is free to do useful things, until a new update is performed.

Although there are 7-segment LED displays drivers in the market, in order to reduce costs (they aren’t cheap) we usually implement the electronics and the control. In these displays you must write a single digit at a time using a refresh rate of 1-2 ms in order to avoid blinkings. So this refreshing is part of your application and might be performed by a task or a callback (if using software timers). This page has a lot of information on this regard. Please look at figures 12, 14 and 15 (this is very important for the discussion).

In the other side, 16x2 LCD cheap displays are parallel by nature, with 8 bits data bus, but 4 bit wide writings are allowed (we save 4 microcontroller terminals). But even in parallel this displays are slow: cleaning the display tooks at least 4.1 ms. And in order to reduce even more terminals we use a I2C expander chip (like PCF8574). With this addition we only use 2 terminals (SCL and SDA from I2C) instead of 6 used in parallel (in its minimum allowable configuration).

In the next picture picture you can see a 8 digits 7-segment display prototype featuring the LPC11U68 microcontroller. In the protoboard there are two chips: 74HC138 for driving segments, and 74HC595 for selecting digits. It isn’t exactly easy to drive them (against integrated solutions), but go ahead, you’ll learn a lot! You can also see a 5 button key-pad. It must be read when user wants and debounced before given an answer, with independent timing of that the display. This system is a perfect example of when a RTOS begins to make sense.