Autres articles / Other articles

FlashForth elements - part 1 - Introduction

published: 4 September 2022 / updated 17 February 2023

Lire cette page en français

 

 

This report is a remix of material from a number Forth tutorials and references, adapted to the FlashForth environment. It provides some examples and explanation of using FlashForth on a PIC18 microcontroller while concentrating on the features of the language rather than the details of the microcontroller hardware. Following an introduction to the FlashForth interpreter, we look at adding our own word definitions to the dictionary and then explore the manipulation of data values on the stack. Flow of program control and more advanced defining words are also explored. These defining words are convenient for making arrays. Finally, strings and formatted numeric output are discussed.

Authors: Peter Jacobs, Pete Zawasky and Mikael Nordman

Introduction

Forth is an interesting mix of low-level access tools and language building tools. It is effectively a small toolkit with which you construct a specialized dictionary of words that work together to form your application code. This tutorial will explore and explain the workings of the FlashForth toolkit running on a Microchip PIC18 microcontroller and complements the more hardware-oriented tutorial, the FlashForth quick reference and the FlashForth website. Our interest is in using Forth on the microcontroller in an embedded system, such as a special-purpose signal timing device, rather than as part of a general-purpose calculation on a personal computer.

There are quite a number of good introductory tutorials, course notes, and references for programming in forth on a desktop or laptop computer, however, FlashForth running on a PIC18 microcontroller is a different environment. In the following sections, we will follow closely J. V. Noble’s tutorial, reusing as many of his examples and explanations verbatim, while adapting the overall tutorial to FlashForth.

Getting started

Although we will be using FlashForth on a PIC18 microcontroller, we communicate with it using a serial terminal program running on a personal computer. On linux, the gtkterm terminal program is convenient and can be started with the command

$ sudo gtkterm --port=/dev/ttyUSB0 --speed=38400 --delay=10 --flow=Xon

Pressing the ENTER ←- key a couple of times should get the display as shown in Figure 1.

The ok<$,ram> prompt indicates that the current base for representing numbers in hexadecimal format and that the current context for making variables is static RAM, rather than the Flash memory and EEPROM that is also available in the microcontroller.

Opening screen using gtkterm

In contrast to Forth on a PC, FlashForth is case sensitive, with most predefined words being spelled with lower case. Also, being intended for use in an embedded system, there is no command to exit the system. FlashForth only stops when the power is removed or a reset occurs.

The interpreter

FlashForth is an interactive programming language consisting of words. Forth words are the equivalent of subroutines or functions in other languages and are executed by naming them. Although FlashForth is interactive at its core, the user doesn’t need to interact with an embedded application if its top-level word is set to automatically execute at power-up. Here is an example of executing a FlashForth word:

decimal ←- ok<#,ram>

This executes the word that sets the base for representing numbers to 10, a format that you are likely to be more familiar with unless you are a student of mechatronics or computing. Now, let’s try something a bit more interesting by entering:

2 17 + .    \ display: 19 <#,ram> 

This time FlashForth more clearly shows its interpretive nature. A small program called the outer interpreter continually loops, waiting for input from the serial port. The input is a sequence of text strings (words or numbers) separated from each other by the standard Forth delimiter, one or more ASCII blank characters.

The text strings are interpreted in only three ways: words (subroutine or function names), numbers, or not defined. The outer interpreter tries first to look for the incoming word in the dictionary that contains the already defined words. If it finds the word, it executes the corresponding code.

If no dictionary entry exists, the interpreter tries to read the input as a number. If the string satisfies the rules for defining a number, it is converted to a number in the microcontroller’s internal representation, and stored in a special memory location, called the top of stack (TOS).

In the example above, FlashForth interpreted 2 and 17 as numbers, and pushed them onto the stack. + is a predefined word, as is ., so they are looked up and executed. The +(plus) word removed 2 and 17 from the stack, added them together, and left the result 19 on the stack. The word . (dot) removed 19 from the stack and sent it on the standard output device, the serial port for FlashForth. Here is a picture of the stack through the process.

word executed 2 17 + .
stack resultTOS21719__
NOS__2____

We might also work in hexadecimal:

hex  
0a 14 * .    \ display: c8 <#,ram> 

If you want to explicitly indicate the base of a number, you can prepend a sigil to the digits of the number. For example, $10, #16 and %10000 all represent the decimal value sixteen.

If the incoming text cannot be located in the dictionary nor interpreted as a number, FlashForth issues an error message.

0A    \ display: 0A ? 
thing \ display: thing ? 

Note that the apparent hexadecimal number 0A was not interpreted as such because of the case sensitivity of FlashForth. Other error messages that you might see include SP ?, for a stack pointer error, and CO ?, for a context error. If the word * was to be executed without there being at least two numbers sitting on the stack, the interpreter would abort, issuing the SP error message, and then wait for new input.

Finally, here is the classic Hello World! program.

: hey  
    ." Hello, World!"  
  ; 

Forth lets you output text using the word ." while the words : and ; begin and end the definition of your own word hey. Note that blank characters are used to delimit each of these words. Now, type in hey and see what happens.

hey  
\ display: Hello, World! ok<$,ram> 

In FORTH language, words are separated by white space - spaces, tabs, and newlines.

Any string of printing characters is a word: asdf foo jello @W#$ gibbet.

The Forth interpreter is very simple. It parses the next word (i.e. it skips whitespace, then collects characters until it sees another whitespace character) and executes it.

So if you are trying to understand a Forth program in detail, you have to look at each word in turn and work out what it does. That sounds simple, but it will trip you up if you insist on looking for algebra. Just go left to right, one word at a time.