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.