Skip to content

STM8 eForth Compile to Flash

Thomas edited this page Nov 18, 2021 · 26 revisions

Memory Control and Autostart Forth Code

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.

Creating Auto-Start Forth Applications

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 sets here 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 of mystart
  • '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!

Basic Memory Control Words

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.

Forth VARIABLE in NVM mode

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 ALLOTing 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 ,.

Memory Control and advanced features

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).

Technical background: eForth and Memory

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 to head (which assumes that the dictionary is in RAM)
  • the eForth interpreter EVAL uses PACK$ through TOKEN just like the compiler. Hereby words from the input stream are copied to the head 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.

Memory in STM8 eForth

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 active CP 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 of VARIABLE, and CREATE, 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 into NVM words.
  • when switching from mode NVM to mode RAM, 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.

Workflow for VARIABLE RAM Allocation

RAM allocation with VARIABLE is very intuitive but it relies on assumptions about a workflow:

  1. start a session by flashing the µC, or by typing COLD or RESET
  2. optionally write some test code in RAM mode, define and use temporary RAM mode helper words, or define startup words
  3. optionally run COLD (or WIPE) to readjust the RAM pool
  4. run NVM to define words or variables in Flash memory
  5. run RAM (or WIPE) to make pointers to the newly defined words, and also VARIABLE RAM allocation, persistent
  6. return to 2. (test your code, preferably automated), to 3. (COLD or WIPE) 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).

Limitations of Compile to Flash

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.

Clone this wiki locally