by
Mikael Nordman
FlashForth is a standalone native Forth operating system
implemented on the Microchip PIC18F and dsPIC30F processor families.
FF (FlashForth) allows you to write and debug complex real-time
applications on the PIC. The complete system including the compiler
is executing on the PIC.
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 link.
The source files are edited and saved
on the computer and uploaded to the PIC as source code.
All
PIC registers and memories can be read and written from the command
line.
When the application is ready, the application word
address is stored in the turnkey vector, and your 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.
Here are a few sources for learning Forth.
http://www.softsynth.com/pforth/pf_tut.html
http://www.mpeltd.demon.co.uk/arena/ProgramForth.pdf
http://www.forth.com/starting-forth/
The ANS forth standard can be found here http://www.taygeta.com/forth/dpans.htm.
Firstly, I want to thank my family for tolerating me during this
project.
Many thanks to Joe Ennis, W7NET, and Pete Zawasky,
AG7C, for pushing FlashForth hard and bugging me with trouble
reports.
Here you can see the bot that Joe Ennis is building. It is running
FlashForth.
http://www.youtube.com/results?search_query=flashforth&search_type=
Send
me a mail via SourceForge if you want to see your application listed
here.
FF can be downloaded from http://www.sourceforge.net/projects/flashforth .
Expand the downloaded archive into a folder. The predefined MPLAB project file is for the folder C:/FlashForth_33.
Select a p18Fxxxx-xxxx-xxxx-xxxx.cfg file which supports your target processor. Update the clock frequency and PLL info according to your needs. If you have a low clock frequency you may need to lower the serial baud rate. The configuration files has configuration options for those PICs that should work together with FlashForth.
To compile FF in MPLAB, define a project with only one source file FlashForth_33.asm. Select the processor type in the IDE. Then locate the relevant p18fxxxx-xxxx-xxxx-xxxx.cfg file and set your configuration parameters.
FF can be compiled with mpasmwin also. Use /pPIC18FXXXX as argument to mpasmwin to select the PIC type.
On Linux, compile with gpasm -pp18fxxxx FlashForth_33.asm. Gpasm must be at least version 0.13.5.
The resulting HEX file can then be downloaded to your PIC. Suitable device programmer hardware is needed. FF can not be installed with a bootloader, since FF replaces the bootloader.
The following PIC processors should be able to run FlashForth. These are also supported in the configuration files.
- 18f242, 18f442, 18f252, 18f452 ->
p18fxx2xx8.cfg
- 18f248, 18f258, 18f448, 18f458 ->
p18fxx2xx8.cfg
- 18f2455, 18f2550, 18f4455, 18f4550 ->
p18f2455-2550-4455-4550.cfg
- 18f2420, 18f2520, 18f4420,
18f4420 -> p18f2420-2520-4420-4520.cfg
-
18f2525, 18f2620, 18f4525, 18f4620 ->
p18f2525-2620-4525-4620.cfg
- 18f6527, 18f6622, 18f6627,
18f6722 -> p18f6722-family.cfg
-
18f8527, 18f8622, 18f8627, 18f8722 ->
p18f8722-family.cfg
- 18f2458, 18f2553, 18f4458, 18f4553 ->
p18f2455-2550-4455-4550.cfg
- 18f2480, 18f2580, 18f4480,
18f4580 -> p18f2480-2580-4480-4580.cfg
-
18f2423, 18f2523, 18f4423, 18f4523 ->
p18f2423-2523-4423-4523.cfg
- 18f2585, 18f2680, 18f4585,
18f4680 -> p18f2585-2680-4585-4680.cfg
-
18f2682, 18f2685, 18f4682, 18f4685 ->
p18f2682-2685-4682-4685.cfg
Expand the downloaded archive into a folder. The predefined MPLAB project file is for the folder C:/FF4.3.
Select the processor type in the IDE. Update ff30.inc with clock configuration and frequency.
Compile the project and program the HEX file to the dsPIC with your favourite device programmer.
FF for the dsPIC should work on all dsPIC 30F chips with flash(>=24Kbytes), eeprom and ram.
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.
Forth source code can be interpreted and compiled by loading it via a terminal emulator.
The default setting is 38400, 1, N, XON/XOFF. It is mandatory to enable inband flow control (XON/XOFF). When the PIC stores data in flash, the PIC chip will stop responding for up to 20 milliseconds. XOFF will prevent the terminal emulator from sending characters to the PIC, while data is beeing stored into flash.
I am using minicom with linux and it works OK without any extra TX delays. The text must be copy/pasted to the terminal window. If you use send ascii file, there appears to be no flow control ( on my system ).
With Windows I use TeraTerm. TeraTerm can send files to the FlashForth target, which is more convenient than using copy/paste.
It is recommended to disable sending of line feed from the terminal emulator. FF will ignore any received LF characters, but the echoing may cause extra line feeds.
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 serial interrupt buffer has overflowed.
Recompile with a slower serial speed to fix the problem. The interrupt buffer is now 78 bytes. If you use slower serial speeds than 38400 you may decrease the buffer size.
If you see an extra '~' output it means that a serial framing or overrun error has occurred.
If your terminal emulator does not respond to the XOFF fast enough, try using TeraTerm with an intercharacter delay of 20 milliseconds. That should fix the problem.
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.
If you get a write error, '^', when compiling Forth words, try adjusting outer_loop and inner_loop constants. For example some version of the 18f2520 series require (2,32) instead of (1,64). See Microchip errata page for details of your particular chip.
Please
note that the chips 18F252, 18F452, 18F258, 18F458, do not work
reliably if you jump over the $3fff address border. So these
chips are NOT recommended for FlashForth if your program grows over
the $3fff border.
This is a fault in the PIC chip, not in
FlashForth. The other 18Fxxxx chips do not have any such errata.
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
dsPIC30F:
FLASH is mapped to $2000 - $fbff
EEPROM is mapped to $fc00 - $ffff
RAM is mapped to $0000 - $1fff
In FlashForth data space is allocated by CREATE ALLOT VARIABLE , C, VALUE DEFER .
The words RAM EEPROM FLASH sets the data area from which the succeeding allocation will be made.
@ ! and other memory access words can be used transparently with all types of memory.
For the PIC18F the mapping penalty is 33 % (4 instruction cycles) for ram stores and fetches. For the dsPIC30F the mapping penalty for ram access is ~50% (3 instruction cycles).
The words MSET MCLR MTST are only for RAM. These words are typically used for setting and clearing bits in PIC registers.
\ A variable in ram
ram variable var1
\ A variable in eeprom
eeprom variable var2
It is not recommended to create variables in EEPROM/FLASH unless these are updated fairly seldom. FLASH can be written to 10000 times until it may fail. EEPROM can be written to 100000 times until it may fail.
Compile a word which creates character arrays in the current data space.
: array create allot ;
Compile a word which creates a 20 character array in eeprom.
eeprom decimal 20 chars array calibrate
ram
It is good practice to always set the data space context to ram after flash or eeprom has been used.
Compile a word which creates 20 cell indexed arrays.
decimal
: counters create 20 cells allot does> swap 2*+ ;
ram counters cnt \ Creates the table cnt
1233 10 cnt ! \ Store 1233 in table index 10
10 cnt @ \ Fetch from index 10
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 single precision number according to the current base (or base prefix), and put the number on the stack. If that fails ABORT is called.
After interpreting a line, QUIT prints Ok and executes the deferred word PROMPT. If you don't want to see the info from .ST, you can re vector PROMPT to for example CELLS which does nothing.
' cells is prompt
' .st is prompt
FF is an 16-bit Forth and the single precision math operations are consequently 16-bit.
The PIC18F FF core has a 16 bit U/MOD on which the other 16-bit division routines are based. U/MOD(300 cycles) is 2,5 times faster than UM/MOD (800 cycles).
The dsPIC 30F obviously has much faster multiplication(9 cycles) and division(27 cycles).
UM/MOD, M+ and UM* are used as base for the 32-bit double precision math words that can be loaded from math.fth.
FF supports single precision 16-bit number conversion.
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.
FF is a subroutine threaded Forth with native code generation.
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.
In FF 4.x the maximum word name length is 15 characters. Earlier it was 31 characters.
In FF 4.x words that can not be interpreted have a 'compile only' bit in the header.
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 PIC 18F the following words are always inlined by the compiler.
drop p+ cwd rdrop false true 1 leave cell chars di ei
On the dsPIC 30F 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
On the PIC 18F 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++
On the dsPIC 30F the following words can be prefixed with INLINE.
mset mclr mtst lshift rshift sp! sp@ swap rot + m+ - and or
xor !p @p p++ um* um/mod u/mod
Inline literals are
fast and do not take too much space. With CON you can define a
constant which is compiled as native code, it does not use DOCREATE. If
you follow the definition with inlined, the compiler will
later compile the constant as an inline literal.
34 con thirtyfour inlined
: native-inline-34
thirtyfour ;
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.
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 COLD 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 TURNKEY, DP, LATEST are stored in flash, but their respective addresses are in ram. These variables 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 in the eeprom.
Since FF refuses to redefine words, certain words, typically oneline definitions, can be compiled from several source files. The first compilation is accepted, and the others rejected. This is quite practical for having the same definition in many files, so that you can compile exactly the words one or more applications needs.
For example i2c.fth and task-test.fth both have defined PORTC, but PORTC will be compiled only once, even if both files are loaded to FF.
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 two seconds, 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<enter>
This will disable the TURNKEY and allow you to make corrections.
Interrupt routines can be written in assembly or in Forth.
The
Forth interrupt word has its own parameter stack of 8 cells. A
FF interrupt word has to be ended with ;I .
There are certain
limitations to which registers and Forth words can be used in an
interrupt word.
It is not recommended to store to flash or
eeprom in an interrupt routine.
The following words are safe
to use inside the Forth interrupt routine.
Words defined by:
variable
con
constant
: create does> ;
value
defer
The following words can be compiled into a Forth
interrupt word :
@ ! c@ c! mset
mclr mtst lshift rshift exit execute fexecute
cell aligned
cell+ cells char+ chars 2@ 2! 2drop 2over
umin umax 0 1
drop swap over rot >r r> r@ dup abs
+ - and or xor
invert negate 1+ 1- 2+ 2- 2* 2/ +! within
<> 0= 0<
= < > u< u> nip tuck ?negate min max false true
The
following immediate words can be used in a interrupt word :
['] [char] [ ] if else then begin while repeat until for next
The
following registers are saved by FF before the interrupt word is
executed.
Sreg(=FSR0), Treg(=FSR1),
PCLATH, TBLPTRL, TBLPTRH, TABLAT.
This makes it
possible to use the above Forth words in the interrupt routine.
Below is a interrupt word which counts the total number of
interrupts.
ram variable
irq_counter
: my_irq
irq_counter
@
1+
irq_counter
!
;i
The following way to store the
variable address as an inline literal, and inlining 1+, gives faster
code.
: my_irq
[ irq_counter literal ] @
inline 1+
[ irq_counter literal ] !
;i
To
activate the interrupt you store the interrupt word xt into the
interrupt vector IRQ.
' my_irq
is irq
IRQ is cleared at warm start, so to enable
the interrupt word
at startup, a initialization word must be
used.
: irq_init ['] my_irq is
irq ;
' irq_init is turnkey
The above
example is a simple example. To use individual interrupt sources the
interrupt enable bits and flag bits must be used.
See
servo.fth 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.
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. PAUSE is also executed in FIND so that compilation does not disturb the real time too much. MS executes PAUSE while it waits for the specified delay to pass.
The words for tasking can be loaded from task.fth.
TASK: (FF3.3 TASK) creates a new task and defines the stack sizes and the additional user area size and the tibsize. Tibsize can always be set to zero for background tasks. In the future, new foreground tasks may be supported.
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 (FF4.x) lists all running tasks.
The tasking commands may only be executed from the operator task.
I have always found DO..LOOP cumbersome to use. I wanted to separate the loop count and the index handling. Therefore in FF 3.0 FOR..NEXT and the P register were introduced.
FOR..NEXT loops exactly the amount of times specified ( also 0 ) .
: star [char] * emit ; ok <16,2>
star *ok <16,2>
:
stars for star next ; ok <16,2>
10 stars
****************ok <16,2>
0 stars ok <16,2>
The loop count is held on top of the return stack and it can
be fetched by R@. LEAVE sets the loop count to 0, so that NEXT will
terminate the loop.
: test
#10
for
r@ . r@ 4 =
if
leave
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 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 on the parameter stack,
\ the destination address is in the P register.
: cmove
swap !p>r
for
c@+ pc! p+
next
r>p drop
;
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.
PCL,
PCLATH, TBLPTRL TBLPRH are used for accessing flash memory. PCLATU
and TBLPTRU must be zero at all times.
$f000-$f03f
is the flash write buffer.
$f040-$f05f is used internally by
FF.
$f060-$f06f is for the interrupt parameter stack.
$f070-$f0ef is for the serial Rx and TX buffers.
$f0f0-$f0f9 is used for mirroring TURNKEY DP and LATEST
$f0fa contains the
interrupt vector
$f0fc
contains the current data section
$f0fe
contains the user pointer UP
$f100-$f1ff is the user area for
the OPERATOR task
$f200-$ff5f is free for application use.
$ff60-$ffff address the PIC special function registers.
The return stack pointer is W15.
The parameter stack pointer is W14
Assembly words use W0..W3.
In addition SKIP, SCAN, N= use W4 and W5. [I does not save W4 and W5, so those words cannot be used in an interrupt routine.
FlashForth_33.asm is the
FlashForth core assembly file.
p18fxxxx.cfg
The main configuration file. It includes the other
needed files depending on the selected processor type.
asm.fth
The
assembler can be loaded from asm.fth. Look in blink.fth, i2c.fth and
irq.fth for examples how to use the assembler words.
MSET MCLR
MTST can be used for bit-manipulation but using inline assembly is
much faster.
blink.fth
blink
is a simple word showing how to access the PIC hardware by using
inline assembly.
case.fth ,
case-test.fth
Contains CASE
OF ENDOF ENDCASE.
core.fth
Contains EVALUATE FORGET DUMP FILL ERASE BLANKS ?DUP PICK
ct.fth , ct-test.fth
Contains
a word for making condition tables. The idea is to use this instead
of CASE statements. Look in seen.fth for an
example. There the see word has been implemented using a condition
table.
i2c.fth
A simple
implementation for accessing a serial eeprom and a temperature sensor
via i2c.
irq.fth
Contains
a counter for the total number of interrupts. A very simple interrupt
routine. Both assembly and Forth versions are shown as examples.
jt.fth , jt-test.fth
Contains
a word for making jump tables. This can be used instead of CASE
statements to execute words corresponding to a certain value.
A better jump table with a much clearer structure. Design by Pete
Zawasky.
math.fth
Here you can find double 32-bit math words.
pic.fth
Some PIC register
adresses are defined here. Thanks to Andrew Smith.
see.fth
The SEE word
decompiles forth words. SEE also decompiles the assembler
instructions bcf, bsf, btfsc, btfss, return, bra, bz and goto.
servo.fth ,
servo-test.fth
Contains
a servo pulse solution as an example of how to set up a interrupt
routine using Forth. It can control 4 servos on port B. The interrupt
routine uses TIMER0 to generate servo pulses.
task.fth
, task-test.fth
Contains
the words TASK, TINIT, RUN, END, SINGLE for task handling.
task-test.fth contain some led blinking background tasks.
words.txt words43.txt
This file contains the word list.
cold COLD! FlashForth V3.3 on PIC18F2520 ok<16,2> decimal ok<10,2> $dead ok<10,2>57005 $beef ok<10,2>57005 48879 hex ok<16,2>dead beef bin ok<2,2>1101111010101101 1011111011101111 hex ok<16,2>dead beef + ok<16,2>9d9c drop ok<16,2> words marker 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 allot .s words ms ticks s0 latest rhere state bl 2- ['] ; :noname : ] [ does> postpone create [char] ihere ( char ' abort" ?abort ?abort? abort prompt quit .st inlined immediate shb interpret in? 'source >in tib tibsize number? >number sign? digit? find immed? (f) cfa>nfa nfa>cfa nfa>lfa @+ c@+ place cmove word \ /string source user base pad hp 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* p++ p+ pc! 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 ." s" type accept 1 umax umin spaces space cr 2dup 2drop 2! 2@ cf, chars char+ cells cell+ aligned align cell c, , here dp cse ram eeprom flash c@ @ c! ! sp@ con constant variable @ex execute exit key? key emit cold warm bra, rcall, call, goto, br3 br2 br1 as0 as1 as2 as3 rshift lshift ic, i, operator khz mtst mclr mset iflush pause turnkey is to defer value cwd literal irq ;i di ei scan skip n= ok <16,2> \ Compile a word which creates h'20' cell indexed arrays in current data memory. : array create 20 cells allot does> swap 2* + ; ok<16,2> \ Create an array with elements in program flash flash array arr1 ok<16,0> 0 arr1 ok<16,0>2528 ' arr1 ok<16,0>2528 2522 \ Create an array with elements in eeprom eeprom array arr2 ok<16,1>2528 2522 0 arr2 ok<16,1>2528 2522 e00a \ Create the array arr3 with elements in ram ram array arr3 ok<16,2>2528 2522 e00a 0 aar3 aar3? 0 arr3 ok<16,2>f200 1 arr3 ok<16,2>f200 f202 1 arr2 ok<16,2>f200 f202 e00c @ ok<16,2>f200 f202 ffff \ Empty the stack .. ..? \ 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<16,2> decimal ok<10,2> $ff82 constant portc ok<10,2> $ff94 constant trisc ok<10,2> $2 user bitmask \ The bitmask ok<10,2> $4 user delay \ The delay time in milliseconds ok<10,2> ok<10,2> : lblink begin bitmask c@ trisc mclr delay @ ms bitmask c@ portc mset delay @ ms bitmask c@ portc mclr again ; ok<10,2> \ Define the first task flash $0 $10 $10 $4 task tblink ok<10,2> \ Define a word that initialises tblink : tblink-init ['] lblink tblink tinit $1 tblink bitmask his ! $100 tblink delay his ! ; ok<10,2> \ Initialise the tblink task tblink-init ok<10,2> \ Run the the tblink task tblink run ok<10,2> \ 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<10,2> : tblink1-init ['] lblink tblink1 tinit $4 tblink1 bitmask his ! $60 tblink1 delay his ! ; ok<10,2> tblink1-init ok<10,2> tblink1 run ok<10,2> \ Wait for 3000 milliseconds 3000 ms ok<10,2> \ End both tasks single ok<10,2> \ Wait for 2000 milliseconds 2000 ms ok<10,2> \ 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<10,2> \ Test that blink2 works blink2 ok<10,2> \ Wait 4096 milliseconds $1000 ms ok<10,2> \ End both background tasks again. single ok<10,2> \Store the execution vector of blink2 in the turnkey vector in eeprom ' blink2 is turnkey ok<10,2> \ Make a warm start warm FlashForth V3.3 on PIC18F2520 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<16,2>
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>