microcontrollers_Lifo_stack.png
This image is made available under the Creative Commons CC0 1.0 Universal Public Domain Dedication

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:

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

Now we can write code for a blink word that will blink the led some number of times:

\ 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:

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.

This guide was first published on Aug 10, 2018. It was last updated on Mar 08, 2024.

This page (Forth) was last updated on Mar 08, 2024.

Text editor powered by tinymce.