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.
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.
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.
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.
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.
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.
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.
+ (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.
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.
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.
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 - )
value1 OF words ENDOF
value2 OF words ENDOF
...
default words ( otherwise clause )
ENDCASE
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.
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.
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.
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.