JeeH runtime libraryΒΆ

PlatformIORegistry

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;
         msIdle(250);
     }
}

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 https://registry.platformio.org/libraries/jcw/JeeH.

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  {...};
    os::put(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  {...};
    os::put(m);
    while  (true)  {
         auto&  r  =  os::get();  // blocking
         if  (&r  ==  &m)
             break;
         // 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;
     Pin::config("B6:PV10,B2:PV9,C9,C10,D13,E2");
     ...
}

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 …