zephyr-hola-mundo-blinky

Hello World in Zephyr: Your First Blinky

  • 4 min

A Blinky is the minimal program that makes an LED blink and serves to verify that the build chain, flashing, and hardware access are working.

It may seem trivial, but getting an LED to blink in a real-time operating system like Zephyr implies that you have correctly configured the environment, the toolchain, the programmer, and that you have a basic understanding of how the OS talks to the hardware.

Let’s write, build, and flash our first Zephyr program.

The Code: main.c

Following the structure from the previous article, create a file src/main.c and paste the code below. We’ll then break down the less familiar parts.

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* 1. Get the Devicetree node for led0 */
/* The "led0" node is usually defined by default on almost all boards */
#define LED0_NODE DT_ALIAS(led0)

/* 2. Verify the node exists and has an associated driver */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

int main(void)
{
    int ret;

    /* 3. Check if the device is ready */
    if (!gpio_is_ready_dt(&led)) {
        return 0;
    }

    /* 4. Configure the pin as active output */
    ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    if (ret < 0) {
        return 0;
    }

    /* 5. Infinite loop */
    while (1) {
        /* Toggle the LED state */
        ret = gpio_pin_toggle_dt(&led);
        if (ret < 0) {
            return ret;
        }
        
        /* Sleep the thread for 1000 milliseconds (1 second) */
        k_msleep(1000); 
    }
}
Copied!

How the Code Works

If you come from Arduino, you might be thinking: “So much code for a simple digitalWrite?”.

Yes and no. Zephyr uses more elements because it aims to validate hardware and maintain portability. These are the main differences:

  1. No Pin Numbers: At no point did we use 13 or GPIO_5. We use DT_ALIAS(led0), which looks in the Devicetree for the pin corresponding to the alias led0. The same code can work on different boards that define this alias and offer a compatible GPIO.
  2. Safety Checks: We use gpio_is_ready_dt. In professional systems, we don’t assume the hardware is present; we ask the OS if the driver has loaded correctly.
  3. “dt” API: You’ll see many functions ending in _dt (Devicetree). These are the modern Zephyr functions that read pin configuration (pull-up, pull-down, polarity) directly from the board’s definition.
  4. k_msleep: We don’t use an empty for loop to wait. k_msleep puts the thread to sleep and frees the CPU so the operating system can do other things (like manage Bluetooth or save power).

This abstraction is the power of Zephyr. If you change the PCB design tomorrow and the LED moves from pin 5 to pin 20, you only change a configuration file (the Overlay), not the C code.

Configuration: prj.conf

For this code to compile, we need to tell the Kernel that we want to use the GPIO driver.

Open the prj.conf file and add:

CONFIG_GPIO=y
Copied!

This option includes the GPIO subsystem in the final configuration.

Building the Project

Return to the terminal with the Python virtual environment activated. For an nRF52840 DK, the current board target is nrf52840dk/nrf52840:

Run:

west build -p always -b nrf52840dk/nrf52840 .
Copied!

If everything goes well, it will finish with a success message.

Flashing the Board

Connect the board to USB.

Zephyr uses the west flash command to program. West is smart and knows which tool to use underneath (JLink for Nordic, Esptool for ESP32, OpenOCD for STM32, etc.).

west flash
Copied!

The runner depends on the board: west flash might use J-Link, OpenOCD, pyOCD, esptool, or another tool. Check the error and your board’s documentation if a program or permission is missing.

If you see write and verification messages, look at the board. Is the LED blinking?

Congratulations! You have just executed the main thread of a real-time operating system.

What Actually Happened?

Boot: Upon powering on, the board executed Zephyr’s startup code (pre-main).

Kernel: The Kernel, memory manager, and drivers (GPIO) were initialized.

Main: The main thread was created, which called the main() function.

Loop: The thread toggles the LED and then sleeps. During that interval, the kernel can execute other threads and put the CPU in idle when no work remains.

:::

Common Troubleshooting

  • Error: DT_ALIAS(led0) does not exist: The board does not define this alias. You will need to add it using an overlay, or choose a board that provides it; changing to led1 will only work if that other alias exists.
  • Permission error on Linux: If west flash fails to open the USB programmer, install the udev rules specified in the board’s or the runner’s documentation.