-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathsimple-tasker.mu4
128 lines (110 loc) · 4.31 KB
/
simple-tasker.mu4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
| This file is part of muforth: https://muforth.dev/
|
| Copyright 2002-2025 David Frech. (Read the LICENSE for details.)
| This is for non-Forth code that simply needs to switch stack pointers
| between two or more independent tasks. Each task has its own stack and a
| small "user area"; yield does not push or pop any registers: that is each
| task's responsibility.
|
| This file should be treated as a template; copy and modify it to fit your
| needs.
loading AVR tasks (simple)
| All of the task stacks live in a single 256 byte page - generally the
| *last* page of the ram. This way task "pointers" only consume a single
| byte in the user area, and task switching is very fast: a single byte load!
|
| The first byte of the user area is the status flag; the second byte is
| the "pointer" to the next task. The third is the saved SPL, and the
| fourth is currently unused. (It used to be SPH, until I realized that it
| was unnecessary.)
|
| A status byte that is <0 means runnable; >=0 means stopped. Doing it this
| way makes it easy to use the status field as a counting semaphore.
__meta
| User pointer is stored in registers r14 and r15. We have made synonyms
| for the registers in target/AVR/meta.mu4
| App code depends on the boot region beginning with the following:
|
| RESET vector
| create-task hook
| yield
here origin 1 vectors + xor .if
origin 1 vectors + goto
warn"
Boot region contains code after the RESET vector. This will break
the tasking code. That code has been removed... "
.then
hook create-task-hook
| yield by default pushes no registers! The caller is responsible for
| saving and restoring, around the call to yield, any registers they care
| about.
label yield
u y movw ( get user pointer into y)
SPL g0 in 2 g0 stu ( get current SPL and save it into user area)
| We have to find a task to run. It's possible that an interrupt could
| wake a sleeping task, so we just try and try until something is ready.
|
| If one of the tasks is a chat/debug stub, its status is always <0, so
| it will always run.
begin
1 yl ldu ( follow pointer to next task)
0 g0 ldu ( load task's status byte)
g0 tst 0< until
( Found a live one; fall thru to run it.) ;c
label run-task
y u movw ( set user pointer from y)
2 g0 ldu SPL g0 out ( restore SPL from user area)
ret ( run!) ;c
| Enter with:
| h1:h0 = initial pc, in ra form (shifted right 1 bit, big-endian)
| h2 = prev user area low byte pointer
| y = current user area
| Exit with:
| h2 = current user area low byte pointer
hooks create-task-hook
label create-task
1 h2 stu ( pointer to prev task)
yl h2 mov ( prev task = this task)
( fall thru) ;c
| Enter with:
| h1:h0 = initial pc, in ra form (shifted right 1 bit, big-endian)
| y = current user area
|
| Clobbers h0.
label init-task
( Create an initial stack frame: just the starting PC.)
y x movw ( x is our temp sp)
-x h1 st -x h0 st ( push pc; host has put this into ra form)
1 x sbiw ( empty descending fixup)
2 xl stu ( save temp SPL to user area)
"ff h0 ldi 0 h0 stu ( set initial status: runnable)
ret ;c
| comment
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| ( This is example code. It has to be loaded *after* all your other code,
| because it sets up and runs the top-level routines that make up your
| program. Consider this a template to start from.)
|
| ( Every task has roughly the following form:)
| label example-task
| example-init rcall
| begin ... <push regs> yield rcall <pop regs> ... again ;c
|
| RESET handler
| label system-startup
| ( Before we do anything else, let's start with a clean slate, by zeroing
| the entire RAM.)
|
| @ram x ldiw #ram w ldiw g0 clr begin x+ g0 st 1 w sbiw 0= until
|
| ( Set up a temporary stack - so we can call setup-task! - below the task
| stacks. Since the stacks consume at most a 256 byte page, the page
| below the last one will do fine.)
|
| @ram #ram + #256 - 1- w ldiw w sp!-iclear
|
| ( Create the tasks and start executing! create-tasks should leave y
| pointing to the first task to run.)
|
| create-tasks rcall run-task rjmp ;c
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~