3
\$\begingroup\$

I have multiple tasks that all write to a screen. Obviously without controlling who can write to the screen when I'll have problems. I thought the easiest way to do this was with queueing.

I have a global QueueHandle. In my int main(void) function I create a queue of size 10 (large enough for my needs) with an item size of that equal to my struct which holds all the data.

struct screenData {
    uint8_t string;
    uint8_t line;
} data;

In the tasks which need to write to the screen they send to the queue:

struct screenData * toSend;
data.string = "TEST";
data.line = 0;

toSend = &data;

xQueueSend ( xQueue, ( void * ) &toSend , xTicksToWait );

In the task which updates the screen it reads from the queue and puts it on the screen:

struct screenData * data;
xQueueReceive( xQueue, &( data ), xTicksToWait );

I then access the data like so: data->string; etc...

However, as far as I understand it, this passes the address of the struct. Which means if I update data, the data held in the struct will be different.

What I want to do is have a queue which I can add to where the data sent is of the type screenData, and it'll read each incoming screenData to put a message on the screen.

Essentially, should I just be sending the whole struct to the queue? In which case, is this the best use of the queue? Or is there a better way to buffer data?

Thanks

\$\endgroup\$
  • \$\begingroup\$ "uint8_t string;", shouldn't that be "uint8_t *string;"? \$\endgroup\$ – Richard May 1 '15 at 19:29
  • \$\begingroup\$ Yup - didn't realise that typo. In the original code it's right. \$\endgroup\$ – CircularRecursion May 1 '15 at 19:45
7
\$\begingroup\$

There two basic approaches that come to mind.

1) Use a queue as outlined by the OP.

According to the FreeRTOS API documentation for xQueueSend, all of the data from your screenData structure is copied into the queue (see description for the pvItemToQueue argument: "A pointer to the item that is to be placed on the queue. The size of the items the queue will hold was defined when the queue was created, so this many bytes will be copied from pvItemToQueue into the queue storage area." This means your assumption is incorrect. After the code execution returns from the xQueueSend call, you can modify data without fear of overwriting what was just queued up.

2) Use a mutual exclusion semaphore or mutex.

(read more about FreeRTOS mutex implementation in the API documentation of xSemaphoreCreateMutex) to synchronize access to the display between different tasks.

The main trade-offs to consider in choosing between a queue or a mutex are as follows:

Using a queue will buffer the data and not block any tasks from executing unless your queue becomes full but requires you to allocate more memory (10 * sizeof(screenData) in this case).

Using a mutex does not require the memory allocation but will block execution of tasks if more than one is trying to access the display at the same time.

\$\endgroup\$
5
\$\begingroup\$

I believe your understanding is incorrect. The FreeRTOS Queue service makes a copy of the data that you send. The size of a queue item is defined when you create the queue. xQueueSend receives a pointer to the item but it makes a copy of that item -- it does not queue the pointer. So if the task that called xQueueSend modifies the original item afterwards, those changes will not be reflected in the copy that was placed on the queue.

Here is the FreeRTOS Queue description

Regarding your questions: Yes, sending a copy of the screenData seems reasonable. I believe your example code is wrong. If you intend to put a screenData item on the queue, and you created the queue with item size equal to sizeof(screenData) then you should pass the address of data to xQueueSend. In other words, you don't need toSend and you should call xQueueSend like this:

xQueueSend ( xQueue, ( void * ) &data , xTicksToWait );

Also, in the function that calls xQueueReceive, you should declare data as a screenData type rather than a pointer to screenData. Then pass the address of data to xQueueReceive.

\$\endgroup\$
  • \$\begingroup\$ So if in the same task I wanted to write to two different lines I can use data, call xQueueSend and then modify data (change the line/string) and call xQueueSend. At the other end the two queue items should have different data? \$\endgroup\$ – CircularRecursion May 1 '15 at 16:08
  • \$\begingroup\$ Yes, the FreeRTOS documentation is clear, "Messages are sent through queues by copy, meaning the data ... is itself copied into the queue rather than the queue always storing just a reference to the data." \$\endgroup\$ – kkrambo May 1 '15 at 16:41
-1
\$\begingroup\$

in general, the best way to handle multiple processes writing to the screen

is to have a dedicated process, which is the only one the actually writes to sysout.

All other processes use a messaging system like 'msgsnd()' to pass

messages to the one process that actually writes to the screen.

The OS queues the messages to be read by the one process that actually writes to the screen.

If that queue gets full, then the sending process will block until there is room on the queue.

this normally results in the user processes do not need to maintain a queue of messages for the screen

\$\endgroup\$
  • 1
    \$\begingroup\$ That's essentially what I explained above. However, I needed clarification about how to put data into the queue! \$\endgroup\$ – CircularRecursion May 1 '15 at 15:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.