Installing FlashForth

The PIC18 version has been updated to be compiled with PIC-AS. MPASM is not supported anymore.

Follow the instructions in PIC18/install.txt

At least the following PIC processors are able to run FlashForth 5

18f242   18f442   18f252   18f452
18f248   18f258   18f448   18f458
18f2455  18f2550  18f4455  18f4550
18f2420  18f2520  18f4420  18f4420
18f2525  18f2620  18f4525  18f4620
18f6527  18f6622  18f6627  18f6722
18f8527  18f8622  18f8627  18f8722
18f2458  18f2553  18f4458  18f4553
18f2480  18f2580  18f4480  18f4580
18f2423  18f2523  18f4423  18f4523
18f2585  18f2680  18f4585  18f4680
18f2682  18f2685  18f4682  18f4685
18f14k50 18f24k50 18f25k50 18f45k50
18f24K20 18f25k20 18f26k20
18f44k20 18f45k20 18f26k20
18f14k22 18f24k22 18f25k22 18f26k22
18f44k22 18f45k22 18f46k22
18f24k42 18f25k42 18f26k42 18f27k42 
18f45k42 18f46k42 18f47k42 18f55k42 
18f56k42 18f57k42
18f25k83 18F26k83
18f65k90 18f66k90 18f85k90 18f86k90
18f25q43 18f26q43 18f27q43
18f26q71 18f46q71 18f56q71

Inlining of words

FF can compile location independent assembler primitives as inline code. The shortest of these words have the inline bit set in the word header for automatic inlining.

Individual words can be inlined by prefixing the word with INLINE.

: newswap inline swap ; 

When compiling a new word that should be inlined automatically, the inline flag can be set with the word INLINED.

: 1+
  [ Sminus w, a, swapf, ]  \ Decrement stack pointer with one
  [ Splus  f, a, infsnz, ] \ Add lower byte, skip next instruction if the result was nonzero
  [ Srw    f, a, incf, ]   \ Add high byte
; inlined                  \ Set the inline header flag

On the PIC18 the following words are always inlined by the compiler.

[i i] drop p+ cwd r@ r> >r rdrop false true 1 endit cell chars di ei

On PIC18 the following words can be prefixed with INLINE.

mset mclr lshift rshift sp@ swap over rot dup + m+ - and or
xor invert 1+ 1- 2+ 2* 2/ !p @p p++ p2+ ticks 
Also words defined by CONSTANT, VARIABLE, 2CONSTANT and 2VARIABLE can be inlined. They compile the constant and the variable address as inline literal code.

If you append the definition with INLINED, the compiler will later compile the constant as an inline literal.

34 constant thirtyfour inlined
: native-inline-34 thirtyfour ;

Interrupt handling

Interrupt routines can be written in assembly or in Forth. FF interrupt words have to be ended with ;I .

On PIC18 Forth the interrupt word has its own parameter stack of 8 cells.

In general Forth words that normally would be used in an interrupt word are interrupt safe.
Words that start the interpreter or compile new words should not be used in an interrupt.
It is not possible to store to flash or eeprom in an interrupt routine.

The following words are not interrupt safe:


The following registers are saved on the return stack by [I and restored by I] :


The following registers are always preserved before the interrupt word is executed:


Below is a interrupt word which counts the total number of interrupts.

ram variable irq_counter
: my_irq
    irq_counter @
    irq_counter !

To activate the interrupt you store the interrupt word xt into the interrupt vector. For PIC18 the interrupt vector is always zero for high priority interrupt handling. The PIC18 low priority interrupts are used internally by FlashForth for the millisecond tick interrupt and for UART receive interrupts.

' my_irq 0 int!

The interrupt vector in ram is cleared at warm start, so to enable the interrupt word at startup, a initialization word must be used.

: irq_init ['] my_irq 0 int! ;
' irq_init is turnkey

The above example is a simple one for PIC18. To use individual interrupt sources the interrupt enable bits and flag bits for each interrupt source must be used.

See servo.txt for an example for a complete servo control routine that uses a timer and interrupts to control 4 servo channels.

Below is the interrupt counter implemented in assembly.

$28 as3 incf,                 ( f d a -- )  
$48 as3 infsnz,               ( f d a -- )
: lfsr,    ( k f -- )
  4 lshift over 8 rshift $f and or $ee00 or i, $ff and $f000 or i, ;  

1     con f,      \ Destination File
0     con a,      \ Force Access Bank
1     con Treg
$ffe6 con Tplus   \ Treg (FSR1) is interrupt safe
ram   variable irq_counter

\ Interrupt routine written in assembly
: my_irq
  [ irq_counter Treg lfsr,  ]
  [ Tplus f, a, infsnz,     ]
  [ Tplus f, a, incf,       ]

By going to compile state before end-of-line, there will be less writes to FLASH and EEPROM and the compilation process will go faster.

Register usage

The PIC hardware stack is used as the Forth return stack.
The FSR0 register is used as the parameter stack pointer. It is called S in the assembler code.
FSR1 is used as a temporary pointer and as temporary storage. It is called T in the assembler code.
FSR2 is used as the return stack pointer by R> >R R@ and for saving the hardware return stack when multitasking. It is called R in the assembler code.
PCL, PCLATH, TBLPTRL TBLPRH are used for accessing flash memory. PCLATU and TBLPTRU must be zero at all times.

Sample session for PIC18F

FlashForth V3.4 PIC18F258
decimal  ok<#,ram>
255  ok<#,ram>255
$ff  ok<#,ram>255 255
%11111111  ok<#,ram>255 255 255
bin  ok<%,ram>11111111 11111111 11111111
hex  ok<$,ram>ff ff ff
2drop drop  ok<$,ram>
p2+     pc@     @p      m?      b?      rdrop   leave   next
for     in,     inline  repeat  while   again   until   begin
else    then    if      until,  again,  begin,  else,   then,
if,     not,    nc,     nz,     z,      br?     true    false
dump    .s      words   >pr     .id     ms      ticks   s0
latest  state   bl      2-      [']     -@      ;       :noname
:       ]       [       does>   postpone        create  cr      [char]
(       char    '       abort"  ?abort  ?abort? abort   prompt
quit    .st     inlined immediate       shb     interpret       'source >in
tib     ti#     number? >number sign?   digit?  find    immed?
(f)     c>n     n>c     @+      c@+     place   cmove   word
parse   \       /string source  user    base    pad     hp
task    rcnt    ssave   rsave   ulink   bin     hex     decimal
.       u.r     u.      sign    #>      #s      #       >digit
<#      hold    up      min     max     ?negate tuck    nip
/       u*/mod  u/      *       u/mod   um/mod  um*     ukey?
ukey    uemit   p++     p+      pc!     p!      p@      r>p
!p>r    !p      u>      u<      >       <       =       0<
0=      <>      within  +!      2/      2*      >body   2+
1-      1+      negate  invert  xor     or      and     -
m+      +       abs     dup     r@      r>      >r      rot
over    swap    drop    allot   ."      s"      type    accept
1       umax    umin    spaces  space   2dup    2drop   2!
2@      cf,     chars   char+   cells   cell+   aligned align
cell    c,      ,       here    dp      ram     eeprom  flash
c@      @       c!      !       sp@     con     constant        variable
@ex     execute key?    key     emit    cold    warm    btfss,
btfsc,  bsf,    bcf,    bra,    rcall,  call,   goto,   br3
br2     as1     as3     rshift  lshift  ic,     i,      operator
cpu_clk mtst    mclr    mset    iflush  pause   turnkey is
to      defer   value   cwd     literal irq     ;i      di
ei      scan    skip    n=      rx1?    rx1     tx1     i]
[i      andlw,  movf,   w,      a,      exit     ok<$,ram> 

 \ Compile a word which creates indexed cell arrays in current data memory.
 : array create cells allot does> swap 2* + ; ok<$,ram>
 \ Create an array with elements in program flash
 flash #10 array flash-array  ok<$,flash>
 \ Get the address of element 0
 0 flash-array hex ok<#,ram>30b6
 \ Create an array with elements in eeprom
 eeprom #30 array eeprom-array  ok<$,eeprom>30b6
 0 eeprom-array  ok<$,ram>30b6 ec0c
 \ Create an array with elements in ram
 ram $20 array ram-array  ok<#,ram>30b6 ec0c
 0 ram-array  ok<$,ram>30b6 ec0c f42a
 2drop drop  ok<$,ram>

 \ move 10 cells
 0 flash-array 0 ram-array #10 cells cmove  ok<$,ram>
 0 flash-array 10 dump
 30b6 :f0 f1 f2 ff ff ff ff ff ff ff ff ff ff ff ff ff ................ ok<$,ram>
 0 ram-array 10 dump
 f42a :f0 f1 f2 ff ff ff ff ff ff ff ff ff ff ff ff ff ................ ok<$,ram>

 \ Define a task loop that toggles PORTC outputs based on the
 \ bitmask, delay determines the toggle period.
 \ delay and bitmask are user variables to make it possible
 \ to use the same task loop in many tasks, so that
 \ each task can have it's own bitmask and delay values.
 \ The compilation of the task definition words is not shown in this example

 -lblink -lblink?

 marker -lblink ok<$,ram>
 decimal ok<#,ram>
 $ff82 constant portc ok<#,ram>
 $ff94 constant trisc ok<#,ram>
 $2 user bitmask    \ The bitmask ok<#,ram>
 $4 user delay      \ The delay time in milliseconds ok<#,ram>
 : lblink
   bitmask c@ trisc mclr  
     delay @ ms 
     bitmask c@ portc mset 
     delay @ ms 
     bitmask c@ portc mclr 
 ; ok<#,ram>

 \ Define the first task
 flash $0 $10 $10 $4 task: tblink ok<#,ram>

 \ Define a word that initialises tblink
 : tblink-init 
 ['] lblink tblink tinit 
 $1 tblink bitmask his ! 
 $100 tblink delay his ! 
 ; ok<#,ram>

 \ Initialise the tblink task
 tblink-init ok<#,ram>

 \ Run the the tblink task
 tblink run ok<#,ram>

 \ tblink is running in the background while tblink1 is compiled

 \ Define, init and run the second task
 flash $0 $10 $10 $4 task: tblink1 ok<#,ram>
 : tblink1-init 
 ['] lblink tblink1 tinit 
 $4 tblink1 bitmask his ! 
 $60 tblink1 delay his ! 
 ; ok<#,ram>
 tblink1-init ok<#,ram>
 tblink1 run ok<#,ram>

 \ Wait for 3000 milliseconds
 3000 ms ok<#,ram>

 \ End both tasks
 single ok<#,ram>

 \ Wait for 2000 milliseconds
 2000 ms ok<#,ram>

 \ Make both tasks start after a warm start or power on
 \ Define a word that initialises and runs both tasks
 : blink2 tblink-init tblink1-init tblink run tblink1 run ;  ok

 \ Test that blink2 works
 blink2 ok<#,ram>

 \ Wait 4096 milliseconds
 $1000 ms ok<#,ram>

 \ End both background tasks again.
 single ok<#,ram>

 \Store the execution vector of blink2 in the turnkey vector in eeprom
 ' blink2 is turnkey ok<#,ram>

 \ Make a warm start
 FlashForth V3.4 PIC18F258

 \ Now the leds should be blinking unless you pressed ESC.

 \ The compilation of the see word is not shown in this example.
 \ Decompile the blink2 word.
 see blink2 
 291c dfb9      rcall tblink-init 
 291e dfe3      rcall tblink1-init 
 2920 dfa8      rcall tblink 
 2922 defb      rcall run 
 2924 dfd0      rcall tblink1 
 2926 def9      rcall run
 2928 0012      return
 tasks operator tblink1 tblink  ok<$,ram>