ENGINEERING TROUBLESHOOTING / CASE STUDY 001

Zephyr RTOS Startup Investigation

STM32F103 · Renode Simulation · Clock Initialization

An STM32F103-targeted Zephyr application built successfully but produced no output when executed in Renode. Program-counter inspection, source-address correlation, and device-tree analysis isolated two clock-readiness waits. An application-level clock configuration change allowed the firmware to complete startup and write to the simulated USART1 console.

01 / Objective and Scope

The objective was to establish a repeatable firmware-development baseline for the STM32F103 before physical hardware was available. The exercise required a custom C application, a Zephyr-based ARM build, execution in Renode, and observable serial-console output. Application behavior was intentionally minimal so that build, initialization, and simulation integration could be evaluated independently.

The target was Zephyr's stm32f103_mini board configuration. Renode modeled the microcontroller and peripherals; it did not compile the firmware. Compilation and linking were performed by the Zephyr build system and ARM cross-toolchain on Ubuntu Linux.

02 / Development Configuration

ComponentConfiguration / Role
HostUbuntu Linux, x86-64
ApplicationC, using Zephyr printk()
TargetSTM32F103 / ARM Cortex-M, stm32f103_mini
Zephyr RTOS4.4.2
West1.5.0
Zephyr SDK / ARM GCCSDK 1.0.1 / GCC 14.3.0
Python / pyenv3.14.10 / 2.8.14
Renode1.17.0
Output interfaceSimulated USART1 console

Python dependencies were isolated in a virtual environment managed alongside pyenv. Zephyr's package was exported for CMake discovery withwest zephyr-export. A successful build of Zephyr's standard blinky sample established that the SDK, compiler, and board definition were functioning before the custom application was introduced.

The application directory contained the following artifacts:

applications/hello_world/
├── CMakeLists.txt
├── prj.conf
├── app.overlay
├── src/main.c
└── build/zephyr/
    ├── zephyr.elf
    ├── zephyr.dts
    └── .config

03 / Firmware and Build Baseline

The custom application used Zephyr's console abstraction rather than a separately implemented UART driver. Its final tested source was:

src/main.c

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

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

CMakeLists.txt

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)
target_sources(app PRIVATE src/main.c)

An early CMake configuration error resulted from misspellingZephyr as Zepher. Correcting the package name and configuring the Zephyr environment resolved package discovery.

prj.conf

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

The generated device tree selected USART1 as the console interface. The final pristine build used:

west build -b stm32f103_mini -d build -p always .
151Build steps completed
13,748 BFlash / 5.24% of 256 KB
4,112 BRAM / 8.37% of 48 KB

The build produced build/zephyr/zephyr.elf, an ARM executable containing the firmware and source-mapping information subsequently used for diagnosis.

04 / Observed Failure

The initial Renode session loaded the STM32F103 simulation usinginclude @scripts/single-node/stm32f103.resc. The custom ELF was loaded into the simulated system, the CPU was reset, and execution was started. Despite the successful firmware build, the USART1 analyzer displayed no application output.

pause
sysbus LoadELF @/home/mannysoto/GitHub/embedded/zephyrproject/applications/hello_world/build/zephyr/zephyr.elf
sysbus.cpu Reset
showAnalyzer sysbus.usart1
start

Because the firmware had compiled and linked, the investigation shifted from application syntax to the processor's actual execution state. The working hypothesis was that startup or hardware initialization had not completed, preventing entry into main().

05 / Diagnostic Evidence

Finding A — LSI oscillator readiness

Execution was paused and the simulated CPU program counter inspected:

pause
sysbus.cpu PC
# 0x08000BA8

The ARM toolchain's addr2line utility mapped that instruction address to the STM32 low-speed internal oscillator readiness check:

~/zephyr-sdk-1.0.1/gnu/arm-zephyr-eabi/bin/arm-zephyr-eabi-addr2line \
  -e build/zephyr/zephyr.elf -f 0x08000ba8

# LL_RCC_LSI_IsReady
# stm32f1xx_ll_rcc.h:992

The corresponding initialization sequence included:

LL_RCC_LSI_Enable();
while (LL_RCC_LSI_IsReady() != 1) {
}

The generated device tree showed enabled RTC and LSI clock nodes. Because the application did not require RTC functionality, an application-level overlay disabled both nodes as a controlled test.

&rtc {
    status = "disabled";
};

&clk_lsi {
    status = "disabled";
};

After rebuilding and reloading, the processor progressed beyond the first observed location, but serial output was still absent.

Finding B — PLL readiness

A second program-counter inspection returned0x08000BD2. Source mapping identified another clock-readiness check:

~/zephyr-sdk-1.0.1/gnu/arm-zephyr-eabi/bin/arm-zephyr-eabi-addr2line \
  -e build/zephyr/zephyr.elf -f 0x08000bd2

# LL_RCC_PLL_IsReady
# stm32f1xx_ll_rcc.h:1408

The board's original configuration selected an external 8 MHz high-speed oscillator and a PLL multiplier of nine, yielding a configured 72 MHz system clock:

&clk_hse {
    clock-frequency = <DT_FREQ_M(8)>;
    status = "okay";
};

&pll {
    mul = <9>;
    clocks = <&clk_hse>;
    status = "okay";
};

&rcc {
    clocks = <&pll>;
    clock-frequency = <DT_FREQ_M(72)>;
};

This observation indicated that execution was again waiting for a clock-ready condition during startup. It did not, by itself, establish whether the behavior originated in the simulation model, its configuration, or an interaction with the selected board definition.

06 / Corrective Configuration

To reduce dependencies on external oscillator and PLL readiness, the application was configured to use the STM32F103 internal 8 MHz high-speed oscillator (HSI). The finalapp.overlay retained the RTC/LSI changes and disabled the PLL, while selecting HSI as the RCC clock source:

&rtc {
    status = "disabled";
};

&clk_lsi {
    status = "disabled";
};

&pll {
    status = "disabled";
};

&rcc {
    clocks = <&clk_hsi>;
    clock-frequency = <8000000>;
    ahb-prescaler = <1>;
    apb1-prescaler = <1>;
    apb2-prescaler = <1>;
};

The firmware was rebuilt, reloaded into Renode, and executed following a CPU reset. With this configuration, startup completed and the application wrote to USART1.

07 / Validation and Result

OBSERVED USART1 OUTPUT

*** Booting Zephyr OS build v4.4.2 ***
Hello Manny from Zephyr!

The result validated the build-to-simulation workflow for this application and configuration: custom C source was cross-compiled into an ARM ELF image, loaded into a simulated STM32F103, and executed with observable console output.

The investigation established a practical workaround for the observed startup behavior. It did not independently prove an underlying defect or missing feature in Renode's oscillator model. The HSI overlay should therefore be treated as a simulation-specific baseline pending separate testing on physical hardware.

08 / Investigation Record

StageObservationEngineering response
BuildARM ELF generated successfullyProceed to simulated execution
Initial runNo USART1 outputInspect CPU program counter
First address0x08000BA8 → LL_RCC_LSI_IsReadyInspect RTC/LSI device-tree configuration
First changeExecution progressed; no console outputDisable RTC/LSI; rebuild and retest
Second address0x08000BD2 → LL_RCC_PLL_IsReadyInspect HSE/PLL/RCC configuration
Second changeHSI selected at 8 MHzDisable PLL; rebuild and reload
Final runZephyr banner and application message displayedRetain known-working simulation baseline

Diagnostic method: Observe → Locate → Inspect → Hypothesize → Modify → Rebuild → Retest.

09 / Engineering Conclusions and Limits

The most consequential diagnostic step was mapping the simulated processor's program counter back to source. This provided direct evidence of the initialization functions in which execution was waiting, rather than relying on changes to the application message or speculative UART troubleshooting.

The work also demonstrated the value of Zephyr device-tree overlays: application-specific hardware configuration could be evaluated without modifying the original board definition. The generatedzephyr.dts and ELF debugging information were essential investigation artifacts.

The completed baseline covers toolchain configuration, ARM cross-compilation, simulated firmware execution, serial observation, program-counter analysis, and clock-configuration diagnosis. It does not yet demonstrate GPIO operation, interrupt handling, multithreading, synchronization, external-device communication, or flashing and debugging the physical STM32 board.

The next planned exercise is a separate Zephyr application with two threads producing periodic console output, allowing scheduling andk_sleep() behavior to be examined without altering this known-working baseline.

Reference Commands

Build

cd /home/mannysoto/GitHub/embedded/zephyrproject/applications/hello_world
west build -b stm32f103_mini -d build -p always .

Renode — Load and execute

# With the STM32F103 simulation already loaded:
pause
sysbus LoadELF @/home/mannysoto/GitHub/embedded/zephyrproject/applications/hello_world/build/zephyr/zephyr.elf
sysbus.cpu Reset
showAnalyzer sysbus.usart1
start

Inspect CPU and correlate source

# Renode monitor:
pause
sysbus.cpu PC

# Ubuntu terminal; substitute the observed address:
~/zephyr-sdk-1.0.1/gnu/arm-zephyr-eabi/bin/arm-zephyr-eabi-addr2line \
  -e build/zephyr/zephyr.elf -f 0x08000bd2