PIC Assembler Coding

I became re-interested in Microchip PIC assembler coding recently. I hadn’t really used it for a number of years (since before 2009). The ‘C’ language is usually efficient enough and easier to code for most projects I do.

I used one of my DIY PIC18F4550 Pinguinos for experimentation. As you might guess, I decided to try the standard “Hello World!” program (i.e. blinking an LED). I wanted to create example PIC18F4550 assembler programs to blink an LED at intervals with a delay routine and with an interrupt timer. Also, I wanted both of them to work stand alone and with the Pinguino bootloader. You might also guess,  and be correct, that the internet is full of tutorials and code on how to blink an LED. Even the Pinguino WIKI shows 6 ways to blink an LED here, however all examples there are in ‘C’.

I did not find much on what I specifically wanted, especially using interrupts with the Pinguino bootloader. However, I did some experimentation and found solutions to make it all work.

Before I show the code, let’s first talk about the tools I am using (under Linux). To assemble the source code, I use GPASM which is part of the GPUTILS package. You can download it here.  I am using a PICKit2 programmer clone from tepuyelectronics.com to burn stand alone programs and the Pinguino bootloader. It looks like this:

PICKit2 Clone Programmer PIC Assembler CodingI use the Microchip pk2cmd software to run it. It can be downloaded from here.This is a command line example to run pk2cmd and load the Pinguino bootloader:

./pk2cmd -PPIC18F4550 -FBootloader_v4.14_18f4550_X40MHz.hex -M -R

Once the bootloader is burned in, ‘C’ programs can be compiled and uploaded via the Pinguino IDE. The output from the GPASM (PIC assembler) produces a hex format file which can be uploaded (to the bootloader) via the ‘stand alone’ uploader8.py Python script. For example:

./uploader8.py 18F4550 interrupt.hex

The Pinguino bootloader(s) for various chips and clock speeds can be downloaded from here. That file also includes the uploader8.py script used to upload hex files to the bootloader.  Those same hex format files can of course be burned in stand alone (no bootloader) using pk2cmd.

Probably the best development tool for PIC microcontrollers is the Microchip MPLAB IDE which you can download from here. You can use it to develop ‘C’ and assembler programs.

Here is an example ‘C’ program for the PIC18F4550 to blink an LED used in an MPLAB IDE project:

/*
 * File:   int.c
 * Author: earl@microcontrollerelectronics.com
 *
 * Created on June 12, 2016, 9:39 PM
 */

#include <xc.h>

#pragma config PLLDIV = 10
#pragma config CPUDIV = OSC1_PLL2       // CPU_clk = PLL/2
#pragma config USBDIV = 2               // USB_clk = PLL/2
#pragma config FOSC = HSPLL_HS          // HS osc PLL
#pragma config FCMEN = ON                       // Fail Safe Clock Monitor
#pragma config IESO = OFF                       // Int/Ext switchover mode
#pragma config PWRT = ON                        // PowerUp Timer
#pragma config BOR = ON                         // Brown Out
#pragma config VREGEN = ON                      // Int Voltage Regulator
#pragma config WDT = OFF                        // WatchDog Timer
#pragma config MCLRE = ON                       // MCLR
#pragma config LPT1OSC = OFF            // Low Power OSC
#pragma config PBADEN = OFF                     // PORTB<4:0> A/D
#pragma config CCP2MX = ON                      // CCP2 Mux RC1
#pragma config STVREN = ON                      // Stack Overflow Reset
#pragma config LVP = OFF                // High Voltage Programming
#pragma config ICPRT = OFF                      // ICP
#pragma config XINST = OFF                      // Ext CPU Instruction Set
#pragma config DEBUG = OFF                      // Background Debugging
#pragma config CP0 = OFF                        // Code Protect
#pragma config CP1 = OFF
#pragma config CP2 = OFF
#pragma config CPB = OFF                        // Boot Sect Code Protect
#pragma config CPD = OFF                        // EEPROM Data Protect
#pragma config WRT0 = OFF                       // Table Write Protect
#pragma config WRT1 = OFF
#pragma config WRT2 = OFF
#pragma config WRTB = OFF                       // Boot Table Write Protect
#pragma config WRTC = OFF                       // CONFIG Write Protect
#pragma config WRTD = OFF                       // EEPROM Write Protect
#pragma config EBTR0 = OFF                      // Ext Table Read Protect
#pragma config EBTR1 = OFF
#pragma config EBTR2 = OFF
#pragma config EBTRB = OFF                      // Boot Table Read Protect

unsigned char counter = 0;
/*
Clock Source : 40Mhz
so FCPU = Clock/4 (40Mhz/4)
FCPU = 10Mhz
Time Period= 1/FCPU (1/10Mhz)
Time Period = 0.1uS
Prescaler = 256
Prescaler Period = 0.1us * 256
Prescaler Period= 25.6us
Overflow Period = 25.6us * 256 = 6553.6us
Overflow Period = 0.0065536 S
For 1 Sec time delay we need 1/0.0065536
1 Sec  = 152.5878 Overflow.
.5 Sec = 76.29
*/

void main() {
  T0PS0  = 1;   //Prescaler is divided by 256
  T0PS1  = 1;
  T0PS2  = 1;
  PSA    = 0;   //Timer Clock Source is from Prescaler
  T0CS   = 0;   //Prescaler gets clock from FCPU 
  T08BIT = 1;   //8 BIT MODE
  TMR0IE = 1;   //Enable TIMER0 Interrupt
  PEIE   = 1;   //Enable Peripheral Interrupt
  GIE    = 1;   //Enable INTs globally
  TMR0ON = 1;   //Now start the timer!

  TRISA = 0;

  while(1);
}

void interrupt ISR() {

  if(TMR0IE && TMR0IF) {
    counter++;
    if(counter == 76) {
      LATAbits.LATA4 ^= 1;
      counter = 0;  
    }
    TMR0IF = 0;
  }
}

Here is basically that same program in assembler (use GPASM to assemble it):

; Assembler Program to Blink an LED
; Pinguino PIC18F4550
; Author earl@microcontrollerelectronics
; Using Interrupt 0 8 bit 
; Stand Alone loaded with no Bootloader

  list p=18f4550, r=hex
  #include <p18f4550.inc>
  #include config1.inc

  errorlevel -302
  errorlevel -313

  org 0x0000 
  goto start 
  
  org 0x0008
  goto hi_isr
  org 0x0018
  goto low_isr
  
  org 0x0c00 

start:
counter  equ  0x10 
  clrf counter 

  movlw b'00000000'
  movwf TRISA
  bcf   LATA,4
     
  bcf INTCON,GIE    ; Disable global interrupts

  bsf RCON,IPEN     ; Enable Interrupt Priority
  bsf INTCON,TMR0IE ; Enable TIMER0 interupt
  bcf T0CON,TMR0ON  ; Timer 0 Off
     
  bsf T0CON,T0PS0   ;   //Prescaler is divided by 256
  bsf T0CON,T0PS1   ;
  bsf T0CON,T0PS2   ;
  bcf T0CON,PSA     ;   //Timer Clock Source is from Prescaler
  bcf T0CON,T0CS    ;   //Prescaler gets clock from FCPU 
  bsf T0CON,T08BIT  ;   //8 BIT MODE
  
  bsf INTCON2,TMR0IP  ; Timer 0 High Priority
  bsf T0CON,TMR0ON    ; Timer 0 On
  bsf INTCON,GIE      ; Enable global interrupts  
     
;Clock Source : 40Mhz
;so FCPU = Clock/4 (40Mhz/4)
;FCPU = 10Mhz
;Time Period= 1/FCPU (1/10Mhz)
;Time Period = 0.1uS
;Prescaler = 256
;Prescaler Period = 0.1us * 256
;Prescaler Period= 25.6us
;Overflow Period = 25.6us * 256 = 6553.6us
;Overflow Period = 0.0065536 S
;For 1 Sec time delay we need 1/0.0065536
;1 Sec  = 152.5878 Overflow.
;.5 Sec = 76.29

loop:
  nop   
  goto loop   
 
  org 0x0200 
hi_isr:

;  movwf W_TEMP                         ; W_TEMP is in virtual bank
;  movff STATUS, STATUS_TEMP            ; STATUS_TEMP located anywhere
;  movff BSR, BSR_TEMP                  ; BSR_TEMP located anywhere
 
  btfss INTCON,TMR0IF
  goto end_int

  incf counter,f 
     
  movf  counter,0
  sublw d'76'

  btfss STATUS,Z
  goto  end_int
    
fl:
  movlw b'00010000'
  xorwf LATA,F
  clrf counter
 
end_int:
;  movff    BSR_TEMP, BSR                  ; Restore BSR
;  movf     W_TEMP, W                      ; Restore WREG
;  movff    STATUS_TEMP, STATUS            ; Restore STATUS
  bcf INTCON,TMR0IF
  retfie
  
  org 0x0300 
low_isr:
  nop
  retfie
 
  end

Here is basically the same assembler program but this time it is set up to be loaded into a Pinguino with a bootloader. (Notice the org 0x0c00 statement to locate the code beyond the bootloader.) It is a bit different as it uses TIMER0 in 16 bit mode.

; Assembler Program to Blink an LED
; From earl@microcontrollerelectonics.com
; Pinguino PIC18F4550
; Using Interrupt 0
; with Bootloader

  list p=18f4550, r=hex
  #include <p18f4550.inc>
  #include config1.inc

  errorlevel -302
  errorlevel -313

  org 0x0c00 
  goto start 
  org 0x0c08
  goto hi_isr
  org 0x0c18
  goto low_isr

  org 0x0c20
start:
counter  equ  0x10 
  clrf counter 

  movlw b'00000000'
  movwf TRISA
  bcf   LATA,4
     
  bcf INTCON,GIE    ; Disable global interrupts
  bcf T0CON,TMR0ON  ; Timer 0 Off
  bsf INTCON,TMR0IE ; Enable TIMER0 interupt
  bcf T0CON,T08BIT  ; use 16 bit mode
  bcf T0CON,T0CS    ; 
     
  movlw 0xD8
  movwf TMR0H
  movlw 0xF0
  movwf TMR0L
     
  bsf INTCON2,TMR0IP  ; Timer 0 High Priority
  bsf T0CON,TMR0ON    ; Timer 0 On
  bsf INTCON,GIE      ; Enable global interrupts  
     
loop:
  nop   
  goto loop   
 
hi_isr:
  btfss INTCON,TMR0IF
  goto end_int

  incf counter,f 
     
  movf  counter,0
  sublw d'255'

  btfss STATUS,Z
  goto  end_int
    
fl:
  movlw b'00010000'
  xorwf LATA,F
  clrf counter
 
end_int:
  bcf INTCON,TMR0IF
  retfie
  
low_isr:
  nop
  retfie
 
  end

The included file pic18f4550.inc is a standard header file which comes with GPASM. The included config1.inc is something I coded which sets up the CONFIG directives. Here is what it contains:

   config PLLDIV = 10
   config CPUDIV = OSC1_PLL2       ;// CPU_clk = PLL/2
   config USBDIV = 2               ;// USB_clk = PLL/2
   config FOSC = HSPLL_HS          ;// HS osc PLL
   config FCMEN = ON               ;        // Fail Safe Clock Monitor
   config IESO = OFF               ;        // Int/Ext switchover mode
   config PWRT = ON                ;        // PowerUp Timer
   config BOR = ON                 ;        // Brown Out
   config VREGEN = ON              ;        // Int Voltage Regulator
   config WDT = OFF                ;        // WatchDog Timer
   config MCLRE = ON               ;        // MCLR
   config LPT1OSC = OFF            ;// Low Power OSC
   config PBADEN = OFF             ;        // PORTB<4:0> A/D
   config CCP2MX = ON              ;        // CCP2 Mux RC1
   config STVREN = ON              ;        // Stack Overflow Reset
   config LVP = OFF                ;// High Voltage Programming
   config ICPRT = OFF              ;        // ICP
   config XINST = OFF              ;        // Ext CPU Instruction Set
   config DEBUG = OFF              ;        // Background Debugging
   config CP0 = OFF                ;        // Code Protect
   config CP1 = OFF
   config CP2 = OFF
   config CPB = OFF                ;        // Boot Sect Code Protect
   config CPD = OFF                ;        // EEPROM Data Protect
   config WRT0 = OFF               ;        // Table Write Protect
   config WRT1 = OFF
   config WRT2 = OFF
   config WRTB = OFF               ;        // Boot Table Write Protect
   config WRTC = OFF               ;        // CONFIG Write Protect
   config WRTD = OFF               ;        // EEPROM Write Protect
   config EBTR0 = OFF              ;        // Ext Table Read Protect
   config EBTR1 = OFF
   config EBTR2 = OFF
   config EBTRB = OFF              ;        // Boot Table Read Protect

If you don’t set up the PIC fuse (configuration) settings correctly (especially for the clock speed) the timing will not work correctly.

Here is the stand alone assembler program to blink an LED using a delay routine:

; Assembler Program to Blink an LED
; Pinguino PIC18F4550
; from earl@microcontrollerelectronics.com
; Using delay routine
; Stand Alone loaded with no Bootloader

  LIST P=18F4550, F=INHX32, N=0, ST=OFF, R=HEX
  #include <p18f4550.inc>
 
  org  0x0000   ; processor reset vector
  GOTO START    ; go to beginning of program

START:
  movlw b'00000000'
  movwf TRISA
loop:
  bcf LATA,4    ;Pinguino User LED on (tied to +5V)
  call Delay
  bsf LATA,4    ;Pinguino User LED off
  call Delay
  goto loop

; Delay = 0.5 seconds
; Clock frequency = 40 MHz

; Actual delay = 0.5 seconds = 5000000 cycles
; Error = 0 %

  cblock
  d1
  d2
  d3
  endc

Delay:
  ;4999993 cycles
  movlw 0x2C
  movwf d1
  movlw 0xE7
  movwf d2
  movlw 0x0B
  movwf d3
Delay_0:
  decfsz        d1, f
  goto  $+6
  decfsz        d2, f
  goto  $+6
  decfsz        d3, f
  goto  Delay_0
  ;3 cycles
  goto  $+4
  nop
  ;4 cycles (including call)
  return

  end

To run that same program in a Pinguino with a bootloader, change the org 0x0000 to org 0x0C00 so it loads beyond the bootloader.  I used a script (found here) to generate the delay routine. Note: You may need to change the code it generates for the specific processor being used. I had to change the relative offsets it generated (the $+ statements) to match the  lengths of opcodes for the PIC18F4550.

The delay routine is interesting to look at for the sake of learning the code involved, however, the interrupt version is ultimately more practical.

If you are interested in PIC assembler coding there are only a few instructions to learn. View the documentation here. Also of interest would be the assembler user guide here.

Leave a Reply

Your email address will not be published.