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