These are some ideas about how to structure the flash and RAM memory for
applications built on top of Mecrisp Forth.
First off: 64 KB of flash memory turns out to be plenty for very substantial
applications. All of the current JeeLabs Energy Monitor code easily fits within
64K (even though the Olimexino has 128K). And that includes the RF69, OLED with
graphics, ADC with DMA, pulse counters, DCF decoding, as well as the core
hardware abstraction layer, clock and time management, SPI and I2C drivers,
timers, PWM, RTC, interrupt-based UART with ring buffers, and the multi-tasker.
Code is added on-the-fly to the Forth “dictionary”, either in flash memory, or
in RAM. All RAM-based definitions are lost on reset or a power-cycle, so this is
really more for development than for permanent use.
The dictionary acts like a “stack of code” (and constant data). Mecrisp includes
primitive words to erase and re-use flash memory, but this needs to be done with
care since words can cross flash page boundaries. The solution to this is the
“cornerstone” word, which can be called to set a marker on a flash page
boundary. The use of cornerstones is really simple - this defines one:
And this erases all definitions added to the dictionary after that definition:
blah itself remains in the dictionary, it’s not self-destructive
like the traditional “
forget” mechanism used in other Forth systems.
With cornerstones, we can define an app structure which simplifies development
and makes it easy to replace just the top layer with new code. This works really
nicely in combination with the “include” mechanism in
Folie, by setting
up source files as follows:
Layer “A” (always.fs)
\ this file must be loaded on top of a pristine Mecrisp image
... lots of definitions ...
: init ... ; \ essential startup configuration (clock, console, ...)
: cornerstone ... ;
This uses the trick from a previous article of having some
code stay around essentially forever, by redefining the built-in
word. With the above code loaded into a fresh Mecrisp setup, we effectively
install some code and
init word which sets the stage for permanent use.
eraseflash” at the Mecrisp command prompt will always revert the
system and flash state to this configuration.
Layer “B” (board.fs)
The next layer is for “board” definitions, i.e. words which are
for a specific µC and board, and words which allow abstracting away some of the
basic differences. This is where a µC-specific implementation of the SPI driver
resides - so that everything on top can see the same API:
... lots of definitions: pins/buttons/LEDs, main h/w interfaces ...
Board definitions rarely need reloading, their purpose is to simplify the
code in the next layers. This would probably be called the “runtime library” in
Note how the source file starts with an
eraseflash to restore the dictionary
to the “always” state of layer A, before it adds the board definitions.
A side effect of calling
eraseflash or a cornerstone, is that Mecrisp will
reset itself afterwards, and run the
init word again. So all board definitions
take place in the context set up by
Layer “C” (core.fs)
The “core” layer is intended for well-tested code, such as hardware drivers and
libraries, which are needed for this specific application. It is likely to
consist mostly of
include lines for
folie, to bring in certain features.
Here is the outline of a sample “core.fs” source file:
... lots of definitions: includes and tested application code ...
As the code for a new app solidifies, it can be added to this source
file (inline or via includes).
In this case, the source code starts with
<<<board>>> to wipe out any other
definitions added afterwards (i.e. the previous version of itself). And it ends
by adding a new conerstone of its own.
Layer “D” (dev.fs)
This layer is for active development. It is not stored in flash but in RAM. That
means that the code and definitions will all disappear on reset. The source code
reflects this difference:
... more definitions: work in progress, debug words ...
No more cornerstones now (they can only be used for flash memory). The goal
here is to make reloading as fast as possible. A lot of development will take
place in this layer, with code moved to the “C” layer once it’s working and
Layer “E” (exp.fs)
Sometimes, development needs more work and more exploration before we can settle
on a design and work out all the details. Layer “E” is for experimentation and
exploration, i.e. code which is not necessarily going to end up in the real
application - tests, little helpers, custom debug words, and actual calls to
these words - the goal here is even faster turnaround:
... yet more definitions, but also actual calls to start up things...
The intention is to allow several different versions of this source code, so
that there could be “
e<sometag>.fs” files for all sorts of trials. And we can
easily keep all of them around forever, just in case we ever need to chase that
tricky issue again one day.
In this case, we don’t start off with a call to
reset but we include the
dev.fs source code (which includes the reset) to make sure all those words are
loaded. With a bit of luck, we can simply hit up-arrow + return to include this
file over and over again, as part of a fast-paced edit-run cycle.
Layer “F (final.fs)
The last layer is the one which turns the board into a complete application,
starting up the minute is is powered up:
... lots of definitions: includes and application code ...
: init ... init ; \ this will auto-start the application
Note that this layer does not load the D and E layers. It’s flash-only.
This one is risky, in that it “seals” the application. If the app is not working
properly and if we haven’t built in an escape hatch of some sort, we will not
regain control of the command prompt. In that case, all that’s left is to
reflash the board by other means: a ROM-based boot or SWD.
One way out is to build a delay into
init, allowing some key press or other
external event to bypass a full auto-run. That way, a reset follow by some
specific action will get us back to that oh so powerful Forth command prompt.
An even better alternative though, is to build in support for an interactive
command prompt, while running all the application code from a second background
task, using the multi-tasker. This way, we can peek and poke while the
application is actually running, and even develop enhanceents and new features
on the fly (in RAM, for easy recovery).
So there you have it: a layered application structure, with built-in support for
permanent console redirection, board-specific extensions, driver/library use,
fast-turnaround development cycles, and an application freeze for turnkey use.
It’s just a thought experiment for now, but worth a try!
There’s no IDE in sight: just your preferred source code editor, Folie, and
As you may have noticed, the above layers are called A, B, C, D, E, and F -
easy to remember!
For comments, visit the