-
Notifications
You must be signed in to change notification settings - Fork 66
STM8 eForth Background Task
In STM8 eForth, the Background Task feature makes interactive development and and test of INPUT-PROCESS-OUTPUT
control or UI tasks easy. It is sufficient for most simple "Ladder Logic" or Arduino-style embedded control applications.
The Background Task feature provides a context switch of the Forth virtual machine, and it doesn't need any multitasking features, like semaphores, since no resources are shared with the foreground task (the console).
There is a trade-off: no interpreter and parser words that influence the console are allowed in a background task (check out the Idle Task if you need that).
Control tasks don't require a console as they use analog or digital I/O. Developing and testing the background task can be done interactively: With the Forth command line the task operation can be inspected, and parameters can be altered.
The background task feature has the following properties:
- fast preemptive context switch of eForth VM with dedicated data stack and PAD
- the variable
BG
holds the address of a background task word - the background task has it's own
BASE
(initialized to 10 before each cycle) -
?KEY
andEMIT
use configurable I/O words (e.g. W1209 uses 7S-LED display and push buttons) - number output with
.
or<# .. # .. #>
will just work (there is a small PAD,HLD
gets initialized before each cycle) - time can be measured by using the cyclic increment of the 16 bit "timer ticker"
TIM
- the "task word" is called in a 5ms cycle, which can be changed by changing the timer reload value.
- the background task uses TIMER2 by default (from STM8 eForth 2.2.24.pre4 on the timer is configurable)
Note1: if the runtime of the background task approaches or exceeds 1ms, or whatever the character rate of serial IO is, buffered UART RX should be applied. Otherwise the interactive console miss characters.
The following code demonstrates non-interactive use of the Background Task:
\ blinky using the background task timer to flash all board outputs
VARIABLE timemask ;
$40 timemask !
: blinky timemask TIM OVER AND = OUT! ;
' blinky BG ! \ set background word, start flashing
$80 timemask ! \ interactive: slower flashing
It can be stopped by changing the execution word address to null, e.g. 0 BG !
.
Due to dedicated character I/O support background tasks can also be used for implementing a simple user interface (like a parameter settings menu). The support code for boards with LCD or LED displays can map background task character I/O to the words KEY?
and EMIT
, and a simple form of "keyboard repeat" makes creating menus trivially easy.
The following very simple RAM monitor for the W1209 board (or similar) is an example for an interactive background task:
\ Simple W1209 7-Seg Display Memory Monitor
\ key mapping
\ set = 'A': display address
\ + = 'B': increment address
\ - = 'D': decrement address
NVM
VARIABLE monaddr
: inc ( -- ) \ increment monaddr
1 monaddr +! ;
: dec ( -- ) \ decrement monaddr
-1 monaddr +! ;
: cadr ( -- ) \ react on keys "+" and "-"
DUP 66 = IF inc THEN 68 = IF dec THEN ;
: disp ( -- ) \ show monaddr while key pressed
monaddr BKEY IF ? ELSE @ C@ . THEN ;
: montask ( -- ) \ react on key
HEX ?KEY IF cadr ELSE disp THEN ;
: startup ( -- )
128 monaddr ! \ initialize address
[ ' montask ] LITERAL BG ! \ start in background
HI ;
' startup 'BOOT !
RAM
The word startup
sets the Background Task address BG
to montask
so that it will be executed by the 5ms ticker interrupt. The start-up word is then put into the initialization chain by setting 'BOOT
. The monitor will now run concurrently to the Forth console.
Perhaps running the background task every 5ms is too often. Changing the TIM2 registers can lengthen (or shorten) the delay. But how to check? You change the prescaler, as shown below, and/or the reload values.
$530E CONSTANT TIM2_PSCR
$5306 CONSTANT TIM2_EGR
VARIABLE BG_CNT
: MYBG 1 BG_CNT +! ;
' MYBG CONSTANT 'MYBG
: .TIMCHK ( N -- ) \ WAIT N MS counting number of BG ticks
0 BG_CNT ! \ INITIALISE COUNTER
'MYBG BG ! \ START BACKGROUND INCREMENTING OF COUNTER
[ 1 TIM2_EGR 7 ]B! \ FORCES BACKGROUND TIMER TO START AGAIN
DUP
MS \ WAIT SOME TIME
CR BG_CNT ? ." LOOPS" CR \ PRINT RESULT
BG_CNT @ / . ." APPROX MS PER LOOP" CR
0 BG ! \ TURN OFF BACKGROUND TASK
;
An example:
$7 TIM2_PSCR C!
8000 .TIMCHK
97 LOOPS
82 APPROX MS PER LOOP
Note: the following write-up from the PoC gives an overview of the solution
The Background task PoC does a context switch, where the essential data of the Forth virtual CPU is retained. There is a small dedicated data stack, which gets reset with every cycle tick. In theory, the data stack could be shared with the console, but in practice determining the SP in an interrupt routine is tricky (it might be in X
, Y
, YTEMP
or even on the return stack).
Using the background task is simple:
- write a word that doesn't require input from the stack
- write the address of the word to the address provided by
BG
- at the end of stack doesn't need to be balanced
Example:
VARIABLE timer
: tSet TIM 300 + timer ! ;
: tTest TIM timer @ - 0 < ;
: delay KEY@ IF tSet THEN tTest IF 1 ELSE 0 THEN OUT! ;
' delay BG !
Press a key on the W1209 (or run tSet
from the Forth command line) to activate the relay for about 1.5 s (note: there is catch: since TIM
, a 16 bit counter, rolls over the pulse will repeat every 328s).
The main problem that this lightweight approach needs to solve is that the state of the Forth system is stored in the USR
area (0x60 .. 0x7F). As we won't use the outer interpreter, nor the compiler in the background task, only some USR
data is relevant: formatted output uses some USR
variables and PAD memory.
With the option HAS_BACKGROUND
active, the RAM memory has the following layout:
-----------------+
0000h .. 004Fh : Free for user variables
-----------------+
0050h .. 005Fh : Module variables
-----------------+
0060h .. 007Fh : USR area (and some core storage, e.g. XTEMP)
-----------------+
0080h .. HERE-1 : user dictionary
-----------------+
HERE .. HERE+79 : gap for new word definitions
PAD .. SPP-DEPTH : scratchpad memory, e.g for text output
-----------------+
SPP-DEPTH..032Fh : SSP (data stack, growing down)
0330h .. 034Fh : BSSP (background data stack, growing DOWN)
-----------------+
0350h .. 039Fh : TIB (Terminal Input Buffer)
-----------------+
03A0h .. 03FFh : RPP (return stack)
-----------------+
When no background process is running, a memory dump of the area with SPP
, BSSP
, TIB
, and RPP
can look like this:
HEX 300 F0 DUMP
300 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
310 0 0 0 0 0 0 0 0 0 0 0 0 0 70 0 70 _____________p_p
320 0 0 0 20 0 30 3 27 3 29 0 10 3 20 3 FF ___!_!___)_+_0__ <- SPP
330 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
340 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________ <- BSPP 16 words
350 33 30 30 20 46 30 20 44 55 4D 50 6A 6A 6A 6A 6A 300 F0 DUMPjjjjj - TIB start 80 bytes
360 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A jjjjjjjjjjjjjjjj
370 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A jjjjjjjjjjjjjjjj
380 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A jjjjjjjjjjjjjjjj
390 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A jjjjjjjjjjjjjjjj <- TIB end
3A0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________ -> RPP
3B0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
3C0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
3D0 0 0 0 0 0 0 B 0 A 0 3 81 B 0 3 81 _______________
3E0 0 21 C1 23 33 3 24 0 CF 0 82 6F 8C A7 0 0 A! #0_A%__(_:__o
3F0 95 30 0 C 95 6D 0 0 0 10 89 78 91 8B 91 CD _____|_____x___M <- RPP 96 bytes
Note that the TIB
was filled with j
for better visibility.
On the W1209, the following code brings the background task ticker counter TIM
on the 7S-LED display:
: show BASE @ HEX TIM 7S . BASE ! ;
' show BG !
The following code stops the background routine, and dumps the RAM contents:
0 BG !
HEX 300 F0 DUMP
300 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
310 0 0 0 0 0 0 0 0 0 0 0 0 0 70 0 70 _____________p_p
320 0 0 0 20 0 30 3 27 3 29 0 10 3 20 3 FF ___!_!___)_+_0__
330 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
340 0 0 0 0 0 70 0 EE 97 6C 0 39 0 60 0 A _____p_n_l_9_`__ <- BSPP used in background
350 48 45 58 20 33 30 30 20 46 30 20 44 55 4D 50 45 HEX 300 F0 DUMPE
360 58 20 54 49 4D 20 37 53 20 2E 20 42 41 53 45 20 X TIM 7S . BASE
370 21 20 3B 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A ! ;jjjjjjjjjjjjj
380 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A jjjjjjjjjjjjjjjj
390 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A jjjjjjjjjjjjjjjj
3A0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
3B0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
3C0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ________________
3D0 0 0 0 0 0 0 0 89 9 23 3 24 9 1 3 81 _________#_$___
3E0 0 81 38 23 33 3 24 0 F1 0 82 6F 8C A7 0 0 A! #0_A%__(_:__o
3F0 95 30 0 C 95 6D 0 0 0 10 89 78 91 8B 91 CD _____|_____x___M
Since the Background Task doesn't have a dedicated USR
area, the USR
variable HLD
, used by the output formatting routines, must be saved in the context switch (else if numbers were printed on a 7S-LED display in the background the output of DUMP
would be mangled).
Conclusions:
- for simple background code the
BSPP
area with 16 cells (32 bytes) is sufficient - working without a dedicated
USR
area works for simple tasks - at least the output routines must have a different set of
USR
variables