* timer.asm * Routines for using the timer system. #include "debug.asm" *--------------------------------------------------------------------------------------------------- * Subroutine: setup_periodic_alarm * Purpose: * Arrange for an alarm to go off at regular intervals, and call a specific subroutine every * time it does. Uses the HC11's built-in Timer Output Compare facility. Supports up to 5 * independent alarms. User is responsible for doing the CLI to enable interrupts (either * before or after calling this subroutine). * Inputs: * A = Alarm number, 0-4 * X = Pointer to subroutine to invoke (not an ISR). * Y = Alarm period in timer cycles, 0-65535. * Side effects: * Timer output compare TOC(A+1) is set up to call subroutine at X. * All registers are trashed. *--------------------------------------------------------------------------------------------------- * First, declare frame pointer offsets for local variables: lOffset: equ 0 ; For offsets calculated from alarm numbers. lPseudo: equ lOffset+2 ; For pointer to pseudo-vector address. lCCR: equ lPseudo+2 ; For preserving caller's condition code register. lPeriod: equ lCCR+1 ; For preserving caller's Y / period of alarm. lSub: equ lPeriod+2 ; For preserving caller's X / pointer to user subroutine. lAlarm: equ lSub+2 ; For preserving caller's A / alarm number. sLocals: equ lAlarm+1 ; Total space occupied by local variables. * Error messages. alarm_too_big: fcc "Alarm number is too big." fcb EOT * The actual subroutine starts here. setup_periodic_alarm: * Save arguments, reserve space for local variables, and set up frame pointer. psha ; Stack variable lAlarm <- alarm number A. pshx ; Stack variable lSub <- subroutine pointer X. pshy ; Stack variable lPeriod <- alarm period Y. tpa ; Copy caller's predicate bits (CCR) to A. psha ; Stack variable lCCR <- caller's predicates. tsy ; Let Y point to top of stack (lCCR). xgdy ; Put frame pointer Y into D. subd #lCCR ; Adjust it to make space for additional locals above lCCR. (lOffset, lPseudo) xgdy ; Put frame pointer back into Y. tys ; Move stack pointer above locals frame, in case we want to call subroutines. * Do some error checking on our input arguments. ldaa lAlarm,y ; A = Alarm number. cmpa #4 ; Max possible alarm number. bls spa4 ; If that or lower, we're OK. ldx #alarm_too_big ; Select error message: Alarm is too big. jsr outstrg ; Print error message to SCI. jmp spa3 ; Exit from subroutine without really doing anything. spa4: * Disable interrupts before we start changing anything. sei ; Mask interrupts while we're rearranging things. * First, we calculate the pseudo-vector address in Buffalo's jumptable that we'll need to set. ldaa lAlarm,y ; A = Alarm number. ldab #JUMPSIZE ; B = Size of a jump instruction. mul ; D = Offset to the jumptable entry we want std lOffset,y ; Store it in lOffset local variable. ldd #PVTOC1 ; Point D at last TOC jumptable pseudo-vector entry. subd lOffset,y ; Subtract the lOffset we saved earlier. std lPseudo,y ; Store D in lPseudo local variable. * Next, find the address of the appropriate ISR. ldab lAlarm,y ; Load alarm number. lslb ; Double to get a word offset. clra ; Clear MSB of D; effectively transfers B->D. std lOffset,y ; Store in lOffset local variable. addd #TOC_ISRs ; Add base address of TOC ISR table. xgdx ; Transfer TOC ISR table entry pointer to X. ldd 0,x ; Transfer TOC ISR pointer to D. * Now, store the ISR address in the pseudo-vector location. ldx lPseudo,y ; Load pseudo-vector pointer we computed in previous section. std 0,x ; Store TOC ISR pointer at pseudo-vector location. This sets up the vector. * Figure out where to store our subroutine pointer. ldd lOffset,y ; Load offset we computed earlier. addd #sub1 ; Add base of subroutine pointer table. xgdx ; X now points to subroutine pointer table entry. * Store the subroutine pointer there. ldd lSub,y ; Load pointer to the actual subroutine into D. std 0,x ; Store it in the subroutine pointer table entry. * Now, compute the appropriate mask bit to select OCa in the TMSK1 and TFLG1 registers. ldab #OC1I ; Bit mask for OC1I register bit. (Also is OC1F.) ldaa lAlarm,y ; This is the alarm number 0-4 we pushed earlier. spa0: beq spa1 ; If it's 0, the mask we have now is the right one. lsrb ; Shift the mask right one position (go to next OC) deca ; Subtract 1 from alarm number. bra spa0 ; Keep looping. spa1: * The following code actually enables the interrupts on Output Compare #a to occur. stab TFLG1 ; Storing "1" turns OFF bit OCaF in TFLG1 (clears state of OCaF flag). orab TMSK1 ; This prevents the next line from turning off other OCxI bits. stab TMSK1 ; Turns on bit OCaI in TMSK1 (enables interrupt OCaI). * Remember the period, for use by the ISR. ldd lOffset,y ; Load offset computer earlier. addd #per1 ; Add to base of table of periods. xgdx ; Point X at the entry we want. ldd lPeriod,y ; Load the period. std 0,x ; Store X in the table of periods. * Finally, we set the initial OCa alarm time relative to the present time. ldd lOffset,y ; Load offset which we computed earlier. addd TOC1 ; Add to base of TOC registers to point to TOCa. xgdx ; Put the pointer into regIX. ldd TCNT ; Load the present system timer value. addd lPeriod,y ; Add it to the alarm period that we saved earlier. std 0,x ; Store it in the appropriate TOCa register. (This line sets the initial alarm trigger time.) * Finally, if alarm 4 (OC5) was selected, we have to configure pin PA3 as OC5 rather than IC4. ldaa lAlarm,y ; Load A with the alarm number. cmpa #4 ; Was it alarm #4? (Representing OC5.) bne spa2 ; If not, then don't... ldx #PACTL ; Select PACTL register. bclr 0,x I4O5 ; Clear the I4/O5 bit (select OC5 rather than IC4 for pin PA3). spa2: * Throw away all the locals, restore stack pointer and the I bit. Don't bother restoring any other registers. spa3: ldaa lCCR,y ; Load caller's CCR (really we just care about I). tap ; Transfer it to the real CCR. (This may re-enable interrupts, if they were enabled in caller.) xgdy ; Move frame pointer into D. addd #sLocals ; Bump it up past all the locals. (Including pushed registers.) xgdy ; Transfer it back to Y. tys ; And from there back to the stack pointer. Now it's safe to return. rts ; Return from setup_periodic_alarm subroutine. *------------------------------------------------------------------------ * Interrupt Service Routines that may be set up by setup_periodic_alarm. *------------------------------------------------------------------------ ISR_TOC1: ldd TOC1 ; Get current TOC1 alarm time. addd per1 ; Add the period for this periodic timer. std TOC1 ; Store it as the new alarm time. ldx sub1 ; Load first subroutine address. jsr 0,x ; Jump to that subroutine. ldaa #OC1F ; Select OC1F flag (in TFLG1 register). staa TFLG1 ; Write 1 to OC1F in TFLG1 to turn off OC1F, which resets TOC1. rti ; Return from ISR_TOC1. ISR_TOC2: ldd TOC2 ; Get current TOC2 alarm time. addd per2 ; Add the period for this periodic timer. std TOC2 ; Store it as the new alarm time. ldx sub2 ; Load 2nd subroutine address. jsr 0,x ; Jump to that subroutine. ldaa #OC2F ; Select OC2F flag (in TFLG1 register). staa TFLG1 ; Write 1 to OC2F in TFLG1 to turn off OC2F, which resets TOC2. rti ; Return from ISR_TOC2. ISR_TOC3: ldd TOC3 ; Get current TOC3 alarm time. addd per3 ; Add the period for this periodic timer. std TOC3 ; Store it as the new alarm time. ldx sub3 ; Load 3rd subroutine address. jsr 0,x ; Jump to that subroutine. ldaa #OC3F ; Select OC3F flag (in TFLG1 register). staa TFLG1 ; Write 1 to OC3F in TFLG1 to turn off OC3F, which resets TOC3. rti ; Return from ISR_TOC3. ISR_TOC4: ldd TOC4 ; Get current TOC4 alarm time. addd per4 ; Add the period for this periodic timer. std TOC4 ; Store it as the new alarm time. ldx sub4 ; Load 4th subroutine address. jsr 0,x ; Jump to that subroutine. ldaa #OC4F ; Select OC4F flag (in TFLG1 register). staa TFLG1 ; Write 1 to OC4F in TFLG1 to turn off OC4F, which resets TOC4. rti ; Return from ISR_TOC3. ISR_TOC5: ldd TOC5 ; Get current TOC5 alarm time. addd per5 ; Add the period for this periodic timer. std TOC5 ; Store it as the new alarm time. ldx sub5 ; Load 5th subroutine address. jsr 0,x ; Jump to that subroutine. ldaa #OC5F ; Select OC5F flag (in TFLG1 register). staa TFLG1 ; Write 1 to OC5F in TFLG1 to turn off OC5F, which resets TOC5. rti ; Return from ISR_TOC5. *------------------------------------------------------ * Persistent variables needed by setup_periodic_alarm. *------------------------------------------------------ * Table of pointers to all of our ISRs for the TOC interrupts. TOC_ISRs: fdb ISR_TOC1 fdb ISR_TOC2 fdb ISR_TOC3 fdb ISR_TOC4 fdb ISR_TOC5 * Programmers: Be sure to locate the below variable data region in RAM. * Data region for remembering the periods for the 5 possible periodic alarms. per1: rmb WORDSIZE per2: rmb WORDSIZE per3: rmb WORDSIZE per4: rmb WORDSIZE per5: rmb WORDSIZE * Data region for remembering the addresses of the subroutines to call upon alarm. sub1: rmb WORDSIZE sub2: rmb WORDSIZE sub3: rmb WORDSIZE sub4: rmb WORDSIZE sub5: rmb WORDSIZE