When it comes to embedded systems, especially in fields like industrial control or medical devices, getting your code to run efficiently, predictably, and safely is paramount. You’ve likely put in the hard work to design detailed, robust systems, but without a solid grasp of real-time concepts and concurrency, your efforts might not achieve the desired reliability. This is where understanding Real-Time Operating Systems (RTOS) like Zephyr comes into play.
At Emtech, we build a lot of embedded systems, so I’d like to share and explore why real-time systems and concurrency are crucial for embedded developers and how they can help you build better, more reliable products.
A real-time system isn't necessarily a fast system; it's a system that guarantees a response within a predictable timeframe. This predictability is what separates a real-time system from a general-purpose one. There are two main categories:
Concurrency is the ability of a system to manage multiple tasks that appear to run simultaneously. This is a key concept because in a microcontroller with a single CPU, you aren't achieving true parallelism like you would on a multi-core desktop processor. Instead, the system is rapidly switching between tasks, creating the illusion of parallel execution. For example, you might have one task reading a sensor every 10 milliseconds, another detecting a button press, and a third transmitting status information. These tasks are concurrent, not parallel.
Concurrency is typically implemented through:
However, sharing resources introduces risks like race conditions, data inconsistencies, and priority inversion. To manage these risks, we use synchronization tools like semaphores, mutexes, message queues, and events.
The scheduler is the heart of the RTOS, organizing tasks and managing the CPU. The fundamental criterion for scheduling is priority: each task has a numerical priority, and the scheduler always executes the highest-priority ready task. In Zephyr, lower numbers represent higher priority (whereas in other RTOS, such as FreeRTOS, the convention may be the opposite).
But what happens when tasks have the same priority?
We also distinguish between two main scheduling modes:
Zephyr is a lightweight, modular, and real-time operating system optimized for embedded systems. We've been increasingly porting our projects to this platform at Emtech because of its practicality and robust features.
Key features include:
A Zephyr application is structured by combining hardware descriptions, system configuration, and multithreaded code.
1. DeviceTree: Describes the hardware, including the board (Figure 2), pins, peripherals (LEDs, UARTs), and any connected sensors. It allows you to describe your hardware in a standardized way, making the code portable across different boards. At compile time, a devicetree_generated.h file is created with macros that your C code can use.
2. Kconfig: Activates or deactivates RTOS functions, drivers, and other features. Since Zephyr is modular, this lets you enable only what you need, like UART or I2C support.
3. CMake/West: This is the build system. west is a tool that compiles, flashes, and administers the project.
4. main() / Threads: This is the entry point for your program, where you can either use the main() function or create your own tasks.
To demonstrate synchronization, let's look at a classic producer-consumer problem.
This can be easily implemented in Zephyr using a message queue (k_msgq) which acts as a buffer. The producer generates data and places it in the queue, while the consumer blocks until data is available for processing. This is a clean and efficient way to handle data transfer between tasks.
The code is straightforward: you create two threads (producer and consumer) and a message queue of a specific size. The producer loop generates data and sends it to the queue, while the consumer loop waits to receive data before processing it. This ensures that the tasks are synchronized and data is not lost.
To wrap up, here are some best practices to keep in mind when designing concurrent embedded systems:
In the world of embedded development, mastering real-time and concurrency concepts is not just an option—it's a necessity for building efficient and reliable systems. By leveraging a powerful RTOS like Zephyr and following best practices, you can manage complexity and create robust applications that meet even the strictest timing requirements. As one of our engineers noted, the ease of switching between different hardware platforms with Zephyr is excellent, making development much more flexible. It truly simplifies the process of building sophisticated embedded solutions.
Written by Alejandro Casanova
Edited by Adrián Evaraldo
For further inquiries, contact us: info@emtech.com.ar