Description

STM8 eForth is a compact, structured and tested Forth framework for cheap µCs. The code is stable: binaries and support are available for a range of STM8 devices and low-cost boards, e.g. the W1209 thermostat.

STM8 eForth is based on Dr. C.H. Ting's STM8EF. As an STC Forth it compiles to machine code. The project uses the latest SDCC tool chain with STM8 assembler and linker for building the Forth kernel, and C can be integrated with Forth for scriptable applications!

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

Details

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!

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:

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

When I started this project the STM8S003F3P6, an STM8S Value Line µC with 8K Flash and 1K RAM in a TSSOP20 package, was the cheapest device in its class. Cheap enough to enable an industry of low-cost electronics control devices, e.g. thermostats or voltmeters, with the left-overs of mass production runs. This market for low-mid-range µCs encouraged Nuvoton to market the mostly pin-compatible N76E003AT20, an 8051 family device.

For the initial goal of this project this was bad news: from then on the Nuvoton chip was the go-to device on the spot market, a chip with an architecture utterly unsuitable for a self-contained Forth system!

Right now, however, the STM8S003F3P6 is at a very low price point: I just received 10 pcs for $1.86, which is as low as it gets!

Don't get me wrong, the Nuvoton chip won't disappear and it's very likely to receive a thermostat like the W1209 with an "non-hackable" chip but at least the TSSOP20 package can be soldered with ease.

The default build environment got an update to SDCC 3.8.6 (but it was tested with SDCC 3.9.0, too), and the binary export from uCsim got an upgrade (it's a nice Python script now instead of a lean-and-mean AWK scriptlet).

More internal constants can be exported with #require, STM8L chip got better support thanks to @Eelco. Coding with inline machine instructions is easier thanks to a number of bit copy and assembly interface words.

STM8 eForth is now a serious alternative to coding C coding for STM8 µCs, especially if Flash space is a premium or if interactive features or configuration in the field are required.

Recently, many other pages got updates. Especially a lot of graft was moved from the STM8 eForth Programming introduction page to improved topic pages in the Wiki.

The Forth VM makes context switching very efficient. In my opinion (I wrote many interrupt handlers for industrial and safety critical applications), implementing interrupts in Forth is easer than in C.

The STM8 eForth Interrupts in the STM8 eForth Wiki explains the bit that there is to know. The MODBUS server and the nRFL01 libraries implement low level code using STM8 interrupts.

As an example, here is the STM8S UART RX handler from the MODBUS library:

This example contains many stack comments (the stuff in round brackets) and just a bit of code for copying characters from the UART to a buffer, protecting against buffer overrun, and providing a time stamp for the MODBUS "end of transmission" detection.

The most striking point is that in this use case Forth is much more used as a macro assembler for a very simple virtual machine than as a programming language. The programmer builds the most simple "machine" that will do the job. No unnecessary abstractions.

I kept STM8 eForth stable for a long time but now there is a good reason for preparing a new release:

For the current work on a very lightweight MODBUS Server in STM8 eForth I needed some data from the target (e.g. clock frequency), and some other aides for low-level programming (e.g. an easy to use and stable interface between Forth and machine code).

The implementation of MODBUS in Forth is a pleasant experience: Moore's problem oriented language approach works quite well for embedded control and protocol implementations. I simply design the machine so that it implements layers of a protocol, and I do that in a language that describes the problem well. Thereby a language emerges, because my abstractions either describe the problem well, or I change it so that it describes the problem better.

It's safe to say that the implementation is very code efficient and highly functional at the same time (thanks to an interpreter-compiler on a MODBUS node).

Before the release of STM8 eForth 2.2.23, I'd like to improve the compilation of Forth code in the continuous integration environment.

Controlling the WS2812 with STM8 eForth was something I've had on my to-do list for a long time. The lore of W2812 timing is already long and twisted, and any new implementation, it seems, has to add a bit to it :-)

Mine is the following:

Do it the Aristotle way: Relax the timing but maintain the balance!

I coded the inner loop for a byte transfer and the bit timing in assembly (in a self balancing way). The outer loop is coded in Forth. The result is quite compact:

I reviewed the STM8 eForth Wiki under and tried to make it more readable for the casual visitor. Also removing some of the Forth evangelizing hyperbole doesn't hurt. Forth is still amazing, not only for philosophical reasons, but nobody likes Forth extremists, right?

I found no documentation on parameter passing in the SDCC STM8 port, and after some experiments I have the following hypothesis:

parameters are copied to the return stack in the relevant size, ready for access with "SP indirect addressing", and in the reverse order of the definition

8bit return values are in A

16bit return values are in X

32bit return values are in MSW X - LSW Y

SDCC passes most data on the (return) stack. The main difference to a stack oriented language is that this parameter passing behavior is not defined in the language, it's a mere implementation detail. The effect is that stack transfers have to be done over and over again, which creates a lot of waste.

From the software engineering point of view it becomes apparent that a minor change of paradigm (maintaining the stack is the responsibility of in the called function instead of the calling function) can be the defining feature of the architecture.

Become a Hackaday.io Member

For beginers a well dokumentet memory map for ram flash and eeprom if used for dichenary would be helpful. also a map of how dichenary entries are saved in ram ore flash. i have read the eforth dokumentation but i still is not totaly clear to me. wbr Mikael.

The STM8 eForth method is rather unique (at least that's what seasoned Forthers say), but it's also robust and, due to ALIAS words, even the EEPROM can be used to increase the available dictionary space.

how can I check all the different usage of memory; flash ram dictionary space user variable space etc. I would like to monitor the memory space automaticly used by the forth vm to understand more and know when i have consumed some area of memory. wbr mikael.

I have been playing with your fantastic eforth now for a while and must say it is both extremly fanny/addictive yet powerful. For me the whole forth concept was new but quickly i got to like the speed of testing and clean way of writing the code. So thank you. I have som questions: for the real beginners more example code with comment would be great specially for using hardware like spi i2c timmers and more. If I build a program but then realize that one of my earliest building words need to be altered, how can i manage this? Is it possible to edit a word by decompiling with SEE and then edit and recompiling ? wbr Mikael

If you need to recompile a word at the base of your dictionary, the Forth development cycle usually requires re-compiling code from the source. Since the source code in most cases isn't stored on the µC, tools like e4thcom, Manfred Marlow's nifty Forth terminal, or codeload.py, can be used to upload code efficiently (take note of the #include, #require, and \res statements in the examples). It's also possible to change existing code in-place, e.g. by replacing the code of an existing word with a JP to a new word (this has a twist due to the STM8 eForth ALIAS feature!). For a rather elaborate example check out the lib/CURRENT and lib/VOC implementations.

The STM8 eForth core consists of optimized assembly code (even the compiler does some optimizations), and therefore a simple SEE implementation will fail. In order to understand the core it's easiest to compare it with the code in Dr. C.H. Ting's "eForth Overview",

STM8S are used frequently, but I'd like to see some evidence before I buy that it's "likely a genericized or pin-and-code compatible version available on the Chinese market".The Meloncraft, however, isn't half bad.

I am having an issue with MARKER. Compilation stops when I call a word that is defined just before in the same file. If I use MARKER from version 2.2.19 it works fine. Did I miss something, once again?

OK, I found what I was doing wrong: I forgot to define a dummy word before calling MARKER. In version 2.2.18 ( I mentioned 2.2.19 but that should be 2.2.18, I skipped 2.2.19) this was done when you uploaded MARKER, in version 2.2.20 you must do it yourself.

MARKER needs at least one word definition in RAM to operate, and that's not obvious. I'm rarely ever confronted to that because of the "#include STARTTEMP ... TARGET ... ENDTEMP" template I apply. In my main.fs, TARGET is usually defined as ": TARGET NVM ;" before STARTEMP in the first module even issues "#require MARKER" ( https://github.com/TG9541/stm8ef/blob/master/lib/STARTTEMP ).Maybe I should re-introduce a dummy word in MARKER.

I am trying to communicate with sdhc cards FAT32 file systems in SPI mode. Using one array for 512 byte data buffer works great but now I try to define a second array as a filename buffer and I run into this problem. Do you have a solution? Btw I tried with the latest version, 2.2.20.

Edit: the bug was even worse than I thought - the above code does the job.

By the way: you don't need ULOCKF / LOCKF if you set the interrupt vector in NVM mode. I'll also put the interrupt vector addresses into the STM8S103.efr file so that setting an interrupt works with a simple ! (store), and IVEC! is no longer needed.

Wow, that was really fast. I should have asked you a week ago, would have saved me a lot of time.

I tried setting the interrupt vector in nvm mode but with 2.2.17 this forced the chip in a strange loop, even after a power cycle. I then had to reflash the mindev board. (I think I really need to get into assembly).

Microchip produced a humorous self-critical video about the failings of the PICkit 3 vs the older 2. Perhaps ST Micro should be shamed to do the same with their products. I realise that this is off topic but still a good example of how not to do things and take ownership for getting it wrong. My ST Visual Programmer is 3.4.0 Jul 12 2017.

Having lived in Quebec a long time ago, there is a local swear word that sounds like ST. This is what I say every time I use ST software and hardware. STVP does not even show the latest factory ST-LINK/V2 as an option - 'ST-LINK All Versions' would be useful. The thing never connected as I tried a few times a year over many years. They do nothing to alert you that the target device needs external power. I always had clones that did the job so there was no great urgency to learn it.

Many (all?) of the cheaper USB - STM programming adaptors had figured this out a very long time ago. Even Microchip PICkit 2/3 monitors power on the target but gives the option to power their external devices.

Simply logging in to ST to get free software goes into an email / validation death spiral loop that is a complete waste of our valuable time. If the people of ST responsible for this nonsense worked for me, then they would be all unemployed.

Hi Thomas, I am enjoying your project for more then a year now. Today I joined hackaday to be able to participate. I bought a couple of W1209 and mindev boards to play with. I also have an ssd1306 i2c oled display (about $3 at Aliexpress). I managed to drive it using the i2c capabilities of the chip, no bit-banging. This was a few months ago, with version stm8ef-bin.2.2.8 and a MINDEV board. I used $" to compile the text to be send to the display (e.g. : txt $" text" ;) I upgraded to more recent versions, up to 2.2.16, but executing a word that contains $" freezes (or reinitilizes, as with COLD) the board. What am I doing wrong here? Do I have to raise a flag in globconf.inc? Can you point me in the right direction?

Thank you for your kind reply and invitation, I would like to join. I have to warn you though that I have little coding experience but I am learning thanks to projects like yours. How do I proceed to upload the code?

I just saw you have a separate project with W1209 thermostat boards. I use two of such devices in my household: one as a replacement thermostat in a refrigarator. The other one in the boiler of the central heating unit. Maybe I can make small contributions on that project too.

the only way to gain coding experience is coding. There is still a lot you can do, e.g. write tests (which improves coding skills significantly!), or write library code (e.g. I2C), or do a review (also a great way to learn), or spread the word... Please let us know your GitHub ID, and you'll receive an invite!

A warning about W1209 in safety critical applications:* only use hobby stuff for adding features that aren't safety critical* always rely on built-in safety features of the product * make sure to get a review of your design from someone who knows safety

thanks for the code below. Already I have learned about the B! word from reviewing your code. That's going to help me enormously with my W1209 project where I want to turn an input port into an output port for some of the time.

You're welcome, I am glad it is of use to you. I uploaded another file on my github account with io-manipulating words: io.fs. These words use a numbering of the gpio-portpins. The word io. displays the state of the IDR,ODR,DDR,CR1 and CR2 for a portpin. (e.g. $15 io. displays these bit states for the second port (Port B), pin 5. Maybe useful for you as well.

I'm a newbie and have implemented eForth on a STM8S103F3. While I have successfully implemented the "mystart" boot example which remains in NVM following a power reset, I just can't seem to get any other created word to be available. Could I have some guidance please?

Hi @Peter, sorry, I noticed your message just now, which is very unfortunate. Yesterday I independently found the problem you were facing. The problem, which existed since 5/August/2017, has been fixed. A new binary release is being prepared.

Hi Thomas. On my second MinDev I was able to re-programme it using STVP ID 3.4.0. However as it says "stm8eForth v2.2" which is identical to other boards, how do I distinguish it elegantly via communication? Incidentally the first board which has been running a blink programme as an endless loop on "problem 2.2.13" refuses to re-programme via SWIM reporting not blank though ROP is off. Any suggestions on recovering it?

Hi Thomas, small update to follow. I don't know whether it's my ignorance but I was expecting to be able to reply as another indent to your reply 09/29/2017 at 18:06 but replying via 09/24/2017 at 07:24.

To cover my experience described earlier I'll split it into two responses in case anybody wants to work on that part of it. I have not been doing any compiling but just running STVP in windows burning IHX files as required. Any compiling I'm likely to do will be in Windows as I've found setting up a Linux similar to what I use a challenge (...ST). Zorin I'd suggest is a better start that many.

I solved how to re-programme a stubborn STM8S103F3P6 mentioned by specifically selecting the Option Byte window tab and then using Program > Current Tab. Then I was able to program as I normally do so by using Program > All tabs. Possibly a glitch but nothing else worked.

To distinguish between the different versions of 2.2.xx appearing as v2.2 I've started to modify the IHX file so that the same 4 bytes now display 2v17 for example. I'll explain the procedure for newbies like me used to Windows.

1. Copy the IHX file and rename including the version and change the file type to HEX. e.g. C0135.IHX becomes C0135-2v17.HEX.

2. Open the file using STVP and choose the Program Memory tab to display both the hexadecimal and ascii contents.

3. Go to around line 000081E0 and you should see in the ascii columns on the right stm8 eForth v2.2. Put the cusor on the v and update. The hexadecimal will change.

4. Save

Now on STVP default settings the files are easily accessible. For me the approach above is easier than directly locating the file with STVP and using the Save As to create the HEX copy.

My applications are to turn off a immersion heating element in a cooking pot when the water reaches near to boiling and secondly to operate a fan in a solar dryer at a temperature around 100C, with computer logging every 10 minutes or so.

Thanks for your kind offer of assistance, but I will try to get some unmodified W1209s operating in a circuit, just to get started first.

If I was able to reflash them, I would hope to retain the 3 digit temperature display. Which sounds easy as you say they are fully supported by STM8EF vectored I/O.

I guess the key pins don't operate when they are used for serial connection, which is OK because the temperature points could be hard coded in software.

2. When putting the header on for the SWIM connector use 5 pins instead of 4. The 5th pin is a convenient place to tie one end of the diode to for the half duplex link.

3. While the SWIMCOM binary may work on the W1209 board, for the newcomer there is a lot of reward in seeing the "4th" string on the LED display once the binary is flashed. With that feedback you know it is just a matter of getting the serial comms to work.

4. With the extra pin the SWIM connection would be a much better place to have half duplex communications. It would prevent the serial communication from disrupting measurements on the sensor pin while debugging and gives you a pin that your push on connector holds better.

5. Is there a place to share code snippets? For instance, I wanted to blank the display before sending another character to it. I used

: Clrdisplay \ clear anything showing

32 E7S 32 E7S 32 E7S ;

I also wanted a delay between displaying “On” and “Off”. I used the following but I suspect there is an easier way:

If there was a way to share these snippets it may prove useful to the newcomer. Maybe I just don’t understand the features of github well enough *grin*.

Anyway, I can recommend this forth implementation to anyone looking to hack one of these boards. I’m having a blast . I only wish I had found about this project before I built my electric gate controller with an atmel avr chip programmed in assembly. I could have just brought a cheap relay board and achieved the same thing without having to fabricate a board, populate it then program it in assembly.

2, 3, 4: using a 5-pin header and a diode is a good idea! I'm currently experimenting with using the W1209 key GPIOs (PC4, PC5) for full-duplex communication, which has several advantages. What do you think?

The usability of STM8EF programming is currently under active development. Especially a. and b. should be helpful for new users:

I created an issue on GitHub https://github.com/TG9541/stm8ef/issues/42It would be great if you could contribute to taking the decisions for a better W1209 support. Also feel free to open an issue on GitHub for topic oriented discussion.

great stuff. I've been at it all day so real life drags me away for now. I don't have much to contribute on full or half duplex serial. I used Teraterm and slowed it down (25ms each character, 500ms for each line return) so the compiling could keep up. I was happy with that.

The Github Gist is interesting. Over time it could be really useful. Perhaps the main Github wiki needs to alert readers to it's existence?

I'm happy to help out with the W1209 where I can. You've no doubt heard of the saying "the blind leading the blind".

The W1209 is a great platform and it is very exciting to think that much of what I used F-PC for years ago I could do on something costing so little. I plan to contribute as much as I can.

the voice of users is important, especially in a hobby project where there is no such thing as "market research" - users know very well what makes a product useful for them. One I'd like to figure out is if connecting a serial interface to the keys "+" and "-" is acceptable (the keys will remain usable), at last during interactive programming. The other thing is whether support for "sensor header COM" needs to be maintained, or if I can simply replace it with something better :-)

Thomas mentioned that a better file loader would be nice. Here is my attempt. Simple to start with, but obviously capable of being expanded with features later. It is in Python2 and runs from the command line of the host machine (mine is LinuxMint).

Usage: Save this code as a file (say named loadserial.py) and change its permissions to be executable (just the lines in between the code tags). I put loadserial.py in my local /bin folder. Edit loadserial.py so the port matches what you use when using a terminal console to connect to STM8 machine.

WARNING: I've just noticed that the indentation was inconsistently displayed, and python is indentation sensitive. So be very careful with just copy-and-paste. I'll put a copy of it up on RigTig's Big 3d Printer project here on hackaday.io.

Either put FILE on first line of the file to be sent, or type it into a terminal console and close it, then use a local command line interface thus: <code> filename file2send </code>. Enjoy!

I just tried your loadserial.py script - the handshake seems to work, and compiling code to NVM is very fast compared to the "worst case delay" method!

However, I had to interrupt the script with ctrl-c after the transfer was finished. The reason was that my Forth code ended with "HAND", after which loadserial.py waited in vain for the handshake signal.

I can imagine that an improved uploader does the following:

* handle FILE and HAND (no need to include those in source file), or

* terminate transfer when the response is anything but the handshake character

The next thing on a programmer's wish list is a way for including source files (nested, of course). I can also imagine testing if "base code" has already been transferred, e.g. using some query-response between the Forth system and the uploader.

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!

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

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.

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)

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.

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.

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!

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

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.

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!

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

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.

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

'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

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

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

nvm

: awuint savec awu_csr1 c@ drop restc ;

' awuint 1 ivec!

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

ram

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.