Better Assembler Coding Practice

This tutorial is a result of some good criticism I got on the PICList on my coding practice. Adding these things makes code more readable, easier to manage, and more easily extensible.

Variable Declaration

So far we have been using a rather obnoxious way of declaring variables:

        var1    equ     0x01
        var2    equ     0x02
        bla     equ     0x03

If we want to add a var3, we need to reset every variable that comes after it. Additionally, if we want to move where this block of variables is in memory, we have to reset all of the variables. This is what I like to call a “pain in the ass.” So here's a much easier way to declare a set of variables with consecutive addresses:

        cblock  0x01
                var1
                var2
                foo
                bar
                bla
        endc

Now all those variables are easily relocatable and new ones are easily added. Aw yeah.

Interrupt Vectors

I don't have a use for interrupt vectors yet, but they're good to have around. Some people use interrupt vectors as an event-handling type thing, and don't even have a main loop like we've been doing. It's good practice to have them around even if you're not using them.

So here's how I understand it. There are two interrupt “vectors”, high and low priority. Basically what I think this means is that if a high priority interrupt is triggered the PIC, by default, goes to a certain address in memory and executes code there. That code needs to determine what triggered the interrupt and deal with it accordingly. The same goes for low priority interrupts, but they go to a different address. These vectors are placed before the program code on the PIC, so you need a reset vector that the PIC goes to when it resets, that will skip over the interrupt vectors and execute the program code. Here's what that looks like:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;   Vectors

        org     0x0000                                  ; reset vector

Start
        goto    Init

        org             0x0008                          ; high priority interrupt vector
Interrupt1
        goto    HighInterrupt

        org             0x0018                          ; low priority interrupt vector
Interrupt2
        goto    LowInterrupt

So on a reset or power up, the PIC goes to 0x0000, which then sends it to the label Init. This also makes it easy for you to restart all your code by writing something like

        goto Start

The default high priority interrupt vector is 0x0008, and the low priority vector is 0x0018. For the sake of neatness these vectors just skip to another place in memory where the actual interrupt handling code is. Right now my interrupt handling code is just

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;   Interrupt Handling

;; High Interrupt
HighInterrupt
        _puts   "High priority interrupt triggered.\n\r\0"
        retfie  0
        goto    Start
;; Low Interrupt
LowInterrupt
        _puts   "Low priority interrupt triggered.\n\r\0"
        retfie  0
        goto    Start

Which means it prints a message using _puts, which I'll show you in a second, and returns from the interrupt with 0. Basically it does nothing except notify you that the interrupt was triggered. If I was actually using interrupts I would put something that would test the interrupt registers (PIR1, etc.) to see which interrupt flags were set and execute handling code based on those flags. There's more info on that on the net if you want to figure it out.

Include Files

I thought I had covered this before, but I couldn't find it, so here goes. Include files are assembler files with the extension “.inc” instead of “.asm”. We're already using one, “18f452.inc”. By using an include statement the text of the include file is inserted at that point verbatim. For example, I moved all the UART subroutines to a file called UART.inc, which I include just before the other subroutines like this:

        #include 

This makes it easy to manage and reuse code. It also keeps the file size of your main file down, making it easier to find certain sections without scrolling through hundreds of pages of code. In the next tutorial we'll write an include file for using the I2C bus.

Printing Strings

This has nothing to do with good coding practice. In fact I think it qualifies as questionable coding practice. But it's handy.

The following macro and subroutine allow you to print null terminated ASCII strings over the PIC's UART. I did not write this code. I don't even really understand how it works. I know it uses the stack to do its magic.

_puts MACRO s                           ; output a NUL-terminated inline string
                call    putstrpc
                dw              s
        endm

;.
;.
;.

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;  Strings

;; print NUL-terminated string at TBLPTR
;;              from http://forum.microchip.com/tm.asp?m=69235
putstrtail:
loop:   tblrd   *+
                movf    TABLAT,w
                bz              done
            rcall       UART_Put
            bra         loop
done:return

;; print NUL-terminated string at PC
putstrpc:
                movff   TOSU,TBLPTRU
                movff   TOSH,TBLPTRH
                movff   TOSL,TBLPTRL
putstr2:rcall   putstrtail
        ; adjust return address to after end of string
fixuppc:btfsc   TBLPTRL,0       ; is ptr odd?
                tblrd   *+                      ; yes, bump it by one
                movf    TBLPTRU,w       ; copy to return PC
                movwf   TOSU            ; ** cannot use movff **
                movf    TBLPTRH,w
                movwf   TOSH
                movf    TBLPTRL,w
                movwf   TOSL
        return

Note that it uses the UART_Put routine we defined earlier. You then call it like this:

        _puts "Hello, World.\n\r\0"

The \0 is null, and is absolutely necessary. The \r is a carriage return. The \n is a new line. So this would print “Hello, World.” and then move the cursor to the beginning of the next line.