Skip to content

JeeH runtime library


There are many different frameworks for microcontrollers: Arduino, CMSIS, Espressif, MBed, STM32Cube, Zephyr, etc. Some cover a single vendor’s platform, others have support for a wide range of µC architectures.

JeeH is a runtime library written to be used alongside a main framework. The main focus is to support all the STM32 µC families, with very limited trial builds for AVRs, ESPs, and Teensy’s.

JeeH is written in modern C++ (C++17 and later), and uses templates (sparingly) to generate very compact and efficient code. Here is a minimal example for a Blue Pill (STM32F103):

#include <jee.h>
using namespace jeeh

Pin led ("C13");

int main () {
    led.mode("P");      // push-pull output

    while (true) {
        led = !led;

This example compiles to less than 1200 bytes of flash and uses 150 bytes of RAM. Adding a serial port and calling printf will increase that by about 1 kB. There is virtually no overhead, as the C++ compiler is able to optimise the heck out of all this.

JeeH is made for PlatformIO, and can be used by adding lib_deps = JeeH to a project’s platformio.ini file - this wil automatically download the most recent release.

JeeH is work-in-progress. There is currently no other documentation than the source code.

For more details, see

JeeH v2.0a2 (Jan 2023)

This project is progressing nicely. The new multi-tasking kernel is turning out much better than expected: a few simple “primitives” hide much of the complexity of concurrent programming.

Multi-tasking idioms

Some idioms are also starting to become apparent:

  • The default message receive mode is blocking, but it’s easy to implement a timeout by blocking on a timeout, instead of waiting for the reply:

    Message m {...};
    if (auto p = os::delay(100); p != nullptr) {
        verify(p == &m);
        // the timer did not trigger, so the reply came back in time

    The reasoning here is that either the timer’s or the request’s reply will come back first. In the latter case, os::delay will cancel its timer and return the message it got instead.

  • To poll for incoming messages without blocking, we can use a delay of zero:

    if (auto p = os::delay(0); p != nullptr) {
        // there is a message for us
  • Things get a bit more complicated when more message replies can be outstanding. In this case, the following logic may be of use:

    Message m {...};
    while (true) {
        auto& r = os::get(); // blocking
        if (&r == &m)
        // something else happened, deal with it
    // the reply for m is now available

In a way, this message-based approach makes all sequencing of events explicit. Program flow is never interrupted, it only needs to deal with events when it is expecting them.

Memory-mapped QSPI flash

There is now a QSPI driver for the F723 Disco board which supports memory-mapped reads as well as flash writes and page erases. Since the entire 64 MB flash is mapped into memory, it can also be used for code execution (or “XIP” - eXecute-In-Place - as STM calls it).

The qspi::init code is a nice example of how simple GPIO pin setup has become:

void qspi::init () {
    RCC(EN_QSPI, 1) = 1;

The code above configures a few pins for pull-up, in high-speed (“very fast”) mode, using alt mode 10 and 9, respectively). Pins with no config info are set the same as the preceding pins.

Memory-mapped flash will be used for a new “Mapped Romable File Store” implementation, a simple file-structured storage design with contiguous files and wear leveling which can be used for internal flash as well as (memory-mapped) QSPI flash.

A first implementation can be found here. This includes a receive-side implementation of the ZModem protocol to allow quick uploading of files over serial to an attached µC. With the picocom utility, this is a matter of typing CTRL/A, CTRL/S, and then a file name.

Blocking DMA-based UART

Speaking of serial: the UART-DMA driver is now part of JeeH and is working out very nicely. It greatly reduces the CPU load and is now usable as blocking I/O driver via the multi-tasker.

As with all blocking os::* calls, the multi-tasker automatically enters a low-power idle state when there is nothing else to do by waiting for the next interrupt. Stop / standby modes and clock speed reduction have not yet been implemented.

Kernel vs Task Mode

Drivers play a special role in the multi-tasker, just as in TriPOS on which much of its design is based: they can listen to and reply to messages from any task, but unlike tasks they do not require a context switch or their own stack. This makes them more efficient at dealing with a large number of messages.

Drivers run in “kernel mode”, which is simply a way to prevent context switches and postpone the message-sending parts of interrupt handlers. In the future, kernel mode could also manage the MPU (Memory Protection Unit) present in most ARM µCs, to prevent access to hardware registers from non-privileged “task mode” and perhaps more importantly: to prevent tasks from messing up each other’s memory space.

MPU ≠ MMU, but …

ARM Cortex µCs do not have a Memory Management Unit (MMU), which allows implementing virtual memory and remapping address ranges. But there is a very interesting trick possible in boards with external static RAM (such as the F723 Discovery), which turn out to offer a very similar capability. Stay tuned …