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