· Nordic  · 4 min read

Understanding Semaphores in Zephyr OS on nRF7002DK

Semaphores are powerful synchronization primitives in Zephyr OS, enabling efficient task coordination and resource management in real-time embedded systems. In this blog post, we explore how semaphores work in Zephyr OS, with a practical example on the nRF7002DK board. Learn how to implement a producer-consumer pattern using semaphores to synchronize threads, and discover best practices for embedded development.

Table of Contents


Introduction to Semaphores in Zephyr OS

In Zephyr OS, a semaphore is a synchronization primitive used to manage shared resources or coordinate tasks in concurrent embedded systems. It maintains a counter that represents the availability of a resource or the occurrence of an event. Semaphores are particularly useful in real-time applications where tasks must operate predictably and efficiently.

Key operations include:

  • Give: Increments the semaphore count, signaling resource availability.
  • Take: Decrements the count, allowing a task to proceed or block until the semaphore is given.
  • Reset: Sets the count to zero.

Semaphores in Zephyr are lightweight and versatile, supporting both binary (count ≤ 1) and counting (count > 1) use cases, making them ideal for task synchronization and resource management.


Why Use Semaphores on nRF7002DK?

The nRF7002DK, powered by the nRF5340 SoC and nRF7002 Wi-Fi chip, is a robust platform for developing IoT and wireless applications. Its dual-core architecture (application and network cores) and real-time capabilities make it a perfect fit for Zephyr OS, which is designed for resource-constrained devices.

Semaphores are essential on the nRF7002DK for:

  • Thread Synchronization: Coordinating tasks on the application core, such as handling sensor data or Wi-Fi events.
  • Resource Sharing: Managing access to peripherals like UART, I2C, or SPI.
  • Producer-Consumer Patterns: Enabling efficient data exchange between tasks, such as sending sensor readings over Wi-Fi.

This post demonstrates a practical semaphore-based application on the nRF7002DK’s application core, showcasing Zephyr’s capabilities in a real-world scenario.


Example: Producer-Consumer with Semaphores

Below is a Zephyr OS example that implements a producer-consumer pattern using a semaphore on the nRF7002DK. The producer thread generates data, and the consumer thread processes it, with a semaphore ensuring proper synchronization.

Code: main.c

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(nrf7002dk_sem_example, LOG_LEVEL_INF);

K_SEM_DEFINE(data_sem, 0, 5);

#define BUFFER_SIZE 10
static int data_buffer[BUFFER_SIZE];
static int buffer_index = 0;

void producer_thread(void *arg1, void *arg2, void *arg3)
{
    while (1) {
        data_buffer[buffer_index] = buffer_index + 1;
        LOG_INF("Producer: Generated data %d at index %d", 
                data_buffer[buffer_index], buffer_index);
        buffer_index = (buffer_index + 1) % BUFFER_SIZE;
        k_sem_give(&data_sem);
        k_msleep(1000);
    }
}

void consumer_thread(void *arg1, void *arg2, void *arg3)
{
    while (1) {
        if (k_sem_take(&data_sem, K_MSEC(2000)) == 0) {
            int read_index = (buffer_index - 1 + BUFFER_SIZE) % BUFFER_SIZE;
            LOG_INF("Consumer: Processed data %d from index %d", 
                    data_buffer[read_index], read_index);
        } else {
            LOG_ERR("Consumer: Timeout waiting for data");
        }
    }
}

#define PRODUCER_STACK_SIZE 1024
#define CONSUMER_STACK_SIZE 1024
#define PRODUCER_PRIORITY 5
#define CONSUMER_PRIORITY 5

K_THREAD_DEFINE(producer_id, PRODUCER_STACK_SIZE, producer_thread, 
                NULL, NULL, NULL, PRODUCER_PRIORITY, 0, 0);
K_THREAD_DEFINE(consumer_id, CONSUMER_STACK_SIZE, consumer_thread, 
                NULL, NULL, NULL, CONSUMER_PRIORITY, 0, 0);

void main(void)
{
    LOG_INF("nRF7002DK Semaphore Example Started");
}

Configuration: prj.conf

CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_BACKEND_UART=y

How It Works

  1. Semaphore Setup:

    • A semaphore (data_sem) is defined with an initial count of 0 and a maximum count of 5, limiting the number of unprocessed data items.
    • The semaphore signals when the producer has added data to the buffer.
  2. Producer Thread:

    • Writes data to a circular buffer (data_buffer) and increments buffer_index.
    • Calls k_sem_give to signal data availability.
    • Sleeps for 1 second to simulate work (e.g., polling a sensor).
  3. Consumer Thread:

    • Waits for the semaphore using k_sem_take with a 2-second timeout.
    • On success, reads the latest data from the buffer and logs it.
    • If the timeout expires, logs an error.
  4. Circular Buffer:

    • A simple array (data_buffer) stores data, with buffer_index managing writes and reads.
    • This example assumes one producer and one consumer for simplicity.
  5. Logging:

    • Zephyr’s logging module outputs messages to the nRF7002DK’s UART, viewable via a terminal emulator (baud rate: 115200).

Setting Up the nRF7002DK

To run the example on the nRF7002DK:

  1. Install Prerequisites:

    • Set up nRF Connect SDK and Toolchain using VS Code
  2. Build the Application:

    • Place main.c and prj.conf in a project folder.
    • Create and build a configuration using VS Code extension
  3. Flash the Firmware:

    • Connect the nRF7002DK via USB.
    • Flash using VS Code extension
  4. View Output:

    • Use a terminal emulator (e.g., minicom, baud rate 115200) to see logs:
[00:00:00.123] <inf> nrf7002dk_sem_example: nRF7002DK Semaphore Example Started
[00:00:01.234] <inf> nrf7002dk_sem_example: Producer: Generated data 1 at index 0
[00:00:01.235] <inf> nrf7002dk_sem_example: Consumer: Processed data 1 from index 0

Best Practices for Semaphores

  • Initialize Correctly: Set appropriate initial and maximum counts for your use case.
  • Use Timeouts: Prevent indefinite blocking with timeouts in k_sem_take.
  • Avoid Priority Inversion: Be cautious with high-priority tasks, as semaphores lack priority inheritance (use mutexes for critical sections).
  • Monitor Semaphore State: Use k_sem_count_get for debugging.
  • Optimize for Embedded Systems: Minimize contention and keep semaphore operations lightweight.

Conclusion

Semaphores in Zephyr OS provide a robust mechanism for task synchronization and resource management, making them invaluable for real-time embedded applications. The nRF7002DK example demonstrates how to use semaphores to implement a producer-consumer pattern, showcasing Zephyr’s capabilities on a modern IoT platform. By following best practices and leveraging Zephyr’s APIs, developers can build efficient, reliable systems for the nRF7002DK and beyond.

Try extending this example by integrating the nRF7002’s Wi-Fi capabilities or connecting to real sensors. For more details, explore the Zephyr OS documentation and Nordic Developer Zone.

  • nrf7002-dk
  • nordic-semiconductor
  • nrf-connect-sdk
  • zephyr-os

Related articles

View All Articles »

Zephyr OS Logging Module: A Guide with nRF7002DK Example

Discover how to leverage the Zephyr OS logging module for efficient debugging and diagnostics in embedded systems. This guide dives into the module’s features, configuration, and practical implementation using the nRF7002DK, a versatile development kit for IoT applications.

Your Practical Roadmap to Mastering Zephyr OS with the nRF7002DK

Jumping into Zephyr OS development on the nRF7002DK can feel overwhelming — but it doesn't have to be. In this step-by-step roadmap, we'll guide you from your first blinking LED to full-fledged Wi-Fi-connected sensor devices, so you can become a confident embedded developer.

Mastering Mutexes in Zephyr OS: A Deep Dive with nRF7002DK Examples

Mutexes are vital for thread synchronization in real-time operating systems like Zephyr. This comprehensive guide explores mutexes in depth, detailing their role in preventing race conditions, and provides practical examples using the nRF7002DK with nRF Connect SDK. Learn to leverage Zephyr’s mutex APIs for robust embedded applications.

Getting Started with Zephyr OS: A Beginner's Guide for Nordic Development with nRF Connect SDK

New to Nordic Semiconductor development and the nRF Connect SDK? Discover Zephyr OS, the powerful open-source real-time operating system (RTOS) at its core. This beginner-friendly guide introduces Zephyr’s key features, explains why it’s ideal for Nordic’s nRF microcontrollers, and explores its role in building IoT applications. Learn how to leverage Zephyr with the nRF Connect SDK to kickstart your embedded development journey.