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.