PIC Assembler Tutorial 1

This tutorial will introduce you to the basics of assembly for the PIC 18F452. It's pretty similar to the 16Fs as far as I can tell, but with more instructions, and should be identical for all the 18Fs, but if you've heard otherwise, you probably know better than me.

There are a few open source compilers for high level languages for the PIC18. The one I've used is JAL, which is very nice, except last I checked it didn't work for serial output. It can be good to get started. Here are some JAL tutorials by its author. They are excellent. There is also a C compiler in the works, though when I checked it didn't have easy support for the 18F.

Programming Environment

If you're using Windows you may as well go ahead and use the Microchip MPASM and the MPLAB IDE. They're not open source, but if you're using Windows you can't be too concerned about that.Otherwise, look into GPUTILs for linux or MacOSX. All of those provide ample documentation on using them, so I'll skip to the actual programming.

Setup

First, it's important to note that the MPASM assembler is sensitive to indentation, you'll get warnings if instructions are in the wrong column.

As in all programming, start by putting a descriptive header in your document. In PIC asm the comment is a semicolon. The header looks something like this:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; test.asm: This is a description of my first PIC Assembler program.
;; 
;; By Ian Smith-Heisters ian{remove this}\at 0x09{remove this}.com
;;   12-03-2004: version 1 released
;;   12-03-2004: fixed some stuff
;;   12-03-2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Next, add the header information for MPASM. This consists of a title, some compiler options and include statements. They should be indented one tab.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; test.asm: This is a description of my first PIC Assembler program.
;; 
;; By Ian Smith-Heisters ian{remove this}\at 0x09{remove this}.com
;;   12-03-2004: version 1 released
;;   12-03-2004: fixed some stuff
;;   12-03-2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    title "My First Program"
    List p=18f452,f=inhx32
    #include <p18f452.inc>

The title can be anything enclosed in quotes. The list statement lets you set some options, here we set the model of PIC we're using and the format of the hex (or something. the f=inhx32 is always in there, so I put in there). You can also use the list statement to set things like what the default number is (hex, decimal, or binary). There are probably other things you can control with list too. I'm sure the datasheet or some other document has all that if you're interested. The include statement brings in a header written by Microchip that gives names to numbers, otherwise you would have to program entirely in hex. Make sure your include file is in your path when you try to compile.

Now comes a very important part. You have to set all the configuration flags. IF YOU DO THIS WRONG YOU CAN BORK YOUR PIC! So do it right. You can find all the configurations in the header file (p18f452.inc) but I'll ellucidate them here.

    __CONFIG    _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
    __CONFIG    _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
    __CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
    __CONFIG    _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
    __CONFIG    _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
    __CONFIG    _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
    __CONFIG    _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
    __CONFIG    _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L
    __CONFIG    _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
    __CONFIG    _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
    __CONFIG    _CONFIG7H, _EBTRB_OFF_7H

Now, theoretically you don't have to configure all 11 config bytes, if you're using some defaults. However, I think it is very good practice to always explicitly declare all of them. If you're doing everything just like me, you can just cut and paste the above instructions. But it would be very good for you to understand what you're doing. I'm just going to give a brief snippet on each, if you want more detailed information or all the options for each flag check the datasheets, they're very good on this point.

_CONFIG1H

The first configuration byte, _CONFIG1H (H stands for high, L stands for low) sets your clock options. Here I've set it to use an external crystal on pins OSC1 and OSC2.

_CONFIG2L

CONFIG2L sets the brown out reset and the power up timer. The brown out reset says that if the amount of current going to your PIC dips below a certain level, here 2.0V, the PIC will reset. _BOR_ON_2L turns on the brown out reset, and _BORV_20_2L sets it to reset at 2.0V. I think what the power-up timer means that the PIC will wait for while after you give it power for it to start doing things. I once had this on and couldn't for the life of me figure out why my PIC wasn't working.

_CONFIG2H

This sets the watchdog timer on or off. The watchdog timer, as I understand it, watches the PIC (duh). If it stops executing instructions for a certain amount of time the WDT resets the PIC. I set mine off, because I clearly don't really understand it. The other thing I understand even less is what the postscaler count is. Set yours to 128 unless you know better: it seems to work for me.

_CONFIG3H

the CCP2 pin mux has something to do with multiplexing, where a set of pins either go to the same place (eg. putting something into pin X would be the same as putting it into pin Y) or act independently. But that would be a guess.

_CONFIG4L

Set stack overflow and underflow reset on or off, Low Voltage Programming, and debug. All I know about STVR is from the name: if the stack overflows or underflows, the PIC will reset (gee, thanks Ian, that really clears things up). Debug, I think, allows you to send output to your ICD (in-circuit debugger) if you have one. Or, maybe you could just set it on and do debug conditionals whether or not you have an ICD. LVP is self-explanatory, or rather, all I understand about it is its name.

_CONFIG5L
_CONFIG5H
_CONFIG6L
_CONFIG6H
_CONFIG7L
_CONFIG7H

These are the write protection flags. If you turn any of them on you will not be able to program your PIC again. I did that. Luckily I had two PICs. They would be handy (I think) if you were doing production and wanted the programming to be permanent, or you were making a bootloader. Turn them all off for now. Make sure each and every one is off. Is that clear enough?

The last thing you need to do to set things up is tell the compiler where in the PIC's memory this program is going to go. You do this with an org statement. Org does a lot of things that I don't understand in the least. I'm sure the MPLAB help has more information on it. For now, a simple

     org 0x0000

should suffice for our purposes. Things get trickier when you get into interrupts and importing macros and things, but hey, it works for me, and that's all that matters.

To summarize, we have a standard kind of header that you lug from file to file. You could probably implement it as an include, but for some things I like to have them explicitly in the file, if only for small programs like we're doing. The header should look something like this:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; test.asm: This is a description of my first PIC Assembler program.
;; 
;; By Ian Smith-Heisters ian{remove this}\at 0x09{remove this}.com
;;   12-03-2004: version 1 released
;;   12-03-2004: fixed some stuff
;;   12-03-2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    title "My First Program"
    List p=18f452,f=inhx32
    #include <p18f452.inc>

    __CONFIG    _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
    __CONFIG    _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
    __CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
    __CONFIG    _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
    __CONFIG    _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
    __CONFIG    _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
    __CONFIG    _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
    __CONFIG    _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L 
    __CONFIG    _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
    __CONFIG    _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
    __CONFIG    _CONFIG7H, _EBTRB_OFF_7H

org 0x0000

A Simple and Incosequential Program

This code does nothing you can see, but will serve to give you an idea of how to do some programming in PIC assembler.

The first thing to know is how to assign a variable. To assign a variable you first have to create a place for it in memory. You do this with the equ instruction.

COUNT equ 0x01
NUM   equ 0x02

What this does is gives the addresses 0x01 a friendly name to the compiler, so every time you refer to COUNT, you're really just referring to a place in memory.

In PIC assembler there's what's called the "working register." The working register, or w register, or wreg for short, is basically just a temporary place to store things. You can put numbers in there and use them in an operation with other numbers, or put numbers in the WREG so a subroutine can use them. Now we want to make sure the WREG is clear, so we use the instruction clrf, which clears any byte. Instructions like clrf never go in the first column.

clrf WREG

Instructions usually take three kinds of arguments, literals, F, or W. Literals are where you supply a number directly. I think the only instruction that uses that is movlw, which moves a literal to W. F refers to an arbitrary place in memory, and W is the WREG.

Now we discover labels. Labels give us the ability to skip around in our program, which allows us to create loops, subroutines, and reference points. A label is just a series of characters in the first column, like

Init

Now, once we've been going through our program, we can step back to the initial state by saying goto init. What we'll do in our initialization is make sure the COUNT variable is set to zero, and then set NUM to a number, by moving the literal number, 100 in decimal to W, then moving it into NUM

    clrf COUNT ; set COUNT to zero
    movlw d'100' ; move the literal decimal 100 to W
    movwf NUM ; move W to NUM, so NUM holds 100

Now we'll define another section of our program where we'll increment count in a loop. We do this with a label, a goto statement, and an incf instruction, which increments a number, and stores it either in W or back where it got it. We'll store back where we got it from.

IncCount
    incf COUNT, F
    goto IncCount

Now, that's fine and dandy, but useless. Well, I won't fix its uselessness, but I will show you how to use conditionals. Conditionals compare a number and then if it matches the condition skips the next line. You can use that one line to leverage conditional branching. So we'll take the code just above and add a conditional, so that if COUNT is greater than NUM we'll go back to the beginning initialization.

IncCount
    incf COUNT, F
    movf NUM, W
    CPFSGT COUNT ; if F is greater than W, skip the next line
    goto IncCount ; if F wasn't greater, we'll loop again
    goto Init ; otherwise we'll go to initialization and start all over again

So, to wrap up, your whole file should look something like this:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; test.asm: This is a description of my first PIC Assembler program.
;; 
;; By Ian Smith-Heisters ian{remove this}\at 0x09{remove this}.com
;;   12-03-2004: version 1 released
;;   12-03-2004: fixed some stuff
;;   12-03-2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    title "My First Program"
    List p=18f452,f=inhx32
    #include <p18f452.inc>
    
    __CONFIG    _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
    __CONFIG    _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
    __CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
    __CONFIG    _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
    __CONFIG    _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
    __CONFIG    _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
    __CONFIG    _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
    __CONFIG    _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L
    __CONFIG    _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
    __CONFIG    _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
    __CONFIG    _CONFIG7H, _EBTRB_OFF_7H

    org 0x0000

; create variables
COUNT equ 0x01
NUM equ 0x02

    clrf WREG ; clear W

Init
    clrf COUNT ; set COUNT to zero
    movlw d'100' ; move the literal decimal 100 to W
    movwf NUM ; move W to NUM, so NUM holds 100

IncCount
    incf COUNT, F
    movf NUM, W
    CPFSGT COUNT ; if F is greater than W, skip the next line
    goto IncCount ; if F wasn't greater, we'll loop again
    goto Init ; otherwise we'll go to initialization and start all over again

    end

The end of the program should be specified with the 'end' instruction. Again, this code does nothing useful. Despite that, try to load it into your PIC just to make sure you can write to it. Make sure all write protection flags are off if you want to reuse your PIC. Also in the MELabs Programmer software, there's an option to NOT reload the assembler file each time. This can get confusing during debugging, when you think you fixed something, but it still doesn't work. Be sure to turn that option off now to save pain later.