This is an old revision of the document!


Zephyr RTOS: Intro, Building a simple app

Zephyr OS is a small, open-source real-time operating system (RTOS) designed for resource-constrained embedded and IoT devices, from tiny microcontrollers up to more capable edge hardware. It focuses on predictable, low-latency behavior with a preemptive kernel, strong configurability (you build only what you need), and broad hardware support through a clean device-driver model. Zephyr includes common embedded features like threads, synchronization primitives, timers, power management, networking stacks (e.g., TCP/IP, Bluetooth LE), and security options, all maintained under the Linux Foundation with an emphasis on portability, modularity, and production use.

This tutorial walks you through:

  1. setting up a fresh Zephyr development environment (“from scratch”) on a computer
  2. building Zephyr’s hello_world sample for an ESP32‑S3 target
  3. flashing it onto the Hacktor watch
  4. seeing the “Hello World” log over the USB connection

1. Install Zephyr “from scratch” on your computer

Zephyr’s official Getting Started Guide is the source of truth for host setup (Ubuntu/macOS/Windows), Python environment, west, and SDK installation. Reference: https://docs.zephyrproject.org/latest/develop/getting_started/index.html

Below is a practical “do this in order” setup that follows that guide.

1.1 Install OS dependencies

Follow the Install dependencies step for your OS in Zephyr’s Getting Started Guide (Ubuntu/macOS/Windows). Guide: https://docs.zephyrproject.org/latest/develop/getting_started/index.html

Typical things you’ll need include: Python 3, CMake, Ninja, Git, and system build tools.

1.2 Create a Zephyr workspace and Python virtual environment

Pick a workspace folder. In this tutorial I’ll use ~/zephyrproject on Linux/macOS. (Windows users can use something like C:\zephyrproject.)

# Linux/macOS
mkdir -p ~/zephyrproject
cd ~/zephyrproject
 
python3 -m venv .venv
source .venv/bin/activate

On Windows PowerShell (example):

mkdir C:\zephyrproject
cd C:\zephyrproject
 
py -m venv .venv
.\.venv\Scripts\Activate.ps1

1.3 Install west

Zephyr’s official west install guide: https://docs.zephyrproject.org/latest/develop/west/install.html

pip install -U west

1.4 Initialize and update the Zephyr workspace

# Still inside ~/zephyrproject (or your chosen folder)
west init -m https://github.com/zephyrproject-rtos/zephyr --mr main
west update

Export Zephyr’s CMake package (so CMake can find Zephyr):

west zephyr-export

Install Python requirements for Zephyr and its modules:

west packages pip --install

(That exact command appears in the Getting Started flow and is commonly the next step after west update.)

1.5 Install the Zephyr SDK (toolchain)

Zephyr SDK documentation: https://docs.zephyrproject.org/latest/develop/toolchains/zephyr_sdk.html

Install the Zephyr SDK for your OS, then ensure your environment can find it (either via the SDK setup script registering it, or via environment variables like ZEPHYR_SDK_INSTALL_DIR / ZEPHYR_TOOLCHAIN_VARIANT=zephyr).

Note: ESP32‑S3 is Xtensa based, so you’ll need the Xtensa toolchain that’s included with the Zephyr SDK.

2. ESP32 (Espressif) prerequisites inside Zephyr

2.1 Fetch Espressif RF binary blobs

Zephyr’s Espressif HAL requires binary blobs (RF calibration, etc.). Zephyr board docs for Espressif boards call this out and recommend fetching after west update. Example (ESP32 DevKitC doc): it explicitly instructs west blobs fetch hal_espressif.

Run:

# From your workspace root (~/zephyrproject)
west blobs fetch hal_espressif

If you skip this, you may see build or runtime failures for ESP32 targets.

3. Pick a Zephyr “board target” for Sparrow

Hacktor is not (yet) an upstream Zephyr board (maybe you could help with that?), so we need a compatible Zephyr board definition to build/flash a first image.

You have two reasonable approaches:

  1. A. Quick path (recommended for Hello World): use an upstream ESP32‑S3 board definition that is already 4 MB flash and uses USB‑C programming.
  2. B. Proper port: create a custom “out‑of‑tree” Zephyr board for Hacktor (best long‑term, required once you use GPIOs/peripherals and want correct pin mapping).

This tutorial focuses on A to get you a successful first boot quickly, and then outlines B.

3.1 Approach A: Use ''esp32s3_devkitc'' as a close match

Zephyr includes the Official Espressif ESP32 S3 Devkitc board, which is explicitly documented as an ESP32-S3-WROOM-1, 8 MB Quad flash and 8MB Octal PSRAM. Board doc: https://docs.zephyrproject.org/latest/boards/espressif/esp32s3_devkitc/doc/index.html

Limitations:

  • Pin mapping (LED, I2C, etc.) won’t match Hacktor.
  • For hello_world, pin mapping doesn’t matter.

4. Build “Hello World” for Hacktor and route console over USB

This section is where the Hacktor‑specific “special care” matters:

  • ensure 8 MB flash
  • ensure console output goes over the USB Serial/JTAG port (not UART0)

We will:

1. create a small application folder
2. add a devicetree overlay that selects the USB serial device for console
3. build and flash

4.1 Create a tiny application directory

From your Zephyr workspace root:

cd ~/zephyrproject
 
mkdir -p hacktor_hello
cd hacktor_hello

Create these files:

  • CMakeLists.txt
  • prj.conf
  • src/main.c
  • a board overlay file

You can copy the sample hello_world, but I recommend creating a tiny app so you fully control the console configuration.

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
 
set(BOARD esp32s3_devkitc/esp32s3/procpu CACHE STRING "Default Zephyr board")
 
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(minimal_zephyr_app)
 
target_sources(app PRIVATE
    src/main.c
)

src/main.c

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
 
int main(void)
{
	while (1) {
		printk("Hello World!\n");
		k_sleep(K_SECONDS(1));
	}
}

prj.conf (minimal logging/console)

CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_SERIAL_ESP32_USB=y
CONFIG_PRINTK=y

4.2 Devicetree overlay: force console to USB Serial/JTAG

On ESP32 chips with a USB Serial/JTAG device exposed to Zephyr as a UART, you can route the console by changing the devicetree chosen nodes.

A Zephyr issue discussing USB serial output on ESP32 shows an overlay pattern like:

  • set zephyr,console to &usb_serial
  • enable the usb_serial node

(See Zephyr issue #60825 about using usb_serial for console on ESP32-class devices.)

In the root of the project folder, create:

app.overlay

/ {
	chosen {
		zephyr,console = &usb_serial;
	};
};
 
&usb_serial {
	status = "okay";
};

Why this matters for Hacktor:

  • DevKit‑style ESP32 boards often default the console to UART0 (meant for an external USB‑UART bridge).
  • Hacktor uses USB Serial/JTAG over the USB connector, so the console needs to be routed there.

4.3 Build the application (HP core)

Activate your Python venv first if it isn’t active:

build.sh
cd ~/zephyrproject
source .venv/bin/activate

Then go to your hacktor_hello folder and create this build script, name it build.sh:

#!/usr/bin/env bash
 
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR"
DEFAULT_BOARD="esp32s3_devkitc/esp32s3/procpu"
 
if [ -d "$PROJECT_ROOT/../.venv/bin" ]; then
    export PATH="$PROJECT_ROOT/../.venv/bin:$PATH"
fi
 
if [ -z "${ZEPHYR_BASE:-}" ]; then
    if [ -d "$PROJECT_ROOT/../zephyr" ]; then
        export ZEPHYR_BASE="$PROJECT_ROOT/../zephyr"
    else
        echo "ZEPHYR_BASE is not set and ../zephyr was not found." >&2
        exit 1
    fi
fi
 
BOARD="${BOARD:-$DEFAULT_BOARD}"
BUILD_DIR="${BUILD_DIR:-$PROJECT_ROOT/build}"
FLASH_PORT="${FLASH_PORT:-${ESPTOOL_PORT:-}}"
FLASH_BAUD="${FLASH_BAUD:-}"
 
PRISTINE=0
RUN_FLASH=0
ERASE_FLASH=0
 
while [ $# -gt 0 ]; do
    case "$1" in
        -p|--pristine)
            PRISTINE=1
            ;;
        -f|--flash|--upload)
            RUN_FLASH=1
            ;;
        --erase)
            ERASE_FLASH=1
            ;;
        --port)
            shift
            if [ $# -eq 0 ]; then
                echo "--port requires a value." >&2
                exit 1
            fi
            FLASH_PORT="$1"
            ;;
        --baud)
            shift
            if [ $# -eq 0 ]; then
                echo "--baud requires a value." >&2
                exit 1
            fi
            FLASH_BAUD="$1"
            ;;
        -h|--help)
            cat <<EOF
Usage: ./build.sh [--pristine] [--flash] [--port <device>] [--baud <rate>] [--erase]
 
Environment overrides:
  BOARD=<board target>       Default: $DEFAULT_BOARD
  BUILD_DIR=<build dir>      Default: $PROJECT_ROOT/build
  ZEPHYR_BASE=<zephyr path>  Auto-detected from ../zephyr if unset
  FLASH_PORT=<device>        Serial port for flashing
  FLASH_BAUD=<rate>          Serial baud rate for flashing
EOF
            exit 0
            ;;
        *)
            echo "Unknown argument: $1" >&2
            exit 1
            ;;
    esac
    shift
done
 
if [ "$PRISTINE" -eq 1 ]; then
    rm -rf "$BUILD_DIR"
fi
 
cmake -GNinja -B "$BUILD_DIR" -S "$PROJECT_ROOT" -DBOARD="$BOARD"
cmake --build "$BUILD_DIR"
 
if [ "$RUN_FLASH" -eq 1 ]; then
    flash_cmd=(west flash --no-rebuild -d "$BUILD_DIR" -r esp32)
    runner_args=()
 
    if [ -n "$FLASH_PORT" ]; then
        runner_args+=(--esp-device "$FLASH_PORT")
    fi
 
    if [ -n "$FLASH_BAUD" ]; then
        runner_args+=(--esp-baud-rate "$FLASH_BAUD")
    fi
 
    if [ "$ERASE_FLASH" -eq 1 ]; then
        runner_args+=(--erase)
    fi
 
    if [ ${#runner_args[@]} -gt 0 ]; then
        flash_cmd+=(-- "${runner_args[@]}")
    fi
 
    "${flash_cmd[@]}"
fi
 
echo
echo "Build complete:"
echo "  Board: $BOARD"
echo "  Build dir: $BUILD_DIR"
echo "  ELF: $BUILD_DIR/zephyr/zephyr.elf"
 
if [ "$RUN_FLASH" -eq 1 ]; then
    echo "  Flash: completed"
    echo "Opening serial terminal on ${FLASH_PORT} (115200)..."
    exec screen "${FLASH_PORT}" 115200
fi

4.4 Flash Sparrow over USB

Connect Sparrow over USB‑C.

Then build and flash by running:

./build.sh --upload --port [your_usb_port]

You will see the app being built, uploaded and then a serial terminal will print out the Hello World message.

5. A more complex project

Clone https://github.com/dantudose/Hacktor_Basic

This is a more advance example and the starting point of your tutorial. It initializes the watch's display and touchscreen, imports the LVGL graphic library and builds a simple interactive app. Also, it initializes the shell, so you have a basic command line interface over the serial port.

The project currently does four things:

  • routes the Zephyr console and shell to the ESP32-S3 native USB serial/JTAG port
  • initializes a GC9A01 240×240 round LCD over SPI3
  • initializes a CST816T-style touch controller on I2C
  • runs a minimal LVGL UI that shows Hello!, touch coordinates, and a touch indicator dot

Build the project and upload it with:

./build.sh --upload --port /dev/xxx

You should see the screen display the hello message, the touch screen interaction and the hacktor:~$ shell prompt. Type help for a list of commands.

6. Hackathon projects

iothings/hackathon.1775124687.txt.gz · Last modified: 2026/04/02 13:11 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0