More about SwiftX…

Embedded Programming Example

compiler generators based on ANSI Forth

The process of cross compiling a program in SwiftX is consistent with the recommended practices of top-down design and bottom-up coding and testing. However, Forth adds another element: extreme modularity. You don't write page after page of code and then try to figure out why it doesn't work; instead, you write a few brief definitions and exercise them, one by one.

As one of many examples, suppose we are designing a washing machine. The high level definition might be:

: WASHER ( -- ) WASH SPIN RINSE SPIN ;

The colon indicates that a new word is being defined; following it is the name of the new word, WASHER. The remainder are the words that comprise this definition. Finally, the definition is terminated by a semi-colon.

Typically, we design the highest-level routines first. This approach leads to conceptually correct solutions with a minimum of effort. But in Forth — SwitfX's native "compiler compiler" language — words must be compiled before they can be referenced, so code listings begin with the most primitive definitions and end with the highest-level words.

A complete, annotated listing of the washing machine program is given below. Items in parentheses or following a backslash (\) are comments. The first few lines define a hardware port address (in this case, on a 68HC12) and six bit masks used to access individual bits on that port. Subsequent lines define application words that do the work.

The source code in this example is nearly self-documenting; the comments identify groups of words and show the parameters being passed to certain words. When reading,

: WASHER ( -- ) WASH SPIN RINSE SPIN ;

it is obvious what RINSE does. To determine how it does it, you read:

: RINSE ( -- ) FILL-TUB AGITATE DRAIN ;

When you wonder how FILL-TUB works, you find:

: FILL-TUB ( -- ) FAUCETS ON TILL-FULL FAUCETS OFF ;

Reading further, one finds that FAUCETS is a mask specifying the bit of the port that controls the faucet, while ON is a word that turns on that bit.

Even from this simple example, it may be clear that Forth is not so much a language as a tool for building application-oriented command sets. The definition of WASHER is based not on low-level Forth words, but on words with names like SPIN and RINSE that make sense in the context of the application.

When developing this embedded program, you would follow your top-down logic, as described above. But when the time comes to test the application code example using SwiftX's Cross-Target Link (XTL), you will see the real convenience of Forth's interactivity.

If your hardware is available, your first step would be to see if it works. Even without the code in the file, you could read and write to target hardware registers by typing phrases such as:

HEX PORT C@ .

The word C@ fetches a character (byte) from the address specified by PORT. This would read port address 01 and display its current bit values. The similar phrase PORT C! (used in ON and OFF) stores a value in the address.

You could also type:

2 ON 2 OFF

to see if the clutch, controlled by the bit masked by 2, engages and disengages. If the hardware is unavailable, you might temporarily re-define PORT as a variable you can read and write, and so test the rest of the logic.

You can load your source code file using the command INCLUDE <filename>, whereupon all functions defined in the specified file are parsed and available for testing. You can further exercise your I/O by typing high level, application-appropriate phrases such as:

MOTOR ON

or

MOTOR OFF

…to see what happens. Then you can exercise your low-level words, such as:

DETERGENT ADD

…and so on, until your highest-level words are tested.

As you work, you can use any additional embedded systems programmer aids provided by SwiftX. You can easily change your embedding code and re-load it. But your main ally is the intrinsically interactive nature of Forth itself, the programming language with which SwiftX cross compilers are generated.

Annotated example source code

( Washing Machine Embedded Application )
\ Port assignments
01 CONSTANT PORT

\ bit-mask     name       bit-mask      name
    1 CONSTANT MOTOR       8 CONSTANT FAUCETS
    2 CONSTANT CLUTCH   16 CONSTANT DETERGENT
    4 CONSTANT PUMP       32 CONSTANT LEVEL

A colon begins a new definition.

\ Device control
: ON ( mask -- ) PORT C@ OR PORT C! ;
: OFF ( mask -- ) INVERT PORT C@ AND PORT C! ;

Definitions can contain generic SwiftX words and any others you've defined…

\Timing functions
: SECONDS ( n -- ) 0 ?DO 1000 MS LOOP ;
: MINUTES ( n -- ) 60 * SECONDS ;

: TILL-FULL ( -- ) \ Wait till level switch is on
     BEGIN PORT C@ LEVEL AND UNTIL ;

…so, application-specific functions are defined in terms of previous definitions…

\ Washing machine functions
: ADD ( port -- ) DUP ON 10 SECONDS OFF ;
: DRAIN ( -- ) PUMP ON 3 MINUTES ;
: AGITATE ( -- ) MOTOR ON 10 MINUTES MOTOR OFF ;
: SPIN ( -- ) CLUTCH ON MOTOR ON
    5 MINUTES MOTOR OFF CLUTCH OFF PUMP OFF ;
: FILL-TUB ( -- ) FAUCETS ON TILL-FULL FAUCETS OFF ;

\ Wash cycles
: WASH ( -- ) FILL-TUB DETERGENT ADD AGITATE DRAIN ;
: RINSE ( -- ) FILL-TUB AGITATE DRAIN ;

…until you reach the main application definition.

\ Top-level control
: WASHER ( -- ) WASH SPIN RINSE SPIN ;
size in memory of embedded application

The final program (with the XTL and unused functions removed from the kernel) is quite small, as you can see in the table. The RAM figure includes stack space and system variables. Stack requirements vary when cross compiling, depending on the processor; for example, an 8051 would require less than the amounts shown here. With most microcontrollers, the compiled code can reside in the target's on-board ROM and RAM.