FLASHFORTH for the
Microchip PIC 18, 24, 30, 33 series
and
Atmel Atmega (Arduino) series



Introduction

FlashForth is a standalone native Forth operating system implemented on the Microchip 8-bit PIC18F and 16-bit PIC24, 30, 33 and the Atmel Atmega microcontroller families.
FlashForth also works on the popular Arduino boards.

FF (FlashForth) allows you to write and debug complex real-time applications. The complete system including the compiler is executing on the microcontroller.

A Forth interpreter, compiler, assembler, multitasker and user definable interrupts are provided.

A computer with a terminal emulator is used for communicating with FF via a serial or USB link.
The Forth source files are edited and saved on the computer and uploaded to the microcontroller as Forth source code.

All microcontroller registers and memories can be read and written from the command line.

When the application is ready, the application word address can be stored in the turnkey vector, and your autonomous embedded application has been set up.

FlashForth is mostly compatible with the ANS'94 standard. This guide describes the differences, and features not covered by the ANS'94 standard.

FlashForth is licensed according to the Gnu Public License v3.

If you feel that FlashForth has been useful for you, please contribute with a donation.

TRY FlashForth via TELNET now !

You can now try different FlashForth boards via Telnet !

Arduino Mega2560 R3 @ 16 MHz using a 38400 baud USB-UART bridge.
telnet -l arduinomega flashforth.dlinkddns.com

Arduino Uno R3 @ 16 MHz using a 38400 baud USB-UART bridge.
telnet -l arduinouno flashforth.dlinkddns.com

PIC18F2620 @ 12 MHz using a 38400 baud UART on a homebrew board.
telnet -l pic18f2620 flashforth.dlinkddns.com

PIC24HJ128 @ 55 MHz using a 38400 baud UART on a homebrew board.
telnet -l pic24hj128 flashforth.dlinkddns.com

The password is ff . End the session with control-c .
If you get stuck you can warm start the board with control-o .
Use control-H for backspace.
On linux set the terminal code for backspace with 'stty erase ^H' before opening the telnet connection.

The excellent Elements of FlashForth tutorial is essential for a basic understanding of how to use FlashForth.
It is brought to you by Peter Jacobs of the University of Queensland.

Learning FlashForth

Here is the wordlist for FF5.0.

Peter Jacobs (University of Queensland) has written two excellent FlashForth tutorials and a reference sheet.

Elements of FlashForth Tutorial
FlashForth 5 Tutoria Guidel
FlashForth 5 Reference Sheet

Here are a few general sources for learning Forth.

Pforth tutorial.
Programming Forth by Stephen Pelc.
Starting Forth by Leo Brodie.

The ANS Forth Standard.

Support for FlashForth

Support can be obtained via the MAILING LIST

Register HERE to the mailing list.

Credits

Many thanks to Joe Ennis, W7NET, and Pete Zawasky, AG7C, for pushing FlashForth hard and bugging me with trouble reports.
Thanks to Igor, OM1ZZ for contributing a proto board and two PICs for the 24 and 33 PIC series.
Thanks to Brian Howell of WCU for contributing a PicKit2.

FlashForth Users

Western Carolina University, Kimmel School, Electrical and Computer Engineering Technology
is using FlashForth with PIC18F and dsPIC30F for teaching microcontroller and DSP concepts.

University of Queensland.

PZEF Company.

IBHessler.

And many more.

Obtaining FlashForth

FF can be downloaded from http://www.sourceforge.net/projects/flashforth.

Download FlashForth 5.0. Expand the downloaded archive into a folder.
The git repository contains the latest developments.
The git repository can be cloned with the git command:

git clone git://flashforth.git.sourceforge.net/gitroot/flashforth/flashforth

Installing FlashForth

Installing on PIC18F

Follow the instructions in PIC18/install.txt

At least the following PIC processors are able to run FlashForth 5.0. The PIC18FxxJxx is not supported

Installing on dsPIC30, 33 and PIC24

Select the processor type in the IDE. Update relevant include file with clock configuration and frequency and other settings.

Compile the project and program the HEX file to the PIC with your favourite device programmer.

FlashForth has been run at least on dsPIC30F4012, dsPIC30F4013, dsPIC33FJ128GP802 and pic24HJ128GP502

FlashForth should work on all 16-bit PIC chips with enough flash(>=24Kbytes) and enough ram.

Installing on Atmega and Arduino

Select the uC type by including the correct include file, e.g. m328def.inc for Atmega 328.
Configure the clock and baudrate according to your hardware and preferences.
This is done in the configuration file, avr/src/config.inc.

Compile the code with AVR Studio and program the hex file with a suitable JTAG or ICSP programmer to your chip.
The fuses must be set to BOOTSIZE=1024 words and the BOOTRESET should be active.

FlashForth has been tested on Atmega 2560, 128 and 328 sofar.
It should also work on Atmegas 168 and 644.

FlashForth has been tested on the Arduino Duemilanove, Mega 2560 R3 and Uno R3.

Preprogrammed Atmega328 chips with FF for the ArduinoUno R3 can be ordered from oh2aun at gmail.com note.gif

Case sensitivity

FF is case sensitive. All FF core words are written in lowercase letters and hex numbers must be entered in lower case also.

Although the FF core words are in lower case, in documentation the FF core words may be written in UPPERCASE to make it clear that a Forth word is referred to.

Interacting with FlashForth

Forth source code can be interpreted and compiled by loading it via a terminal emulator.

FF supports terminal communication via UART or USB serial emulation.
The USB serial emulation requires that you use a PIC18F chip with inbuilt USB tranceiver.

The default UART setting is 38400, 1, N, XON/XOFF. It is mandatory to enable inband flow control (XON/XOFF). When FF stores data in flash, the chip will stop responding for up to 20 milliseconds. XOFF will prevent the terminal emulator from sending characters to FF while data is being stored into flash.

CTS/RTS (HW flow control) is available as a compilation option.

USB serial emulation flow control is handled by the USB protocol. Use HW flow control in the terminal emulator.

Minicom with linux works OK without any extra TX delays. Forth source files can be sent to the PIC using the 'send ascii file' (CTRL-A S) function.

With Windows TeraTerm works just fine.

Note that the Arduino boards USB-serial converter prevents the XON/XOFF flow control from working properly.
Therefore a 1 millisecond delay should be inserted between each character sent to the Arduino board.
At least TeraTerm can do that.

FlashForth recognises CRLF or only CR as end of line in ACCEPT. LF and CR are not echoed by ACCEPT

U1- and U2- can be used for disabling flow control if the end application can not support flow control on the serial interface.

Problems ?

Normally communication with the PC and writing to flash works very reliably, but...

If to you see a vertical bar '|' output from FlashForth, it means that the UART RX interrupt buffer has overflowed.

It is usually caused by the PC reacting slowly on XOFF.
'setserial /dev/ttyS0 low_latency' improves the situation on Linux.
On Windows, disabling the UART buffers improves the situation.
Another alternative is to use TeraTerm with an intercharacter delay of a few milliseconds.

Increasing the UART RX interrupt buffer size and sending XOFF for a small buffer fill level can also improve the situation.

If you see an extra '~' output it means that a serial framing or overrun error has occurred.

If you see a '^' output from FlashForth, it means that the verification of a program memory write has failed. FlashForth will try to write the same buffer only once, then an warm start will be made.

The data space

The PIC memory is mapped by FF:

PIC18F:
FLASH is mapped to  $0000 - $ebff
EEPROM is mapped to $ec00 - $efff
RAM is mapped to    $f000 - $ffff

dsPIC30, dsPIC33, PIC24:
RAM is mapped to    $0000 - RAMSIZE-1
FLASH is mapped to  RAMSIZE - $fbff  
EEPROM is mapped to $fc00 - $ffff

Atmega:
RAM is mapped to    $0000 – (RAMSIZE-1)
EEPROM is mapped to RAMSIZE – (EEPROMSIZE-1)
FLASH is mapped to (0xffff – FLASHSIZE + 1) - 0xffff

In FlashForth data space can be allocated by CREATE ALLOT VARIABLE 2VARIABLE , C, VALUE DEFER .

The words RAM EEPROM FLASH sets the data area from which the succeeding allocations will be made.

@ ! C@ C! and other memory access words can be used transparently with all types of memory.

The exception to this rule are the words MSET MCLR MTST BSET BCLR BTST that can only address RAM.
These words are typically used for setting and clearing bits in registers.

For PIC18 the mapping overhead for ram @ and ! is 4 instruction cycles.
For PIC24 the mapping overhead for ram @ and ! is 3 instruction cycles.
For Atmega the mapping overhead for ram @ and ! is 2 instruction cycles.

\ A variable in eeprom
eeprom variable var2

\ A variable in ram
ram variable var1

It is not recommended to create variables in eeprom unless these are updated fairly seldom.

Data areas in flash are normally used for constant data and constant execution vectors.

Typically flash cells can be written over 10000 times until it may fail. Eeprom cells can typically be written over 100000 times until it may fail.

As an example here is a word which creates character arrays in the current data space.

: carray: ( n “name” -- ) create allot does> + ;

Create a 20 character array in eeprom called CALIBRATE.

eeprom
decimal 20 carray: calibrate
ram

It is good to always set the data space context back to ram after flash or eeprom has been used.

Compile a word which creates indexed cell arrays.

: array: create cells allot does> swap 2* + ;

ram 20 array: cnt     \ Creates the array cnt ( size 20 cells )
1233 10 cnt !        \ Store 1233 in table index 10
10 cnt @             \ Fetch from index 10

Create a table in flash with constant data.

flash create flash-table $1234 , $3456 , #12345 , %1010101010 ,
ram

Create a table in eeprom with some data.

eeprom create eeprom-table $e123 , $e456 , #8888 , %1110111011101110 ,
ram

The Interpreter

The Forth interpreter is a normal Forth interpreter. It parses words delimited by space and tries to find the word in the dictionary. TAB is ignored. If the word is found it is executed, if not, FF tries to convert it to a number according to the current base (or base prefix), and put the number on the stack. If that fails ABORT is called.

The interpreter can only be used by the OPERATOR task, not by the background tasks.

After interpreting a line, QUIT prints Ok and executes the deferred word PROMPT.
By default PROMPT executes .ST which prints a number base prefix symbol, the current data area memory type and the parameter stack contents.
If you don't want to see the info from .ST, you can re vector PROMPT to for example CHARS which does nothing.

  ' chars is prompt
 ' .st   is prompt
 

Math

FF is a 16-bit Forth and the single precision math operations are consequently 16-bit.

FF also supports double precision 32-bit math.

UM/MOD, M+ and UM* are used as base for the extra 32-bit double precision math words that can be loaded from math.txt.

For working with 48 and 64 bit numbers there are words in qmath.txt.
The words UT* UT/ UT/* have 48 bits precision.
The words UQ* UQ/MOD QM+ D>Q have 64 bits precision.
The 64 bit words are sofar only implemented for PIC18.

Number conversion

FF supports single precision 16-bit and double precision 32-bit number conversion.

Double precision numbers are identified by a trailing dot.

Input numbers can be prefixed by % # $ to achieve binary decimal and hexadecimal number conversion without changing BASE.

Output numbers are always converted according to BASE.

The Compiler

FF is a subroutine threaded Forth with native code generation.

Code is always compiled to flash memory. PIC and ATMEGA can only execute code from flash.

Constants, variable addresses and literals are compiled as native code.

DUP and 0= before IF WHILE UNTIL are optimized away.

All the structured conditional words generate native code.

: and ] puts FF in compilation state. ;  ;I and [ enters the interpreter state.

The maximum word name length is 15 characters.

Words that should not be interpreted have a 'compile only' bit in the header. Interpreting these words will result in an ABORT and restarting the interpreter, that is jumping to QUIT.

The compiler performs tail call -> goto optimisation at the end of a word.

It is possible to call the word beeing currently defined.
If the word is the last word before ';' it will result in a branch back to beginning of the word.
If the current word is called earlier, it will result in recursion.

Inlining of words

FF can compile location independent assembler primitives as inline code. Some of these words have the inline bit set in the word header.

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 the PIC24 the following words are always inlined by the compiler.

cwd ivt aivt [i i] ei di u1txq u1rxq drop over >r r> r@
invert negate 1+ 2+ 1- 2- 2* 2/ !p>r r>p p+ p2+ >body
cell cell+ cells char+ chars 2drop 0 1 nip nfa>lfa leave
rdrop bl ticks cpu_clk false true + - and or xor !p @p

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

On PIC24 the following words can be prefixed with INLINE.

mset mclr mset bset bclr lshift rshift sp! sp@ swap rot m+
um* um/mod u/mod m* sm/rem /mod mod /

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

rp@ >< cell+ cells char+ chars invert 1+ 1- 2+ 2- 2* 2/
p+ @p p2+ ei di dup drop rdrop >body idle busy

On the Atmega the following words can be prefixed with INLINE.

ticks 1 over swap + - and or xor mset mclr lshift rshift
sp@ sp! !p p++ flash eeprom ram cell false true state ticks >pr
d+ d2/ dinvert fl- fl+

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 ;

Dictionary search order

The core dictionary is always searched before the user dictionary. It is not possible to redefine existing words. These measures have been taken to make the system more robust and to make it possible to recover to the basic state, without the need to flash the chip again. It also makes the code clearer, since there will not be two words with the same name.

Dictionary management

In order to recover to an earlier dictionary and memory allocation state, use MARKER. Always before defining new words define a marker. Otherwise you may need to return to an earlier marker or to say EMPTY which will empty the dictionary and reset all memory allocations to default values. A marker will restore TURNKEY, DP and LATEST. IRQ is not affected.

FORGET can be used to forget a user word, but FORGET can only adjust the FLASH DP. This means that allotted EEPROM or RAM will not be reclaimed if you use FORGET.

Note that the eeprom variables TURNKEY, DP, LATEST are cached in ram during interpretation of a input line and also during compilation state. This makes compilations run faster, and there will be less wear of the eeprom.

Since FF refuses to redefine words, certain words, typically one line definitions, can be compiled from several source files. The first compilation is accepted, and the others rejected. This is quite practical for having some short definition in many files, so that you can compile exactly the words one or more applications needs.

For example i2c_base.txt and task-test.txt both have defined PORTC, but PORTC will be compiled only once, even if both files are loaded to FF.

The word FL- can be used for disabling writes to flash and eeprom. It is useful for making sure that no writes to flash or eeprom occur.

Boot sequence

After processor reset a check is made to see if a turnkey word should be executed. If the eeprom value TURNKEY contains a nonzero value, it must be an address of a valid user word. Unless the user presses ESC within the turnkey timeout, the user word is executed. If ESC is pressed, the user word will not execute. Instead the forth interpreter is entered.

  ' my_application is turnkey

If your TURNKEY word is crashing, press ESC and as a first command give:

 false is turnkey

This will disable the TURNKEY and allow you to make corrections.

When FlashForth start it prints out a variable amount of characters indicating the restart reason:

P = Power on  reset (ALL)
B = Brown out reset (ALL)
W = Watchdog timeout reset (ALL)
S = Software Reset Instruction (PIC18, PIC24)
O = Return stack overflow (PIC18, PIC24)
U = Return stack underflow (PIC18)
E = External reset (Atmega, PIC24)
M = Math error (Divide by zero) (ALL)
A = Address Error (PIC24) 

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.

On PIC24-30-33 and Atmega, the interrupts use the parameter stack of task that happened to be executing when the interrupt occured.

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:

PIC18: n=
PIC24:
Atmega:

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

PIC18: Sreg(FSR0L FSR0H) TBLPTRL TBLPTRH TABLAT PRODL
PIC24: TBLPAG W13 RCOUNT
PIC30: W0 W1 W2 W3 TBLPAG W13 RCOUNT
PIC33: TBLPAG W13 RCOUNT

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

PIC18: Treg (FSR1L FSR1H) PCLATH
PIC24: W0 W1 W2 W3
PIC30:
PIC33: W0 W1 W2 W3
Atmega: R0 R1 R16 R17 R24 R25 R26 R27 R28 R29 R30 R31

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

  ram variable irq_counter
  : my_irq
    [i
      irq_counter @
      1+
      irq_counter !
    i]
  ;i


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 not supported by FlashForth.

  ' my_irq 0 int!


The dsPIC30 interrupt vectors are stored in flash in the Alternate Interrupt Vector Table. The PIC24 and dsPIC33 interrupt vectors are stored in ram, pointed to from the Alternate Interrupt Vector Tables. The PIC18, PIC24, dsPIC33 and Atmega interrupt vectors in ram are 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,       ]
  ;i

NOTE:
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.

Background tasks

FF can execute background tasks concurrently with the operator task.

The task switching is made cooperatively by executing PAUSE. PAUSE is executed in I/O words KEY and EMIT so that background tasks can run while the console is waiting for input or queuing for output. MS executes PAUSE while it waits for the specified delay to pass.

If IDLE_MODE in the configuration file is enabled, the processor will enter the idle powersaving mode in PAUSE in the OPERATOR task.
If you want to allow the processor to go into idle mode use the word IDLE. If not, use the word BUSY.
An interrupt will exit the idle mode and the processor will run until the next time idle mode is entered.

The percentage of time that the processor is busy can be read by the LOAD word. The integration interval is 256 milliseconds.

The words for tasking can be loaded from task.txt.

The user area lives in ram. The user area is initialized from the task definition in flash.

TASK: creates a new task and defines the stack sizes and the additional user area size and the tibsize.
Tibsize can be set to zero for background tasks, except if numeric output is used.
The end of the TIB is shared with the HOLD buffer. A task that uses “. U. <# # #s #>” etc., will
need a small TIB for number formatting.

Each task has its own PAD which starts at end of TIB. When allocating ram you must allot space for the PAD if it is being used.

The FF kernel does not use PAD. So if you want to use PAD in the OPERATOR task it is up to you to allot space for PAD. This non-standard behavior exists to save ram.

TINIT initializes a task with the XT of the task loop. It also initializes the task user area.

RUN makes the task run. It inserts the task in the round-robin linked list.

END ends a task. It removes the task from the round robin linked list.

SINGLE ends all tasks except the operator task.

TASKS lists all running tasks.

The tasking commands may only be executed from the operator task.

KEY, KEY?, EMIT can be deferred and used in a background task to interact for example with a keyboard and a LCD display.

\ Task loop for displaying data on the LCD display
: lcd_display ( -- )
  lcd_init
  ['] lcd_emit 'emit !     \ Use LCD emit
  hex
  begin
    #00 lcd_at             \ Position cursor at beginning of first line
    ." Ticks: " ticks u.4  \ Display the current number of ticks
  again
;

10 20 20 0 task: lcd_task  \ tibsize stacksize rsize addsize --
' lcd_display lcd_task tinit
lcd_task run

FOR..NEXT and DO..LOOP

I have always found DO..LOOP cumbersome to use. I wanted to separate the loop count and the index handling. The FF core implements FOR..NEXT and a re-entrant P register.

DO ?DO LOOP +LOOP LEAVE I J UNLOOP can be added from Forth source code.

FOR..NEXT loops exactly the amount of times specified ( also 0 ) .

  : star [char] * emit ; ok <$,ram>
  star *ok <@,ram>
  : stars for star next ; ok <$,ram>
  10 stars ****************ok <$,ram>
  0 stars ok <$,ram>

The loop count is held on top of the return stack and it can be fetched by R@. ENDIT sets the loop count to 0, so that NEXT will terminate the loop.

  : test
    #10
    for
      r@ . r@ 4 =
      if
        endit
      then
    next
  ; ok
  test 9 8 7 6 5 4 ok

If you EXIT a FOR..NEXT loop you must drop the loop count with RDROP

  : test
    #10
    for
      r@ 4 =
      if
        rdrop exit
      then
      r@ .
    next
  ; ok
  test 9 8 7 6 5 ok

The P register

The P register can be used as a variable or as a pointer. It can be used in conjunction with FOR..NEXT or at any other time.

!P>R pushes the current P value on the return stack and sets a new value to P.

In a definition !P>R and R>P should always be used to allow proper nesting of words.

R>P pops a value into P from the return stack.

!P sets a new value into P. Use !P only from the command line, or between !P>R and R>P in a definition.

@P lets you fetch the value of P.

P+ increments P by one.

P2+ increments P by two.

P++ ( n -- ) adds n to P.

P@ P! PC@ PC! are used to access memory via the pointer.

Always remember to balance the return stack in all branches of your code.

 \ CMOVE src dst u -- copy u bytes from src to dst
 \ The source address is kept on the parameter stack.
 \ The destination address is kept in the P register.
 : cmove
   swap !p>r
   for
      c@+ pc! p+
   next
   r>p drop
 ;

Register usage and memory usage

FF for PIC 18F

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 a temporary pointer and as temporary storage, Its called A in the assembler code.
FSR2 is not interrupt safe, but it is used in words that should not be used in an interrupt word.
PCL, PCLATH, TBLPTRL TBLPRH are used for accessing flash memory. PCLATU and TBLPTRU must be zero at all times.

FF for dsPIC 30F, 33 and PIC 24

The return stack pointer is W15.
The parameter stack pointer is W14.
The P register uses W13
Assembly words use W0..W3.
In addition SKIP, SCAN, N= use W4 and W5.

FF for Atmega

SP: The return stack pointer
Y : The parameter stack pointer
X,Z: Temporary data and pointers
r24,r25: Cached TOS value
r22,r23: Internal flags
r20,R21: The P register
All other registers are used internally by FlashForth.

Sample session for PIC18F

warm
FlashForth V3.4 PIC18F258
ESC
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>
words
marker
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>
 ok<#,ram>
 : lblink
   bitmask c@ trisc mclr  
        begin        
     delay @ ms 
     bitmask c@ portc mset 
     delay @ ms 
     bitmask c@ portc mclr 
   again 
 ; 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
 warm
 FlashForth V3.4 PIC18F258
 ESC

 \ 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
 ok<$,ram>
 tasks operator tblink1 tblink  ok<$,ram>

Sample session for dsPIC 30F

warm
FlashForth V4.3 on dsPIC30F
(C) Mikael Nordman GPL V3
ESC
flash ok <$,flash>
eeprom ok <$,eeprom>
ram ok <$,ram>
decimal ok <#,ram>
bin ok <%,ram>
hex ok <$,ram>
see see
41b0 0007 faad rcall '
41b2 0007 f61f rcall cr
41b4 0007 f7c3 rcall hex
41b6 0078 0f3e mov.w [W14++], [W14]           \ DUP
41b8 0007 fe2e rcall u.4
41ba 0078 0f3e mov.w [W14++], [W14]           \ DUP
41bc 0007 f1c4 rcall cf@
41be 0007 fe2b rcall u.4
41c0 0007 fe2a rcall u.4
41c2 0007 ffd8 rcall (see)
41c4 0007 f616 rcall cr
41c6 00e0 001e cp0 [W14]                      \ IF also DUP
41c8 003a fff6 bra nz, 41b6                   \ IF also 0=
41ca 0057 0762 sub W14, 2, W14                \ DROP
41cc 0006 0000 return
ok <$,ram>
see ms
3aa2 0007 fff9 rcall ticks
3aa4 0007 f7e7 rcall +
3aa6 0007 f488 rcall pause
3aa8 0078 0f3e mov.w [W14++], [W14]           \ DUP
3aaa 0007 fff5 rcall ticks
3aac 0007 f7ef rcall -
3aae 0007 f83a rcall 0<
3ab0 00e0 002e cp0 [W14--]                    \ IF 
3ab2 0032 fff9 bra z, 3aa6                    \ IF
3ab4 0057 0762 sub W14, 2, W14                \ DROP
3ab6 0006 0000 return
ok <$,ram>
see t1go
441c 0024 3fe0 mov 43fe , W0                 \ literal for tloop address
441e 0078 2f00 mov.w W0, [++W14]
4420 0007 ffe3 rcall t1
4422 0007 ff2e rcall tinit
4424 0007 ffe1 rcall t1
4426 0007 ff63 rcall run
4428 0006 0000 return
ok <$,ram>
tasks operator t1 ok <$,ram>
1 ok <$,ram>1
2 ok <$,ram>1 2
34 ok <$,ram>1 2 34
+ ok <$,ram>1 36
- ok <$,ram>ffcb
. -35 ok <$,ram>