1;* ======================================================================== *;
2;*  The routines and data in this file (timer.asm) are dedicated to the     *;
3;*  public domain via the Creative Commons CC0 v1.0 license by its author,  *;
4;*  Joseph Zbiciak.                                                         *;
5;*                                                                          *;
6;*          https://creativecommons.org/publicdomain/zero/1.0/              *;
7;* ======================================================================== *;
8
9;;==========================================================================;;
10;; Joe Zbiciak's Task Routines:  Timer-based Tasks                          ;;
11;; http://spatula-city.org/~im14u2c/intv/                                   ;;
12;;==========================================================================;;
13
14;; ======================================================================== ;;
15;;  GLOBAL VARIABLES USED BY THESE ROUTINES                                 ;;
16;;                                                                          ;;
17;;  Note that some of these routines may use one or more global variables.  ;;
18;;  If you use these routines, you will need to allocate the appropriate    ;;
19;;  space in either 16-bit or 8-bit memory as appropriate.  Each global     ;;
20;;  variable is listed with the routines which use it and the required      ;;
21;;  memory width.                                                           ;;
22;;                                                                          ;;
23;;  Example declarations for these routines are shown below, commented out. ;;
24;;  You should uncomment these and add them to your program to make use of  ;;
25;;  the routine that needs them.  Make sure to assign these variables to    ;;
26;;  locations that aren't used for anything else.                           ;;
27;;                                                                          ;;
28;;  NOTES                                                                   ;;
29;;   -- TSKQHD and TSKQTL must be initialized to 0.                         ;;
30;;                                                                          ;;
31;;   -- The symbol OVRFLO isn't required.  If it's not available, then      ;;
32;;      the code won't count overflows.                                     ;;
33;;                                                                          ;;
34;;   -- The symbol WTIMER isn't required.  If it's not available, then      ;;
35;;      the function WAIT will not be included.                             ;;
36;;                                                                          ;;
37;;   -- The TSKQ must have room for TSKQM + 1 words.  That is, if TSKQM     ;;
38;;      is 15, then TSKQ must have room for 16 words.                       ;;
39;;                                                                          ;;
40;;   -- The TSKDQ must have room for 2*(TSKQM + 1) bytes.  That is, if      ;;
41;;      is 15, then TSKDQ must have room for 32 bytes.                      ;;
42;;                                                                          ;;
43;;   -- The TSKTBL must have room for MAXTSK*4 words.                       ;;
44;; ======================================================================== ;;
45
46                       ; Used by       Req'd Width  Description
47                       ;------------------------------------------------------
48;MAXTSK EQU     4      ; DOTIMER/etc   EQUATE       Maximum # of timer tasks
49;OVRFLO EQU     $130   ; DOTIMER       8-bit        Number of queue overflows
50;WTIMER EQU     $131   ; DOTIMER/WAIT  8-bit        Number of queue overflows
51;TSKQHD EQU     $132   ; DOTIMER/etc   8-bit        Task queue head
52;TSKQTL EQU     $133   ; DOTIMER/etc   8-bit        Task queue tail
53;TSKQ   EQU     $300   ; DOTIMER/etc   16-bit       Task queue
54;TSKDQ  EQU     $140   ; DOTIMER/etc   8-bit        Task data queue
55;TSKQM  EQU     $F     ; DOTIMER/etc   EQUATE       Task queue length mask
56;TSKACT EQU     $134   ; DOTIMER/etc   8-bit        # of active timer tasks
57;TSKTBL EQU     $310   ; DOTIMER/etc   16-bit       Timer-task table
58
59
60;; ======================================================================== ;;
61;;  DOTIMER                                                                 ;;
62;;                                                                          ;;
63;;  This function handles all the timer-based tasks.  It provides support   ;;
64;;  for periodically-occuring events.                                       ;;
65;;                                                                          ;;
66;;  >>  NOTE:  Call this early in your ISR, or call it with interrupts  <<  ;;
67;;  >>  disabled!  This code is NON-REENTRANT.                          <<  ;;
68;;                                                                          ;;
69;;  This routine counts down task timers and schedules tasks.  We have up   ;;
70;;  to MAXTSK active timer-based tasks at one time.  The tasks may be a     ;;
71;;  mixture of one-shot and periodic tasks.  The following describes the    ;;
72;;  timer evaluation logic that is applied to each task.                    ;;
73;;                                                                          ;;
74;;   -- If the count is initially <= 0, the task is inactive and            ;;
75;;      is skipped.                                                         ;;
76;;                                                                          ;;
77;;   -- The counter value is decremented by 2.  If the count goes to        ;;
78;;      zero, the task is triggered and its count is reinitialized.         ;;
79;;      Thus, tasks w/ even periods are periodic tasks.                     ;;
80;;                                                                          ;;
81;;   -- If the count goes negative, the task is triggered and its           ;;
82;;      count is not reinitialized.  Thus, tasks w/ odd periods are         ;;
83;;      one-shot tasks.                                                     ;;
84;;                                                                          ;;
85;;  This arrangement allows us to have one-shot and repeating tasks with    ;;
86;;  minimal overhead.  Repeating tasks clear bit 0 of their period count,   ;;
87;;  and one-shot tasks set bit 0.                                           ;;
88;;                                                                          ;;
89;;  When tasks are triggered, their procedure address is written to the     ;;
90;;  task queue.  The task queue is drained by RUNQ routine in "taskq.asm".  ;;
91;;                                                                          ;;
92;;  The task queue is a small circular buffer in 16-bit memory with head    ;;
93;;  and tail pointers in 8-bit memory.  The circular buffer is 16 entries   ;;
94;;  large by default.  You can make it larger or smaller by changing the    ;;
95;;  macro TSKQM.  This code queues the task directly.  It does not call     ;;
96;;  QTASK.                                                                  ;;
97;;                                                                          ;;
98;;  If the task queue overflows, the overflowing task is dropped.  If the   ;;
99;;  symbol OVRFLO is defined, overflows are tallied to that variable.       ;;
100;;  Also, a task-queue overflow will cause DOTIMER to not count-down        ;;
101;;  the rest of the tasks in the task table during this tick.               ;;
102;;                                                                          ;;
103;;                                                                          ;;
104;;  WAIT TIMER SUPPORT                                                      ;;
105;;                                                                          ;;
106;;  If WTIMER is defined, this routine will also count down the busy-       ;;
107;;  wait timer used by WAIT.                                                ;;
108;;                                                                          ;;
109;;                                                                          ;;
110;;  TASK CONTROL TABLE ENTRY LAYOUT                                         ;;
111;;                                                                          ;;
112;;      Word 0:  Function Pointer for task                                  ;;
113;;      Word 1:  Instance data (passed in R1 to task)                       ;;
114;;      Word 2:  Current down-count                                         ;;
115;;      Word 3:  Counter reinitialization value.                            ;;
116;;                                                                          ;;
117;;==========================================================================;;
118DOTIMER     PROC
119            PSHR    R5              ; Save return address
120
121            MVI     TSKACT, R0      ; Iterate over active tasks
122            TSTR    R0
123            BEQ     @@notasks
124            MVII    #TSKTBL+2, R3   ; Point to the task control table
125
126@@taskloop: MVI@    R3,     R1      ; Get tasks's current tick count
127            TSTR    R1              ; Is it <= 0?
128            BLE     @@nexttask      ; ... yes? Skip it.
129
130
131            SUBI    #2,     R1      ; Count it down.
132            BNEQ    @@noreinit      ; If it went to zero, reinit count
133
134            INCR    R3
135            MVI@    R3,     R1      ; Get new period for task
136            DECR    R3
137            SUBR    R2,     R2      ; Z=1, S=0 (causes the BGT to not be taken)
138
139@@noreinit: MVO@    R1,     R3      ; Store new period.
140            BGT     @@nexttask      ; If this count didn't expire, ...
141                                    ;  ... go to the next task
142@@q:
143            ; Schedule this task by adding it to the task queue
144            MOVR    R3,     R4
145            SUBI    #2,     R4
146            MVI@    R4,     R2      ; Get task function pointer
147            MVI     TSKQHD, R1      ; Get task queue head
148            INCR    R1              ; Move to next slot
149            ANDI    #TSKQM, R1      ; Stay within circ buffer
150            CMP     TSKQTL, R1
151            BEQ     @@overflow      ; Drop this task if queue overflows. (!)
152
153            MOVR    R1,     R5
154            ADDR    R1,     R5      ; R5 is for task data queue
155            ADDI    #TSKDQ, R5      ; Point to task data queue
156
157            MVO     R1,     TSKQHD  ; Store updated task queue head
158
159            ADDI    #TSKQ,  R1      ; Point to actual task queue
160            MVO@    R2,     R1      ; Store the function pointer
161            MVI@    R4,     R2      ; Get task instance data
162            MVO@    R2,     R5      ; Store low half of instance data
163            SWAP    R2,     1
164            MVO@    R2,     R5      ; Store high half of instance data
165
166@@nexttask: ADDI    #4,     R3      ; Point to next task struct
167            DECR    R0
168            BNEQ    @@taskloop      ; Loop until done with all tasks
169        IF  DEFINED OVRFLO
170            B       @@notasks
171
172@@overflow:
173            MVI     OVRFLO, R0
174            INCR    R0
175            MVO     R0,     OVRFLO
176        ELSE
177@@overflow:
178        ENDI
179
180@@notasks:
181
182        IF  DEFINED WTIMER
183@@wtimer:
184            ;; Count down the wait-timer, if there is one
185            MVI     WTIMER, R5
186            DECR    R5
187            BMI     @@wt_expired
188            MVO     R5,     WTIMER
189@@wt_expired:
190        ENDI
191
192            PULR    PC
193            ENDP
194
195
196
197
198;; ======================================================================== ;;
199;;  STARTTASK                                                               ;;
200;;      Puts a task into a task slot for general timer-based scheduling.    ;;
201;;                                                                          ;;
202;;  INPUTS:                                                                 ;;
203;;      R5 -- Invocation record:                                            ;;
204;;              DECLE -- Task number                                        ;;
205;;                                                                          ;;
206;;              DECLE -- Task function pointer                              ;;
207;;                                                                          ;;
208;;              DECLE -- Task initial period * 2.                           ;;
209;;                       If bit 0==1, this is one-shot task.                ;;
210;;                                                                          ;;
211;;              DECLE -- Task period re-init.  Useful if the task's first   ;;
212;;                       delay != recurring delay, or for one-shots that    ;;
213;;                       will be retriggered.                               ;;
214;;                                                                          ;;
215;;      R2 -- Task instance data (optional)                                 ;;
216;;                                                                          ;;
217;;  OUTPUTS:                                                                ;;
218;;      Task record is set up.  (User still needs to update TSKACT to       ;;
219;;      make the task records active, if necessary.)                        ;;
220;;                                                                          ;;
221;;  Registers R0, R4 are trashed.                                           ;;
222;; ======================================================================== ;;
223STARTTASK   PROC
224            MVI@    R5,     R0
225            SLL     R0,     2       ; R0 = R0 * 4 (four words/entry)
226            MVII    #TSKTBL,R4      ; R4 = &TSKTBL[0]
227            ADDR    R0,     R4      ; R4 = &TSKTBL[n]
228            DIS                     ; Entering critical section.
229            MVI@    R5,     R0      ; Get Function pointer
230            MVO@    R0,     R4      ; ... and write it
231            MVO@    R2,     R4      ; Write task instance data
232            MVI@    R5,     R0      ; Get task period
233            MVO@    R0,     R4      ; ... and write it
234            MVI@    R5,     R0      ; Get task period reinit
235            MVO@    R0,     R4      ; ... and write it
236            EIS
237            JR      R5
238            ENDP
239
240;; ======================================================================== ;;
241;;  STOPTASK                                                                ;;
242;;      Stops a given task by setting its period to -1.  Task can be        ;;
243;;      retriggered/restarted by calling RETRIGGERTASK.                     ;;
244;;                                                                          ;;
245;;  INPUTS:                                                                 ;;
246;;      R3 -- Task number.                                                  ;;
247;;                                                                          ;;
248;;  OUTPUTS:                                                                ;;
249;;      Task is disabled.                                                   ;;
250;;      R0 == -1, R3 points to task delay count.                            ;;
251;; ======================================================================== ;;
252STOPTASK    PROC
253            SLL     R3,     2       ; R3 = R3 * 4 (four words/entry)
254            ADDI    #TSKTBL+2, R3   ; R3 = &TSKTBL[n].delay
255            CLRR    R0
256            DECR    R0              ; R0 == -1.
257            MVO@    R0,     R3      ; TSKTBL[n].delay = -1
258            JR      R5
259            ENDP
260
261;; ======================================================================== ;;
262;;  RETRIGGERTASK                                                           ;;
263;;      Restarts a task by copying its delay re-init field to its delay     ;;
264;;      field.  Can be used on one-shot tasks or tasks which have been      ;;
265;;      stopped with 'STOPTASK'.                                            ;;
266;;                                                                          ;;
267;;  INPUTS:                                                                 ;;
268;;      R3 -- Task number.                                                  ;;
269;;                                                                          ;;
270;;  OUTPUTS:                                                                ;;
271;;      R0 -- Task delay                                                    ;;
272;;      R3 -- points to task delay count                                    ;;
273;; ======================================================================== ;;
274
275RETRIGGERTASK:  PROC
276            SLL     R3,     2       ; R3 = R3 * 4 (four words/entry)
277            ADDI    #TSKTBL+3, R3   ; R3 = &TSKTBL[n].reload
278            DIS
279            MVI@    R3,     R0      ; Get reload value
280@@chain:    DECR    R3              ; Offset 2 is delay count
281            MVO@    R0,     R3
282            EIS
283            JR      R5
284
285            ENDP
286
287;; ======================================================================== ;;
288;;  WAIT                                                                    ;;
289;;      Busy-waits for the number of ticks specified in R0.                 ;;
290;;                                                                          ;;
291;;      This function is build only if WTIMER is defined.                   ;;
292;;                                                                          ;;
293;;  INPUTS:                                                                 ;;
294;;      R0 -- Number of ticks to wait                                       ;;
295;;      R5 -- Return address                                                ;;
296;;                                                                          ;;
297;;  OUTPUTS:                                                                ;;
298;;      R0 -- cleared                                                       ;;
299;; ======================================================================== ;;
300        IF  DEFINED WTIMER
301WAIT        PROC
302            MVI@    R5,     R0
303            MVO     R0,     WTIMER
304            CLRR    R0
305@@loop:     CMP     WTIMER, R0
306            BNEQ    @@loop
307            JR      R5
308            ENDP
309        ENDI
310
311;;==========================================================================;;
312;;  RATELIMIT                                                               ;;
313;;      Like "WAIT", except it waits for the count from the previous call   ;;
314;;      to expire.  The idea is that you set a timer now, and later wait    ;;
315;;      for it to go off.                                                   ;;
316;;                                                                          ;;
317;;  INPUTS:                                                                 ;;
318;;      R5 -- Number of ticks to wait next time, followed by return address ;;
319;;                                                                          ;;
320;;  OUTPUTS:                                                                ;;
321;;      R0 -- Number of ticks to wait next time                             ;;
322;;==========================================================================;;
323        IF  DEFINED WTIMER
324RATELIMIT   PROC
325            CLRR    R0
326@@loop:
327            CMP     WTIMER, R0
328            BNEQ    @@loop
329
330            MVI@    R5,     R0
331            MVO     R0,     WTIMER
332            JR      R5
333            ENDP
334        ENDI
335
336;; ======================================================================== ;;
337;;  End of File:  timer.asm                                                 ;;
338;; ======================================================================== ;;
339