eForth for cheap STM8S gadgets

Turn cheap modules from AliExpress into interactive development kits!

Similar projects worth following
STM8 eForth is a compact and structured Forth for interactive programming of cheap µCs. Code and binaries support many low-cost modules (e.g. thermostats like the W1209 or the C0135 MODBUS relay board), and a layered build system makes it easy to add others!

The project supports most STM8 devices, brings a library and serial interface options, uses test automation with uCsim and Docker on Travis-CI, and enjoys e4thcom support for interactive programming.

STM8 eForth is based on Dr. C.H. Ting's STM8EF, an STC Forth that produces machine code. The SDCC tool chain with STM8 assembler and linker is used for building the Forth core, and Forth can be used with C for scripting.

The list of Forth features includes Idle- and Background Tasks, interrupt handlers, I/O redirection, 7S LED or LCD drivers, analog and digital I/O, string evaluation, VOC dictionary extensions, and the unique ALIAS.

What is it?

STM8 eForth is among the smallest user friendly Forth systems for µCs: it brings the simplicity of a 1980s style Forth to today's low-cost controllers. The project provides code for STM8 variants, board support for selected low cost targets, and docs. STM8 eForth has a long feature list but it uses very little memory. Innovative solutions, steady support, and an active community makes using it for new projects easy!

The code and binaries on GitHub can be used in many ways:

  • as an alternative firmware for Chinese commodity boards (e.g. thermostats, DCDC converters, and relay boards)
  • build embedded systems with an interactive shell (scriptable and extensible)
  • for creating smart sensors with SPI, I2C, or RS232 and a scripting shell, e.g. for RaspberryPi, Arduino, or ESP8266
  • as an interactive environment for exploring the STM8 architecture
  • for learning Forth - it's easy and fun (find out why in the text below!)
  • ...

Why a Forth for Cheap Chinese boards?

Because it's fun: cheap mass-produced imperfection is a playground for creativity :-)

The W1209 has long been my favourite target: it's a rather complete embedded control board with a UI at a very good price. It's as cheap as it looks, and the challenge is in it's imperfections: the guy who designed the board clearly didn't have a connected application in mind, and I had a lot of fun making it do things it was never intended to do.

There are challenges, like the lack of communication ports. The "sensor connector" can either be used as a a 2-wire RS232 "bus" or for sensing. If you need sensing and communication at the same time the project also provides a full-duplex 3-wire RS232 interface through the key pins (while keeping the keys functional). A plug-in system makes it easy to test new ideas, like using the "update connector" as a home brew field bus interface!

Which target boards are supported?

Besides generic targets for STM8S001, STM8S103, STM8S105, STM8S207 and STM8L051, there is currently support for the following boards:

Some more boards can be supported on request, e.g.

@Elliot Williams worked on using the ESP-14 as an IoT deviced (the ESP-14 module combines an ESP8266 with an STM8S003F3P6).

Programmable power supplies based on the XH-M188 and a cheap DC/DC converter are both work in progress. There are also several types of STM8S003F3 based voltmeters that are known to work.

Read more about possible future targets below.

Why Forth?

Again, because it's fun!

Forth starts out as a stack machine with a tiny instruction set and minimal hardware requirements. It fits in a few KiB, and the target, even a lowly µC, can even be used as the development system. The Forth stack machine is a minimalistic VM on a standard CPU, but there are also hardware implementations (e.g. for FPGAs, or a 144 core Forth processor). The VM is ideal for fast context switching and Forth easily meets hard-real-time requirements. It's no surprise that Forth was used in many NASA projects.

Consider this:

  • compared to other programming environments Forth is easy to understand fully
  • like Lisp, Forth has a REPL (Read-Evaluate-Print-Loop) which enables software testing in a way impossible with "Edit-Compile-Run-Debug" (e.g. Arduino)
  • it's easy to build Domain Specific Languages (one can literally program the compiler!)
  • the stack-oriented "factoring" method results in implicit data flow and modular code
  • Interpreter-compiler, basic OS functions fit in just 4K code :-)

A Forth programmer is in control of all levels of problem abstraction, a unique advantage in a...

Read more »

Original version of STM8EFalong with docs as received from Dr. C.H. Ting on 21/Nov/2016. The docs are worth reading, the eForth binary will run on the STM8S Discovery.

Zip Archive - 21.97 kB - 11/21/2016 at 20:13


  • 1 × ST-Link V2 ICP adapter (e.g. $2.00 from AliExpress) The ST-Link on an STM8S Discovery Board can be used, too
  • 1 × serial interface adapter /w 3.3V signal level (e.g. $0.60) e.g a USB interfacea with a CH340, or better a PL2303, chip
  • 1 × STM8S target device as listed in the GitHub Wiki (e.g. a $0.70 "STM8S103F3P6 minimal system board") e.g. "STM8S103F3P6 STM8S development board" from your favorite China source
  • 1 × Some headers, patchwires, breadboard etc (about $2.00) and other things you fancy, e.g. a RC-servo, SPI display, LEDs ...

  • STM8L101F3: experimental support added to STM8 eForth

    Thomasa day ago 0 comments

    I merged support for the STM8L101F3 yesterday. For very low power applications the chip is definitely worth looking at (the datasheet claims that it needs around 6µA at 38kHz clock when running from RAM).

    New are:

    The BG task works now: timer register addresses are different from other STM8L devices. That's why it will be necessary to use "\res MCU: STM8L101" for many use cases (not for getting GPIO symbols, though).

    I also managed to download the code with stm8flash (!). In case someone wants to try it, here is how to wire the chip:

    What's still missing is a working STM8L001J3M3 based system. The one chip on my breadboard works when I change bytes in the Flash ROM in an OpenOCD terminal session (commands mdb and mwb) but writing an ELF file with stm8-gdb stops after the first four bytes. I'm confident that there is a spare chip in the basement...

    It definitively looks like the STM8L101 jinx (or self-inflicted failure) is halfway broken.

  • STM8L101F3L3: first STM8 eForth prompt

    Thomas3 days ago 0 comments

    Thanks to OpenOCD and GDB: STM8L101 now works!

    Besides resolving the problem of actually flashing this chip (I never managed to do that with STM8FLASH), and finding the right configuration for OpenOCD (see the previous log entry), setting the SP to a valid address for this chip (1.5K RAM!) resulted in a prompt (GDB again proved helpfull).

    For now I'm using the following

    ;       STM8L101F3 device and memory layout configuration
            TARGET = STM8L051F3     ; let's use this for now - to be fixed later
            RAMEND =        0x05FF  ; "RAMEND" system (return) stack, growing down
            EEPROMBASE =    0x1000  ; "EESTART" EEPROM start address (not really)
            EEPROMEND =     0x10FF  ; "EEEND" 256 bytes EEPROM (see datasheet...)
            FLASHEND =      0x9FFF  ; "FLASHEND" 8K devices
            FORTHRAM =      0x0030  ; Start of RAM controlled by Forth
            UPPLOC  =       0x0060  ; UPP (user/system area) location for 1K RAM
            CTOPLOC =       0x0080  ; CTOP (user dictionary) location for 1K RAM
            SPPLOC  =       0x0550  ; SPP (data stack top), TIB start
            RPPLOC  =       RAMEND  ; RPP (return stack top)

    Here is a initialization code snippet of that enables TX push-pull:

    ;       BOARDINIT  ( -- )
    ;       Init board GPIO (except COM ports)
            ; Board I/O initialization: enable USART TX->PC3, RX->PC2
            MOV     CLK_PCKENR1,#0x21
            BSET    PC_DDR,#3
            BSET    PC_CR1,#3

    There are some points that need to be worked on (e.g. BG, EEPROM, RAM layout) and many things are untested. All the important bits works, e.g. writing to NVM!

    This means that I'll have to review many docs that state that it's unlikely that STM8 eForth will ever support this chip.

  • STM8L101: some new findings

    Thomas3 days ago 0 comments

    Using the methods described in the previous log entry, I tried programming the STM8L101, and using STM8-GDB and OpenOCD already proves to be *very* useful!

    Connecting with OpenOCD works. I first used openocd -f interface/st-link.cfg -f target/stm8l152.cfg -c "init" -c "reset halt"). but it turned out that the that target configuration uses 128 bytes block size - Low Density devices have 64 bytes block size - and programming failed after the 5th block (setting breakpoints with GDB and inspecting memory with x/i helped a lot).

    I'm now using the following OpenOCD configuration (stored as target/stm8l101.cfg):

    #config script for STM8L101
    set FLASHEND 0x9FFF
    set BLOCKSIZE 0x40
    proc stm8_reset_rop {} {
       mwb 0x4800 0xaa
       mwb 0x4800 0xaa
       reset halt
    source [find target/stm8l.cfg]

    With this file programming an STM8L101F3P6 chip actually works!

    I made a trimmed-down STM8 eForth board configuration without background task but running it results in a reset

    (gdb) run
    Starting program: /home/thomas/source/stm8s/stm8ef/out/STM8L101F3/STM8L101F3.elf 
    Remote debugging using localhost:3333
    0x00008bd2 in ?? ()
    Loading section SSEG, size 0x1 lma 0x1
    Loading section HOME, size 0x73 lma 0x8000
    Loading section GSINIT, size 0x1a lma 0x8073
    Loading section GSFINAL, size 0x3 lma 0x808d
    Loading section CODE, size 0x1152 lma 0x8090
    Start address 0x8073, load size 4579
    Transfer rate: 7 KB/sec, 915 bytes/write.
    Program stopped.
    0x00008000 in ?? ()

     I tentatively set a breakpoints and single stepped execution until I learned that returning from a LITERAL (implemented with TRAP) doesn't work. That's very good (and very fast!) progress :-)

  • For STM8 programming OpenOCD is cool stuff

    Thomas3 days ago 0 comments

    The combination OpenOCD / GDB for the STM8 appears to be a bit inaccessible. In fact it's a really good "text adventure game", and I've had fun finding my way in a "software landscape" with a long history and a specific patois.

    It turns out that one can set breakpoints or watchpoints and inspect memory with OpenOCD alone - GDB required for that, and instead one can use commands from the built-in gdbserver through a telnet session. The combination of STM8-GDB and OpenOCD can replace stm8flash for most (if not all) use case. I guess that writing STM8 flash, configuration, and option bytes with a script, instead of using GDB, is possible too.

    New applications, like writing a tethered Forth that runs on a PC and interacts with the target device through OpenOCD is within reach, and it might be interesting to explore work by @Lars Brinkhoff and in that direction (@RigTig once started there).

    What's more, both OpenOCD and GDB are scriptable. OpenOCD in the TCL dialect Jim (Rule #1: Everything is a string, Rule #2: If you think otherwise, See Rule #1).

    I guess it's about time to get one of those unloved STM8L101F3P6 and STM8L001J3M3 devices from the basement. Maybe I now have the tools to program some Forth into them :-)

  • STM8L at the center of the STM8 eForth 2.2.26 development cycle

    Thomas08/22/2020 at 06:29 0 comments

    This release will be about STM8L devices (i.e. the likes of STM8L051F3, STM8L151R6 or STM8L162R8). While STM8S peripherals are simple and the pin and clock configuration is trivial, the STML designers clearly had both many more, and much larger, spec sheets.

    Many "more specs" one can clearly see in the number of configuration dependencies one has to infer when configuring one of the many features of the peripherals. @Andrew Clapp started with the C code abstractions for STM8L051 provided by ST: it turned out that it's easy to get results but the code uses the peripherals in a quite wasteful way (e.g. repeated initialization). The code, however, shows that ST's library designers also had a hard time creating something that's both flexible and lean. 

    For STM8 eForth this means that some things that were implicit or hard-coded need to be reviewed and restructured, and tested. A good example is Issue #340: the background task priority now works across devices and families (although STM8L is still limited to using TIM2 for the BG interrupt).

    Another target is symbol files: @Eelco provided a first draft of an STM8L151.efr file. I then made a synthesis of datasheets and improved the generic STM8L.efr so that it covers most STM8L devices (that is all except the STM8L101 family).

    On GitHub you'll find a new pre-release STM8 eForth 2.2.26.pre2 that contains the changes mentioned above and some more goodies.

  • Accessing STM8 Far Memory

    Thomas07/28/2020 at 05:21 0 comments

    STM8 architects some 16bit on top of the ST7 6800-like architecture (at least some fast arithmetic instructions), and the address space for programs was extended by 24bit "Far adressing".

    Unfortunately, access to the "Far" 24bit memory space (in practice it's just 19bit [see edit]) is only possible for the following instructions:

    • 8 bit load/store instructions, limited to "absolute" and "indirect addressing" memory access
    • Jump, call and interrupt vectors for code access

    That's OK since RAM and EEPROM are in an 8bit (zero page) or 16bit memory space, and it doesn't matter where they're executed. Forth, however, cares a lot about ROM addresses: as XTs (Execution Tokens) are data, too, there is no easy way to put "threaded Forth code" into 32K to 96K Flash memory in the extended address space of "High Density" devices like STM8S207 or STM8L162.

    In Forth, the address space can be used for (carefully crafted) interrupt service routines (there is no need to keep these in the dictionary), and for library code written Forth, assembler or C that has an entry point in the 16bit address space.

    Using the STM8 eForth compiler for creating code that runs in the Far address space must take care not to use anything that's based on BRANCH, ?BRANCH or 16 bit addresses on the data stack (that is IF, ELSE, THEN, BEGIN, UNTIL, WHILE REPEAT, FOR, AFT, NEXT, DO, LOOP...).

    That doesn't mean that it's impossible, though: by compiling code that only uses words from 16 bit ROM to RAM and that's limited to relative addressing for control structures, and then copying that code to Far memory, it should work. Anchoring such structures in the 16 bit address space would, of course, still be necessary. 

    At least, from release 2.2.26.pre1 on,  STM8 eForth provides the words FC@ and FC! for accessing memory in the far address space. Words for moving memory between Far memory and the 16bit address space (using block programming and transfer) are planned. As Flash block write code must be executed from RAM, the whole thing is a bit more complicated. As soon as that's available, some of the advanced use cases can be addressed.

    EDIT: I used to believe that the address space is 18bit but there is evidence that there were STM8AF51B High Density devices on offer with 256K Flash memory (and 12K RAM, two I2C, two SPI, 3 x UART, 4x16bit timer... ). References to it have almost disappeared since.

  • Release STM8 eForth 2.2.25 - A bit too fast! A bug-fix and an extra.

    Thomas07/10/2020 at 15:06 5 comments

    One can never test enough - literally.

    The STM8 eForth test automation in Travis-CI tests the background task, but it doesn't test its cadence. Maybe that can be done, and maybe it would have been worth doing that. I could have done a manual test - just flashing a W1209 would have shown a flickering LED display - but I didn't. You live and learn...

    @Andrew Clapp first discovered that something was wrong. I then identified the cause: a classic "don't use LDW for timers"-error. I first thought that it only hits STM8L devices but also STM8S, this means the whole release was affected. A quick bug-fix release therefore is the best option.

    @Eelco the praise came too soon - please check if the 2.2.25 release still solves your problem!

    On the plus side: I added INTRX for buffered console RX to the library.  Compilation to RAM at 115200 baud really feels much faster than at 9600 baud!

  • Release STM8 eForth 2.2.24 - Has it really been a year?

    Thomas07/04/2020 at 06:15 8 comments

    "Has it really been a year?" isn't a question - it's the name of the latest release. It's been more than a year since the last release, and there are certainly many reasons for that.

    There were so many changes in the build infrastructure ("modular build"), supported by modularization and some generalizations, that there have been no less than four pre-releases.

    The motivation behind it was a number of STM8 eForth applications that each required some kind of "board support" in drivers, vocabulary selections, peripherals usage and whatnot while the STM8 eForth core itself hardly changed at all.

    On the other hand, configuration options for the following things have improved by a lot:

    • Binaries through "modular build": it's now trivially easy to create a downstream GitHub repository with build automation
    • Peripherals abstraction for timers and serial interfaces used in the core: it's now possible to use any 16 bit timer for ticking the background task, both UARTs of STM8S High Density µCs can be used
    • STM8L devices received more love (e.g. simulated serial interface, peripherals abstraction)
    • Most of the code for anything "non-core" (e.g. board I/O) is now in include files and variants can be put in a board folder where it has precedence

    And feature creep, of course. I'll have to spend some time to document all new features ;-)

    Now there is finally some stability as the following POCs showed the desired results:

    It's safe to say that STM8 eForth is now quite mature and that it will support a range of applications with little effort. Future developments are planned for exploring options in less frugal systems.

  • Improved support for STM8L devices

    Thomas06/26/2020 at 14:40 0 comments

    Were it not for @Eelco and @Ivan Alf STM8L support in STM8 eForth would still be very basic (call me lazy but I don't like comparing th e STM8S and the STM8L reference manual - in my opinion ST should have written it in a way that makes it more visible where things changed and where not.

    Lately. @Andrew Clapp needed a simulated second serial interface for the STM8L051F3, which was only available for STM8S devices. Supporting the TIM4 based UART simulation to the STM8L was quite simple (STM8L TIM4 is like a STM8S TIM6 with minor improvements) but getting the GPIO configuration right required many changes in the way interrupts are defined in the STM8 eForth core code. It was also a good opportunity to test and improve the modular build feature.

    A repository with the working STM8 eForth code for STM8L051F3 is here. In the next STM8 eForth release it will be easier to apply configurations for STM8L.

  • STM8 eForth Wiki: Discussion of `FOR .. NEXT` updated

    Thomas05/29/2020 at 08:31 1 comment

    The STM8 eForth wiki has a page on the usage of eForth `FOR .. NEXT` and its variants. It's a good example why people think that Forth is strange, and that eForth is even more so but it deserves a closer look.

    I updated the page to better explain different loop constructs. The short read provides some insight into building simple code generators.

View all 154 project logs

  • 1
    Step 1

    Get some cheap hardware (e.g. a STM8S103F3P6 breakout board for $0.65 and a ST-Link V2 dongle for $2). download the binary release, flash it, and have fun!

    If you like it, and you want to hack board support code for your favorite STM8China gadget, you need:

  • 2
    Step 2

    a Linux SDCC tool chain installation (installation instructions for SDCC & stm8flash are in the Wiki)

  • 3
    Step 3

    Clone the project on GitHub

View all 4 instructions

Enjoy this project?



RigTig wrote 02/23/2017 at 05:44 point

Thanks Thomas for a great environment in which to have lots of fun. I needed VARIABLE to be defined in NVM, but to keep its data in RAM. Some variables need to change for every data line processed (megabytes of GCODE), so using NVM is just not going to cut it for real use. Besides NVM access is slow. So, here is my replacement definition for my project:

: variable create here , 2 $6e +! does> @ ;

Now, this works only in NVM mode, because it makes no sense to use it in RAM mode anyway. Besides the DOES> part would be wiped every COLD or restart. The magic address of $006E is the address of next available RAM when in NVM mode.

If you want to test after a restart or COLD, you need to adjust the address of next available RAM to be after the last used address by a variable. In my case, the last used address was $009e. Note that in RAM  mode, the address of next available RAM is at $006A. So, I typed

$a0 $6a !

and then initialised all variables and they just work. Now this hack is not for every project (obviously), but does show what can be done when a need arises. Enjoy!

  Are you sure? yes | no

Thomas wrote 02/23/2017 at 21:35 point

Hi RigTig! That's indeed a nice hack, and it is much along the lines of what I had planned.

I'd like to propose the following solution: 

1. some memory below the user dictionary in RAM shall be set aside by adjusting the reset value of USRCP
2. the next variable address for NVM routines shall be stored in the (new) variable USRVAR, which shall be initialized from USRCP
3. when switching from NVM to RAM the reset value of USRCP shall be set to the value of USRVAR

I'd like to check if it's possible to make VARIABLE work transparently in NVM and RAM mode. Most likely writing a different word is easier.

  Are you sure? yes | no

RigTig wrote 02/24/2017 at 04:09 point

Thanks for compliment. I haven't really tried to make VARIABLE work in both RAM and NVM, but I am sure it'd work. The key is just understanding that there is one level of indirection (address of value instead of value), so RAM variables use an extra 2 bytes over the non-indirect version. Nice to avoid wasting ram, but not really a show-stopper. Coding in assembler should be far more memory efficient than the Forth version, but it's the joy of Forth to be able to do these kinds of things at all (and optimise later when you find that it is really a good idea!).

A hard reset or even COLD needs to preserve the ram space needed by variables, but I prefer not to lock in a pre-determined limit on the number of variables. I also hate wasting valuable resources by committing them for just-in-case scenarios. 

So let's consider the use cases. Is there a need to support programming to NVM, then RAM, and back to NVM? If we say that all NVM variables need to be defined before RAM gets any code, is that reasonable? At least all the ram needed is in one block in this case. Probably a bit hard to communicate to programmers, and practically impossible to enforce.

Even if there is some code compiled into ram before or after NVM variables are created, the only thing needed is to set the ram space used for parsing commands to be above the last used ram for any variable. This happens anyway until COLD or hard reset. Maybe all that is needed is a persistent vector stored in NVM to be used instead of the $0080 for start of ram space for code and variables. Now the incentive is for the programmer not to waste space, so it becomes 'obvious' that defining all NVM variables before using ram for anything else is just better management of the limited ram. VARIABLE needs to update the persistent vector each time, based on current ram pointer. Variables defined in ram waste space after a COLD, but that might just be a price to pay (and is quite ok during interactive development, methinks). Mmm... and RESET needs to reset the vector to first available ram for code and variables back to its compilation default ($0080).

I am sure to have missed something in this ramble, but hey, that's what hacking is about isn't it? If I knew what I was doing, then it is not real hacking! And what other language allows you to play around with how the language itself works, so Go Forth.

  Are you sure? yes | no

Thomas wrote 02/24/2017 at 06:41 point

RigTig, we're on the same page :-) 

If you read me previous comment carefully you'll find that the "machine" needed for implementing is mainly coded in the difference between "the reset value of USRCP" and "USRCP" (there is one error though: point 2. should be "in the new variable USRVAR which shall be initialized from the reset value of USRCP"). 

One could argue that this means waste of RAM, but actually it's just a buffer for certain use cases. You already mentioned some uses cases, and how much they would "surprise Joe Programmer".

My model for the programming workflow is this:

1. start a session with COLD, reset, or flashing the µC

2. write some test code in RAM (i.e. do the things you'd normally do with the original STM8EF)

3. run COLD and set the stage (e.g. define helper words like here:
4. run NVM, define words, variables etc as you like

5. run RAM, make pointers to the newly defined words, and also to USRVAR persistent

6. return to 4. (write more persistent code) or to 2. (test your code, preferably automated)

Of course, the casual user may miss the finer parts of the "Stage/NVM/RAM/Test/COLD" cycle, but they will notice quickly that words defined in RAM can't be compiled-in (only interpreted) in NVM, and that code compiled in NVM is lost if they forget to run RAM.

As you pointed out, a certain coding style, like defining variables first, isn't difficult to get used to. Setting aside a small buffer (e.g. 32 bytes) as a variable space wouldn't be a big deal, and it would enable the "setting the stage" use case without the risk of immediately overwriting words like IVEC. It's of course also possible to cycle through the steps 2..6 more often (also as a part of the source code) , but a buffer would add some flexibility. In the extreme case (use many variables, use a huge stack), writing test code in RAM would suffer. But hey, when testing words one tests the units, not the whole program where the stack reaches its maximum size.

  Are you sure? yes | no

Thomas wrote 02/26/2017 at 09:35 point


Please have a look at this:

The new code has the features discussed above. The behavior of VARIABLE and ALLOT is transparent in NVM mode, and in most cases there is no need to manage RAM allocation. Only if one allocates more than 32 byte RAM in a session it's necessary to cycle through COLD before using variables newly defined in NVM mode.

Edit: preliminary documentation of the new feature is here:

  Are you sure? yes | no

RigTig wrote 02/27/2017 at 01:08 point

Thomas, I have to say that your approach is just brilliant. I love the idea of being able to just get more variable space if needed, and not wasting any ram either. 

P.S. We might be both on the same page, but I'm only partway down. I am still getting my head around the STM8EF code. I keep going back and changing the options for a new flash image and it is installed in a second or two. What fun to play with!

  Are you sure? yes | no

Thomas wrote 02/27/2017 at 07:17 point

@RigTig: thanks for your support! I just pushed a "size reduced" revision to the variable branch. Since I had to do some shuffling, some "review" and "testing" by "an independent person" would be great (in a hobby project that's what's known as "playing with the code" :-)

Currently some RAM gets wasted, but that could be bettered by giving the programmer control over the headroom for RAM allocation (which would be easy).

By the way, I ordered two of the radio modules you've been working on.

  Are you sure? yes | no

Elliot Williams wrote 01/23/2017 at 08:55 point

Hiya Thomas,

Got an ESP-14-powered device up and running and installed in our basement.  Long story, must write up.

Have you played around with power saving modes on the STM8?  I'm trying to get the part into the AWU / active-halt mode.  

For one, I need the assembler's HALT command, which I've been doing in the worst brute-force means possible: HERE $8e81 , EXECUTE.  (That's HALT and RET in machine code.)  

It halts, at least.  :)  

Coming back out of halt is messy -- it looks like the clocks aren't returned to their original states and so on. I'm probably going to need to implement some start-up code.  Heck, for my purposes, hooking into COLD for a complete reset will work too... That's what I'll try next.

Just wondering if you've worked on any of the low-power modes.  Either WFI (wait-for-interrupt) or the active-halt/AWU look tasty.

  Are you sure? yes | no

Thomas wrote 01/23/2017 at 19:14 point

That's great :-)

The power saving modes (like the watchdog) still are on my "important things that I plan to do" list. You know, that's the list on the sheet after "new and exciting things I want to play with", which in turn comes after "bugs I must fix now".

Let's put it on the "important new features for pilot applications" list :-) 

What we need is:

* a word HALT that contains the HALT instructionknow

* a word SLEEP, that stops unnecessary interrupts (user defined, and application specific). This word should run HALT. When the execution continues right after HALT, SLEEP shall re-enable "waking" interrupts

* if required a word to restore clock settings (RM0016 mentions something in 10.2.2 and in 9.9.4 "Clock master switch register (CLK_SWR)", but right now I don't undertsand why the clock changes)

Do you plan to trigger a wake-up through console events? The simulated COM port should support this use case!

  Are you sure? yes | no

Thomas wrote 01/23/2017 at 19:45 point

I added the HALT word, and it works better than expected. Here is a demo with a blinky:

    : g tim 40 and 0= out! ; ok
    ' g bg ! ok
The when I press enter after HALT the LED stops flashing. The "ok" after HALT appears after I press enter a second time.

  Are you sure? yes | no

Elliot Williams wrote 01/24/2017 at 11:52 point


re: clocks: I read something somewhere sometime about them needing a reset.  I can't find that anymore.  I may be crazy.  

I saw some other STM8 code ( that runs the AWU without re-clocking, strongly suggesting that I'm crazy.

That code, though, makes it look like (if interrupts are enabled) the AWU reset lands in the AWU ISR, which is uninitialized ($0000) in the vector table at $800C.

I just ran your BG example above, and it halts, but never returns until hit with a hard reset. I wonder if your code is working b/c it NOPs off to the next ISR and you got lucky.  Or does it actually try to execute whatever's at $0000?

So: how do we set up ISRs in eForth?  (Or, how do you write bytes directly to flash?)

  Are you sure? yes | no

Thomas wrote 01/24/2017 at 19:23 point

TL;DR: the quick-fix: an AWU "driver" that does it all but I would prefer a Forth solution and this requires some design decisions.

Long version:

Due to limitations in the SDCC tool chain any interrupt must be declared in main.c. Writing ISR vectors to Flash might work, but it requires a good approach for registering (and unregistering) interrupts to be viable (I'm thinking of RESET). Also Forth VM context switching would have to be done before executing any Forth code.

Another approach would be a "catchall" interrupt handler for several interrupts that then redirects to Forth code. This has the advantage that the context switch can be handled in a uniform way, but the dispatching won't be very efficient (or again a lookup).

This brings us to the next problem: some interrupt sources require resetting some bit in some peripherals control register. Leaving that to user code is very error prone, and a "catchall" interrupt handler would have to do it for all possible sources or leave it to user code.

What do you feel about of a middle way?
* Interrupt handler declared in main.c
* basic handler code in assembler or c to do a context switch, and to clear the trigger source
* handler code in Forth registered through something like BG

A last point: how many concurrent "Forth code interrupts" can we allow?
* Level0 we have the console
* on Level1 is the BG interrupt
* on Level2 is TIM4 (for COM simulation)

I guess that some stuff like TIM4 shouldn't have to compete with other code (the current code is efficient as it gets). Most likely it's possible to drop the interrupt level in BG code to Level0, and use Level1 for Forth handlers without character-I/O. The latency would still be in the lower µs range.

  Are you sure? yes | no

Elliot Williams wrote 01/25/2017 at 12:43 point

How does the 'BOOT mechanism work?  If you could do placeholders for the various ISRs like that, the user could write their handler function and store its address in the right place?  That seems very Forthy to me.  <code>: awu-isr stuff ; ' awu-isr ISR_AWU ! </code> or something.  One of these functions / memory locations per IRQ and you'd be done?

On resetting the flags as you leave the ISR: I think that should be user code rather than bloating up the system with it.  Yeah, it's going to hang the system if you do it wrong.  If I could count the number of times I've pressed the reset button...

On context switching in ISRs:  I'm not sure I understand the full details.  Unlike C, there's not necessarily any context to switch?  If the ISR maintains stack balance then there's no need for any context?  Leave whatever's on the stack, and it'll still be there when the interrupt is done?

For me, personally, I'd just be stoked to have a pointer to an address that I could set to execute when the AWU IRQ fires.  The rest, I can handle in code, I hope. :)  (Assuming that the return from interrupt works right.)

  Are you sure? yes | no

Thomas wrote 01/25/2017 at 20:20 point

'BOOT is simple: it returns the address of the "Parameter" field (like DOVAR). To safe code I used it to get the address of the whole following table of initialization values for USR variables. After switching to "NVM" it's possible to simply overwrite all these values. There is a 2nd copy to restore these values, e.g. to "forget" user vocabulary in Flash memory with RESET.

Yes, the 'BOOT method can be used in for interrupts, too, but that would require one more level of indirection.

About context switching:

my first approach was to re-use the Data Stack, but I quickly learned that X isn't always a valid Data Stack Pointer: it does that at the start and the end of a word, but not always in between ("always" is a very important attribute when writing interrupt code). Before implementing the background task, I tried  to make sure that X always represents a valid stack pointer in all primitive words. However, I failed to get it working until I started using a 2nd Data Stack for the background task (which I didn't like since it appears wasteful). Later on, I applied coding techniques that use X for reducing code size. Of course, it's possible to re-factor the code. It would be interesting to compare other multi-tasking Forth implementations. 

I went in a different direction: In several refactoring rounds I removed the following variables entirely: TEMP, XTEMP, PROD1, PROD2, PROD3, CARRY, and I also made the I/O context leaner.

Now, for code without character I/O only YTEMP must be saved. Otherwise also BASE, PAD, and HLD must be taken into consideration. And, of course, we need a stack. One approach would be to have a floating "stack pad" to work around the "X!=TOS" problem.

I guess it will take some time to implement a full featured solution for Forth interrupt handlers.

A minimal solution might look like this:
* a word IVEC! to set an interrupt vector
* a word SAVEC to save the context
* a word RESTC to restore the context, ends with IRET

The application could then define a word in the following way:  

: handler SAVEC ( some stuff ) RESTC ;

 ' handler 1 IVEC! \ set the AWU interrupt handler

Now that I'm looking at it, this doesn't look too bad.

Edit: I made some corrections, added some details, and added one more option for a solution

  Are you sure? yes | no

Thomas wrote 01/25/2017 at 22:23 point

I added the solution above for testing to the develop branch. Due to the mentioned limitations it's currently necessary to initialize the interrupt to priority low (0:1) (it shares the data stack with the ticker).

I also changed TIM4 to prio "highest", which might allow to implement all user defined interrupts with priority "high" later on. This would then require 3 data stacks with the sizes normal (console), medium (background task) and small (interrupt handler).

  Are you sure? yes | no

Thomas wrote 01/26/2017 at 20:48 point

@Elliot Williams:

Here is a starting point for Forth code user interrupts and AWU usage:


  : awuint savec awu_csr1 c@ drop restc ;

  ' awuint 1 ivec!

  : initawu 38 awu_apr c! 1 awu_tbr c! 16 awu_csr1 c! ;


When I run HALT with this code, it returns immediately. Since I didn't find the time to make sense of the AWU configuration, I simply took the AWU timing values from the page you mentioned before.

Please not that this currently only works when I run HALT from the console (I still need a solution for the Data Stack problem). Running HALT from the background task would change the contents of the first element on the stack (which would work if the stack were empty).

A quick fix here is to assume that X represents TOS when HALT is executed (which is the case), and skip initializing the stack. Please note that this only works for HALT, and not in the general case.

  Are you sure? yes | no

Thomas wrote 01/23/2017 at 21:51 point

Changes are in the develpp branch on GitHub. The 2.2.6.snapshot release contains new binaries :-)

  Are you sure? yes | no

Youlian Troyanov wrote 01/26/2017 at 04:51 point

please write your long story about esp-14 :)

  Are you sure? yes | no

Thomas wrote 01/27/2017 at 22:19 point

Elliot, in order to get a simple and practical solution, I now propose the following:

1) In RAM code IVEC! (its only used once for setting an interrupt handler

: IVEC! ( a n - -  ) 2* 2* $800A + ! ;

2) Implement HALT as a user word:

: HALT  ( -- ) [ $8E C, ] ;

3) Implement your interrupt handler using SAVEC and RESTC (make sure not to use more than 8 cells on stack)

This will work for any interrupt. Please make sure to change the interrupt down from highest to high.

  Are you sure? yes | no

jaromir.sukuba wrote 01/23/2017 at 05:17 point

Another tip for *possible* STM8 target

I didn't buy this one, haven't seen the schematics, but to me it totally smells like it could have STM8 under the display. Googling for XK-001T-1 didn't bring much info, though.

  Are you sure? yes | no

Thomas wrote 01/23/2017 at 06:18 point

Yes, that's possible. In most cases one won't find any schematics, and also the XH-, XK, M- or B monikers aren't always used the same. There is a small list of modules that are very likely STM8S based in the Details Section of this project (in the section "How can I spot suitable boards?"). If there is any interest, I can publish a list with advertised properties and the "street price".
Edit: here is a link with a picture showing the PCB legend:
Based on the outline of the µC I would expect it's not STM8 but a STC15 based, a µC which I've seen several times on "timer" boards (MCS51-like

  Are you sure? yes | no

Elliot Williams wrote 12/13/2016 at 23:13 point

Got my ESP-14 up and running last week, and then got distracted.  :)

Short story: it's just a STM8 chip and an ESP8266, like it says on the package.  The TX/RX lines are internally connected, so I was running your Forth on the STM8 with the ESP8266 powered down, and running all manner of software on the ESP with the STM8 powered down.

Been thinking about how to use both at once. 

a) Jeelink is a nice transparent serial port over ESP8266, which would provide remote wireless development of the Forth system on the STM8.  The idea of telnetting over WiFi into an STM8 is funny enough that I'm definitely going to do this.

b) Since the serial port is the only way in to the ESP8266, and the STM8 has only that one hardware serial port, I suppose that bit-banged serial or I2C/SPI could be used to talk to the console. I don't know how hard/easy this is. But then you'd have an STM8 that could issue AT WiFi commands, for instance, or run routines in NodeMCU, which might be very cool.

c) The other option is to code up the ESP and STM8 to take turns based on control characters: 0xFE toggles the ESP on/off the line, and 0xFF toggles the STM8, for instance.   This requires modifying _both_ firmwares, but would allow for the console, ESP, and STM8 to share the UART lines and talk to each other.

Just brainstorming so far. No real hacking yet. 

The breakout board I made for the module just fit it onto a breadboard, because I didn't really know what to expect from the module. It will probably want a transistor so that the STM8 can turn off the ESP8266 for power-saving when necessary, and will certainly want at least a jumper for flashing the ESP.  

Thanks for the case insensitive addition, and for do loops! This is a fun system to play around with.

  Are you sure? yes | no

Thomas wrote 12/14/2016 at 19:31 point

Options a) and b) look good to me, especially in combination. How about connecting a PNP transistor for the ESP8266 power supply to PD1/SWIM? Normally one would access the STM8 serial port through ESP-Link, and the ICP interface could be used for direct access to the ESP8266 serial interface by simply pulling down both NRST and PD1/SWIM. Direct serial access to the STM8 could be acchieved by telling it to power the ESP8266 down (this might even work using PD1/SWIM once more, e.g. by using an RC element which can be detected testing its timing).

Option c) would also be possible, but at least one of the devices would have to be able to swap RxD and TxD, and the other devices would need a "tristate" mode on TxD. The Bus approach I took for the W1209 might also work for more than two devices.

A fourth option could be to have a Forth word that issues the initialization AT commands on the STM8, and execute it with 'BOOT.

I hope to find the time for some hardware hacking in the holiday season :-)

  Are you sure? yes | no

Elliot Williams wrote 12/16/2016 at 21:22 point

"esp-link" not jeelink.  Tried it and had a telnet / web-console controllable STM8 running your Forth.  Took like 10 minutes.

Then I spent 3 hours trying to implement something like c) in NodeMCU.

First, I thought I'd set up two TCP connections: one for the ESP to be executed locally, and one to pass through to the STM8.  Didn't work b/c NodeMCU can only do one TCP connection, it seems.

Then I thought I'd use MQTT as the transport mechanism.  But there's some glitch there with MQTT and the UART port not working right.  I'll hack more at it before I give up, but it might be time to move on to MicroPython or ESP Basic for the interactive ESP part.

Anyway, try out the esp-link for the ESP when you get around to it.  It's kinda fun.  It _does_ however leave me wanting a more capable microchip on the remote end.  For another couple bucks, I could get a lot more flash, peripherals, and etc to tether to the ESP.

All of this playing around has helped me refine what's needed in a breakout board for this thing, though.  :)

  Are you sure? yes | no

Thomas wrote 12/16/2016 at 22:35 point

Again great news, and I'm going to test esp-link too. Multiplexing communication through MQTT topics was the first thing that came to my mind. About a year ago I tried working with MQTT and NodeMCU, but I was disappointed with the stability of the platform (though I really liked working with Lua).
I guess that the case for ESP-14 is rather thin: as I mentioned before, it looks more like proof that the ESP8266 wasn't able to meet customer requirements than like the solution the world's been waiting for. But who cares as long as it's fun hacking.
In my opinion, a decent Forth environment on the ESP8266 would be rather attractive: C.H. Ting hacked something recently, but it was just the kernel, not a complete framework with persistent vocabulary (and maybe even with source stored in the Flash memory, and maybe even a JavaScript based IDE served from an embedded web server on the chip).

  Are you sure? yes | no

Thomas wrote 11/28/2016 at 21:33 point

Hi Elliot, it's great to hear that someone got it running, and that the docs were good for a smooth start. Anyhow, congrats for the "STM8EF Blinky"! Did you try to do that BG style, too? 

I had a look at the CAPS issue (yes, I've been thinking about that for a while ;-) ). There are some potential clashes (e.g. PARSE/parse, NEXT,next, ABORT"/abort") but the lowercase words are the hidden "implementation part", and I don't see that their name is set in stone. I decided to name them after their assembly labels (pars, donxt, and aborq). 

New code with lowercase support is on GitHub (just set CASEINSENSITIVE = 1 in If you'd like to give it a try without building, please let me know (I can drop a binary into the files section here). If there are no issues I'll make it the default.

The ESP-14 will be one of my next targets. However, I didn't find the time to make a breakout PCBs with power supply for this module. Controlling the ESP8266 supply through the STM8S003F3 would be cool. If someone with good access to PCB prototyping could do that job I'd be more than happy to contribute some ideas about the schematics.

  Are you sure? yes | no

Elliot Williams wrote 11/29/2016 at 15:57 point

I just got an ESP-14 in the mail from ebay today.  I'll be making a breakout for it sometime in early Dec.  (Right now, I'm churning out HaD articles like mad.)  I'll share when I do.

I still have no idea if it makes any sense to run a (powerful) ESP8266 off of a (much smaller) STM8 chip.  But I'm willing to find out.  :)

I also ordered one of those LED/relay boards. Again, just for fun, but maybe I'll do something with it.

Thanks for thinking about caps.  I'll definitely rebuild and reflash. 

No, I didn't get into the multitasking / backgrounding. I just got the thing up and running, not much more.

  Are you sure? yes | no

Thomas wrote 11/29/2016 at 19:13 point

The ESP-14 is quite strange. I can only guess that an OEM required a solution from Espressif that meets non-functional constraints (e.g. dependability, power consumption, or periphery set) that could not be met by the ESP8266. I don't think that a lack of skilled programmers was the reason. The power consumption of the STM8S003F3 in "active halt mode" is quite low, and for a data logging sensor node a battery life of a year or more with a 100mAh battery might be feasible.

The W1209 boards are really fun, especially with a background task. When you try using STM8EF with it, please let me know if the docs for the single wire half-duplex solution are sufficient.

About the case-insensitive input: you're welcome (the option has a price tag with "23 bytes" on it :-) )

  Are you sure? yes | no

RigTig wrote 12/15/2016 at 10:44 point

I've created an adapter for ESP14 (and ESP12) to 22-pin DIL, if you haven't done anything else yet (see new project here called 'ESP-12 and ESP-14 adapter to DIL'). My ESP14s arrived today!

  Are you sure? yes | no

Elliot Williams wrote 11/28/2016 at 13:54 point

Hiya! Been following along, finally got a few minutes to flash stuff to one of those min-dev boards.  Great fun!  I haven't done anything useful with it yet, but I've gotten the LED blinking, naturally.

One thing that's driving me nuts is the ALL CAPS commands.  Is there an easy way to either a) lower-case them all or b) make it run case insensitively?  Or would that cause namespace clashes? It makes my shift-finger hurt. 

And that's it for now.  I have to say that your directions (combined with some of the links that you list) made it very easy to get up and running with the system.  Thanks!

I'm planning a few Forth columns for HaD, and I'm still collecting chips that have working implementations.  You've added one more to the list. 

Oh, and I've ordered an ESP-14.  We'll see how that goes.  Looks like fun. 

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates