A Cortex-M native code Forth with a kernel written in Thumb-2 assembly

Public Chat
Similar projects worth following
zeptoforth is a bare-metal Cortex-M Forth whose kernel is written in Thumb-2 assembly and which has a library of add-on code (which includes code implementing much of the niceties of Forth along with code implementing things like multitasking) written in Forth itself. It currently supports the STM32L476, STM32F407, and STM32F746 DISCOVERY boards, but work is planned on porting it to other boards in the near future.

zeptoforth is a portable subroutine-threaded / native code inlining Forth for Cortex-M4/M7 microcontrollers designed to be able to compile to and run from both flash and RAM (the kernel of course exists in flash). At the present it does not support Cortex-M0 microcontrollers as it relies on key features of the Thumb-2 instruction set which are missing from the Thumb-1 instruction set implemented by Cortex-M0 MCU's such as instructions for writing to the bottom 16 bits and the upper 16 bits of values.

The library of code coming with zeptoforth includes support for lambda expressions, for SysTick, for interrupt-driven IO, for multitasking, for event scheduling, for multitasking supports, for a line editor, on the STM32F746 DISCOVERY board for Quad SPI external flash along with blocks and a block editor, and last but not least, LED drivers for driving the LEDs on boards such as DISCOVERY boards. Lambda expressions provide a lightweight way of declaring code that can be called from within other words without having full word headers. SysTick provides support for timing, and both the multitasker and the event scheduler are designed to integrate with it. The multitasker is preemptive and provides the ability to have multiple task priorities; it enables running code while having the REPL still being usable, amongst other things. The event scheduler allows executing words repeatedly, including with time delays, without the overhead of having full data and return stacks for each event; it can be used both with the multitasker and by itself. And the LED drivers are self-explanatory.


This release replaces flash-based vector tables with RAM-based vector tables and eliminates the need for trampolines for interrupt handling.

gzip - 3.97 MB - 06/11/2021 at 03:36


  • 1 × A zeptoforth binary, whether just a kernel or one containing compiled Forth code (the latter is highly recommended)
  • 1 × A compatible board, such as the STM32L476 DISCOVERY or STM32F407 DISCOVERY boards
  • 1 × A means to flash said board (e.g. ST-Link, or in the case of the DISCOVERY boards, a USB to USB Mini cable)
  • 1 × A means to communicate with said board over serial or ST-Link (to enable the use of swdcom)

  • Release 0.18.0

    Travis Bemann06/11/2021 at 03:37 0 comments

    This release revamps the interrupt handling of zeptoforth. Instead of using a fixed vector table in flash which calls a limited number of trampolines, which in turn call interrupt handlers, the vector table is moved into RAM on boot, and every vector can have its own separate handler, with no trampolines needed. By this interrupt handling is not only faster, but can be more fine-grained because different interrupts do not need to share handlers. Another change is that SysTick timing has been fixed for the STM32L476, as it previously was generating SysTicks at half the expected rate.

  • Release 0.17.0

    Travis Bemann06/09/2021 at 01:59 0 comments

    This release renames the byte-oriented words to begin with the prefix c rather than the prefix b to bring zeptoforth more in line with the naming conventions used by other Forths. Additionally, the lambda combinators have been renamed to make their names more succinct and more conventional.

  • Patch-level release 0.16.2

    Travis Bemann05/29/2021 at 05:27 0 comments

    This patch-level release adds an EXTI API along with fixing a bug where the multitasker was spending too much time with interrupts disabled, which was impacting interrupt-dependent programs (such as an LMT01 temperature sensor driver) and fixing a bug where time was being reported as progressing as half its actual rate on the STM32F407. Additionally, an LMT01 temperature sensor driver along with an implementation of Conway's Game of Life using sixels are included.

  • Patch-level release 0.16.1

    Travis Bemann05/03/2021 at 00:56 0 comments

    This patch-level release fixes a major bug in 2! where it left its final argument on the stack.

  • Release 0.16.0

    Travis Bemann04/27/2021 at 03:08 0 comments

    This release moves clock configuration out of the zeptoforth kernel and into the Forth userland. It also adds random number generator drivers to each of the supported platforms, along with changing the system clock of the STM32L476 from 48 MHz to 72 MHz.

  • Patch-level release 0.15.2

    Travis Bemann04/11/2021 at 19:59 0 comments

    This patch-level release includes action pools, a GPIO API, adjustable timeslices for tasks, support for more efficient use of external flash memory when available space is tight, the ability to close (and reopen) channels and fast channels, and support for inserting and deleting rows in the block editor.

  • Patch-level release 0.15.1

    Travis Bemann03/29/2021 at 02:59 0 comments

    This patch-level release is an add-on to release 0.15.0 which renames each of the wordlist constants from *-wordlist to *-module, to make the programmer interface more consistent.

  • Release 0.15.0

    Travis Bemann03/28/2021 at 23:40 0 comments

    This release replaces the old system of using SET-ORDER and SET-CURRENT (even though they still exist behind the scenes) with a new module system aimed at making building code using more complex arrangements of wordlists more user-friendly.

  • Patch-level release 0.14.4

    Travis Bemann03/27/2021 at 03:46 0 comments

    This patch-level release adds task parameters, escaped string literals, adds more words for manipulating blocks on the STM32F746 DISCOVERY, and fixes bugs in multitasking and fchannels.

  • Patch-level release 0.14.3

    Travis Bemann03/21/2021 at 03:45 0 comments

    This patch-level release adds a block editor and some block loading and listing words for the STM32F746 DISCOVERY board as well as abstracting out ANSI terminal-related words so they can be shared by both the line editor and the block editor; additionally, some bugs in the lambda words [: and ;] concerning constants are fixed as well.

View all 71 project logs

Enjoy this project?



Thomas wrote 04/13/2020 at 08:01 point

I like the story behind your project a lot! Since you're using e4thcom some of Manfred Marlow's approaches to the little Forth I'm maintaining might also be useful for your Forth. Also it's quite possible that my works with minor adaptations :-)

  Are you sure? yes | no

Travis Bemann wrote 04/13/2020 at 12:05 point

The main adaptation that would be needed is that it would need to wait until it receives ACK to send another line of code, and it would need to treat NAK as an indicator of an error indicating to stop ending any more data. This is why with e4thcom it is used in noforth mode, because noforth behaves in this fashion.

  Are you sure? yes | no

Thomas wrote 04/13/2020 at 16:09 point

I remember I had a long discussion with Manfred about ACK/NACK vs. OK/? - he didn't like the solution he made for noforth too much and made a point of using the "human readable" handshake with OK and ? (for error). What's your reason for using ACK/NACK?

  Are you sure? yes | no

Travis Bemann wrote 04/13/2020 at 18:28 point

My reason to do ACK and NAK is that it would signal success versus failure without relying on the textual content of what is output - in zeptoforth an exception can really result in anything being output while ACK and NAK could be transparently added to all prompts and all errors without any further changes being necessary - and there was a premade mode in e4thcom to use ACK and NAK.

(Note that I am responding to this post because is not letting me reply to your other post.)

  Are you sure? yes | no

Thomas wrote 04/16/2020 at 18:55 point

I didn't notice your reply, sorry. Hint: in HaD discussions you can simply reply to the post someone else replied to.

OK, now I understand your preferences. When I started this I first tried to work with an XON/XOFF handshake, at least until I figured the following out about USB-serial converters on Linux: 

  Are you sure? yes | no

Travis Bemann wrote 04/17/2020 at 15:05 point

Personally I would prefer hardware flow control, but unfortunately the USB-serial dongle I am using to communicate with the STM32F407 DISCOVERY board lacks pins for RTS/CTS, and the serial over USB connection built into the STM32L476 DISCOVERY board has no option for hardware flow control in the first place.

  Are you sure? yes | no

Travis Bemann wrote 04/16/2020 at 18:01 point

I looked at the code for and noticed that is coded for Python 2.7, which is no longer maintained. Could you do a port for Python 3? This could be very useful for people who do not run Linux (and thus e4thcom is not a feasible option, as apparently people have not had luck with FreeBSD's Linux compatibility layer with it). (I could add more screen support for also adding XON and XOFF, but screen still would not support loading files from within other files with anything like #include or #require.)

  Are you sure? yes | no

Thomas wrote 04/16/2020 at 18:39 point

Sure I can port it to Python 3 :-) To be honest, when I wrote it I didn't know much Python and 2.7 was the default on the Ubuntu 14.04 I was using then.

Do you know of anyone who uses the e4thcom 64bit binary on WSL 2?

  Are you sure? yes | no

Travis Bemann wrote 04/17/2020 at 14:51 point

I have not heard of anyone trying 64-bit e4thcom with WSL 2, but then, I have not heard of anyone who has used WSL 2 in the first place. Most of the people I know in #forth on freenode are either Linux (and not Linux on top of Windows) or BSD users.

  Are you sure? yes | no

Thomas wrote 04/18/2020 at 08:50 point

I gave it a try. It appears to work but it's not fully tested:

  Are you sure? yes | no

Thomas wrote 04/18/2020 at 13:01 point

I tried telnet to ucSim with the python3 variant - Python3 "byte array vs. str"  issues, of coursed. Unfortunately I get timeouts after fixing it. That's very difficult to debug. The serial interface transfer, which is likely the one you're most interested in, should work anyway.

  Are you sure? yes | no

Travis Bemann wrote 04/19/2020 at 16:19 point

Could you add a copyright notice and license block to so I can incorporate it, with some minor modifications, to the zeptoforth codebase? I should note that there is no such thing as the public domain in Germany.

  Are you sure? yes | no

Thomas wrote 04/19/2020 at 18:24 point

Sure, I'll do that. MIT is OK, I s'pose?

Edit: done

  Are you sure? yes | no

Travis Bemann wrote 04/19/2020 at 18:52 point

That would work, particularly since the MIT license is GPL-compatible.

  Are you sure? yes | no

Travis Bemann wrote 04/19/2020 at 21:41 point

I put my changes to to make it work with zeptoforth in my fork of your gist, and I will include this with zeptoforth so users can upload code without relying on e4thcom.

  Are you sure? yes | no

Elliot Williams wrote 04/13/2020 at 07:41 point

Cool!  Going to have to check this out.  

Lemme see if I have a 407 Disco around here somewhere...

I agree that Forth misses lambdas, just b/c it's hard to think up good names all the time.    

Although on multitasking/timers, honestly, I end up just writing "begin do_something 100 ms again", which works as well, if your "ms" definition has a "yield" in it.  But this keeps the chip from going into a low-power mode, b/c it's always round-robinning into delay statements.  If the scheduler knew...  

So yeah.  I'm going to have a peek at your multitasking setup.

  Are you sure? yes | no

Travis Bemann wrote 04/13/2020 at 12:00 point

I would appreciate more people trying it out, even though that requires their owning the same hardware as myself. (It is hard for me to maintain the code for hardware I do not own, e.g. I cannot create zeptoforth_full binaries without physically owning the hardware in question.)

Lambdas are a feature missing, unfortunately, from most Forths, even though there are a few with them, such as Retro (of course Retro is not a conventional Forth by any means).

The multitasker is designed to put the MCU to sleep when no task is actively executing, even though it wakes up the processor when a SysTick occurs, when a byte is received over a USART, or, if data is pending in the serial TX circular buffer and the USART TX shift register becomes free. So if one writes:

: foo begin do-baz 100 ms again ;

' foo 256 256 256 spawn constant foo-task

foo-task task-enable

it will sleep the MCU, aside from it getting woken up by SysTicks and by any input the user feeds into the REPL.

  Are you sure? yes | no

Travis Bemann wrote 04/18/2020 at 17:37 point

You said you might try it out on the F407. If you had any problems with the zeptoforth_full-[version].bin file for the F407, there were issues with it being corrupt, so try it out with the latest release binaries, where I made sure to check that it now works.

  Are you sure? yes | no

crun wrote 04/12/2020 at 20:44 point

Could you explain why vs mecrisp?

Re M0, you say "it relies on key features of the Thumb-2 instruction set which are missing from the Thumb-2 instruction set". Could you clarify?

  Are you sure? yes | no

Travis Bemann wrote 04/12/2020 at 21:22 point

To be completely honest, it fits much of the same niche as Mecrisp-Stellaris, but there being one Forth for a platform has not stopped anyone from making another (e.g. there already was a Cortex-M Forth before Mecrisp-Stellaris, namely Riscy Pygness). This project exists largely because I wanted to create a bare-metal native code embedded Forth of my own, and the STM32L4 and STM32F4 series microcontrollers were an attractive target, big enough to fit a good-sized Forth with plenty of room to spare, with good documentation for peripherals, and without being effectively a PC on a chip (e.g. the Raspberry Pi boards) with peripherals that may require proprietary binary blobs and thus not amenable to development by an individual such as myself.

About the M0, the issue is that 32-bit literals are difficult to implement without 16-bit low and 16-bit high immediate MOV/MOVT instructions, and while I tried to implement literals that use a mixture of MOVS, LSLS, and ORRS instructions the problem is that when allocating space for a literal ahead of time, and then writing to it later, it is hard to predict how long that sequence of instructions will be (unless one assumes the very longest length and then fills out the rest of the space with NOPs). So as a result I am focusing on Cortex-M3/M4 for now, and will  implement support for the Cortex-M0 later if I see the need for it.

  Are you sure? yes | no

crun wrote 06/10/2020 at 07:35 point

Looking into M4 because of the dsp floating point performance. So 4th for the little F411 dip boards would be ok with me.  Interesting how to integrate Forth with the arm dsp libraries though.

  Are you sure? yes | no

alessandro.cordova wrote 04/11/2020 at 17:43 point


  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