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

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 advanced 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

The goal of the hackathon is simple:

  • build a useful, fun, or technically impressive smartwatch app
  • use the above starter project as the base
  • demo the app running on real watch hardware at the end of the event

Participants should focus on binging up watch hardware, contributing to the Hacktor Zephyr port and building a single polished app, not a full smartwatch operating system.

Suggested Projects

This is just a list of ideas to get you started. They are purely for orientation purposes, you can choose to implement them or you can propose a totally different project.

1. Fitness Tracker

  • Difficulty: Medium
  • Main hardware: IMU, display, touch, haptics, fuel gauge
  • Core idea: Count steps, estimate activity level, and show progress toward a daily goal.
  • Minimum viable demo: Step counter, distance estimate, calories estimate, daily goal ring.
  • Stretch goals: Auto-walk detection, inactivity reminders, workout mode, local history, sync with a mobile app.

2. Watchface Studio

  • Difficulty: Hard
  • Main hardware: display, touch, battery gauge, BLE optional
  • Core idea: Build custom watchfaces with selectable themes and complications in a web app. Upload these watchfaces to the watch.
  • Minimum viable demo: At least three watchface styles with time, battery, and date.
  • Stretch goals: Animated watchface, gesture wake, synced phone weather, editable layouts.

3. BLE Phone Companion

  • Difficulty: Medium to Hard
  • Main hardware: BLE, display, touch, haptics, speaker optional
  • Core idea: Connect to a phone app and exchange useful data.
  • Minimum viable demo: Phone connects over BLE and sends notifications or simple text messages to the watch.
  • Stretch goals: Music controls, find-my-phone, phone battery sync, quick replies.

4. Gesture Remote

  • Difficulty: Medium
  • Main hardware: IMU, BLE or Wi-Fi, haptics
  • Core idea: Use wrist gestures to control another device.
  • Minimum viable demo: Recognize 2-3 gestures and map them to actions such as next slide, previous slide, play/pause, or camera shutter.
  • Stretch goals: Calibration mode, gesture training, context-aware control modes.

5. Pomodoro / Focus Coach

  • Difficulty: Easy
  • Main hardware: display, touch, haptics, speaker optional
  • Core idea: Help the user stay focused with work/break cycles.
  • Minimum viable demo: Configurable timer, session progress, vibration alert at timer end.
  • Stretch goals: Habit streaks, productivity stats, distraction tracking, BLE sync to a phone.

6. Voice Memo Watch

  • Difficulty: Hard
  • Main hardware: microphone, speaker, flash, PSRAM, touch
  • Core idea: Record and play short voice notes directly on the watch.
  • Minimum viable demo: Record, save, list, and replay short clips.
  • Stretch goals: Compression, timestamps, BLE export to phone, keyword tagging.

7. Fall Detection / Safety Alert

  • Difficulty: Hard
  • Main hardware: IMU, haptics, BLE
  • Core idea: Detect a likely fall and trigger an alert flow.
  • Minimum viable demo: Simulated fall detection with on-watch confirmation and BLE alert message to a phone app.
  • Stretch goals: Motion confidence scoring, inactivity follow-up, emergency contact workflow.

8. Sleep / Restlessness Tracker

  • Difficulty: Medium
  • Main hardware: IMU, fuel gauge, display
  • Core idea: Track overnight movement and estimate sleep quality.
  • Minimum viable demo: Movement logging, simple sleep score, timeline of motion intensity.
  • Stretch goals: Smart wake-up window, sleep trends, nap mode, phone sync.

9. TinyML Activity or Keyword Detector

  • Difficulty: Hard
  • Main hardware: IMU or microphone, PSRAM, flash, display
  • Core idea: Run a lightweight ML model on-device for activity classification or keyword detection.
  • Minimum viable demo: Recognize a few gestures, motions, or spoken keywords and react in the UI.
  • Stretch goals: User training data collection, confidence metrics, low-power trigger mode.

10. IoT Dashboard / Smart Home Controller

  • Difficulty: Medium
  • Main hardware: Wi-Fi or BLE, display, touch, haptics
  • Core idea: Turn the watch into a compact controller for sensors or smart-home devices.
  • Minimum viable demo: Show live values such as temperature, light, or room status, and toggle at least one remote action.
  • Stretch goals: Home Assistant integration, quick scenes, secure pairing, offline cache.
iothings/hackathon.1775125787.txt.gz · Last modified: 2026/04/02 13:29 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