End a run with semihosting
Call SYS_EXIT_EXTENDED so OnMCU stops as soon as the firmware finishes.
A bare-metal test cannot return a result to an operating system. Instead, it can use an ARM semihosting trap to tell OnMCU that it passed or failed.
Use SYS_EXIT_EXTENDED, operation 0x20, with a two-word parameter block:
word 0: 0x00020026 ADP_Stopped_ApplicationExit
word 1: exit code 0 for success, non-zero for failureOn 32-bit Arm targets, put the operation in r0 and a pointer to the block in r1, then execute bkpt 0xAB. OnMCU reads the subcode and ends the job.
Why use the extended operation
The older SYS_EXIT operation cannot carry a separate status code on 32-bit targets. SYS_EXIT_EXTENDED carries both the stop reason and the application's exit code.
Result propagation
A zero device code completes the OnMCU job, and onmcu run returns 0. A non-zero device code fails the job, and the CLI returns 10.
C
This code comes from the shared library in onmcu/c-examples:
#ifndef SEMIHOSTING_H
#define SEMIHOSTING_H
void sys_exit(int code) __attribute__((noreturn));
#endif#include "semihosting.h"
#include <stdint.h>
#define SYS_EXIT_EXTENDED 0x20
#define ADP_STOPPED_APPLICATION_EXIT 0x20026u
static int semihost_call(int op, void *arg)
{
register int r0 __asm("r0") = op;
register void *r1 __asm("r1") = arg;
__asm volatile("bkpt 0xAB" : "+r"(r0) : "r"(r1) : "memory");
return r0;
}
void sys_exit(int code)
{
uint32_t block[2] = {
ADP_STOPPED_APPLICATION_EXIT,
(uint32_t)code,
};
semihost_call(SYS_EXIT_EXTENDED, block);
// The host normally tears down the session above.
for (;;) {
}
}Call it after the final result is known:
int main(void)
{
int result = run_tests();
if (result == 0) {
rtt_write_str("all tests passed\n");
sys_exit(0);
}
rtt_write_str("tests failed\n");
sys_exit(1);
}Compile semihosting.c into the linked firmware and include its header. It uses no host-side standard library.
Rust
The semihosting crate provides the same operation:
[dependencies]
semihosting = "0.1"fn finish(success: bool) -> ! {
if success {
defmt::info!("all tests passed");
defmt::flush();
semihosting::process::exit(0);
} else {
defmt::error!("tests failed");
defmt::flush();
semihosting::process::exit(1);
}
}defmt-test already uses semihosting to halt after a test suite. Follow Rust and defmt-test to use OnMCU as its Cargo runner.
Prevent the fallback from returning
The semihosting call is expected to end the session. Keep a non-returning loop after the trap in hand-written C or assembly in case the firmware is run without a semihosting-capable host. Do not let execution fall into unrelated memory.
Preserve the last log line
The debugger can stop immediately after the exit trap. Flush RTT or defmt before exiting. For a hardware UART, wait until its transmit path has drained; a logger flush that covers only a memory buffer may not wait for the serial shift register or USB bridge.
Do not rely on the timeout
If the firmware finishes and enters an infinite loop, OnMCU still sees a running job. It stops only at the configured timeout, and the CLI returns 12. A semihosting exit ends successful tests at once and leaves the timeout for real hangs.