As I’ve started to dig into [FreeRTOS], I have found too few useful compilable examples on how to get started. The FreeRTOS tutorial is complicated and theoretical. It doesn’t leave you with any working running code. The Officially Supported demo for STM32 works, but it’s way too complicated, and assumes you have lots of hardware. Not a great way to get started. Maple comes with a tragically simple freertos-blinky.cpp, but all it does is show you how to blink the LED. What’s next?? Fortunately, I came across a page of FreeRTOS examples for WSN430 that helped a lot.
In this blog post, I wanted to share my own experience with porting real code to FreeRTOS, which does something more useful than blink a light, but not something unfathomably complicated. Along the way, this will explain how to exploit many of the features of FreeRTOS
Get the code from github: https://github.com/maniacbug/FreeRTOS
Warming up: The easy examples
My FreeRTOS port contains four simple examples, which serve as good warmups to understand FreeRTOS. They are best approached in this order:
- Blinky. The usual suspect… Blinks the LED, FreeRTOS style. Obviously overkill for this simple case, but it should help show what’s going on.
- Twotasks. Blinks the LED AND prints a message, both on separate timers.
- Buttonled. Toggles the LED when you press and release a button on Pin 2. Demonstrates one way to handle debouncing.
- Manybuttons. Prints the state of many buttons when they change state. Demonstrates how to use parameters for a task, using the same task handler for multiple buttons.
My final goal for this post is to port the very simple ‘pingpair’ example from RF24. This is the ‘hello world’ of RF radios, sending a message back and forth between two radios.
Let’s start by looking at the tasks. These are the main worker bees of a FreeRTOS application:
- vSendTask: Periodically sends a packet to the other unit
- vReceiveTask: Waits for incoming packets on the payload queue, and prints them out to the Serial. Then prepares an ack packet for the next received packet.
- vStatusTask: Waits for status information on the status queue, and prints them out to the Serial
- vRadioSoftIrqTask: Handles the work of the radio interrupt.
The handling of interrupts is particularly interesting, using a ‘soft irq’ to do the real work. The idea behind this is that the interrupt handler does the absolute minimum possible work in the handler itself. In this case, all it does is wake up the vRadioSoftIrqTask, and return to regular processing. The problem with doing a lot of work in the interrupt handler is that EVERYTHING is put on hold while it executes, without regard to whether there is something more important happening. In the case of RF radio, receiving an incoming packet is important, but there is a 3-packet FIFO buffer, so we can wait a bit before processing them if something more important is happening.
- xRadioIrqSemaphore: This is the mechanism that the radio irq uses to launch the softirq. The vRadioSoftIrqTask simply waits forever on this semaphore, and the radio irq handler simply posts to this semaphone and exits.
Mutexes are used to protect resources where we only want to have one task using them at a time.
- xRadioMutex: All accesses to the radio are protected by this mutex, otherwise a high-priority task could interrupt a lower one who is currently using the radio for something else.
- xConsoleMutex: Ensures that two things don’t write to the serial console at once. Currently, I wait on the mutex. To avoid deadlocks, it may be better to just abort if it’s not free. So far this has not been a problem.
Queues are how we pass information between tasks. Some tasks generate information, another task consumes it and does something with it.
- xPayloadQueue: When the radio receives a frame, it places it into the Payload Queue. This way the radio’s softirq can be decoupled from the application logic. The radio fills the queue, and the application consumes it to do what it likes.
- xStatusQueue: Status messages are placed into this queue.
When porting FreeRTOS, there are a few decisions to be made. These are embodied in FreeRTOSConfig.h
- Memory allocator. I use heap_1.c, which is manages a static heap, and allows allocation only. No memory is ever freed. I think this is appropriate for a microcontroller with 2K SRAM.
- Heap size. 1500 bytes of heap seems to be the practical limit.
- Minimum stack size. 100 bytes of stack per task was the limit I found under experimentation, but 150 was better. Most tasks that do anything will need to be created with a 150+ stack size.
FreeRTOS on Arduino Uno?
FreeRTOS works great on 1284P and Maple. After some heavy experimentation, I was able to get the pingpair sketch working on an Arduino Uno clone. It turns out that a heap of 1300 and per-task stacks of 150 did the job. In total, 380 bytes of SRAM remain free when the sketch is loaded.
The question is whether this is a good idea. This is just a simple ‘hello world’ for radios, 380 bytes doesn’t leave much for more complex application logic or more tasks.