Introduction
 
In this demo we will create and run a program which illustrates key features of timers and interrupts. It is assumed that you have read the overviews on interrupts and timers. This demo also illustrates a few general features of assembly programming, such as program structure, the use of directives, masks, etc..
 
 
Part 1 - Program Overview
 
The source code below is that of a program which is used to flash LEDs using interrupt-driven hardware timers. This is similar in functionality to that of lab 3 which used subroutines and software timing, though the design here is very different.
 
Have a good look at the program. It seems long, but much of what is written are comments and EQU directives to improve readability. To emphasize the binary nature of the data, some of the EQU directives were written in binary form (leading % sign) rather than in hex form (leading $ sign). Whether you use hex or binary is a matter of personal preference; most commonly you will see the hex form used.
 
The active region of the program is in the middle of the program listing, and is where the actual assembly instructions are located. The active region essentially has three functional areas:
 
   1. The first area involves initialization, where we set up data direction registers, set up the interrupt registers, test flash the LEDs, etc.
 
   2. Following this is the "main" loop, which is just one line (an infinite loop). This is the main branch of our program - we just sit here until an interrupt service routine is activated. Though it is only one line here, in other programs we would be using this section to accomplish other tasks.
 
   3. The third part of the program is the interrupt service routine area, and this is the part of the program where most of the useful action occurs. There are three interrupt service routines - each is activated by the hardware in response either to an external hardware event, such as pushing a button, or an internal hardware event, such as a timer overflow.
 
Many programs have the structure seen below: header comments, memory assignments, EQU and data directives at the beginning of the program; active code/interrupts/subroutines in the middle of the program; and interrupt vectors defined at the very end.
 
Note that we make extensive use of masks in the code below. We use masks to create binary patterns in given registers. The mask can represent a binary pattern or value that is written directly to a register. Or, with certain commands such as the "bclr" (bit clear) command, the mask can be used to selectively change only certain bits in a register. In the latter case, usually a mask bit is set to a "1" if we want to change a given bit, and set to "0" if we want to ignore a certain bit. As an example, suppose that we use "mask EQU %00010011" and later use the instruction "bclr $address1, #mask". This will clear bits 0,1 and 4 of the memory at address1, but will leave the other bit values unchanged.
 
      ;****************************************************************
      ;*
      ;* UTEP EE3376
      ;* timer_demo.asm
      ;* 4 Feb 2005
      ;*
      ;* Each pushbutton activates different LED each for a different time
      ;* sw2 lights LED2 (port B, bit 2) 1 second
      ;* sw3 lights LED3 (port B, bit 3) 5 seconds
      ;* Port H ISR turns on LEDs
      ;* Timer Overflow ISR turns off LEDs 0-6 at correct time
      ;*
      ;* Use Real Time Interrupt ISR to flash LED7 on/off every quarter-second.
      ;* There is an intentional flaw in the ISRs that makes the LED7
      ;* flash rate erratic. The flaw associated with using "movb..." instead of
      ;* "bset" or "blcr" commands.
      ;*
      ;* In the hardware, want Port H Dip Switches all set to "Off" (= logical 1)
      ;* Port H pusbuttons reversed from expected bit order:
      ;* Port H pushbutton SW2: when pushed Port H bit 3 (of 0-7) is logical 0
      ;* Port H pushbutton SW3: when pushed Port H bit 2 (of 0-7) is logical 0
      ;
      ;* Code Warrior based, Runs on the HCS12, assumes 48 MHz Core clock
      ;* M-clock = bus clock = 24 MHz (controls timer TCNT)
      ;* OSCCLK = 4 MHz controls RTI interrupt rate
      ;*
      ;* Has the Port J code needed for the Rev.E Dragon12 boards
      ;*
      ;****************************************************************
 
      ; export symbols
              XDEF        Entry    ; export 'Entry' symbol
              ABSENTRY    Entry    ; for absolute assembly: mark as application entry point
 
 
      PSEUDO_ROM    EQU        $1000    ; absolute address to place code/constant data
      RAM            EQU        $2000    ; absolute address to place variables
      STACK            EQU        $3C00    ; top of stack
 
      ; registers
      portb            EQU        $0001
      portb_ddr        EQU        $0003    ; Port B Data Direction Register
      porth            EQU        $0260
      porth_ddr        EQU        $0262    ; Port H Data Direction Register
      porth_psr        EQU        $0265    ; Port H Polarity Select Register
      porth_ier        EQU        $0266    ; Port H Interrupt Enable Register
      porth_ifr        EQU        $0267    ; Port H Interrupt Flag Register
      tscr1            EQU        $0046    ; Timer System Control Reg 1
      tscr2            EQU        $004D    ; Timer System Control Reg 2
      tflg2            EQU        $004F    ; Timer Int. Flag 2 (holds timer overflow flag)
      crgflg            EQU        $0037    ; Clock and Reset Generator (CRG) Flags Register
      crgint            EQU        $0038    ; CRG Interrupt enable register
      rtictl            EQU        $003B    ; Real Time Interrupt Control Register
 
      ; masks and binary patterns
      input            EQU        %00000000    ; set ddr to input port
      output            EQU        %11111111    ; set ddr to output port
      falling            EQU        %00000000    ; falling edge for polarity
 
      ledsoff            EQU        %10000000    ; mask to set LED bits 0-6 to off
      ledson            EQU        %11111111    ; set all LED bits to on (lights LEDs)
      led2on            EQU        %00000100    ; bit 2 for LED2
      led3on            EQU        %00001000    ; bit 3 for LED3
      led7mask        EQU        %10000000    ; mask for LED7 to toggle on/off
 
      sw2ifl            EQU        %00001000    ; mask to test if sw2 IFL set
      sw3ifl            EQU        %00000100    ; mask to test if sw3 IF set
      sw2and3on        EQU        %00001100    ; set bits 2 and 3 on for sw2 and sw3
      clear_ifl            EQU        %11111111    ; clear ALL Interrupt Flags by writing 1 to IFR
 
      set_ten            EQU        %10000000    ; enable timer in tscr1 reg
      set_toi            EQU        %10000100    ; sets TOI, and PRn prescale factor to 16 in tscr2
      set_rtie            EQU        %10000000    ; enable RTI
      set_rti            EQU        %01111111    ; set RTI prescaler to 16x2^16 (~ 262 msec int. rate)
      ack_rtiflg        EQU        %10000000    ; acknowledge the RTI flag
 
      ; constants
      sec            EQU        $17        ; TCNT overflows every 43.6 msec; 23 ($17) overflow/sec
      five_sec        EQU        $73        ; 115 ($73) TCNT overflows every 5 sec
 
      ;-----------------------------------------------------
      ; variable/data section
                      ORG        RAM
      ; Insert your data definitions here.
 
      counter            ds.b    1            ; counter for timer overflow (TOV) ints
      setpoint            ds.b    1            ; setpoint for TOV counter
      led7_toggle        ds.b    1            ; toggle bit for LED7 in the RTI ISR
 
      ;------------------------------------------------------
      ; code section
                  ORG        PSEUDO_ROM    ;set PC to $1000
 
      ; Insert your code following the label "Entry"
      Entry:
 
                    ; the following two lines for Port J are needed for Rev.E boards
                    ; it won't hurt to leave them with the Rev C and Rev D boards
                    movb  #$FF,$026A      ; make Port J output via DDR
                    movb #$00, $0268      ; make Port J low to enable Port B LEDs
          
                  ; do some port initialization
                  lds    #STACK            ; initialize stack pointer
                  movb    #$00, counter            ; initialize TOV counter
                  movb    #$00, led7_toggle        ; initialize LED7 toggle bit to 0 (off)
                  movb    #input, porth_ddr        ; set Port H to input
                  movb    #output, portb_ddr        ; set Port B to output
                  movb    #falling, porth_psr        ; set Port H interrupt polarity to falling edge since
                                      ; Port H pin goes low when we push switch
                  
                  ;flash all LEDs for one second
                  movb    #ledson, portb
                  movb    #sec, setpoint    
 
                  ; do some timer/interrupt initialization
                  movb    #sw2and3on, porth_ier        ; enable Port H ints for sw2 and sw3
                  movb    #set_ten, tscr1        ; enable timer
                  movb    #set_toi, tscr2        ; enable timer overflow and set prescaler
                  movb    #set_rti, rtictl        ; enable RTI and set RTI prescaler
                  movb    #set_rtie, crgint        ; enable RT interrupts
 
                  cli                ; global interrupt enable
                  
                  ; our main program infinite loop
      loop:            bra    loop            ; sit here forever
 
      ;------------------------------------------------------
      ; ISR for Real Time Interrupt - pulses LED7 every quarter second
 
      ISR_RTI:
                  ; check to see if toggle is on or off
                  ldab    led7_toggle
                  cmpb    #$00             ; if led7_toggle = 1, go to led7on
                  bne    led7on
                  
                  ; if toggle off, turn LED7 off and change toggle to 1
      led7off:            bclr    portb, #led7mask    ; toggle LED7 off
                  movb    #$01, led7_toggle    ; set toggle to on
                  bra    rti_end
 
                  ; if toggle on, turn LED7 on and change toggle to 0
      led7on:            bset    portb, #led7mask    ; toggle LED7 on
                  movb    #$00, led7_toggle    ; set toggle to off
 
      rti_end:            movb    #ack_rtiflg, crgflg        ; clear RTI flag
                  rti
 
      ;------------------------------------------------------
      ; ISR for Timer Overflow Interrupt - turns off LEDs if counter > setpoint
      ; the variable "counter" is used in this ISR and in the Port H ISR
 
      ISR_TOV:
                  inc    counter
                  ldaa    counter
                  cmpa    setpoint            ; if counter = setpoint, turn off leds
                  bne    tov_end
                  movb    #ledsoff, portb        ; turn off LEDs 0-6 - intentional error here
 
                  movb    #$00, counter        ; reset counter
      tov_end:        movb    #clear_ifl, tflg2        ; clear timer overflow flag
                  rti
 
      ;------------------------------------------------------
      ; ISR for Port H - if we only clear out individual flags of porth_ifr,
      ; eg, 'movb #sw2IFR, porth_ifr' then we get pending switch interrupts.
      ; As written, all the Port H pending interrupts are cleared
      ; the variable "counter" is used in this ISR and in the TOV ISR
 
      ISR_PTH:
                  movb    #$00, counter        ; reset counter
 
      sw2:            brclr    porth_ifr,#sw2ifl,sw3    ; if sw2 IF not set, branch to sw3
                  movb    #led2on,portb          ; LED2 will light if Port H bit 3 low - intentional error here
                  movb    #sec, setpoint
                  movb    #clear_ifl, porth_ifr    ; clear all Port H IFL's
                  rti
 
                  ; if sw2 wasn't pressed, then sw3 must have been pressed
      sw3:            movb    #led3on, portb        ; LED3 will light if Port H bit 2 low - intentional error here
                  movb    #five_sec, setpoint
                  movb    #clear_ifl, porth_ifr    ; clear all Port H IFL's
                  rti
 
 
      ;------------------------------------------------------
      ; Interrupt Vectors - use DBug12 Mappings
            ORG $3E4C
            dc.w    ISR_PTH            ; Address of Port H Interrupt ISR
            
            ORG    $3E5E
            dc.w    ISR_TOV            ; Address of Timer Overflow Interrupt
            
            ORG    $3E70
            dc.w    ISR_RTI            ; Address of Real Time Interrupt
 
      ;****************************************************************
 
 
Part 2 - Create Program using the RTI to Flash LED7, and Run on Dragon12
 
   1. In Code Warrior, create a project called "timer_demo", and cut/paste the above source code and save it as "timer_demo.asm". Save the absolute executable as "timer_demo.asm.abs". Build the program and load the s1 record into the Dragon 12.
 
      > Remember, we set up Code Warrior to automatically create an s1 record from the .abs file and to add a .s1 extension to it, so the file you actually load is "timer_demo.asm.abs.s1". Because Windows XP/2000 often won't show file extensions in the Hyperterminal file browser, you may have to guess by the file type by the icon next to it. Look in your Code Warrior project directory on your network drive to see which icon is assigned to the various files. If you use "View -> Details" from the Win2K/XP menu, you can see the file icon, file name and file type for each file in your directory.
 
   2. With the program loaded in the Dragon12, start it using the "g 1000" command. LED7 should flash on for about 1/4 second, then off for 1/4 sec., on for 1/4 sec., etc. It will do this until you hit the yellow "abort" pushbutton.
 
      > If you do hit the yellow abort button, you can try to rerun the program by typing in "g 1000" again. Sometimes you need to hit the "reset" button before you hit do "g 1000". If this doesn't work, reset and reload the program and run it from scratch using "g 1000".
 
   3. With the program running, push the "sw2" pushbutton and verify that LED2 comes on for 1 second. Push the "sw3" button and note that LED3 comes on for about 5 seconds. All the while, LED7 is flashing as before.
 
   4. Note that pushbuttons "sw4" and "sw5" are dead - if you push them, nothing visible happens. But they do still trigger a Port H interrupt - we just haven't added any code to make them do anything.
 
   5. There is a problem, though. LED7 is flashing, but the timing is irregular (you may have to watch it for a minute or two see this). The root cause of the irregular flashing is with some of the "movb" commands in the ISR_PTH and ISR_TOV routines; the "movb" command affects all bits in address. For instance, the "movb #ledsoff, portb" in the ISR_TOV routine writes the binary pattern %10000000 to the port B register. This causes LEDs 0-6 to go off, but also turns LED7 on which can sometimes interfere with the on/off period of LED7.
      
      > The solution to the problem is to use the "bset" and "bclr" commands as a substitute for the "movb #ledsoff,portb" command. The bset/bclr commands will only affect bits designated by the masks, ie, bclr/bset action will only affect the bits whose corresponding mask bit is set to "1". So, imagine that we create a new mask called #leds0to6off = %01111111. When we write "bclr portb, #leds0to6off" we are clearing all bits with a "1" in the mask pattern, thus turning off LEDs 0-6, but leaving bit 7 (LED7) unaffected. If bit 7 was off before, it stays off, and if it was on, it stays on.
 
   6. Make the corrections to the code noted below:
 
     (a) - In the EQU directives section, create a mask called "leds0to6off" and give it a value of %01111111
 
      (b) - In the ISR_TOV routine, change the "movb #ledsoff, portb" line to "bclr portb, #leds0to6off"
 
      (c) - In the ISR_PTH routine, change the "movb #led2on, portb" line to "bset portb, #led2on" (use the same led2on mask we originally created)
 
   (d) - Also in the ISR_PTH routine, change the "movb #led3on, portb" line to "bset portb, #led3on" (use the same led3on mask we originally created)
 
  7. Rebuild and verify that the program now runs correctly. LED7 should flash at a regular rate now, and should be unaffected by pushing sw2 or sw3.
 
 
 
Timer and Interrupt Demo