AVR Forth

After the general discussion of Forth we may now look at the Forth implementation used for the AVR. IRTC creates subroutine threaded code with optimisation if the FAST-CODE directive is used. Many Forth words are compiled as in-line code to further improve execution speed.

Data Stack Size

The Forth Data stack size is 16 bits. Eight bit byte values are represented as a 16 bit value with the upper 8 bits set to zero. 32 bit, double, values are represented by two stack values, the top value the most significant. The top stack value is always in the TOSL, TOSH registers, R26 and 27, the X register. The Y register, R28, DPL and R29, DPH, holds the pointer to the rest of the data stack. Macros PUSHT and POPT push and pop the top stack item on and off the external stack. Also R24 and R25 are used as a temporary second stack item, SECL and SECH, that has macros PUSHS and POPS.

Return Stack

The return stack for the Forth word calls is controlled by the stack pointer SPL and SPH in the AVR I/O space. The PUSH and POP mnemonics control the stack along with CALL and RET.

Data Stack Operators, DUP SWAP OVER ROT DROP NIP TUCK

As all the parameters are passed on the stack Forth has several words to manipulate the stack contents.

DUP (S s1 - s1 s1 ) will duplicate the top stack item, leaving two identical values.

SWAP (S s1 s2 - s2 s1 ) exchanges the top two stack items.

OVER (S s1 s2 - s1 s2 s1 ) copies the second stack item onto the top of the stack, pushing down the first.

ROT (S s1 s2 s3 - s2 s3 s1 ) brings the third stack item to the top of the stack and the top and second are pushed down.

DROP (S s1 - ) removes the top stack item, opposite to duplicate.

NIP (S s1 s2 - s2 ) removes the second stack item.

TUCK (S s1 s2 - s2 s1 s2 ) copies the top stack item beneath the second stack item.

These operators allow us to use the stack for local storage during words and re-ordering the results to pass on to the next word.

Memory Operators C@ C! @ ! MC@ and M@

The AVR has two memory areas to which it has access, code and RAM. C@ and C!, fetch from and store to a single 8 bit RAM location. These two words are optimised during compilation if fixed values, literals, are used as the operands. By using I/O before an I/O register C@ and C! may be used to access the I/O locations as though they are RAM. @ and ! are 16 bit RAM operators. All these use a 16 bit address to access the location.

The code space in the AVR is only accessible indirectly via the Z register and the LPM instruction. Tables may be made using CREATE to construct lists of values in the code space with C,-T and ,-T the compiler words to store bytes and words into the code space. When the table name is later executed it leaves a 16 bit address on top of the stack that points to the beginning of the list. MC@ and M@ may then use this address to return the stored values.

Note: The address used by MC@ and M@ is a byte address not a word address. CREATE leaves a word address and this must be doubled.

CREATE 7-SEG.DIGITS (S - a )

( 0 ) $3F C,-T ( 1 ) $06 C,-T ( 2 ) $5B C,-T ( 3 ) $4F C,-T

( 4 ) $66 C,-T ( 5 ) $6D C,-T ( 6 ) $7D C,-T ( 7 ) $07 C,-T

( 8 ) $7F C,-T ( 9 ) $6F C,-T

 : GET-DIGIT (S b - b' ) 7-SEG.DIGITS 2* + MC@ ;

Here the table returns an address which is doubled by 2* and added to the offset of the digit required by +. MC@ uses the new byte address to fetch the value to the stack.

Logical Operators, AND OR XOR NOT

In embedded controllers the need for logical operators is paramount for the bit control necessary.

AND (S s1 s2 - s3 ) creates the logical AND of the top two stack items.

OR (S s1 s2 - s3 ) creates the logical OR of the top two stack items.

XOR (S s1 s2 - s3 ) creates the logical XOR of the top two stack items.

INVERT (S s1 - s1 ) inverts all the bits of the top stack item.

The code produced is in-line and optimised.

Bit Control, ON OFF CHECK

These are TRANSITIONal definitions that do not appear in the Library. Their Library counterparts are SET-BITS, RES-BITS and TEST-BITS.

ON (S m fa - ) takes an 8 bit mask and address and sets all the bits set in the mask high at the address leaving the rest unchanged.

OFF (S m fa - ) takes an 8 bit mask and address and clears all the bits set in the mask low at the address leaving the rest unchanged.

CHECK (S m fa - m' ) returns the logical AND of the 8 bit mask and the contents of the address.

These are only 8 bit functions and operate on any file address. They may be used to manipulate flags, registers or ports producing tight code.

Maths, + - UM* UM/MOD

 + (S s1 s2 - s3 ) adds the top two stack items leaving the result on top of the stack.

- (S s1 s2 - s3 ) subtracts the top stack item from the second leaving the result on top of the stack.

UM* (S s1 s2 - s3 s4 ) multiplies the top two stack items to leave a double result on top of the stack i.e. 8*8=16, 16*16=32 bits.

UM/MOD (S s1 s2 s3 - s4 s5 ) divides the double value under the top stack item by the top stack item leaving two values, remainder second and quotient on top i.e. 16/8=8+8. 32/16=16+16 bits.

Increment and Decrement, 1+ 2+ 1- 2- 2* 2/

1+ (S s1 - s2 ) increments the top stack item by one.

2+ (S s1 - s2 ) increments the top stack item by two.

1- (S s1 - s2 ) decrements the top stack item by one.

2- (S s1 - s2 ) decrements the top stack item by two.

2* (S s1 - s2 ) arithmetic left shift of the top stack item by one.

2/ (S s1 - s2 ) arithmetic right shift of the top stack item by one.

Constants and Variables

Often we require a value that is fixed and used throughout our application, for example the bit on a port. This may be compiled as a CONSTANT;

 2 CONSTANT PUMP

IRTC will cause this to be compiled as an in-line literal whenever the word PUMP is encountered. If the COMPACT-CODE directive is set, a subroutine will be compiled immediately and all references will be via calls. This may save code space but does not allow the optimiser to reduce code.

When the value required needs to be changed during execution a VARIABLE is defined;

VARIABLE COUNTER

Here VARIABLE compiles an in-line literal of the address of a 16 bit, two byte, location in the file. These locations start at the address in RAM-START and an error is generated if too many variables are declared resulting in the pointer going above the value in RAM-END. The variable allocation pointer is incremented automatically by each variable declaration. The pointer may be set absolutely by VORG. VARIABLE allots two bytes and CVARIABLE one byte. @ and C@ may be used to access the contents, ! and C! to change the value.

Note: The contents of a variable location are undefined at compile time. It is necessary to initialise the contents at the start of your application if not implicitly done so by your code.

Control Structures

The flow of control in a program is normally sequential; control structures allow you to alter this by a branch or loop. The change is often brought about by a piece of (runtime) data. This gives the program the ability to choose which direction it will take.

Control structures must be used inside a : definition and may not start in one definition and finish in another. They cannot be used directly from the command line. But they may be nested inside one another as long as they do not overlap.

IF ... THEN

Use: flag IF true words THEN

The 16 bit flag controls the outcome and is consumed by IF. If the flag is non-zero the true words are executed, otherwise they are not.

If the flag is required again it may be duplicated by DUP. If the flag value is only required between IF .. THEN it may be conditionally duplicated by ?DUP.

IF ... ELSE ... THEN

 Use: flag IF true words ELSE false words THEN

This is similar to IF ... THEN above but with execution phrases for both true and false (zero) values of the flag.

BEGIN ... AGAIN

Use: BEGIN words AGAIN

This structure forms an endless loop that is only terminated by a AVR reset. This is often the structure used in your application run word.

BEGIN ... UNTIL

Use: BEGIN words flag UNTIL

This structure forms a loop that is always executed at least once. The loop will return to BEGIN as long as the 16 bit flag is false (zero) when tested by UNTIL. As soon as flag is true (non-zero) the loop is terminated. The flag is consumed by UNTIL in both cases.

BEGIN ... WHILE ... REPEAT

Use: BEGIN words flag WHILE words REPEAT

This is perhaps the most powerful of the Forth control structures. The loop starts at BEGIN and executes all the words as far as WHILE. If the 16 bit flag on the data stack is true (non-zero) the words between WHILE and REPEAT are executed, and the cycle returns to BEGIN. If the flag tested by WHILE is false (zero) the loop is terminated. WHILE consumes the flag.

CASE ... OF ... ENDOF ... ENDCASE

Use: CASE (S b - )

The case statement used in IRTC is the result of a competition run by the Forth Interest Group (FIG) in the USA. It was invented by Dr. Charles E. Eaker, and was first published in Forth Dimensions, Vol. II No. 3.

CASE exists to replace large and unwieldy chains of IF ... ELSE ... THEN statements.

The function of CASE is to execute one action dependent on the 16 bit value passed to it on the stack. The OF words compare value1,2 etc. to the stack value and if equal the words between OF and ENDOF are executed, the structure is then exited. If no match is found the words between the last ENDOF and ENDCASE are executed with the input stack value still on the data stack.

FOR ... NEXT

Use: index FOR words NEXT

This is the simplest counted loop in IRTC. The index is transferred to the Return Stack and the words between FOR and NEXT executed. NEXT decrements the index and if true (non-zero) returns to FOR. If the index decrements to zero, false, it is removed from the Return Stack and the loop terminates.

DO ... LOOP

Use: limit index DO words LOOP

DO transfers the 16 bit values of the loop limit and the start index to the Return Stack. The words between DO and LOOP are executed at least once. LOOP increments the index and tests it against the limit. If the index is still less than the limit the loop executes again. If not the Return Stack is cleared and the loop exits.

To leave the loop early use LEAVE. This forces the index to be the same as the limit so that the loop will exit when LOOP is next run.

Conditional Tests

0= (S s1 - f ) the flag is true if the top stack item is zero.

0< (S s1 - f ) the flag is true if the top stack item is less than zero.

0> (S s1 - f ) the flag is true if the top stack item is positive, greater than zero.

= (S s1 s2 - f ) the flag is true if the top two stack items are equal.

<> (S s1 s2 - f ) the flag is true if the top two stack items are not equal.

< (S s1 s2 - f ) the flag is true if s1 is less than s2.

> (S s1 s2 - f ) the flag is true if s1 is greater than s2.

U< (S s1 s2 - f ) the flag is true if s1 is logically less than s2.

U> (S s1 s2 - f ) the flag is true if s1 is logically greater than s2.

Seeing is believing

As IRTC creates AVR machine code and optimises it as it goes it may be reassuring to see the result. The words SEE and LIST allow the compiled code to be dissassembled e.g.

SEE SERVER <cr>

[ SERVER ]
0111    RCALL [ ADDR@ ]
0112    RCALL [ EXECUTE ]
0113    ST -Y , TOSL
0114    ST -Y , TOSH
0115    LDI TOSL , $A5
0116    LDI TOSH , $00
0117    RCALL [ CHAR-OUT ]
0118    RJMP [ SERVER ]
0119    RET ok

This is an example of the SERVER word dissassembled from the Mega16 TLM.

Controlling a Port

Now we have communication we may modify any of the RAM locations, hence the ports. The command FDUMP will list all the file locations and C@ will return the value of a single location.

To read from PortA all we need is:

I/O PORTA C@ H.

This returns the value of PortA.

all the registers and flags are known to IRTC, if you use one from a device other than the one chosen an error will be issued.

To write to PortA, first DDRA must be set to an output on the port pin we wish to use. Say we are using bit 0, then we could set this to an output with:

$01 I/O DDRA C!

The port bit may then be set high with:

1 I/O PORTA C!

or low with:

0 I/O PORTA C!

The same applies to any other port timer or internal function.

 

Contents