One of the big problems of creating multitasking programs is keeping all of the tasks in sync which is why there are so many systems in place to help with that.
Today, I am going to show you a simple FreeRTOS example about one of those systems called a queue. Queues are normally used when you have a lot of tasks that need to send a lot of data, but to make this example very clear, we will stick with a simple two task program that sends a single number between the tasks.
Here is the code
QueueHandle_t connection;
void sender_task(void* arg) {
while (true) {
int num = random(0, 10);
xQueueSend(connection, &num, portMAX_DELAY);
Serial.printf("Sent: %i \n", num);
delay(500);
}
}
void receiver_task(void* arg) {
while (true) {
int num;
xQueueReceive(connection, &num, portMAX_DELAY);
Serial.printf("Recived: %i \n", num);
}
}
void setup() {
Serial.begin(115200);
connection = xQueueCreate(10, sizeof(int));
xTaskCreate(sender_task, "SEND", 2048, NULL, 1, NULL);
xTaskCreate(receiver_task, "RECEIVE", 2048, NULL, 1, NULL);
}
void loop() {
}
How It Works?
Now that you have seen the code, let’s look through the interesting sections of it to see how queues work.
Queue Handlers
Obviously if we create a queue, we want to use it, but in FreeRTOS instead of directly accessing and using a queue, you need to go through a queue handler, so the first thing, we are going to do, is create one like this.
QueueHandle_t connection;
The First Task
The whole point of a queue is to allow two or more tasks to send information to each other, so we need to create some tasks. Lets start with the one that writes information to the queue.
void sender_task(void* arg) {
while (true) {
}
}
Next, we will need to create some data to send. When you create a queue, you tell it what size of data it should expect. We haven’t done this yet, but when we do we will set it to the size of integers, so lets create a random integer.
void sender_task(void* arg) {
while (true) {
int num = random(0, 10);
}
}
When we actually want to send data to the queue, we use the xQueueSend() function. It takes three arguments: the queue handler, a pointer to the data to send, and the maximum time to wait, if the queue is full.
void sender_task(void* arg) {
while (true) {
int num = random(0, 10);
xQueueSend(connection, &num, portMAX_DELAY);
Serial.printf("Sent: %i \n", num);
delay(500);
}
}
Reading the Queue
Now we are going to create the task function that receives data from the queue. It starts like most task functions with an infinite loop.
void receiver_task(void* arg) {
while (true) {
}
}
Our queue is setup to hold integers, so to read them we need to create a blank integer.
void receiver_task(void* arg) {
while (true) {
int num;
}
}
And then we use the xQueueReceive() function to get the data. It is almost exactly like the xQueueSend() function, the only difference is that when you call this function instead of sending num it actually sets num equal to what ever is in the queue.
void receiver_task(void* arg) {
while (true) {
int num;
xQueueReceive(connection, &num, portMAX_DELAY);
Serial.printf("Recived: %i \n", num);
}
}
Creating the Queue
It is kind of surprising, but up to this point, we have not even created the queue yet. To create it, we will need to use xQueueCreate() in the setup function.
void setup() {
Serial.begin(115200);
connection = xQueueCreate(10, sizeof(int));
}
That function takes two arguments: the first is the maximum amount of data it should hold and the second is how big each piece of data should be.
The last little thing to take notice of is that we set our queue handler equal to the return value of xQueueCreate().
To finish up, we will need to start each of the tasks.
void setup() {
Serial.begin(115200);
connection = xQueueCreate(10, sizeof(int));
xTaskCreate(sender_task, "SEND", 2048, NULL, 1, NULL);
xTaskCreate(receiver_task, "RECEIVE", 2048, NULL, 1, NULL);
}
Bonus
While this example is very good at explaining the basics of queues, it is not that interesting, so as a little bonus here is a slightly more complex example that uses one task to retrieve some text and another to send the text out of an LED by Morse code.
I will let you try to figure this one out on your own.
#define LED_PIN 23
#define MORSE_SPEED 100
#define MESSAGE "Hello there"
QueueHandle_t connection;
void process(void* arg) {
while (true) {
char text[] = MESSAGE;
int len = strlen(text);
for (int i = 0; i < len; i++) {
xQueueSend(connection, &text[i], portMAX_DELAY);
Serial.printf("Sent: %c \n", text[i]);
}
delay(30000);
}
}
void dot() {
digitalWrite(LED_PIN, true);
delay(MORSE_SPEED);
digitalWrite(LED_PIN, false);
delay(MORSE_SPEED);
}
void dash() {
digitalWrite(LED_PIN, true);
delay(MORSE_SPEED * 3);
digitalWrite(LED_PIN, false);
delay(MORSE_SPEED);
}
void end_letter() {
digitalWrite(LED_PIN, false);
delay(MORSE_SPEED * 2);
}
void space() {
digitalWrite(LED_PIN, false);
delay(MORSE_SPEED * 4);
}
void blink_led(void* arg) {
while (true) {
char c;
xQueueReceive(connection, &c, portMAX_DELAY);
c = toupper(c);
switch (c) {
case 'A':
dot();
dash();
end_letter();
break;
case 'B':
dash();
dot();
dot();
dot();
end_letter();
break;
case 'C':
dash();
dot();
dash();
dot();
end_letter();
break;
case 'D':
dash();
dot();
dot();
end_letter();
break;
case 'E':
dot();
end_letter();
break;
case 'F':
dot();
dot();
dash();
dot();
end_letter();
break;
case 'G':
dash();
dash();
dot();
end_letter();
break;
case 'H':
dot();
dot();
dot();
dot();
end_letter();
break;
case 'I':
dot();
dot();
end_letter();
break;
case 'J':
dot();
dash();
dash();
dash();
end_letter();
break;
case 'K':
dash();
dot();
dash();
end_letter();
break;
case 'L':
dot();
dash();
dot();
dot();
end_letter();
break;
case 'M':
dash();
dash();
end_letter();
break;
case 'N':
dash();
dot();
end_letter();
break;
case 'O':
dash();
dash();
dash();
end_letter();
break;
case 'P':
dot();
dash();
dash();
dot();
end_letter();
break;
case 'Q':
dash();
dash();
dot();
dash();
end_letter();
break;
case 'R':
dot();
dash();
dot();
end_letter();
break;
case 'S':
dot();
dot();
dot();
end_letter();
break;
case 'T':
dash();
end_letter();
break;
case 'U':
dot();
dot();
dash();
end_letter();
break;
case 'V':
dot();
dot();
dot();
dash();
break;
case 'W':
dot();
dash();
dash();
end_letter();
break;
case 'X':
dash();
dot();
dot();
dash();
end_letter();
break;
case 'Y':
dash();
dot();
dash();
dash();
end_letter();
break;
case 'Z':
dash();
dash();
dot();
dot();
end_letter();
break;
case ' ':
space();
break;
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
connection = xQueueCreate(20, sizeof(char));
xTaskCreate(process, "PROCESS", 2048, NULL, 1, NULL);
xTaskCreate(blink_led, "BLINK", 2048, NULL, 1, NULL);
}
void loop() {
}
I you are looking for some more cool examples about FreeRTOS, you might like to see how FreeRTOS Semaphores work.