PIC18F2550 Assembly Tutorial 1

Welcome to the first of a series of assembly tutorials for the Microchip PIC18F2550 (datasheet) or 2550 for short. I have been using this at university for about two years now, both programming in C and ASM. You might think “why would anyone ever write in assembly when you’ve got C, which looks a lot easier to write a program with”. The idea with writing code in assembly is full control. You directly control every operation the processor will execute which, if you’re an experienced programmer, will result in faster program execution and less overhead.

But enough of the jibber jabber, let’s get started. We’ll start simple and gradually increase complexity, ending with serial communication between 2 microcontrollers.

I’m assuming you are familiar with Microchip’s IDE called MPLab so I won’t be explaining how it works, it’s fairly straightforward anyway. The big difference between C and ASM compilation is that you do not use a linker file when compiling your ASM project (give it a try, include the linker, you’ll notice MPLab throws an error).

First of all, I’m using a bootloaded PIC so some vectors (such as the interrupt en reset vectors) have an “offset”. Let’s take a look at the following bit of code:

	title "SPI MASTER"
	list p=18F2550, r=hex, n=0
	#include ;	
 
	org	0x800		; Reset Vector
	goto	Start
	org	0x808		; Interrupt Vector  HIGH priority
	goto	inter			
	org	0x818		; Interrupt Vector  LOW priority
	goto 	inter
	org	0x820			
 
Start

This is the very beginning of the assembly program. As said before, vector mapping several things as well as giving the program a name. The actual program begins after the “Start” label. Whenever you see such a label (which can be any word you like) you can use the GOTO instruction to jump to the code directly after the label.

Initializing the PIC’s control registers takes a long time but it’s essential for the correct behavior of the processor. DO NOT FORGET THIS or rest assured you will spend a long time trying to figure out why your code doesn’t work. So, we start with configuring the IO pins of PORT A through C:

	CLRF 	PORTA 	; Initialize PORTA by clearing output data latches		
	MOVLW 	0x00 	; Set PORTA pins as output
	MOVWF 	TRISA 	; Value used to initialize data direction 
 
	CLRF 	PORTB 	; Initialize PORTB by clearing output data latches
	MOVLW 	0x00 	; Set PORTB as output
	MOVWF 	TRISB	; Value used to initialize data direction	
 
	CLRF 	PORTC 	; Initialize PORTC by clearing output data latches
	MOVLW 	0x00	; Value used to initialize data direction
	MOVWF 	TRISC

We start by clearing anything that might be on the data latches of the ports, then we move a certain value to the WREG (working register, or its shorthand version: w). There’s an easy way to remember what defines input or output. You’ve got 1’s for Input (1 looks like an I) and 0 for output (0 looks like an O). Finally we move the value from the working register to the Tri-state register of each port. Note that it is recommended to configure any floating IO pin as output.

Next up we make sure to disable interrupts while we configure some more registers. BCF means “Bit Clear File” and BSF is the exact opposite, it sets a bit.

	bcf	INTCON,GIE	; Turn Off global interrupt
	bcf	UCON,3		; to be sure to disable USB module
	bsf	UCFG,3		; disable internal USB transceiver
	MOVLW	0x00
	MOVWF	ADCON0		; AD convertor OFF
	movlw	0x00		; No priority on interrupts
	movwf	RCON	
	bsf	INTCON, GIE

For this simple tutorial we do not have to set a lot of registers, let’s just make sure the AD convertor is turned off and the interrupt registers are set correctly.. After all the registers are configured we allow interrupts again.

Once this is done, we move on to the next important routine, which is the interrupt. In this example we’re not using interrupt priorities so there is just one label called “inter”.

inter			     ; using single interrupt priority
	BTFSS INTCON,TMR0IF  ; Check for TMR0 overflow
	RETFIE
 
	END

Let’s take a look at what we have here. We use the “inter” label to know the following code will be executed when an interrupt happens. We’re not actually doing anything in this example besides checking if Timer 0 has overflowed (we haven’t really configured timer 0, this is just to show you how interrupts work). When jumping into the interrupt routine, the interrupt enable bit will be cleared by the hardware, the “RETFIE” command will set the “GIE” bit again and return to wherever the program was when the interrupt happened.
I also prefer to put the interrupt routine at the end of the file, hence why there is a (mandatory) “END” at the last line of code.

Now that that’s out of our way we can create our first really simple program. We’ll start by making a simple upcounter using PORTB. If you have 8 resistors and LEDs, connect them to RB0 through 7 now. As the timer would go really, really quickly we’ll have to add a delay routine so you can actually see the counting. Start by defining 3 counter variables at the top of the file:

  count1 equ  0x30 ; Add these directly above org 0x800
  count2 equ  0x31  
  count3 equ  0x32

Then we come to the actual program, this code is located directly above “inter”:

Init  
  BSF PORTB, 0	; Set one bit so we can shift it around
 
Main
  RRCF	PORTB	; Move the bit one spot to the right
 
  SETF	count1	; Set count1 to 0xFF
  SETF	count2
  MOVLW	0x05	; Move 0x05 to the WREG
  MOVWF	count3  ; Move the 0x05 in the WREG to count3
 
Delay
  DECFSZ count1	; Decrement count1, skip the next instruction if count1 = 0x00
  GOTO	 Delay
  DECFSZ count2
  GOTO	 Delay
  DECFSZ count3
  GOTO	 Delay    
  GOTO	 Main

First of all we set RB0 to 1 so we can shift it around. Next up we do an actual shift with “RRCF”, we then set the counter values and continue into the Delay routine. The logic here is the following:

1. Decrement count1
2. If count1 is not equal to 0, go to 1.
3. Decrement count2
4. If count2 is not equal to 0, go to 3.
5. Decrement count3
6. If count3 is not equal to 0, go to 5.
7. Go back to Main

I explicitly set the counter values each loop to make sure they have the correct values I want. If you would prefer an upcounter simply remove the Init part and replace “RRCF” with “INCF”. For a downcounter use “DECF”.

That concludes this tutorial, we’ll cover some more advanced things in the next one. If you have any questions or remarks feel free to post them in the comments.

Continue with the second tutorial.