Zeptoforth
Forth is the choice of programming language of the Colibri system, and we have chosen an implementation from Travis Bemann, called zeptoforth. It is an incredible Forth platform and it aligns very well with the Colibri mindset and hardware. Forth is not a popular language, probably because it is so easy to implement that a) too many incompatible implementations exist, and b) no company will make money of it and hence not spend any marketing to make it popular.
Forth itself has been used a lot in NASA missions and Open Firmware is in Forth, endorsed by the IEEE and used by ARM, IBM, Apple and Sun. It just didn't manage to convince the masses of programmers. It is known to be small, reliable, relatively performant and easy to port to new MCU architectures.
zeptoforth is impressive, and fulfills most of the Original Requirements (see below), and failing only in Popularity. Zepto Forth is complete with both fixed-point as well as floating-point operations, multitasking, channels, action queues, modules, support for hardware timers and much more features. Its compiler is running on the target, so one can upload/store source code on the target and not needing external tools, SWD support and it is even possible to run a block editor on the target. The documentation is very extensive and detailed and there are ample examples available.
Our first try to upload zeptoforth onto Streamline MCU STM32f407 went without glitch, after setting the serial port for Streamline Connect to USART2.
What is Forth?
Forth is a stack-oriented language that has 2 stacks, one for data and one call stack for return addresses. It has very simple syntactic rules;
- Everything is a "word" which must be surrounded by whitespace. All non-whitespace bytes are allowed in word.
- A single backslash (\) is start of comment, and everything will be filtered out until parser reaches new line.
- Everything between a pair of parentheses " ( xyz ) " is a comment. They can be stretching multiple lines. Nested parentheses are not supported.
- Thereafter, try to locate word in dictionary. If found, "use" (depends on whether compiling or executing) it.
- If word is not found in dictionary, try to parse it as a number. If successful, push number on stack.
- Most words operate on the stacks. Typically manipulating the Top-Of-Stack (TOS), by popping value(s) off the top and pushing the result back on top. But words can also manipulate the dictionary of words, to create new words, including but not limited to global variables/buffers.
- Forth does not have many common programming language constructs, such as local variables, exceptions, closures, object orientation, and so forth (pun intended). But the language is flexible enough that people sometimes implement these at Forth level, so that you can use them if you want, but are not suffering under the bloat if you think you don't need it. And perhaps you change your mind later, and instead of finding a new language implementation that has the feature you want, you can simply add it to what you have. Few other languages have this flexibility built into it.
- Memory is addressable directly, which means full control of MCU peripherals.
- Forth is NOT a unified/standardized language and except for a small number of core words, there is no consensus of what the base vocabulary is. Many
think that the nearest a standard (ANS Forth) is too bloated and not suitable for all the target devices that a more lean Forth can run on. And since it is rather easy to implement a rudimentary Forth, there is a plethora of variants, and that in turn means that many libraries won't work out-of-the-box and will require tweaking by the user.
HelloWorld is very simple;
." Hello, World!"
or if you want to define a Word for it;
: hello ." Hello, World!" ;
Colon ":" is the word to define other words, and the definition ends with semicolon ";". Another example
: add4 4 + ; \ adds 4 to the value on TOS.
It is common/recommended practice to put the stack manipulation info as a comment after the defined word.
: add4 ( n -- n+4 ) 4 + ;
or
: add4 ( n -- result ) 4 + ;
It is the same thing, just human-readable comment to hint at what the word does.
Instead of repeating the entire set of documentation of zeptoforth, go to the Getting Started with zeptoforth on its Github wiki.
Original Firmware Requirements
Before deciding on zeptoforth, there were a bunch of requirements, divided into "must-have" and "nice-to-have" categories.
Must-have
- Preemptive, prioritized multitasking ✓
- Fit into STM32WLE5 with a LoraWAN stack (not small) ✓
- Program update via LoraWAN Downlinks ✓
- Source code in the field ✓
- Able to run on I/O boards as well ✓
Nice-to-have
- Not requiring a tool chain, such as compilers, linkers and so on ✓
- Existing drivers for MCU peripherals ✓
- Ready-to-deploy for variety of STM32 MCUs ✓
- Ready-to-deploy for Raspberry Pi RP2354 ✓
- REPL for interactive development on the device ✓
- Compile to assembler for ultimate performance ✓
- Plenty of examples and strong community ✗
- Easy to learn syntax ✗
- Run from RAM as well as Flash, to save erase cycles on Flash ✓