Skip to content

PIC18 assembly

Assembly translates human-readable instructions into machine code (bytes in the PIC’s Flash). In the UNEXPO course you use MPASM inside MPLAB v8: you write a .ASM file, assemble it, and get a .HEX file to program the microcontroller.

This page completes the Foundations block with Architecture, Registers, and Bits and hexadecimal. You already know what WREG, SFRs, and masks are; here you learn how to write the program line by line.

Your code (.ASM)
↓ MPASM (assembler)
.HEX file
↓ Programmer / MPLAB
PIC18F4550 Flash → the chip executes instructions

Later in MPLAB and Proteus you will see the full toolchain. Config bits and the configuration word go in the same .ASM but are stored in a special configuration area, not as normal executable code.

Anatomy of an MPASM file — “sandwich” layout

Section titled “Anatomy of an MPASM file — “sandwich” layout”

In the UNEXPO course your instructor asks for a sandwich order in the .ASM, similar to int main() in C:

LayerContentsC analogy
TopHeader, CONFIG, variables (CBLOCK)#include, defines, global variables
MiddleSubroutines (CALL / RETURN)Helper functions
BottomMain programmain()
EndENDEnd of file

Only at the top goes the reset vector (ORG 0x0000 / 0000H + GOTO programa): the hardware starts at 0000H and must jump to main at the bottom.

; ══ TOP — header, config, variables ═══════════════════
LIST P=18F4550
#include <P18F4550.INC>
CONFIG FOSC = HS
CONFIG WDT = OFF
CONFIG LVP = OFF
CBLOCK 0x20
counter RES 1
; ── Reset vector (jumps to main at the bottom) ───────
ORG 0x0000
GOTO programa
; ══ MIDDLE — subroutines ═════════════════════════════
setup_ports:
BCF TRISB, 0 ; RB0 output
RETURN
turn_on_led:
BSF LATB, 0 ; RB0 high
RETURN
; ══ BOTTOM — main program (like main) ════════════════
programa:
CALL setup_ports
CALL turn_on_led
loop:
GOTO loop ; infinite loop
END
DirectiveFunction
LIST P=18F4550Tells the assembler which MCU
#include <P18F4550.INC>Imports register and bit names
CONFIG …Configuration word (oscillator, WDT…)
ORG addrSets the Flash address where code starts
CBLOCK / RESReserves RAM bytes for GPR variables
ENDMarks end of file
  • Label (start:, loop:): name of a Flash location; target of GOTO or CALL. Not a number — MPASM turns it into an address when assembling.
  • Comment (; text): ignored by MPASM — documents your code.

A literal is a fixed value written in the source line. It is not a register name (LATB, counter) or a label (programa:): it is the actual number you want to use.

Think of it like a constant in C: the value you already chose and write as-is, not something you read from a RAM variable. If in C you would write result = 10;, in assembly you write MOVLW D'10' — that 10 is the literal (your constant on that line).

In the MPASM manual it appears as k when the instruction embeds a value:

ManualYour codeMeaning
MOVLW kMOVLW D'10'Load literal 10 into WREG
ANDLW kANDLW 0xF0AND between WREG and literal 0xF0
ADDLW kADDLW B'00000001'Add literal 1 to WREG

The assembler encodes that literal into bytes inside the Flash instruction. The PIC does not “look up” a literal in RAM: the value travels inside the opcode.

MPASM requires prefixes to make the number base explicit. The same value can be written several ways:

NotationExample in .ASMSame value (report)Base
DecimalD'200'20010
BinaryB'11110000'2402
Hex (0x)0xF0F0H16
Hex (h suffix)0F0hF0H16 in MPASM

Useful rules when writing literals:

  • Decimal: always D'number' with quotes — D'10', D'255'.
  • Binary: B'01010101' — handy for masks (see Bits and hexadecimal).
  • Hex in .ASM: 0xF0 or 0F0h (MPASM accepts both; sometimes 0FFh avoids confusion with a label).
  • Byte range: an 8-bit literal goes from 0 to 255 (0x000xFF). Out-of-range values trigger an MPASM error.

Examples in real instructions:

MOVLW D'10' ; WREG = 10 (decimal)
MOVLW 0xF0 ; WREG = 240 = F0H
MOVLW B'00000001' ; WREG = mask for bit 0 only
ANDLW 0x0F ; WREG = WREG AND 0FH (keep low nibble)
BSF LATB, 0 ; the 0 is also a literal: bit number

For conversion between decimal, binary, and hex (tables, masks, 0x vs FFH), use Bits and hexadecimal. Here the focus is how to write the value inside the .ASM.

Directives are not executed on the PIC; they tell MPASM where or how much to reserve. They also use numeric literals:

DirectiveExampleWhat the literal means
ORGORG 0x0000Flash address (0000H = reset vector)
CBLOCKCBLOCK 0x20Starting RAM address for variables (20H)
LISTLIST P=18F4550Here the “literal” is the MCU name (symbol, not a number)
ORG 0x0000 ; code starts at 0000H
CBLOCK 0x20 ; variables from 20H in RAM
counter RES 1 ; RES 1 = reserve 1 byte (another kind of literal)

Same notation idea: 0x0000 = 0000H; 0x20 = 20H. See equivalents in Bits and hexadecimal — hex notations.

The letter f in instructions — what it is and how to use it

Section titled “The letter f in instructions — what it is and how to use it”

Before suffixes ,W and ,F, you need a MPASM manual convention that shows up in almost every instruction.

Step 1 — How instructions look in the manual

Section titled “Step 1 — How instructions look in the manual”

In documentation, instructions do not use concrete names like LATB or counter. They use the letter f as a slot you fill in:

Manual instructionMeaning in plain words
MOVF f, WCopy register f into WREG
MOVWF fCopy WREG into register f
INCF f, FAdd 1 to register f and store the result in f
BSF f, bSet bit b of register f to 1
CLRF fClear register f to zero

Manual f means: “put here whichever file register you need”. In English: file register — any byte in data RAM.

Step 2 — Replace f with the register you need

Section titled “Step 2 — Replace f with the register you need”

When you program, you substitute f with the concrete register you want to read, write, or modify:

Manual (generic)Your code (concrete)What it does
MOVF f, WMOVF LATB, WCopy LATBWREG
MOVF f, WMOVF counter, WCopy counterWREG
MOVWF fMOVWF LATBCopy WREGLATB
INCF f, FINCF counter, Fcounter = counter + 1

In the line MOVF LATB, W, the manual would write MOVF f, W. In that example, f and LATB are the same thing: the only register involved (besides WREG) is LATB. There is no hidden register called F.

; Manual: MOVF f, W
; Your code: MOVF LATB, W
; └─┬──┘
; └── this is what the manual calls "f"

Same with your own variable:

; Manual: INCF f, F
; Your code: INCF counter, F
; └────┬────┘
; └── "f" = counter in this program

Any data RAM byte can take the f slot. Two families (see Registers and Architecture):

If you replace f with…TypeExample use
TRISB, LATB, PORTB, INTCONSFRControl PIC hardware
counter, temp (with RES)GPRStore your program data

LATB and counter are file registers because they can be the f in an instruction. SFR vs GPR differs by purpose, not by being a “file register”.

Step 4 — Do not confuse f with suffix ,F

Section titled “Step 4 — Do not confuse f with suffix ,F”

On one line you may see two different uses of the letter F:

SymbolWhat it isExample
f (lowercase, from manual)Which register you use — replace with LATB, counterMOVF LATB, W
,F (suffix at end)Where the result goes — back into that same register (not WREG)INCF counter,F
,W (suffix)Where the result goes — into WREGMOVF LATB,W

Full example broken down:

MOVF LATB, W
; └─┬─┘ └┬┘
; f destination = WREG (suffix ,W)
; in real code, f = LATB
INCF counter, F
; └───┬───┘ └┬┘
; f destination = same register (suffix ,F)
; in real code, f = counter

The next section details ,W vs ,F with more instruction examples.

With the previous section clear: in MOVF LATB, W, manual f = LATB in your code; suffix ,W means the result goes to WREG.

Many instructions have two destination forms:

; f = counter; destination ,W → result in WREG
MOVF counter, W ; WREG = counter (counter unchanged)
; f = LATB; destination ,W
MOVF LATB, W ; WREG = LATB
; f = counter; destination ,F → result in same register
INCF counter, F ; counter = counter + 1

Mental rule: ,W → result in WREG; ,F → result in the same register that replaces f (LATB, counter…).

InstructionWhat it doesExample
MOVLW kLoad literal k into WREGMOVLW D'10'
MOVWF fCopy WREG → register fMOVWF LATB
MOVF f,dCopy register fd (,W or ,F)MOVF PORTB, W
CLRF fSet register f to 0CLRF counter
CLRWSet WREG to 0CLRW

Typical load-and-store pattern:

MOVLW D'55' ; WREG = 55
MOVWF counter ; counter = 55
InstructionOperationUpdates STATUS
ADDLW kWREG = WREG + kYes (Z, C, DC, OV, N)
ADDWF f,dWREG + f → dYes
SUBLW kWREG = k − WREGYes
SUBWF f,df − WREG → dYes
INCF f,df + 1 → dYes (Z, N…)
DECF f,df − 1 → dYes
DECFSZ f,dDecrement; if result is 0, skip next instructionYes

Example — loop for N iterations:

MOVLW D'10'
MOVWF counter
loop:
; ... work ...
DECFSZ counter, F ; counter--; if 0, skip GOTO
GOTO loop
; loop ends here

After adds and subtracts check STATUS flags: Z (zero?), C (carry?), N (sign).

InstructionOperation
ANDLW k / ANDWF f,dBitwise AND
IORLW k / IORWF f,dBitwise OR
XORLW k / XORWF f,dBitwise XOR
COMF f,dComplement (NOT) of f

Details and masks in Bits and hexadecimal. Practice 1 requires routines with these operations.

Bit instructions — one pin without touching the rest

Section titled “Bit instructions — one pin without touching the rest”
InstructionEffect
BSF f,bBit b of f → 1
BCF f,bBit b of f → 0
BTFSS f,bSkip next line if bit = 1
BTFSC f,bSkip next line if bit = 0
; Read button on RB3 (input)
BSF TRISB, 3 ; RB3 input
BTFSS PORTB, 3 ; RB3 = 1?
GOTO button_released ; if RB3 = 1 → jump to destination
; ... code if button pressed (RB3 = 0) ...
button_released:
; continues if button is released (RB3 = 1)
InstructionFunction
GOTO labelUnconditional jump — PC goes to label
CALL subCall subroutine — saves return address on stack
RETURNReturn from CALL — restores PC from stack
RETFIEReturn from interrupt (ISR)
; ── subroutine (middle of sandwich) ──
delay_1s:
; ... timing loop ...
RETURN
; ── main program (bottom, like main) ──
programa:
CALL delay_1s ; go to delay_1s; save return address
BSF LATB, 0 ; on return, turn LED on
GOTO programa

The stack is LIFO: the last CALL returns first with RETURN. See Architecture — PC for more detail.

Compare and decide — conditional branches

Section titled “Compare and decide — conditional branches”

PIC18 has no CMP instruction. You compare by operating and reading STATUS:

; counter == 0?
MOVLW D'0'
XORWF counter, W ; if equal → W=0 → Z=1
BTFSS STATUS, Z ; skip if Z=1 (counter was 0)
GOTO not_zero ; if not zero → jump to destination
; counter == 0 ...
GOTO zero_done
not_zero:
; counter != 0 ...
zero_done:
; RB0 on?
BTFSS LATB, 0 ; skip if LATB bit 0 = 1
GOTO led_off ; if RB0 = 0 → jump to destination
; LED on (RB0 = 1) ...
GOTO led_done
led_off:
; LED off (RB0 = 0) ...
led_done:
InstructionSkips if…
BTFSS STATUS, ZResult was zero (Z=1)
BTFSC STATUS, ZResult was not zero (Z=0)
BTFSS STATUS, CThere was carry (C=1)

Translate “turn on an LED on RB0” into concrete steps using GPIO registers:

LIST P=18F4550
#include <P18F4550.INC>
CONFIG FOSC = HS, WDT = OFF, LVP = OFF
ORG 0x0000
GOTO programa
; ── (no subroutines in this minimal example) ──
programa:
BCF TRISB, 0 ; 1) RB0 as output
BSF LATB, 0 ; 2) RB0 high → LED ON
loop:
GOTO loop ; 3) loop (program never exits)
END

In assembly you do not say “turn on an LED.” You say: set pin direction, write the latch, keep the program running.

Ejercicio · Parcial

Trace this fragment

MOVLW D'3'
MOVWF counter
INCF counter, F
DECFSZ counter, F
GOTO $-1
  1. counter becomes 3, then INCF4.
  2. DECFSZ decrements: 3 — not zero → no skip.
  3. GOTO $-1 goes back to DECFSZ.
  4. Repeats until counter = 0DECFSZ skips the GOTO and the loop ends.

$-1 is the previous instruction (MPASM implicit label).

  • Forgetting END: MPASM may error or produce incomplete code.
  • MOVLW vs MOVWF: MOVLW loads WREG; MOVWF stores WREG into a register — not interchangeable.
  • Writing outputs to PORTx: use LATx for outputs; read inputs with PORTx.
  • Mixing up BTFSS and BTFSC: one skips if bit is 1, the other if 0 — check button wiring.
  • Missing P18F4550.INC: without the include, TRISB and LATB are undefined symbols.
  • Label without colon (programa instead of programa:): the assembler will not recognize the jump target.
  • Main program at top, subroutines below: in UNEXPO it is the opposite — programa: at the bottom, subroutines in the middle (sandwich / main at the end).

What does f mean in manual MPASM MOVWF f?

It is a placeholder: the manual does not know which register you will use. You write MOVWF LATB or MOVWF counterf becomes that name. In MOVF LATB, W, f and LATB are the same thing.

What is a literal in MPASM?

A fixed value written in the line (D'10', 0xF0, B'10000000') — like a constant in C — that the assembler embeds in the instruction. It is not a register (counter) or a label (loop:).

What does MOVLW D'5' followed by MOVWF counter do?

Loads 5 into WREG and copies it to GPR variable counter.

What is ORG 0x0000 (0000H) for?

It places code at address 0000H in program memory (reset vector).

What is the difference between GOTO and CALL?

GOTO jumps without saving a return address. CALL jumps to a subroutine and pushes the address to return with RETURN.

What does DECFSZ counter, F do?

Decrements counter; if the result is 0, it skips the next instruction (useful for loops).

Interactive exam

MPASM assembly

Based on: instrucciones pic.pdf

0 of 0 answered

MOVLW k loads a value into...
The ORG 0x0000 directive indicates...
MOVWF counter copies the value from...
The END directive in MPASM...
CALL subroutine followed by RETURN is used to...
INCF counter, F does...
DECFSZ counter, F when counter reaches 0...
#include <P18F4550.INC> is used to...
RES 1 after a name in CBLOCK...
To compare if counter == 0 without a CMP instruction, you use...
In MPASM syntax, the letter f in MOVWF f means...