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);
}
}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:
- No Pin Numbers: At no point did we use
13orGPIO_5. We useDT_ALIAS(led0), which looks in the Devicetree for the pin corresponding to the aliasled0. The same code can work on different boards that define this alias and offer a compatible GPIO. - 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. - “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. k_msleep: We don’t use an emptyforloop to wait.k_msleepputs 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=yThis 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 .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 flashThe 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 toled1will only work if that other alias exists. - Permission error on Linux: If
west flashfails to open the USB programmer, install theudevrules specified in the board’s or the runner’s documentation.