The Forth language is quite different from most others, certainly from C or Python. The syntax is different. The architecture is different. How arguments and results are passed around is different.
Forth has been around since the late 1960s has been popular for embedded programming, and is a logical choice for makers and IoT projects: it thrives in environments where memory is limited and efficiency is important. This makes it perfect for use with microcontrollers.
There are a few Forth implementations available for microcontroller boards, but the one we're going to look at has explicit support for Adafruit's latest SAMD51 boards (experimental but seems to work). It's a version of ainsuForth, specifically (https://github.com/wa1tnr/ainsuForth-gen-exp-m4. Github user wa1tnr
is none other than @nis
from the Adafruit Discord!
Installing
An advantage of ainsu
is that it's packaged as an Arduino project; once you clone/download the repo, you just load it into the Arduino IDE then compile and flash onto your board.
If you're not familiar with the Arduino IDE and it's use, we have a guide for that to get you up and running. That will work with any of the SAMD boards.
Download the code from Github. Inside the ainsuForth-gen-exp-m4-master directory you get when you unzip the file you just downloaded will be a file named ainsuForth-gen-exp-m4.ino. Start up your Arduino IDE and open that file. Select your board and port as usual. Build and upload to your board. You will need to place your M4 board into bootloader mode by double pressing reset in order for the uploader to see it.
Connect via the Arduino serial terminal, screen or whatever way you use to connect to the Serial I/O on your board. When you do you'll be greeted by the REPL. Forth will output a banner with some information and then wait for you to type code. Try 1 2 + .
followed by the return/enter key. In response you should see 3 ok
ainsuForth - 2018 - wa1tnr words: 81 YAFFA - Yet Another Forth For Arduino, Copyright (C) 2012 Stuart Wood 1 2 + . 3 ok
Stack Machines
Forth is a stack based language. That means that passing arguments to functions and getting results from them is done using Forth's stack instead of sending arguments in the function call and having a returned result.
A Stack Refresher
If you've been programming for a while, you may be familiar with stacks. In the case you're not, let's have a quick look at this very useful data structure.
A stack is a data structure you can put things into and take things out of. Its most interesting feature is that things come out in the reverse order of how they were put in. The act of putting something into a stack is called pushing and removing is popping.
For example, if 1
, 2
, and 3
are pushed into a stack in that order, the thing that will be popped first is the 3
. The next pop will return the 2
. And so on. At any point the most recently pushed item is available at the top of the stack.
You can visualize a stack as a pile of items. When you push you are placing an item on the top of the pile, and when you pop you are taking the item from the top of the pile. You generally can't see anything in the pile except for the thing on top (i.e. the last thing that was pushed). Forth does provide ways to peek deeper into the stack or make a copy is items deeper in the stack (which are then pushed onto the top).
Trying to access the top of an empty stack or pop from it results in an error. In some cases there is a maximum size to a stack; pushing onto a full stack will be an error.
Syntax
When they appear in code, simple data items like numbers get pushed onto the stack. Names of functions (called words in Forth) cause the named function to be executed. So the general pattern is to put arguments on the stack, then use a word. For example, to add 2
and 3
:
2 3 +
This pushes 2
, then pushes 3
, then executes the function named +
. The +
function pops two numbers, adds them, and pushes the result onto the stack.
If a word has a result, as in this case, it is left on the stack when the word finishes executing. Because of this, you don't have to store results in a variable to be passed into another function; it just sits on the stack waiting to be used. For example (2 + 3) * 4
could be coded as:
2 3 + 4 *
The result of 2 3 +
(which is 5
), is left on the stack, 4
is then pushed onto the stack and the *
function pops two arguments from the stack (4
then 5
), multiplies them and pushes the result (20
) back onto the stack.
The .
word will pop the top item from the stack and display it . If you type some code in the REPL that leaves a result on the stack you'll need to use .
to see it.
Another word that's handy, particularly used with .
is dup
. It makes a copy of the top item on the stack, and pushes it.
Combining the two, you can use dup .
to see the item on the top of the stack without changing it.
Everything in Forth, data items and words alike, are separated by whitespace. Unlike Python, indentation is irrelevant and whitespace serves only to separate words.
Documentation
Because of the stack architecture of Forth, words are documented a bit differently. If you look at docs, you'll see something like
+ ( n1 n2 -- sum ) Adds.
The part in parentheses defines the inputs and outputs of the function. things to the left of the --
are parameters that will be popped from the stack by the word. Things to the right are the results. Words can have any number of results. They're simply left on the stack when the word has done its job.
Hardware Control Words
The word pinMode
sets the mode (input/output) of a pin. To set pin 13 (connected to the onboard LED) to output you would use
1 13 pinMode
This is documented as:
pinMode (mode pin -- ) Set pin to mode.
Similarly, there is pinWrite
:
pinWrite (value pin -- ) Write value to pin.
So to turn on the onboard light we could (after setting the mode) use:
1 13 pinWrite
To turn it off we can use:
0 13 pinWrite
To abstract (as we love to do as programmers) and make our code easier to read, we can write some words for those operations. To write a word, we start with a colon (:
), followed by the name of the new word, followed by the code, and finally terminated by a semicolon (;
).
: ledOn 1 13 pinWrite ; : ledOff 0 13 pinWrite ;
With those in place we can simply use the ledOn
and ledOff
words to control the LED.
There's a delay
word, of course, that echos the functionallity of the Arduino C equivalent:
delay (millis -- ) Sleep for millis milliseconds.
\ blink (count --) : blink 0 do ledOn 300 delay ledOff 400 delay loop ;
To blink 5 times, you would write 5 blink
.
From above, the lines
ledOn 300 delay ledOff 400 delay
should be fairly understandable: turn the led on, sleep 300mS, turn it off, and sleep 400mS.
The do
and loop
words require a bit more explanation, and should shed a little more light on how Forth works.
The doc for do
is
do (limit initial -- )
The body (everything between do
and loop
) is executed for each value from initial
up to (but not including) limit
. So, if initial is 0, it behaves just like Python's
for _ in range(limit):
The loop
word increments the current index and compares it to limit
. If it's still less, the loop repeats. If it's equal, the loop ends and the program continues with the code following loop
.
A key to this example, and generally understanding how Forth works, is to notice that do
pops two parameters from the stack. In our blink
word, we pushed a 0
then used do
. Where did it's other parameter come from? We put it on the stack before calling blink
in 5 blink
. So when do
pops its parameters, there's a 0
on the top of the stack, and a 5
just below it.
Limitations
ainsu Forth is very much a work in progress. It doesn't yet have support for I2C, SPI, or other interfaces you would expect in a system for programming microcontrollers. That said, it's a good start and all the language mechanisms are in place. It's opensource so anyone can get involved and extend it.
It would benefit from having a way to use the Express boards' SPI flash filesystem the way CircuitPython does. As of this writing, it's awkward getting code into it: pasting into a serial session.
Resources
That should give you a little taste of what Forth is like. If it leaves you curious and wanting to know more, here are some resources:
- Starting Forth is one of the classic Forth introduction and tutorial. PDF and online versions are freely available.
- https://github.com/wa1tnr/ainsuForth-gen-exp-m4 A nice implementation supporting the SAMD51 chip used in Adafruit's M4 boards.
- FORTH, Inc. A long time Forth consultancy. Their site contains a wealth of information.
The SAMD chips are relatively new and there are existing ARM Cortext-M0 and M4 versions of FORTH, so expect more alternatives to be available before much longer.
Page last edited March 08, 2024
Text editor powered by tinymce.