Skip to content

STM8 eForth Background Task

Richard edited this page Jan 22, 2021 · 9 revisions

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).

Background Task Properties

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 and EMIT 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.

Background Task Examples

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.

Changing Default Tick

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

Background Task technical background

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.

Memory layout

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
Clone this wiki locally