An application written in Mecrisp Forth consists of a number of different parts:
- The Mecrisp kernel itself: this is 20 KB of Matthias Koch’s hand-craftedassembly code, turning a µC into a Forth compiler / engine, with over 300 pre-defined “words”.
- Compiled code, stored in flash memory - these will always be present on power-up.
- Compiled variables, buffers, and code, stored in RAM - variables will be reset to their initial values on power-up and after each reset, but any code in RAM will be gone.
As delivered, the JeeNode Zero rev4 comes with ≈ 23 KB of compiled Forth code pre-installed in flash memory, leaving ≈ 21 KB of flash for additional code, as you can see in the greeting:
Mecrisp-Stellaris RA 2.3.3 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jz4> 3B5E0728 ram/flash: 4960 21248 free ok.
Forth compiles code to a “dictionary”, which is essentially a growing stack of word definitions. There is a dictionary in flash, and there’s a second one in RAM. Conceptually, words defined later override earlier definitions, and appear later in the dictionary. Words defined in RAM override words defined in flash, i.e. lookup starts in RAM, and continues in flash if not found.
It’s all very clean, but there is a small gotcha:
compiletoflash ok.
: a ." flash!" ; ok.
compiletoram ok.
: a ." ram!" ; Redefine a. ok.
compiletoflash ok.
a flash! ok.
compiletoram ok.
a ram! ok.
forgetram ok.
a flash! ok.
So while compiling to flash, the words in RAM are not visible! The reason for
this is that words in flash can’t refer to words in RAM, as these would be
gone on the next power cycle or reset. Note that you can refer to code
in RAM via variables, and run them from flash using execute
.
Development is very different from development in C or C++, because in Forth everything happens on the µC itself. This leads to a different way of structuring code - it helps to make a clear distinction between: on the one hand drivers and other relatively stable code, and on the other hand code which is currently being written and debugged.
A really effective way to develop code in Mecrisp Forth is to load all stable code in flash, and to keep all work-in-progress code in RAM. That way, a simple reset always restores the µC to a clearly defined state - no matter how bad the bugs are and no matter how many hardware settings the new code might have messed up.
Which is exactly why the JeeNode comes with a fair amount of pre-installed code. You won’t have to install anything to try out the ADC, PWM, I2C, SPI, or the RF69 wireless radio driver - they are all available out of the box. Even basic OLED support and graphical and text display primitives plus a small font are pre-installed.
This burnt in code is split into three sections:
Always - this code really should hardly ever need to be replaced, and may in fact require special tricks to update (on the F103, this is the case for the USB console driver)
Board - this code implements drivers for the most important hardware peripherals, such as GPIO, ADC, PWM, I2C, and SPI - it also defines the
LED
constant (pin PA8 on a JNZ rev4)Core - this part can easily be replaced, depending on what you need for a specific project - as pre-installed, it contains among others, drivers for the RFM69 radio module and the OLED
On a JNZ rev4, each section is defined by a source file, which in turn includes everything else:
jz4/always.fs currently only defines
cornerstone
, which is used to mark off each sectionjz4/board.fs defines the pins assigned to LED + RFM69 and adds essential drivers from flib
jz4/core.fs is the most interesting part, and can easily be customised. Here is the main code:
<<<board>>> compiletoflash include ../flib/spi/rf69.fs include ../flib/any/varint.fs include ../flib/i2c/ssd1306.fs include ../flib/mecrisp/graphics.fs include ../flib/mecrisp/multi.fs cornerstone <<<core>>>
The logic of the core.fs
file is as follows:
- it’s meant to be uploaded via Folie’s “
!s core.fs
” command and since it refers to other files by a relative path, this must be sent from a specific directory (more on that below) <<<board>>>
is a “cornerstone” defined as a last step in theboards.fs
file and calling it erases all definitions from flash which have been defined afterboard.fs
was installed- since normally
core.fs
is loaded right afterboard.fs
, the result is that re-sending thecore.fs
file will erase itself and everything newer, and then save updated definitions - as a last step, a cornerstone called
<<<core>>>
is defined - by calling this later on, you can erase all definitions added afterwards, so this restores flash to a known state
Since it’s the last word defined in the above sections, you can type
“<<<core>>>
” whenever you want to reset flash memory to that “standard” state.
(note that cornerstones always end with a software reset, so this also wipes out
anything in RAM).
Cornerstones provide a nice mechanism to manage flash memory - they act as
markers to erase all newer definitions after their own position in the
dictionary. Note that cornerstones can only be used in flash memory. For
clearing all RAM definitions, there is forgetram
.
To install a customised version of core.fs
on a JeeNode Zero:
Get a copy of the
embello
repository on GitHub, either as download from the home page or (preferably) as agit clone
, which makes it much easier to track changes.Go to the directory with the relevant files, i.e. “
cd explore/1608-forth/jz4/
” and make changes tocore.fs
- you may have to re-order includes in case of dependencies.Launch Folie (add “
-r
” option when not using a SerPlus) and enter “!s core.fs
” - you should see a number of messages, as flash gets erased and all the included files are sent.
Here is a transcript of the send process, omitting most of the “Erase” lines for brevity:
ok.
!s core.fs
1> core.fs 3: <<<board>>>
Erase block at 00008700 from Flash
Erase block at 00008780 from Flash
Erase block at 00008800 from Flash
[...]
Finished. Reset
Mecrisp-Stellaris RA 2.3.3 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jz4> 3B5E0728 ram/flash: 6804 30976 free ok.
1> core.fs 4: cr compiletoflash
ok.
1> core.fs 5: ( core start: ) 00008700 ok.
1> core.fs 13: ( core end, size: ) 0000ACA8 9640 ok.
The first effect is that the flash dictionary will be reset, releasing all the
memory used by the previous version of core.fs
and whatever might have been
added to flash later on. The second effect is that a fresh core.fs
configuration will be compiled and saved in its place.
By installing new drivers and dropping the ones you don’t need, each JeeNode
Zero can be configured exactly as required, but keep in mind that even with a
standard core.fs
setup, modified drivers can also be loaded in RAM or added
to flash, superseding prior definitions.
Redefining a word to supersede the previous version is common practice in Forth (you’ll get a “Redefine” message whenever this happens), but beware that older definitions will continue to refer to the original code (“early binding”). Here is an example to illustrate that behaviour:
: a 123 . ; ok.
a 123 ok.
: b a a ; ok.
b 123 123 ok.
: a 456 . ; Redefine a. ok.
b 123 123 ok.
: b a a ; Redefine b. ok.
b 456 456 ok.
If you keep this behaviour in mind, there’s usually less need to erase and reload code in flash. Instead, you can simply load that code again in RAM, or append it to flash. Once you run out of free memory, that’s of course a good reason to do a full erase/re-flash as described earlier.
These same approaches can be used for board.fs
, but don’t forget to reload
core.fs
as well.