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.
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.