MSP430 Forth

After the general discussion of Forth we may now look at the Forth implementation used for the MSP430. 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 TOS register, R7. Register, R5, DP, 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.

Return Stack

The return stack for the Forth word calls is controlled by the stack pointer SP in the MSP430. 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! @ ! FLASH!

The MSP430 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 or I/O 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 MSP430 is accessible for reading by using C@ and @. 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. C@ and @ may then use this address to return the stored values.

Note: C,-T and ,-T only store in the Host image. You need to use ?DNLD to update the Flash in Remote.

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 + C@ ;

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

FLASH! allows the Flash write word, sector erase and erase to be performed. The value to be stored is placed on the data stack along with the address and the Flash function including the $A5 in the upper byte to enable the write. This is the function used by IRTC to program the code.

C@ (S a - b ) Fetch a byte b from address a.

@ (S a - n ) Fetch a word n from address a.

C! (S b a - ) Store byte b to address a.

! (S n a - ) Store word n to address a.

FLASH! (S n a $A540 - ) Store word n to address a in Flash module.

FLASH! (S a $A502 - ) Erase sector at address a in Flash module.

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 RAM 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. 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 MSP 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 MSP430 machine code and optimises it as it goes it may be reassuring to see the result. IRTC contains a simple de-compiler which decompiles the code using only the basic set of MSP430 op-codes. It decompiles until it reaches a RET instruction which is the normal end to a definition. If one is not found it will continue until the ESC key is pressed.

SEE SERVER

[ SERVER ]

FF16 12B0 CALL #[ ADDR@ ]

FF1A 12B0 CALL #[ EXECUTE ]

FF1E 8325 SUB #2 , R5

FF20 4785 MOV R7 , 0000(R5

FF24 4037 MOV #0090 , R7

FF28 12B0 CALL #[ CHAR-OUT ]

FF2C 3FF4 JMP [ SERVER ]

FF2E 4130 RET ok

As you may see, if an address that is known to be that of a Target word is found, the de-compiler substitutes the Target word name for the hex address.

You may also LIST an area of code by specifying the start address and the number of instructions to list.

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 RAM locations and C@ will return the value of a single location.

To read from Port1 all we need is:

I/O P1IN C@ H.

This returns the value of Port1.

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 Port1, first P1DIR 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 P1DIR C!

The port bit may then be set high with:

1 I/O P1OUT C!

or low with:

0 I/O P1OUT C!

The same applies to any other ports, timers or internal function.

 

Contents