A Simple Message Queue for C

Let’s face it, when you’re doing embedded development, you really don’t have a lot of great tools at your disposal. If you’re lucky, you might have a C99-compliant compiler and a microcontroller with floating-point hardware and DMA. If you’re unlucky you might have a microcontroller that doesn’t actually have a stack and a compiler that doesn’t support using structures as function arguments!

Constraints imposed by embedded hardware and/or development toolchains can certainly add to the difficulty of building a well-architected application, especially if your background is not in embedded development. But that doesn’t mean that it can’t be done.

One tool that I find especially useful when building embedded applications is a message queue. Queues can be useful for all sorts of things, from passing data between isolated components of the application, to creating a semblance of an immutable state holder.

c-message-queue

Recently, I put together a simple queue that can easily be pulled into nearly any embedded application. I creatively named it, “c-message-queue”. The implementation is defined in a single header file and consists of just two macros. These macros allow you to create as many queues as your applications needs. The type of objects held in the queue are strongly typed, and enqueueing and dequeueing is done via strongly-typed functions as well. The memory which contains the queue is statically allocated at compile time so it doesn’t require any mallocing at runtime.

How it Works

I’ll walk through an example of creating a queue and demonstrate how it works. For this example, the queue will hold messages that are defined as follows:


struct message {
  char data[8];
};

Queue Declaration

The queue declaration defines what the queue will hold and what its capacity will be. Generally, the declaration would go in the header file of the module that’s going to use the queue and/or expose access to it. However, you can put it in an a source file if no other modules will need to interact with the queue.

We declare the queue by using the QUEUE_DECLARATION macro like this:


QUEUE_DECLARATION(my_message_queue, struct message, 8);

This creates a definition of a structure called my_message_queue that contains the queue itself. Once allocated, the queue will have a capacity of eight messages. This macro also creates three function prototypes: one to initialize the queue and two for enqueueing and dequeueing items into or out of the queue.


void my_message_queue_init(struct my_message_queue * p_queue);
enum enqueue_result my_message_queue_enqueue(struct my_message_queue * p_queue, struct message * p_new_item);
enum dequeue_result my_message_queue_dequeue(struct my_message_queue * p_queue, struct message * p_item_out);

As you can see, for each queue you create you get utility functions that are specific to that queue. The type of the p_queue parameter matches the specific type that the queue holds, rather than a void pointer, which makes it impossible to stuff the wrong type of item in the queue, barring a reckless cast.

The return value of each of the enqueue/dequeue functions is an enum value that clearly indicates whether the operation succeeded or failed.

IMPORTANT: The queue size must be a power of 2 due to the implementation of the enqueue and dequeue functions. 

Queue Definition

The second macro that we need is QUEUE_DEFINITION, and we use it like this:


QUEUE_DEFINITION(my_message_queue, struct message);

This produces the implementation of the init, enqueue, and dequeue functions described above. The name and type passed to this macro must match the values passed to the declaration. Notice that we don’t need to specify the size a second time.

Enqueuing and Dequeueing

Once we have our queue declaration and definitions in place, we can actually create a queue and use it.

To allocate and initialize one of our queues, we just do the following:


struct my_message_queue queue;
my_message_queue_init(&queue);

Then, to put an item in the queue, we can call the enqueue function like this:


struct message msg = {
  .data = {‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’}
}; 
enum enqueue_result result1 = my_message_queue_enqueue(&queue, &msg);

Somewhere else in our application we can pull items out of the queue like this:


struct message msg2;
enum dequeue_result result2 = my_message_queue_dequeue(&queue, &msg2);
if (result2 == DEQUEUE_RESULT_SUCCESS) {
   // Do something with msg2
}

That’s it. As you can see, it takes very few lines of code to create a queue and move items into and out of it.

Use c-message-queue in Your Application

If you’re interested in using this queue, you can get the source from GitHub. Just clone the repo and copy queue.h into your project. The repository also contains unit tests for the queue implementation and an example project that demonstrates how you might use a queue to pass data between two parts of an application.

Conversation
  • Christopher says:

    You should mention that a queue must have a size (that is, the number of items) that is a power of 2, otherwise the index calculation in the enqueue and dequeue functions will not work properly.

    Also, the QUEUE_DEFINITION macro in the code on Github (commit 5bc70b2) requires a queue size, in contrast to what you wrote in this article. The queue size isn’t used in that macro, though.

    Other than those minor nitpicks, this looks like a pretty nice and flexible queue implementation.

    • Jordan Schaenzle Jordan Schaenzle says:

      Christopher, Thanks for pointing that out. I meant to write about the power of 2 limitation and then it slipped my mind. I updated the post. Also, I pushed a change to the code on GitHub which removes the unused NUM_ITEMS parameter from the QUEUE_DEFINITION macro. Thanks again!

  • Alexandre Terentiev says:

    Than you for the nice idea! It inspirates me a lot in my project.

    Maybe it is a good idea to change behavior of the size checking, which limits the sizes to the power of 2. Instead of
    (p_queue->write_idx)++ & (capacity – 1);
    it would be better to write something like
    (p_queue->write_idx)++ % capacity;
    I didn’t check this code yet but the logic behind it I think is clear.

  • Comments are closed.