Introduction
 
In this demo we will focus on how we generate input and output for the hcs12, and on how we use subroutines. We will also learn a little about the stack. Understanding these concepts is essential to developing programs for embedded systems.
 
We will create a program that uses the push-button keys labeled SW2 - SW3 on the Dragon12 board as input (these are the yellow pushbutton keys on the lower left of the board). For the output, each key will light a specific LED for a specific amount of time. The program uses a subroutine called "DELAY" which determines the amount of time each LED is on.
 
 
Key Concepts
 
Before doing the demo, read the points below and make sure you understand them.
 
    * The program below is based on using a subroutine. Subroutines are pieces of code that can be used again and again in a program. They are program modules that are logically separate and independent of the main program, and form the bulk of most commercial programs. You will use them a lot, so it is important that you understand them. Subroutines ALWAYS require the stack (for storing the PC at the very least), and the stack MUST be initialized. There is a brief overview of the stack for the HC(S)12 here that includes a simple program called "swap.abs.s1".
 
    * The program below runs in an infinite loop . We start the main program and it will run forever, unless we turn the power off or unless we manually force an exit via the DBug12 "abort" button. Programs in embedded systems are typically written like this - they are always on, running in an infinite loop, doing something over and over again.
 
    * The parameters for the delay loop subroutine are passed from the main program to the subroutine via Register D. There are many ways to pass a parameter to a subroutine, and this is the most commonly used method. It works OK as long as there aren't too many parameters to pass - we can't pass more parameters than there are registers with this technique. Note that we use Register D (16 bits) since the smaller 8 bit registers A and B don't have enough bits to hold the relatively large values we pass to the subroutine.
 
    * We use a technique called polling to determine the switch states. In polling, our CPU is constantly checking the condition of a switch to see if it is open or closed. This is not a problem for simple programs like this, but for more involved cases, it is a waste of CPU time. It is much better to respond to the keys only when they are pushed - in the meantime, the CPU can be doing something else. The technique used for this is to use "interrupts", a topic that will be covered in a later lab.
 
    * We generate a delay using a brute force (and inefficient) routine called a "software delay". For the whole of the delay time, the microcontroller is doing nothing but running in a long loop, and it can't do anything else while it's in this timing loop. It is more accurate and much more efficient to use a hardware timer, which runs in the background and doesn't tie up the CPU. The timer uses an interrupt to notify the CPU when the time has elapsed, and at this point the CPU will then focus its attention on what follows the delay. Using hardware timers and interrupts are explored in a later demo.
 
    * The total time spent in the software DELAY subroutine below is determined by counting the number of clock cycles spent in the routine, and then multiplying this by the clock time per cycle. For instance, in the "startx" loop, each "dex" takes 1 cycle and the "bne" takes 3 cycles if the branch is taken (you can determine the number of clock cycles per instruction from the HCS12 programmers reference book). So, there are four clock cycles per startx loop at ~ 20 nsec per cycle (1/48MHz for the CPU only) to give a cycle time of about 80 nsec per startx loop. If we run 12,000 ($2EE0) startx loops, this will take about 12,000 x 80 nsec ~ 1 msec. The number in the X register, loaded in at the "starty" label, determines the number of startx loops we run. So if we load 12000 ($2EE0) into X, then the starty loop cycle time will be about 1 msec (12000 x 80 nsec). If we then run the starty loop 1000 ($3E8) times, then the starty loop will take 1000 x 1msec = 1 sec to complete. Put another way, we can say that each inner startx loop runs 12000 times at 80 nsec per loop for a total of 1 msec, while the outer starty loop runs 1000 times at 1msec a loop for a total of 1 sec. Note that we have ignored the delay time from the instructions outside of the startx and starty loops. These instructions, such as the 'ldx' and 'dey' are only executed a few times total, and only add a small bit of time to the total time. If very accurate timings were needed, we would have to consider these as well as other factors (such as the overhead time in jumping to the subroutine).
 
    * We use Port H, at address $0260, for input and Port B, at address $0001, for output. These ports have eight pins, each of which can be configured to be either an input or an output port by writing to the Data Direction Register (DDR) for the port. For instance, the Port H DDR is at address $0262, and to set all the pins at Port H to be inputs, we write $00 to address $0262. We might do the write with a command such as "movb #$00,$0262". If we wanted to set them all to outputs, we would write $FF to address $0262. Once we have set the I/O direction on a port via the DDR, we may then write or read to that port as appropriate. For instance, if all the pins on Port B are set as outputs, then to set bit 3 (of 0-7) high and the others low, we would write "$08" ($08 = %0000 1000 in binary) to address $0001. We might do this using "movb #$08, $0001". When we want to read an input port, we check to see what logical values are on the pins of the port. As an example, we would use "ldaa $0001" to read the values on Port B (assuming its set as an input) and store them in Register A. If pin 2 of Port B is low, and the others high, then after the read register A would contain the value %1111 1011 = $FB.
 
      
 
      > Note that when we write constants, such as "#$08" to an address or register, we are using immediate addressing, so we prefix the value with a number sign "#". Also note in that binary numbers are prefixed with a percent sign "%", and hex with a dollar sign "$" (decimal numbers don't use any prefixing).
 
    * In the program below, note the liberal use of comments to help program readability. Also note that use of assembly directives, such as "portb_ddr    EQU    $0003'. This simply means that whenever the assembler sees "portb_ddr" in the code we type in, during the assembly process it will be replaced by the value $0003, which is the address of the port B data direction register. By using EQU directives, we make the program more readable for people since we replace machine code variables with more understandable mnemonics. See the Code Warrior Directives page for more information on directives.
 
    * In the program below, also note the use of WHITE SPACE . "White space" is something which puts space at the beginning of a line, and is either a space character(s) or a tab character(s) or any mix of these. The incorrect use of whitespace is one of the most common problems encountered by beginning Code Warrior programmers. Note that some directives such as the EQU label have no white space. Labels such as "Entry:" also have no white space. Others such as ORG must have white space. Machine instructions such as "mov #ledson, portb" must also have white space. A machine instruction in Code Warrior will not work if there is no whitespace before it.
 
    * The hardware diagram for the Port H and Port B pins is shown on the dragon12_4_revE.jpg. Each output pin PH0-PH3 has two switches on it: a pushbutton switch and a DIP switch. If both switches are open (open circuited/not connected) then the pin is high. If either switch is closed (short circuited/connected) then the pin is low. The LEDs on Port B are ON when the Port B output is set to high or logical 1, and are off when they are set low or logical 0.
 
 
Part 1 - Create Project and Add Source Code
 
   1. Create a new project using the ee3376 stationery, and name the project "iosub" (which will go into a folder called "iosub") where "iosub" is an acronym for "input output subroutines".
 
   2. Click on the Target Settings icon description and choose the Linker Panel. Change the absolute file name to "iosub_hcs12.abs". Click "OK" to accept the Target Settings.
 
   3. Open the source file named "labx_hcs12.asm". In the IDE menu, choose "File > Save As" and then name the file "iosub_hcs12.asm".
 
   4. Copy/paste the source code below into "iosub_hcs12.asm" and Save it. Don't forget to add appropriate comments (Name, Date, etc.)
 
 
 
 
      ;****************************************************************
      ;*
      ;* UTEP EE3376
      ;* iosub_hcs12.asm
      ;* 20 June 2005
      ;*
      ;* Each pushbutton activates a Port B LED for a certain time:
      ;* sw2 lights LED2 (port B, bit 2) for 1 second
      ;* sw3 lights LED3 (port B, bit 3) for 5 seconds
      ;*
      ;* 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 24 MHz CPU clock
      ;* Subroutine "DELAY" parameters passed via Register D
      ;* Need Register D since parameters are > 8 bits
      ;*
      ;* Has Port J code for the Rev. E Dragon12 boards
      ;*
      ;****************************************************************
 
      ; export symbols
              XDEF     Entry    ; export 'Entry' symbol
              ABSENTRY Entry    ; for absolute assembly: mark this 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
 
      portb_ddr        EQU        $0003    ; Port B Data Direction Register
      portb            EQU        $0001
      porth_ddr        EQU        $0262    ; Port H Data Direction Register         
      porth            EQU        $0260
      input            EQU        $00        ; write to ddr to make port an input        
      output            EQU        $FF        ; write to ddr to make port an output
      ledsoff            EQU        $00        ; set all LED bits to off
      ledson            EQU        $FF        ; set all LED bits to on (lights LEDs)
 
      sw2on            EQU        $08        ; sw2 of Port H tied to bit 3 of Port H
      sw3on            EQU        $04        ; sw3 of Port H tied to bit 2 of Port H
      led2on            EQU        $04        ; bit 2 for LED2
      led3on            EQU        $08        ; bit 3 for LED3
 
      msec            EQU        $2EE0    ; 12000 (=$2EE0) loops = 1 msec in startx loop
      sec            EQU        $3E8    ; 1000 steps * 1msec/step = 1 sec delay
      five_sec        EQU        $1388    ; 5000 steps = 5 sec delay
 
      ;-----------------------------------------------------
      ; variable/data section
                  ORG        RAM
      ; Insert your data definitions here.
 
 
      ;------------------------------------------------------
      ; code section
                  ORG        PSEUDO_ROM    ;set PC to $1000
              
      ; Insert your code following the label "Entry"
      Entry:
              ; do some initialization
              lds    #STACK            ; initialize stack pointer
              
                ; 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
          
              movb    #input, porth_ddr        ; set Port H to input
              movb    #output, portb_ddr        ; set Port B to output
              
              ;flash all LEDs for one second            
              movb    #ledson, portb             
              ldd    #sec    
              jsr    DELAY
 
              ; our main program infinite loop             
      loop:        movb    #ledsoff, portb             ; turn LEDs off
                  ldd    #$0                ; initialize Port D
 
      sw2:        brset    porth, #sw2on, sw3         ; if bit 3 Port H high, branch to sw3
                      movb    #led2on, portb          ; LED2 will light if Port H bit 3 low
                  ldd    #sec            
                  jsr    DELAY             ; delay time = (Reg D) * msec
 
      sw3:        brset    porth, #sw3on, loop     ; if bit 2 Port H high, branch to loop
                  movb    #led3on, portb          ; LED3 will light if Port H bit 2 low
                  ldd    #five_sec         
                  jsr    DELAY             ; delay time = (Reg D) * msec
 
                  bra    loop
                  
                  swi                ; end the program
              
              
      ;------------------------------------------------------
      ; Subroutine DELAY - Delay = (D) * msec
      ; where Register D contains number of x (startx) loops
      ; and there is a 1 msec delay per x loop
      ; Add push/pull instructions to protect D, X and Y
 
 
      DELAY:
                  pshd
                  pshx
                  pshy
              
                  tfr        D,Y    ; (D) -> (Y)
      starty:   ldx        #msec    ; start y loop and set x loop to 1 msec
      startx:   dex            ; start x loop - 4 cycles per startx loop if branch
                  bne        startx    
                  dey        
                  bne        starty
              
                  puly
                  pulx
                  puld
              
                  rts
                      
      ;****************************************************************
 
 
   5. The project should be set up correctly now. Double check that the "iosub_hcs12.asm" and "burner.bbl" files are "touched" OK - if they're not, manually mark them by clicking in the touch column.
 
   6. We're now ready to build the project and generate our executable. Click on the Make Icon description to build the program. If all goes well, it will build without error, and you will have a file named "iosub_hcs12.abs.s1" in your "iosub" project folder. This is the executable we will load in the Dragon12 and run. If there were build errors, carefully read the error messages and make corrections as necessary, and then rebuild the project. You can always call the TA over for assistance.
 
Part 2 - Download and Run on HCS12
 
   1. Check that the DIP switches of SW1 (8 position red DIP switch) are in the OFF (switch open) position, pushed up toward the LCD.
 
   2. Connect to the Dragon12 using Hyperterminal, and make sure that D-Bug12 is running OK.
 
   3. Using the DBug12 "load" command, load in the program "iosub_hcs12.abs.s1" that you created above. Make sure that xon/xoff flow control is set (see the D-Bug12 Demo for how to set this if needed).
 
   4. Once your program loads in OK, type in "g 1000" at the prompt. This runs your program. The LEDs should flash on for about a half a second and then turn off. Pushing the sw2 button should light LED2 (of 0-7) for 1 second, and pushing sw3 should light LED3 for 5 seconds. Pushing the other switches sw4 and sw5 should have no visible effect. This program will run until you hit the "abort" button. To restart, just type in "g 1000" again. You can use the single-step "t" command to check short sections of code; set the PC to the address you want before executing the "t" command. You can use the "asm 1000" command to view the final form of your source and machine code.
 
   5. See the DBug12 Demo, HCS12 Stationery and the D-Bug12 Program Demo for more information about loading/running a program via D-Bug12, and creating a project from stationery.
 
 
Input, Output, Stack, Subroutine