Programming in PicForth

PicForth will be familiar for anyone who is already familiar with Forth, but it does have its own peculiarities. For those who are not already introduced to Forth, I recommend picking up a couple of Forth references and tutorials at your local library. Here, I can only emphasize the differences between PicForth and ANS standard Forth, rather than give a complete tutorial on the language.

A Simple PicForth Program

The PicForth entry point is at the main word.
\ Sample PicForth program.

fload picf16.f    \ Load standard definitions.

\ Sum the positive integers from 1 to n.
: sumn ( n -- sum )
    0 swap   \ Sum on the stack.
        1+ 1 do   \ Loop from 1 to n.
           I +
        loop
;

: main   \ PicForth entry point is here.
   \ Print the sums of the positive integers to 100.
        101 0 do 
           I sumn .
        loop
   
   begin again  \ Don't let the main word end.
;
 

Differences from ANS Standard Forth

PicForth is not interactive or extensible. Any words that extend the compiler have been eliminated (specifically DOES>) as I have not yet established how they should behave in a cross compiler environment.

PicForth has two dictionaries that are searched separately: one for compiling mode that is searched in colon definitions, the other for interpreting mode that is searched outside of colon definitions. Colon definitions only add to the compiling dictionary, there are no mechanisms for adding to the interpreting dictionary. This may change in the future.

PicForth and its memory model do not support the string handling, conversion, and I/O functions of a full blown Forth system. It would surprise me if anyone wanted to do these things on a microcontroller with less than 450 bytes of ram, so they are not included. Specifically words like <#, #>, >BODY, and EVALUATE are not included, although . (dot) and ." (dot-quote) are included, as the microcontroller is likely to be attached to an LCD display.

Variables are declared outside of colon definitions, but must be initialized with colon definitions. Because the PIC does not have a contiguous memory map, but rather it is broken into banks, ALLOT is not used. Instead use ARRAY to declare a fixed size array, the compiler will give an error message if it can not fit the array into available ram. Similarly, , (comma) and C, are not included as they depend on contiguous memory allocation. Single byte variables can be allocated with CVARIABLE.

Similarities to ANS Standard Forth

PicForth was developed with the ANS standard in mind. Much of the standard core and extended core dictionary is implemented. Cell width is 16 bits.

Extending the Return Stack

The PIC family of microcontrollers has a small hardware return stack that optimizes the call/return sequence. In the 17CXX series, the size has been enlarged from previous versions to 16 words deep, unfortunately, this may still not be enough for a complex application that nests functions deeply and/or makes use of complex interrupt handling routines. In particular, Forth applications tend to nest words deeply, and this can create problems since there is no way to spill those stack elements to ram.

One solution might be to use an inner interpreter, which for performance reasons PicForth chooses not to use (PicForth is subroutine threaded.) The answer in PicForth is to selectively use an alternate call/return mechanism when performance and code/space are not important. To invoke the alternate mechanism, a new word is defined, :: (double-colon), which is used the same way as the normal : (colon) to define words. Unfortunately, words defined with :: use an inefficient calling mechanism that takes several instructions to invoke (it takes 5 extra instructions (codespace) to make a call this way, and is even more costly in terms of clock cycles) and should only be used for infrequently called words. Also the address of words defined this way should not be taken with ['] or called from assembly.

Enabling and Disabling Interrupts

Directly manipulating the GLINTD (global interrupt disable) bit can lead to hard to detect problems. For example say some piece of code first disables interrupts, does some processing, and then reenables interrupts. Unfortunately, during the processing it called a procedure that also needed to disable interrupts. Since both procedures were directly manipulating the GLINTD bit, the inner procedure inadvertently enabled interrupts before it passed on control to the outer procedure. But of course, the outer procedure had expected interrupts to be disabled until it had explicitly enabled them.

In order to solve this problem, two procedures are given: enable_ints and disable_ints. There is a lock count on the GLINTD bit that is incremented when disable_ints is called, and decremented when enable_ints is called. Only when the lock count reaches zero will the interrupts be re-enabled.

Writing Interrupt Handlers

Writing interrupt handlers for PIC processors used to be like defusing bombs. And the application notes from Microchip are not much help either because they make too many simplifying assumptions about your code. Now writing interrupt handlers in PicForth is easy. The basic syntax to declare an interrupt handler is:

n :interrupt interrupt_name

Where n is the number of the interrupt vector that you want to handle, eg. 32 is the peripheral interrupt vector (consult the Microchip documentation for the address of other interrput vectors.). An interrupt handler is ended with interrupt; instead of just a ; (semicolon.)

You also must include the interrupt entering and leaving routines by including in your code

fload interupt.f

before you declare an interrupt handler. You may want to write the interrupt handler in inline assembly, or if you are not worried about nesting the return stack too deep, you can write interrupt handlers in high level Forth. See here for an example of a peripheral interrupt handler in action.