Zephyr Application Structure

This guide covers best practices for organizing Zephyr applications, from simple projects to complex multi-module systems.

Minimal Application

The simplest Zephyr application:

my-app/
├── CMakeLists.txt
├── prj.conf
└── src/
    └── main.c

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)

target_sources(app PRIVATE src/main.c)

prj.conf

CONFIG_PRINTK=y

src/main.c

#include <zephyr/kernel.h>

int main(void)
{
    printk("Hello from my app!\n");
    return 0;
}

Standard Application

A typical application with more organization:

my-app/
├── CMakeLists.txt
├── prj.conf
├── Kconfig                    # App-specific options
├── README.md
├── src/
│   ├── main.c
│   ├── app_config.h
│   └── modules/
│       ├── sensors.c
│       ├── sensors.h
│       ├── comms.c
│       └── comms.h
├── include/
│   └── app/
│       └── api.h
├── boards/
│   ├── nrf52840dk_nrf52840.overlay
│   ├── nrf52840dk_nrf52840.conf
│   └── stm32f4_disco.overlay
└── dts/
    └── bindings/
        └── my-device.yaml

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(my_app)

# Include directories
target_include_directories(app PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Source files
target_sources(app PRIVATE
    src/main.c
    src/modules/sensors.c
    src/modules/comms.c
)

Large Application Structure

For complex projects:

my-app/
├── CMakeLists.txt
├── prj.conf
├── Kconfig
├── VERSION                    # Semantic version
├── west.yml                   # For multi-repo (optional)
│
├── src/
│   ├── main.c
│   ├── CMakeLists.txt
│   │
│   ├── drivers/               # Custom drivers
│   │   ├── CMakeLists.txt
│   │   ├── my_sensor.c
│   │   └── my_sensor.h
│   │
│   ├── services/              # Application services
│   │   ├── CMakeLists.txt
│   │   ├── data_manager.c
│   │   └── data_manager.h
│   │
│   └── lib/                   # Internal libraries
│       ├── CMakeLists.txt
│       ├── protocol.c
│       └── protocol.h
│
├── include/
│   └── app/
│       ├── api.h
│       ├── config.h
│       └── types.h
│
├── boards/
│   ├── nrf52840dk_nrf52840.overlay
│   ├── nrf52840dk_nrf52840.conf
│   └── custom_board/          # Custom board definition
│       ├── custom_board.dts
│       ├── custom_board_defconfig
│       └── Kconfig.board
│
├── dts/
│   └── bindings/
│       └── vendor,my-sensor.yaml
│
├── tests/
│   ├── CMakeLists.txt
│   ├── prj.conf
│   ├── testcase.yaml
│   └── src/
│       └── main.c
│
├── samples/                   # Usage examples
│   └── basic/
│       ├── CMakeLists.txt
│       ├── prj.conf
│       └── src/main.c
│
└── doc/                       # Documentation
    └── api.md

Workspace Topologies

In-Tree Application (T1)

Application inside Zephyr workspace:

zephyrproject/
├── zephyr/
├── modules/
└── apps/              # Your applications
    └── my-app/

Pros: Simple setup, automatic ZEPHYR_BASE Cons: Mixed with Zephyr updates

Out-of-Tree Application (T2)

Application in separate directory:

~/projects/
├── zephyrproject/     # Zephyr workspace
└── my-app/            # Your application

Build:

export ZEPHYR_BASE=~/projects/zephyrproject/zephyr
west build -b board ~/projects/my-app

Pros: Clean separation, independent versioning Cons: Need to manage ZEPHYR_BASE

Application with Manifest (T3)

Application as west workspace root:

my-app/
├── west.yml           # Manifest pointing to Zephyr
├── CMakeLists.txt
├── prj.conf
├── src/
└── deps/              # Downloaded by west
    ├── zephyr/
    └── modules/

west.yml:

manifest:
  remotes:
    - name: zephyrproject-rtos
      url-base: https://github.com/zephyrproject-rtos

  projects:
    - name: zephyr
      remote: zephyrproject-rtos
      revision: v3.6.0
      import: true

  self:
    path: .

Pros: Pinned Zephyr version, reproducible Cons: More complex setup

Module Pattern

Creating a Module

my_module/
├── CMakeLists.txt
├── Kconfig
├── zephyr/
│   └── module.yml
└── src/
    ├── my_module.c
    └── my_module.h

zephyr/module.yml:

build:
  cmake: .
  kconfig: Kconfig

CMakeLists.txt:

if(CONFIG_MY_MODULE)
  zephyr_library()
  zephyr_library_sources(src/my_module.c)
  zephyr_include_directories(src)
endif()

Kconfig:

config MY_MODULE
    bool "My custom module"
    help
      Enable my custom module.

Configuration Organization

prj.conf Sections

# ===== Core System =====
CONFIG_PRINTK=y
CONFIG_LOG=y

# ===== Networking =====
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y

# ===== Application =====
CONFIG_MY_APP_FEATURE=y
CONFIG_MY_APP_BUFFER_SIZE=512

Multiple Config Files

# CMakeLists.txt
set(CONF_FILE
    ${CMAKE_CURRENT_SOURCE_DIR}/prj.conf
    ${CMAKE_CURRENT_SOURCE_DIR}/debug.conf  # Optional debug config
)

Best Practices

1. Separate Concerns

src/
├── main.c           # Entry point only
├── app_init.c       # Initialization
├── app_tasks.c      # Task definitions
└── modules/         # Feature modules

2. Use Header Guards

/* include/app/api.h */
#ifndef APP_API_H_
#define APP_API_H_

/* API declarations */

#endif /* APP_API_H_ */

3. Document Board Requirements

<!-- boards/README.md -->
# Board Requirements

## nrf52840dk_nrf52840
- I2C0 for sensor (P0.26, P0.27)
- GPIO P0.13 for LED

## stm32f4_disco
- I2C1 for sensor (PB6, PB7)
- GPIO PA5 for LED

4. Version Your Application

Create VERSION file:

VERSION_MAJOR = 1
VERSION_MINOR = 2
PATCHLEVEL = 3

Use in CMakeLists.txt:

include(${ZEPHYR_BASE}/cmake/app/version.cmake)
message(STATUS "App version: ${APP_VERSION_STRING}")

5. Use Sample and Test Directories

Provide examples:

samples/
└── basic/
    ├── README.md
    ├── CMakeLists.txt
    └── src/main.c

Include tests:

tests/
├── unit/
└── integration/

Build Variants

Debug vs Release

# CMakeLists.txt
if(CONFIG_DEBUG_OPTIMIZATIONS)
    target_compile_definitions(app PRIVATE DEBUG_BUILD)
endif()
# Build debug
west build -b board -- -DCONFIG_DEBUG_OPTIMIZATIONS=y

# Build release
west build -b board -- -DCONFIG_SIZE_OPTIMIZATIONS=y

Next Steps

Continue to Part 3 to learn about kernel essentials.


Back to top

Zephyr RTOS Programming Guide is not affiliated with the Zephyr Project or Linux Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.