Why Forth?

7 minute read

As I got back into Forth after about 35 years of other languages, programs and technical stuff, I’ve been asked, Why Forth?

The most personal reason is that I’ve had a life-long passion for the language and kept a bookshelf full of Forth books for over 40 years. However, once I began to use it again, I realized its perfect for embedded processors. I’m surprised how well it works with today’s microcontrollers. And how relatively easy it is to find a port of Forth for many popular microcontrollers. I still think its a good question to ask, so I start this blog with the question:

Why Forth?

This post on the Hackaday website expresses the answer quite well and it’s worth reading both the entry written by Elliott Williams and the comments, many of them written by Forth users.

From Elliott Williams… https://hackaday.com/2017/01/27/forth-the-hackers-language/

“As a concrete example of this chaining, imagine a word, gpio-set that sets a GPIO pin high. It will probably need both a port and a pin number to get the job done. A particularly Forthy way to implement this is to define a word for each pin on the part that you’re going to use: : PA3 PORTA 3 ; Then you can light the LED on pin A3 with PA3 gpio-set. In C, you’d first define a structure that includes a port and a pin number, then define gpio-set to take a structure of that type. In Forth, this is implicit: you make sure that the pin words push a port and pin number onto the stack, and then that pin-handling words expect them. It’s not safe, but it’s simple.”

This isn’t quite Forthy…a more Forthy way to do it would be to know that a specific pin when driven high, lights an LED. Forth extensibility allows one to extend the language very specifically, which means you can define words to be easily understood. You can define the pin as “RED”, the Data Direction Register when configured as output can be “led” and high is defined as “on”, to create a phrase as “RED led on”.

This allows you to add additional LEDs by defining the pins for BLUE, YELLOW, and GREEN. And to turn them on, you write “BLUE led on”, “YELLOW led on” and so on. And everywhere in your program where you want to turn an led on, you know exactly what to write. Or to turn it “off”. And you can write similar definitions to ”toggle” or “blink”. And once you’ve solved these specific definitions, chances are:

  • They are indestructible, bug-free and self-contained
  • They are easy to remember, while writing
  • And easier to understand years later, when you want to make changes

And this is, why Forth.

Read all of Elliot Williams’ article. It is great, well-done and my prior criticism is a minor point. If it doesn’t excite you as to “Why Forth?” the rest of this discussion might not either.

The point Elliot is making is that Forth is a great language for hacking. The best Forth programs are specific to a project. The code and the hardware are one well-defined thing. This isn’t a language in which you write general purpose routines. It is a language that allows (if not demands) you to define it very specifically to the hardware you are writing it for.

The Three Layers of Forth

(in an Elliott comment below the article)

Forth, for any given application, wants to be written in layers — at least three. The first layer is written in Forth, and that’s the “write only” layer in my experience. It’s like a hardware abstraction layer for any microcontroller library. It’s got to be thoroughly tested, but that’s not hard b/c it’s usually very low-level. Once you’re done with that, the serious programming work happens in the middle layer(s). Since most of this is in terms of the HAL vocabulary already, it can be as readable, or more so, than other languages. “More” because Forth reads like language, rather than having all the parentheses and syntactical structures that e.g. C does — forcing you to use a syntax-coloring highlighting editor to get a grip on the code, for instance. Since this is where the “real” program logic happens, it’s great that it’s already made readable by the HAL code. This mindset, that you start off by coding a “domain-specific language” to handle a problem is very Forthy, although not unique to it. What I described also sounds like how people use Arduino or any of the other microcontroller libraries — they take the HAL part as a given and make up their program out of these primitives (analogRead(), etc). The top level is then for “users”. The vocabulary is small, succinct, and does (only) what you’d want a user to do with the system. If the programmer is the user, this is flexible, of course. But this is like the “public” interface for top level objects in a good OO library, or the main() loop in C.

In summary

  1. HAL (hardware abstraction level): Specific to the hardware, intended to be written once
  2. Primitives: Very similar to the Arduino framework of “analogRead()”, serial.Print() etc
  3. User level: Easy to read, acts as a user interface, test interface etc. ”RED led on”

This blog will take this approach when writing Forth programs. The value of doing this is, that it sets up the following process:

  1. Writing HAL content requires having documentationExamine. Please make sure you have both the Arduino pinout and the ATmega328P datasheet while following this example. On the Arduino Uno we can write the following: [using FlashForth on Uno]

    \ HAL: provides constants for LEDs and Buttons
    $0024 constant DDRB  \ Port B data direction register, page 624 datasheet
    $0025 constant PORTB  \ Port B output register, page 624 datasheet
    %00100000 constant BD_LED \ Pin 5, Port B, Arduino UNO pinout diagram
    HAL is the most difficult to develop, however, it is performed only once. The intent of HAL is to deliver the specific words required for the intended microcontroller.

  2. Once we have the constants defined, we can use them to manually test them, which is an interactive way of testing how we need to write the User level. If we know we have to formally initialize the ports use an init word, we write that and add that to the Primitive level. Then we play! We manually test each word in the User interface by typing them in and confirming they work as desired.

    \ PRIM: primitives for setting up LED
    : PB_output ( bit -- ) DDRB mset ;  \ set a B pin as output
    : init ( --- ) \ initialize ports for LED
    	BD_LED PB_output
      ;
    init \ initialize the port used by the LED
    
    \ manual interactive commands to confirm they work
    BD_LED PORTB mset ; \ LED on!
    BD_LED PORTB mclr ;  \ LED off!
    BD_LED  PORTB c@ xor PORTB c! ; \ toggles LED!
    The intent of the Primitive level is to provide sufficient programming capability such that one can begin to make things happen with the microcontroller. In this case, it simply initializes the port so we can test it.

  3. Now that we know we have an idea of what works, we can create words which are easy to understand and are self-documenting.

    \ USER: user interface
    : on ( pin -- ) PORTB mset ; \ set a B pin high
    : off ( pin -- ) PORTB mclr ;  \ set a B pin low
    : toggle ( pin -- ) PORTB c@ xor PORTB c! ; \ toggle the B pin
    : blink ( pin -- ) \ blink B pin 
      begin 
    	dup toggle 200 ms key?
      until
      drop ;

Working Program

All of the steps will result in the following program for blinking the on-board LED of the Arduino UNO along with:

  • blink - turn the LED on and off with a 200 ms delay
  • on - turn LED on
  • off - turn LED off
  • toggle - flip the LED to the opposite state
\ Demonstrates 3 levels of Forth: HAL, Primitives, User 
\ Setup on-board LED on Arduino UNO
\ Goal: BD_LED (on/off/blink/toggle)

\ HAL: provides constants for LEDs and Buttons
$0024 constant DDRB  \ Port B data direction register
$0025 constant PORTB  \ Port B output register
%00100000 constant BD_LED \ Pin 5, Port B

\ PRIM: primitives for setting up LED
: PB_output ( bit -- ) DDRB mset ;  \ set a B pin as output
: init ( --- ) \ initialize ports for LED
	BD_LED PB_output
  ;
init

\ USER: user interface
: on ( pin -- ) PORTB mset ; \ set a B pin high
: off ( pin -- ) PORTB mclr ;  \ set a B pin low
: toggle ( pin -- ) PORTB c@ xor PORTB c! ; \ toggle the B pin
: blink ( pin -- ) \ blink B pin 
  begin 
	dup toggle 200 ms key?
  until
  drop ;

\ BD_LED ( on | off | blink | toggle )

And finally

Having this code makes it easy to integrate into other Forth programs. As I write additional code, I’ll keep this code as part of a library. When I want to indicate something happening, its easy to remember “BD_LED on|off|toggle|blink” as an easy method of indicating something is happening (or not happening).

Comments powered by Talkyard.