This guide provides practical examples and essential information for using the MPLAB XC8 PIC Assembler with mid-range and PIC18 microcontroller families. The examples demonstrate various programming concepts, assembler directives, operators, and command-line options to help you develop efficient assembly code for Microchip PIC devices.
Introduction to MPLAB XC8 PIC Assembler
The MPLAB XC8 PIC Assembler is a powerful tool for developing embedded applications using assembly language. This guide presents working examples that illustrate key programming techniques for both mid-range and PIC18 devices. These examples cover fundamental concepts such as configuration settings, memory management, interrupt handling, and optimization strategies.
When combined with the MPLAB XC8 PIC Assembler User Guide, this document provides a comprehensive resource for developers transitioning from MPASM assembler to the XC8 toolchain. The examples are designed to be practical and immediately applicable to real-world embedded development projects.
Basic PIC18 Device Example
The following complete assembly program demonstrates fundamental concepts for PIC18F47K42 devices, though most elements apply to other PIC18 devices as well. This code repeatedly reads values from PORTA and records the highest value detected.
PROCESSOR 18F47K42
#include <xc.inc>
; Configuration settings
CONFIG FEXTOSC = OFF
CONFIG RSTOSC = HFINTOSC_1MHZ
CONFIG CLKOUTEN = OFF
CONFIG PR1WAY = ON
CONFIG CSWEN = ON
CONFIG FCMEN = ON
CONFIG MCLRE=EXTMCLR
CONFIG PWRTS=PWRT_OFF
CONFIG MVECEN=OFF
CONFIG IVT1WAY=ON
CONFIG LPBOREN=OFF
CONFIG BOREN=SBORDIS
CONFIG BORV=VBOR_2P45
CONFIG ZCD=OFF
CONFIG PPS1WAY=ON
CONFIG STVREN=ON
CONFIG DEBUG=OFF
CONFIG XINST=OFF
CONFIG WDTCPS=WDTCPS_31
CONFIG WDTE=OFF
CONFIG WDTCWS=WDTCWS_7
CONFIG WDTCCS=SC
CONFIG BBSIZE=BBSIZE_512
CONFIG BBEN=OFF
CONFIG SAFEN=OFF
CONFIG WRTAPP=OFF
CONFIG WRTB=OFF
CONFIG WRTC=OFF
CONFIG WRTD=OFF
CONFIG WRTSAF=OFF
CONFIG LVP=ON
CONFIG CP=OFF
GLOBAL max
PSECT udata_acs
max:
DS 1
tmp:
DS 1
PSECT resetVec,class=CODE,reloc=2
resetVec:
goto main
PSECT code
main:
movlw 0x62
movlb 57
movwf BANKMASK(OSCCON1),b
movlw 2
movwf BANKMASK(OSCFRQ),b
movlb 58
clrf BANKMASK(ANSELA),b
clrf max,c
loop:
movff PORTA,tmp
movf tmp,w,c
subwf max,w,c
bc loop
movff tmp,max
goto loop
END resetVecKey Elements in PIC18 Programming
Comments: Assembly source code can use multiple comment styles. Semicolon comments work anywhere in your code, while C-style comments (// and / /) require preprocessing when using the .S extension or the -xassembler-with-cpp option.
Configuration Bits: Proper configuration of device settings is essential for correct program execution. Use the CONFIG directive to set individual configuration bits or entire configuration words. Always consult your device datasheet to understand the function and appropriate values for each configuration setting.
Include Files: Use the #include directive to incorporate other files into your source code. The standard include file <xc.inc> provides access to special function registers and other device features. Preprocessor searches standard directories for files specified in angle brackets and checks the current working directory first for files in quotes.
Common Directives: Essential directives include PROCESSOR to specify the target device and END to mark the end of source code. The PROCESSOR directive ensures code compatibility with the specified device, while the END directive can specify the program entry point.
Predefined Psects: Program sections (psects) group related code and data elements. The PIC assembler provides predefined psects like 'code' for executable instructions and 'udata_acs' for access bank memory objects. Using predefined psects simplifies linking as they automatically associate with appropriate linker classes.
User-Defined Psects: For special memory placement requirements, create custom psects. The example uses a resetVec psect with specific flags: class=CODE associates it with the code memory class, and reloc=2 ensures word alignment for PIC18 instructions.
๐ Explore more assembly programming techniques
Mid-Range Device Programming Example
This example demonstrates assembly programming for PIC16F18446 devices, showing how to handle banked memory and other mid-range specific features:
PROCESSOR 16F18446
#include <xc.inc>
; Configuration bits omitted for brevity
skipnc MACRO
btfsc CARRY
ENDM
PSECT udata_bank0
max:
DS 1
tmp:
DS 1
PSECT resetVec,class=CODE,delta=2
resetVec:
PAGESEL main
goto main
PSECT code
main:
movlw 0x62
movlb 17
movwf OSCCON1
movlw 2
movwf OSCFRQ
PAGESEL loop
BANKSEL max
clrf BANKMASK(max)
BANKSEL ANSELC
clrf BANKMASK(ANSELC)
loop:
BANKSEL PORTC
movf BANKMASK(PORTC),w
BANKSEL tmp
movwf BANKMASK(tmp)
subwf max^(tmp&0ff80h),w
skipnc
goto loop
movf BANKMASK(tmp),w
movwf BANKMASK(max)
goto loop
END resetVecMid-Range Specific Considerations
Assembler Macros: Macros define identifiers that represent code sequences. The example creates a 'skipnc' macro that skips the next instruction if the carry flag is not set. Macros can accept parameters and use special characters in their definitions for flexible code generation.
Banked Memory Usage: Mid-range devices lack the movff instruction found in PIC18 devices, requiring careful bank selection for all data access operations. The BANKSEL directive simplifies bank selection, while the BANKMASK() macro ensures proper address masking for instruction operands.
Delta Flag: Psects containing executable code for mid-range devices must use the delta=2 flag, indicating 2 bytes of storage per address location. This matches the program memory width (14 bits) of mid-range devices.
Working with Multiple Source Files and Paging
Larger projects often benefit from splitting code across multiple source files. This example demonstrates how to share objects and procedures between modules while handling program memory paging:
; File 1: main.S
PROCESSOR 16F18446
#include <xc.inc>
; Configuration bits omitted
PSECT code
readPort:
BANKSEL PORTC
movf BANKMASK(PORTC),w
return
GLOBAL count
PSECT udata_shr
count:
DS 1
PSECT resetVec,class=CODE,delta=2
resetVec:
PAGESEL main
goto main
GLOBAL storeLevel
PSECT code
main:
BANKSEL ANSELC
clrf BANKMASK(ANSELC)
clrf count
loop:
call readPort
PAGESEL storeLevel
call storeLevel
PAGESEL $
movlw 0xFF
delay:
decfsz WREG,f
goto delay
movlw NUM_TO_READ
incf count,f
xorwf count,w
btfss ZERO
goto loop
goto $
END resetVec
; File 2: storage.S
PROCESSOR 16F18446
#include <xc.inc>
GLOBAL storeLevel
GLOBAL count
PSECT udata_shr
tmp:
DS 1
DLABS 1,0x120,NUM_TO_READ,levels
PSECT code
storeLevel:
movwf tmp
movf count,w
addlw low(levels)
movwf FSR1L
movlw high(levels)
clrf FSR1H
addwfc FSR1H
movf tmp,w
movwf INDF1
return
ENDMulti-Module Development Strategies
Global Symbol Management: Use the GLOBAL directive to make symbols accessible across modules and declare external symbols in files that reference them. The EXTRN directive provides an alternative for declaring external symbols without defining them.
Psect Linking and Paging: Identically named psects within the same module are concatenated by the linker, while psects with the same name in different modules are linked separately. This affects paging considerations for calls and jumps on mid-range devices.
Linear Memory Addressing: Enhanced mid-range devices support linear addressing mode, which accesses banked data memory as a contiguous block. The DLABS directive defines objects that span multiple banks, and FSR registers enable indirect access using linear addresses without bank selection.
Compiled Stack Implementation
The compiled stack provides static allocation for local objects, allowing memory reuse across different procedures:
#include <xc.inc>
; Configuration bits omitted
FNCONF udata_acs,?au_,?pa_
PSECT resetVec,class=CODE,reloc=2
resetVec:
goto main
PSECT code
; Function definitions with stack usage
FNSIZE add,0,4
GLOBAL ?pa_add
add:
movf ?pa_add+2,w,c
addwf ?pa_add+0,f,c
movf ?pa_add+3,w,c
addwfc ?pa_add+1,f,c
return
FNSIZE incr,0,2
GLOBAL ?pa_incr
incr:
addwf ?pa_incr+0,c
movlw 0h
addwfc ?pa_incr+1,c
return
GLOBAL ?au_main
GLOBAL result
result EQU ?au_main+0
GLOBAL incval
incval EQU ?au_main+2
FNROOT main
FNSIZE main,4,0
FNCALL main,add
FNCALL main,incr
PSECT code
main:
clrf result+0,c
clrf result+1,c
movlw 2
movwf incval+0,c
clrf incval+1,c
loop:
movff result+0,?pa_add+0
movff result+1,?pa_add+1
movff incval+0,?pa_add+2
movff incval+1,?pa_add+3
call add
movff ?pa_add+0,result+0
movff ?pa_add+1,result+1
movff incval+0,?pa_incr+0
movff incval+1,?pa_incr+1
movlw 2
call incr
movff ?pa_incr+0,incval+0
movff ?pa_incr+1,incval+1
goto loop
END resetVecCompiled Stack Directives
FNCONF: Configures the compiled stack, specifying the psect for stack storage and prefixes for automatic and parameter objects.
FNSIZE: Declares the memory requirements for a procedure's automatic and parameter objects.
FNROOT and FNCALL: Define call graph relationships to enable memory overlapping between procedures that are never active simultaneously.
The linker processes these directives to create a call graph and allocate storage efficiently, with overlapping memory blocks for procedures that don't call each other.
Interrupt Handling and Bit Manipulation
This example demonstrates interrupt service routines and bit manipulation for a PIC16F18446 device, toggling an LED using timer interrupts:
#include <xc.inc>
; Configuration bits omitted
GLOBAL resetVec,isr
GLOBAL LEDState
PSECT bitbss,bit,class=BANK1,space=1
LEDState:
DS 1
PSECT resetVec,class=CODE,delta=2
resetVec:
ljmp start
PSECT isrVec,class=CODE,delta=2
isr:
PAGESEL $
BANKSEL PIE0
btfsc TMR0IE
btfss TMR0IF
goto notTimerInt
bcf TMR0IF
movlw 1 shl (LEDState&7)
BANKSEL LEDState/8
xorwf BANKMASK(LEDState/8),f
notTimerInt:
exitISR:
retfie
PSECT code
start:
movlw 0x33
BANKSEL TRISA
movwf TRISA
movlw 2
BANKSEL OSCFRQ
movwf OSCFRQ
movlw 0x89
BANKSEL T0CON1
movwf T0CON1
movlw 0x1D
movwf TMR0H
clrf TMR0L
BANKSEL PIE0
bcf TMR0IF
bsf TMR0IE
movlw 0x80
BANKSEL T0CON0
movwf T0CON0
bsf GIE
bsf PEIE
loop:
BANKSEL LEDState/8
btfss BANKMASK(LEDState/8),LEDState&7
goto lightLED
BANKSEL PORTA
bsf RA2
goto loop
lightLED:
BANKSEL PORTA
bcf RA2
goto loop
END resetVecInterrupt Service Routine Considerations
ISR Placement: Interrupt service routines must be linked to the appropriate interrupt vector address using unique psects.
Context Saving: Many modern PIC devices automatically save core register states to shadow registers on interrupt entry. For devices without this feature, manual context saving is required.
Interrupt Source Identification: Check both the interrupt enable flag and interrupt flag to confirm the interrupt source before processing.
Bit Objects: Use the 'bit' psect flag to create single-bit objects that conserve memory. Bit addresses require conversion to byte addresses and bit positions for use with bit-oriented instructions.
๐ Learn advanced interrupt handling techniques
PIC18 Interrupts and Vector Tables
PIC18 devices with vector interrupt controllers use a different approach to interrupt handling:
#include <xc.inc>
; Configuration bits omitted
GLOBAL resetVec
GLOBAL LEDState
GLOBAL __Livt
PSECT bitbssCOMMON,bit,class=COMRAM,space=1
LEDState:
DS 1
PSECT resetVec,class=CODE,reloc=2
resetVec:
goto start
PSECT ivt,class=CODE,reloc=2,ovrld
ivtbase:
ORG 31*2
DW tmr0Isr shr 2
PSECT code
tmr0Isr:
bcf TMR0IF
movlw 1 shl (LEDState&7)
xorwf LEDState/(0+8),c
retfie f
PSECT code
start:
bsf BANKMASK(INTCON0),INTCON0_IPEN_POSN,c
bcf GIE
movlw 0x55
movwf BANKMASK(IVTLOCK),c
movlw 0xAA
movwf BANKMASK(IVTLOCK),c
bcf IVTLOCKED
movlw low highword __Livt
movwf BANKMASK(IVTBASEU),c
movlw high __Livt
movwf BANKMASK(IVTBASEH),c
movlw low __Livt
movwf BANKMASK(IVTBASEL),c
movlw 0x55
movwf BANKMASK(IVTLOCK),c
movlw 0xAA
movwf BANKMASK(IVTLOCK),c
bsf IVTLOCKED
; Peripheral setup code omitted
loop:
btfss LEDState/8,LEDState&7,c
goto lightLED
bsf RE0
goto loop
lightLED:
bcf RE0
goto loop
END resetVecPIC18 Vector Interrupt Controller
Vector Tables: PIC18 devices with MVECEN enabled use an interrupt vector table rather than fixed interrupt vectors. Each interrupt source has its own ISR with an entry in the vector table.
Table Placement: The vector table psect uses the ovrld flag to allow overlapping placements from different source files. The ORG directive positions vectors at appropriate offsets within the table.
IVTBASE Registers: The vector table base address is loaded into IVTBASE registers using a special unlock sequence. The linker-defined symbol __Livt provides the table's linked address.
Fast Interrupt Return: The retfie f instruction uses the fast form that restores shadow register contents automatically.
Frequently Asked Questions
What is the difference between MPASM and MPLAB XC8 PIC Assembler?
The MPLAB XC8 PIC Assembler is the modern replacement for MPASM, with improved optimization capabilities, better integration with the XC8 compiler toolchain, and enhanced support for newer PIC devices. It uses a different directive syntax and offers better memory utilization through features like the compiled stack.
How do I set configuration bits in assembly code?
Use the CONFIG directive with setting-value pairs for individual configuration bits or entire configuration words. You can generate the appropriate configuration code using MPLAB X IDE's configuration bits tool or consult the device-specific HTML documentation in the compiler's docs directory.
What are psects and why are they important?
Program sections (psects) are containers that group related code and data elements. They are the smallest entities that the linker places into memory. Psects control memory placement, alignment, and linking behavior, making them essential for effective memory management in assembly programs.
How do I handle banked memory in mid-range devices?
Use the BANKSEL directive before accessing objects in banked memory, and the BANKMASK() macro to ensure proper address masking in instruction operands. Always verify that objects accessed in the same code sequence reside in the same memory bank to avoid runtime errors.
What is the compiled stack and when should I use it?
The compiled stack provides static allocation for local objects that can be reused across different procedures. Use it when you need to conserve data memory by overlapping storage for objects that are never active simultaneously. The compiled stack is particularly useful for reducing memory usage in applications with many functions that have local variables.
How do I create and use bit variables?
Define bit variables in psects with the 'bit' flag, which specifies that the psect uses bit addressing rather than byte addressing. Access bit variables using their byte address (bit_address/8) and bit position (bit_address&7). Bit variables conserve memory when you need to store single-bit flags or status indicators.
What's the best way to handle interrupts in assembly code?
Create a unique psect for your interrupt service routine linked to the appropriate vector address. Implement proper context saving if your device doesn't provide automatic shadow registers. Always check both the interrupt enable flag and interrupt flag to confirm the interrupt source, and use the appropriate return instruction (retfie or retfie f) to exit the ISR.