-
Notifications
You must be signed in to change notification settings - Fork 66
STM8 eForth Compile to Flash
Interactive systems are nice, but in most cases embedded systems need to retain programs, and they should just reliably execute binary code directly after power-on without user interaction.
STM8 eForth provides a middle ground:
- it's an interactive system for compiling binary code to RAM or Flash memory and for testing it
- a "start-up word" can execute compiled code before or while running the interactive system
By using one of the STM8 eForth background execution methods (Background Task, Idle Task or Interrupts), a start-up word can easily return to the console after setting everything up, so that, e.g. control parameters in an embedded system can be tuned or code can be changed interactively (some interrupt handlers may not even require a start-up word).
This page explains how to make autostart applications and provides the technical background of the STM8 eForth NVM
(non-volatile memory) features.
The STM8 IAP (In Application Programming) feature makes writing to a Flash memory as simple as writing to RAM: code and constants can be modified on the fly, also the startup code vector!
In STM8 eForth the Flash memory can be unlocked with the word NVM
(Non-Volatile Memory), which also moves the head of the Forth dictionary to the next free Flash memory location.
As an example, the following code snippet demonstrates how to define a new greeting word, and set it as the start word:
NVM
: mystart ( -- )
CR 3 FOR I . NEXT CR ." Hi!" CR
;
' mystart 'BOOT !
RAM
Here is what happens:
-
NVM
unlocks Flash memory write access and setshere
to the top of the dictionary in the non-volatile memory -
mystart
implements the start-up word (it counts down from 3 to 0, and prints some text) -
'
(tick) gets the address of the code ofmystart
-
'BOOT
gets the address of the startup word pointer (!
stores the address of the startup word) -
RAM
than switches back to volatile memory mode, and it thereby stores the dictionary pointers permanently, and locks the Flash memory.
When you powered-cycle the board (or run COLD
) the new start-up code prints the following:
COLD
3 2 1 0
Hi!
With a startup word it's also possible to initialize an application, e.g. for starting the background task. Here is a very simple example, that shows a running counter on the 3 digit 7S-LED display of a W1209:
NVM
: show ( -- )
TIM U. ;
: startup ( -- )
[ ' show ] LITERAL BG ! CR ;
' startup 'BOOT !
RAM
The example first sets NVM
mode, then defines show
to print the value of the background ticker TIM
. In a background tasks, character output (e.g. .
) go to the board UI (e.g. LED display and keys). Of course, you can also run show
on the console.
The definition of the word startup
uses [
and ]
to change to the interpreter, put the address of show
on the data stack, and insert it into the compiled code with LITERAL
. BG
gets the address of the background task pointer, to which the literal value from the stack is stored with !
.
RAM
stores pointers to Flash memory, and then changes the memory mode back to RAM. After the next reset, the background tasks starts running before control is given to the user.
Note that LITERAL
is required for compiling data from the stack into memory. It's also possible to use CONSTANT
instead of the idiom above:
' show CONSTANT 'show
: startup 'show BG ! CR ;
It's simple to return to the default start-up word (HI
):
NVM
' HI 'BOOT !
RAM
The word RESET
provides another way to start over: it resets the vocabulary in NVM and RAM, and it also resets the 'BOOT
vector and other initialization values.
Note: to retain words compiled in NVM
mode, switching back to RAM
mode is required. Otherwise the the added words won't be linked after COLD
or after a power-cycle, and will be overwritten by new definitions!
Words in Flash are required for autostart programs, while words in RAM are very useful as a temporary dictionary, e.g. for defining temporary alias words, immediate
words, constants, or unit testing. STM8 eForth supports a convenient workflow for selecting the appropriate type of memory.
The following core words control the STM8 eForth memory mode:
Word | Description | Availability |
---|---|---|
NVM |
Switch to Non Volatile Memory mode (Flash) | Core dictionary |
RAM |
Switch to RAM mode and make dictionary changes in Flash permanent |
Core dictionary |
RESET |
Restore original dictionary initialization pointers from shadow pointers(i.e. remove dictionary entries added in NVM mode) |
Core dictionary |
'BOOT |
Push the address of the boot vector on the stack | Core dictionary |
The following listing shows an example session:
RESET ok
: a 2 . ; ok
NVM ok
: b 1 . ; ok
RAM ok
WORDS
a b IRET SAVEC RESET RAM NVM WORDS .S DUMP IMMEDIATE ALLOT VARIABLE CONSTANT CREATE
] : ; OVERT ." AFT REPEAT WHILE ELSE THEN IF AGAIN UNTIL BEGIN NEXT FOR LITERAL C, ,
' CR [ \ ( .( ? . U. TYPE SPACE KEY DECIMAL HEX FILL CMOVE HERE +! PICK 0= ABS NEGATE
NOT 1+ 1- 2+ 2- 2* 2/ */ */MOD M* * UM* / MOD /MOD M/MOD UM/MOD WITHIN MIN MAX < U< =
DNEGATE 2DUP ROT ?DUP BL BASE - 0< OR AND XOR + UM+ I OVER SWAP DUP 2DROP DROP NIP >R
R@ R> C! C@ ! @ 2@ 2! EXIT EXECUTE EMIT ?KEY 'BOOT COLD ok
Note the extra blank between a
(in RAM) and b
(in Flash memory): that's the result of a dummy (empty) dictionary entry in RAM dictionary section that was required for Word List Extensions. It's also a useful reminder of what's in RAM.
In eForth, a VARIABLE creates a words in the dictionary, which contain the word doVAR
in the "code field", and at least one RAM cell in the "parameter field". The role of doVAR
is to put the address of the "parameter field" (the first RAM cell after doVAR
) on the data stack, and then return to the calling word. In NVM
mode the parameter field of a defined word is also in the Flash memory (which makes it a constant).
From STM8EF release v2.2.8 on, there is an extra level of indirection for a VARIABLE in a ROM (in NVM
mode doVAR
is replaced by doVAR@
). A managed RAM pool (assigned by WIPE
, e.g. in COLD
) makes the feature transparent to the user (it "just works").
Arrays in RAM are also supported, however, since CREATE
also has other use cases (e.g. CREATE ... DOES>
, constant data structures in NVM) it doesn't have a mode dependent behavior, and a work-around is required:
: CELLS ( n -- 2*n ) 2* ; \ number of Forth Cells to number of bytes
NVM
VARIABLE abc 9 CELLS ALLOT
: init
abc 10 CELLS $55 FILL
;
RAM
Here, the helper word CELLS
remains in RAM.NVM
changes to Flash mode, VARIABLE
and ALLOT
define the array abc
and allocate a total of 10 CELLS
(20 bytes) of RAM storage. The array (in RAM) represented by the word abc
is filled with the value 0x55.
Note that ALLOT
ing space in NVM (that is not RAM but Flash memory) would require another approach (e.g. by defining a helper word like : DALLOT CP +! ;
). One can, of course, simply use CREATE
and ,
.
There are several extension words in the STM8 eForth lib/
folder. With e4thcom or codeload.py
these words can be loaded with the #require
feature.
Word | Description | Vocabulary |
---|---|---|
WIPE |
Initialize dictionary in RAM: switch to mode RAM , claim RAM for VARIABLE and ALLOT , and create a dummy word in RAM (called by COLD ) |
extended (#require WIPE ) |
PERSIST |
Make the current dictionary the new baseline by copying the main dictionary initialization pointers to the shadow pointers used RESET (including the 'BOOT pointer). This makes user defined Forth words in Flash memory permanent |
extended (#require PERSIST ) |
MARKER |
Create a temporary baseline for code in RAM | extended (#require MARKER ) |
:NVM |
create "headless" code in the NVM dictionary, to be terminated by ;RAM or ;NVM
|
extended (#require :NVM ) |
The page Low-level Interrupts in Forth provides an example for :NVM
and ;NVM
. For creating headerless words with temporary ALIAS headers the phrase :NVM ... ;RAM ALIAS ...
can be used (see, for example, Manfred Mahlow's VOC I2C demo).
For embedded applications, small µCs, lacking mass storage and large RAM, have to store the Forth dictionary in ROM memory. Many low-end µCs use Harvard architecture, and can't run machine code from RAM (e.g. PIC16, MCS51, or AVR). Many implementations work around this by using DTC (Direct Threaded Code) or ITC (Indirect Threaded Code).
Originally STM8 eForth kept newly compiled Forth words in RAM. The core dictionary was in the Flash memory. As a STC Forth (Subroutine Threaded Code), STM8 eForth depends on von Neumann Architecture (unified data/code memory).
Basically using RAM for user code works in the following the way:
- a dictionary header gets modified after writing the name string of a word, or even after linked for linking (
COMPILE-ONLY
,IMMEDIATE
) - the branch target of "structure words" (e.g.
IF
) is written by the "closing structure word" (e.g.THEN
) to a memory location stored on the stack - eForth
VARIABLE
provides in-line RAM memory in the dictionary (no particular memory management is required) - the
PAD
RAM area for formatted character is defined relative tohead
(which assumes that the dictionary is in RAM) - the eForth interpreter
EVAL
usesPACK$
throughTOKEN
just like the compiler. Hereby words from the input stream are copied to thehead
of the dictionary for address look-up. This is code efficient but it assumes that the dictionary is in RAM
The disadvantage is, of course, that any user code disappears when switching of the power supply. Also the available RAM imposes serious limitations on the size of the user code.
The STM8 family applies a modified Harvard architecture: an internal bus bridge simulates a unified address space which makes it behave like a "von Neumann" system, with a minor speed penalty when running code from RAM. Running code from EEPROM isn't possible. Dr. C.H. Ting's original STM8 eForth uses RAM to store user defined words. New Forth words therefore don't "survive" the next a power-cycle.
The STM8 bus bridge makes it possible to write directly to Flash memory, and the IAP (In Application Programming) feature automatically "stalls" the CPU core during write operations (erase works transparently).
The improved STM8 eForth in this repository implements a mode for compiling Forth words to Flash memory using the STM8 IAP feature, the NVM
mode. It's possible to switch between the modes RAM
(temporary words) and NVM
(words stored permanently).
As eForth was designed as an easily portable minimal Forth, code for maintaining multiple dictionaries (along with multi-user features) was removed for Dr. C.H. Ting's Firmware Engineering Workshop. The solution here is to add new words in RAM to the head of the list, while words in Flash get inserted just in front of the first word in RAM.
Most of the functionality relies on the words VARIABLE
, ALLOT
, HERE
, CONTEXT
, and OVERT
, which observe the modes NVM
and RAM
, as well as the semantics of compiler and interpreter:
- STM8 eForth has two instances of
CP
(context pointer) for RAM and Flash: the activeCP
is swapped when switching modes - in mode
NVM
,HERE
points to either RAM or Flash depending on the semantics (compile or interpret) -
CREATE
temporarily switches semantics from interprete to compile, restricting the use ofVARIABLE
, andCREATE
, to interpreter mode (this shouldn't be a problem for small embedded systems) - the dictionary is maintained in a single list, by always linking the dictionary part in Flash back to the part in RAM (RAM words always appear first in a dictionary search)
- the compiler word
CALL,
makes sure that words in RAM can't be compiled intoNVM
words. - when switching from mode
NVM
to modeRAM
, the NVM context pointers are persisted to Flash memory
The word NVM
unlocks the Flash memory. The CP (head of the dictionary list) is set to NVM in compiler mode (after :
or ]
), and to RAM in interpreter mode after ;
or [
).
Note: when the Flash memory us unlocked, write operations to the core code are possible! While this may be used for patching the Forth core it can also render it unusable!
The word RAM
stores the pointers to the last word (LAST), and to the head of list to the initialization table in Flash memory. It then changes the head of list pointer to RAM.
RAM allocation with VARIABLE
is very intuitive but it relies on assumptions about a workflow:
- start a session by flashing the µC, or by typing
COLD
orRESET
- optionally write some test code in
RAM
mode, define and use temporaryRAM
mode helper words, or define startup words - optionally run
COLD
(orWIPE
) to readjust the RAM pool - run
NVM
to define words or variables in Flash memory - run
RAM
(orWIPE
) to make pointers to the newly defined words, and alsoVARIABLE
RAM allocation, persistent - return to 2. (test your code, preferably automated), to 3. (
COLD
orWIPE
) to renew the RAM pool, or to 4. (write more persistent code)
If you need to reserve more than 32 bytes of memory, it's advisable to cycle through the steps 3 to 6 before you write to any variable that uses RAM outside the 32 byte pool area.
Requirements, assumptions and technical details of the feature are described in issue #16.
The STM8S also offers EEPROM memory (from 128 bytes up to 2 KiB) which can be used for storing data. There is no EEPROM memory allocation (for parameters in EEPROM automatic allocation is problematic, since in most cases EEPROM addresses need to stay the same when extending a program).
One of the problems with this approach is that the number of write cycles to Flash memory is limited. However, the 100 write cycles mentioned in the data sheet of entry level devices are only relevant if you care about the guaranteed 20 years data retention at 55°C ambient temperature. At room temperature, writing to a Flash memory cell 1000 or 10000 times is likely to work (of course, a development device can always be replaced if it fails).
However, the main "stress factor" has been addressed: temporary storage for the interpreter is always in RAM, even in NVM
mode. Please note that there are some edge cases where this trade-off may cause problems (e.g. when using ,
in a colon definition).
Another thing to keep in mind is that a limited amount of RAM (32 bytes) is set aside forVARIABLE
definitions at start-up. It's possible to define all variables that are needed in a Forth context (module, library), and to issue a COLD
or WIPE
before defining more words in RAM.