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:
I 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.
Recent Comments