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;
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 β¦