Mastering PIC10F200/202/204/206: A Beginner’s Guide to Writing Assembly Code

Introduction

The Microchip company has a series of microcontrollers called PIC10F200/202/204/206. All of them use the same assembly language structure. The assembly language for them has 33 opcodes, which we will discuss in this post. After that, we will create an assembler, emulator, and compiler to demonstrate how they work. So, let’s begin by explaining the opcodes.

Every instruction is 12 bits in size in binary. Each instruction has different parts, except for one common element: an identifier that makes each instruction unique. Some instructions also include a destination bit, address, literals, etc.

Instructions affect certain bits of specific registers in the microcontroller, which varies for each opcode.

ADDWF f, d

Add W to f.

ADDWF adds the value of register W to register f. If d (destination) is 1, the result will be stored in register f; otherwise, the result remains in register W.
Since this microcontroller is 8-bit, the result cannot exceed 255 (0xFF). This means that if we add two values and the result exceeds 255, it wraps around to start from 0 due to overflow.

ADDWF:

          ┌─ Destination (1 bit)

 0b0001 11df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Let’s look at some example of this:

ASM
; [0x06] = [0x06] + W  
ADDWF 0x06, 1 ; Add W to register 0x06 and store the result in register 0x06

; W = [0x06] + W  
ADDWF 0x06, 0 ; Add W to register 0x06 and store the result in register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.
  • Bit DC in the STATUS register: If a carry from the 4th low-order bit of the result occurs, it is set to 1; otherwise, it is set to 0.
  • Bit C in the STATUS register: It is set to 1 if a carry occurred; otherwise, it is set to 0.

ANDWF f, d

And W with f.

This instruction performs an AND (&) operation between the value of the given address f and the value of register W. If d (destination) is set to 1, the result will be stored in f; otherwise, the result remains in register W.

ANDWF:

          ┌─ Destination (1 bit)

 0b0001 01df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Let’s look at some example for ANDWF:

ASM
; [0x06] = [0x06] & W
ANDWF 0x06, 1 ; Perform AND operation between W and the value of register 0x06, store the result in register 0x06

; W = [0x06] & W
ANDWF 0x06, 0 ; Perform AND operation between W and the value of register 0x06, store the result in register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

CLRF f

Clear f.

This instruction clears the value of the given address f, meaning that the value of the given address f becomes 0 (0b00000000).

CLRF:

            ┌─ Address (5 bit)
           ─┴────
 0b0000 011f ffff
   ─┬──────
    └─ Identifier (7 bit)

This is a simple example for CLRF:

ASM
; [0x06] = 0x00
CLRF 0x06 ; Clear the value of register 0x06

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

CLRW

Clear W.

CLRW works exactly like CLRF but only clears the value of register W and sets its value to 0 (0b00000000). CLRW has no operand.

CLRW:

 0b0000 0100 0000
   ─┬────────────
    └─ Identifier (8 bit)

The usage is very simple:

ASM
; W = 0
CLRW

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

COMF f, d

Complement f.

This instruction complements (~) the value of the given register f, meaning that it converts all the zeros in binary to ones and all the ones to zeros. You can say it reverses the bits. Depending on bit d (destination), if it is set to 1, the result will be stored in f; otherwise, if it is set to 0, the result stays in register W.

COMF:

          ┌─ Destination (1 bit)

 0b0010 01df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

This is a basic example for the COMF command on address 0x06:

ASM
; [0x06] = ~[0x06]
COMF 0x06, 1 ; Complement the value of register 0x06 and store the result in register 0x06

; W = ~[0x06]
COMF 0x06, 0 ; Complement the value of register 0x06 and store the result in register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

DECF f, d

Decrement f.

This code decrements the value of the given register f, and if bit d (destination) is set to 1, it stores the value back in register f; otherwise, the result stays in register W.

If the value of the given address f is set to 0, DECF is unaffected because you cannot decrement from 0.

DECF:

          ┌─ Destination (1 bit)

 0b0000 11df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

The following code is an example for the DECF command:

ASM
[0x06] = [0x06] - 1;  
DECF 0x06, 1 ; Decrement the value of register 0x06 and store the result in register 0x06

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

DECFSZ f, d

Decrement f, Skip if 0.

This is the first 2-cycle opcode we are talking about. This instruction decrements the value of the given register f, and if the result becomes 0, it skips the next instruction and executes a NOP (No Operation) instead. Otherwise, if the result is not 0, it behaves like DECF and does not skip any instruction.
If d (destination) is set to 0, the result stays in register W; otherwise, the result is placed back into address f.

DECFSZ:

          ┌─ Destination (1 bit)

 0b0010 11df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

You can use DECFSZ like this:

ASM
; [0x06] = [0x06] - 1

DECFSZ 0x06, 1 ; Decrement 1 from the value of 0x06 (place back into 0x06) and skip the next instruction if the value is 0  
COMF 0x06, 1 ; Complement the value of register 0x06 and place it back into register 0x06  
ADDWF 0x06, 0 ; Add 1 to register 0x06 and store the value in register W

The DECFSZ instruction does not affect any bits in the STATUS register.


INCF f, d

Increment f.

The value of register f will be incremented by 1, and if d (destination) is 1, the result is placed back into the given address f; otherwise, the result stays in register W.

If the value of the given register is already 0xFF (255), because this is an 8-bit CPU, the result will wrap around and become 0.

INCF:

          ┌─ Destination (1 bit)

 0b0010 10df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Let’s look at two examples for INCF with different destinations:

ASM
; [0x06] = [0x06] + 1
INCF 0x06, 1  ; Increment the value of register 0x06 and store the result in register 0x06

; W = [0x06] + 1
INCF 0x06, 0  ; Increment the value of register 0x06 and store the result in register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

INCFSZ f, d

Increment f, Skip if 0.

Just like INCF, this instruction increments the value of address f by 1, and if the d (destination) is set to 1, the result is placed back into the given address; otherwise, the result stays in register W.

INCFSZ, like DECFSZ, is a 2-cycle opcode, meaning that if the result of the operation becomes 0 (if the value of the given address was more than 255), it skips the next instruction and executes a NOP (No Operation).

INCFSZ:

          ┌─ Destination (1 bit)

 0b0011 11df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Let’s look at some examples for INCFSZ:

ASM
; [0x06] = [0x06] + 1

INCFSZ 0x06, 1 ; Increment 1 from the value of 0x06 (place back into 0x06) and skip the next instruction if the value is 0  
COMF 0x06, 1 ; Complement the value of register 0x06 and place it back into register 0x06  
ADDWF 0x06, 0 ; Add 1 to register 0x06 and store the value in register W

The INCFSZ instruction does not affect any bits in the STATUS register.


IORWF f, d

Inclusive OR W with f.

IORWF instruction performs an Inclusive OR (|) between the value of register W and the value of address f.
If d (destination) is set to 1, it means that the result will be placed back into address f; otherwise, the result stays in register W.

IORWF:

          ┌─ Destination (1 bit)

 0b0001 00df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

We can use this opcode like this:

ASM
; W = [0x06] | W  
IORWF 0x06, 0 ; Inclusive OR between W and 0x06, the result is placed back into register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

MOVF f, d

Move f.

This instruction is very useful, especially when writing a compiler. The MOVF instruction moves the value of the given address f to the given destination d. If d is set to 0, it means that the value of the given address f is loaded into register W. Otherwise, if d is set to 1, the value is placed back into the given address f. This is useful for checking a value because MOVF affects the Z bit in the STATUS register, which helps us determine whether the value of address f is 0 or not.

MOVF:

          ┌─ Destination (1 bit)

 0b0010 00df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Let’s see an example for this:

ASM
; W = [0x06]  
MOVF 0x06, 0 ; Move the value of register 0x06 to register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

MOVWF f

Move W to f.

Another useful instruction is MOVWF. This instruction moves the value of register W to the given address f. This is kind of the opposite of MOVF with destination (d) 0.
MOVF only takes an address f.

MOVWF:

          ┌─ Destination (1 bit)

 0b0000 01df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Opcode (6 bit)

Let’s look at a simple example for MOVWF:

ASM
; [0x06] = W  
MOVWF 0x06 ; Move the value of register W to register 0x06

MOVWF does not affect any bits in the STATUS register.


NOP

No Operation.

The NOP instruction does nothing and is primarily used to create a delay or to skip over other instructions, allowing NOP to execute in their place.

NOP:

 0b0000 0000 0000
   ─┬────────────
    └─ Identifier (12 bit)

Using NOP is simple:

ASM
NOP  ; No Operation

NOP does not affect any of the STATUS register bits.


RLF f, d

Rotate Left f Through Carry.

Each bit in the given address f is shifted one position to the left, with the leftmost bit moved to the carry flag, and the previous carry bit placed in the least significant bit. If d is set to 0, the result stays in the W register; otherwise, if d is set to 1, the result is stored in the given register address.

Rotate Left Through Carry:

 ┌─────────────────────────────────────────────────┐
 │                                                 │
 │    Carry    Binary                              │
 │    ┌───┐    ┌───┬───┬───┬───┬───┬───┬───┬───┐   │
 └──  │ 0 │ << │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ <─┘
      └───┘    └───┴───┴───┴───┴───┴───┴───┴───┘
       C       7   6   5   4   3   2   1   0

The structure for this instruction is as follows:

RLF:

          ┌─ Destination (1 bit)

 0b0011 01df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Using RLF is simple; you can shift the content of the given address to the left by 1.

ASM
; [0x06] = [0x06] << 1
RLF 0x06, 1 ; Rotate the contents of register 0x06 one bit to the left

Affected Bits

  • Bit C in the STATUS register: Loads the bit with the least significant bit (LSB) or most significant bit (MSB), respectively.

RRF f, d

Rotate Right f Through Carry.

Each bit in the given address f is shifted one position to the right, with the rightmost bit moved to the carry flag, and the previous carry bit placed in the most significant bit. If d is set to 0, the result stays in the W register; otherwise, if d is set to 1, the result is stored in the given register address.

Rotate Right Through Carry:

 ┌─────────────────────────────────────────────────┐
 │                                                 │
 │    Carry    Binary                              │
 │    ┌───┐    ┌───┬───┬───┬───┬───┬───┬───┬───┐   │
 └──> │ 1 │ >> │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ ──┘
      └───┘    └───┴───┴───┴───┴───┴───┴───┴───┘
       C       7   6   5   4   3   2   1   0

The structure for this instruction is as follows:

RRF:

          ┌─ Destination (1 bit)

 0b0011 00df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Using RRF is simple; you can shift the content of the given address to the right by 1.

ASM
; [0x06] = [0x06] >> 1
RRF 0x06, 1 ; Rotate the contents of register 0x06 one bit to the right

Affected Bits

  • Bit C in the STATUS register: Loads the bit with the least significant bit (LSB) or most significant bit (MSB), respectively.

SUBWF f, d

Subtract W from f.

This is one of the most used opcodes when it comes to writing a compiler. The SUBWF command subtracts the value of register W from the value of the given address f, and depending on d (destination), if set to 1, the result is placed back into the given address; otherwise, the result stays in register W.

If the value of the given address was already 0, the subtraction would not occur, but some of the bits in the STATUS register will be affected.

SUBWF:

          ┌─ Destination (1 bit)

 0b0000 10df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

You can subtract the value of register W from the given address.

ASM
; [0x06] = [0x06] - W  
SUBWF 0x06, 1 ; Subtract the value of register W from register 0x06 and store the result back into register 0x06

; W = [0x06] - W  
SUBWF 0x06, 0 ; Subtract the value of register W from register 0x06 and store the result back into register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.
  • Bit DC in the STATUS register: If a carry from the 4th low-order bit of the result did not occur, it is set to 1; otherwise, it is set to 0.
  • Bit C in the STATUS register: It is set to 1 if a borrow did not occur; otherwise, it is set to 0.

SWAPF f, d

Swap f.

The four leftmost bits of the given address f are swapped with the four rightmost bits of the same address. If d is 0, the result is placed in the W register. If d is 1, the result is placed in the given address.

SWAP (MSB & LSB)

            ┌────────────────┐
    ────────┴────────        +
    ┌───┬───┬───┬───┬───┬───┬───┬───┐
    │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │
    └───┴───┴───┴───┴───┴───┴───┴───┘
      7   6   5   4   3   2   1   0
            +       ────────┬────────
            └───────────────┘

The structure for this instruction is as follows:

SWAPF:

          ┌─ Destination (1 bit)

 0b0011 10df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

Let’s see an example for using SWAPWF:

ASM
SWAPF 06H, 1 ; Swap the nibbles of register 0x06 and place the result back into the same register

SWAPF does not affect any of the STATUS register bits.


XORWF f, d

Exclusive OR between W and f.

This instruction is used for an Exclusive OR (XOR ^) operation between the value of register W and the given address f. If d (destination) is set to 0, the result stays in register W; otherwise, the result is placed back into the given address f.

XORWF:

          ┌─ Destination (1 bit)

 0b0001 10df ffff
   ─┬───── ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (6 bit)

You can use XORWF the same way as other destination-based instructions.

ASM
; [0x06] = W ^ [0x06]
XORWF 0x06, 1 ; Exclusive OR W with 0x06, and place the result back into register 0x06

; W = W ^ [0x06]
XORWF 0x06, 0 ; Exclusive OR W with 0x06, and place the result back into register W

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

BCF f, b

Bit Clear f.

The BCF opcode sets the given bit (b) of the given address (f) to 0. Basically, it clears the selected bit.

BCF:

         ┌─ Bit (3 bit)
        ─┴─
 0b0100 bbbf ffff
   ─┬──    ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (4 bit)

This is an example of how to use it:

ASM
BCF 0x06, 7 ; Clear bit number 7 (starting from 0) in register 0x06

BCF does not affect any of the STATUS register bits.


BSF f, b

Bit set f.

The BSF opcode sets the given bit (b) of the given address (f) to 1. Basically, it activates the selected bit.

BSF:

         ┌─ Bit (3 bit)
        ─┴─
 0b0101 bbbf ffff
   ─┬──    ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (4 bit)

This is an example of how to use it:

ASM
BSF 0x06, 7 ; Set bit number 7 (starting from 0) in register 0x06

BSF does not affect any of the STATUS register bits.


BTFSC f, b

Bit Test f, Skip if Clear.

The BTFSC opcode takes two parameters: the first is the address (f), and the second is the bit number (b). It tests if the specified bit of the address is set to 0. If so, the next instruction is skipped, and a NOP executes instead; otherwise, the next instruction is executed normally. The BTFSC opcode is a 2-cycle operation.

If bit b in register f is 0, the next instruction is skipped. The instruction fetched during the current execution is discarded, and a NOP is executed instead, making this a 2-cycle instruction.

BTFSC:

         ┌─ Bit (3 bit)
        ─┴─
 0b0110 bbbf ffff
   ─┬──    ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (4 bit)

In the first line, the 5th bit of register 0x06 is set to 0. In the second line, it checks whether bit number 5 is 0 (which it is), causing the next instruction, INCF, to be skipped (executed as a NOP). The instruction in line 4 is then executed.

ASM
BCF 0x06, 5   ; Clear bit 5 of register 0x06
BTFSC 0x06, 5 ; Test bit 5 of register 0x06, if 0 skip the next instruction  
INCF 0x06, 1  ; Increment value of 0x06 by 1 (place back into the register)  
NOP           ; No operation

BTFSC does not affect any of the STATUS register bits.


BTFSS

Bit Test f, Skip if Set.

Just like BTFSC, the BTFSS instruction takes two parameters: an address (f) and a bit number (b). It checks if the specified bit at the given address is set (is 1). If it is, the next instruction is skipped (a NOP is executed instead); otherwise, the next instruction is executed normally.

If bit b in address f is 1, the next instruction is skipped. The instruction fetched during the current execution is discarded, and a NOP is executed instead, making this a 2-cycle instruction.

BTFSS:

         ┌─ Bit (3 bit)
        ─┴─
 0b0111 bbbf ffff
   ─┬──    ──┬───
    │        └─ Address (5 bit)

    └─ Identifier (4 bit)

In the first line, the 5th bit of register 0x06 is set to 1. In the second line, it checks whether bit number 5 is 1 (which it is), causing the next instruction, DECF, to be skipped (executed as a NOP). The instruction in line 4 is then executed.

ASM
BSF 06H, 5    ; Set bit 5 of register 0x06 to 1
BTFSS 06H, 5  ; Tests bit 5 of register 0x06, if 0 skip the next instruction
DECFF 06H, 1  ; Increment value of register 0x06 by 1
NOP           ; No Operation

BTFSS does not affect any of the STATUS register bits.


ANDLW k

AND literal with W.

The value of register W is ANDed (&) with the given 8-bit literal k, and the result is placed back into the W register.

ANDLW:

             ┌─ Literal Value (8 bit)
        ─────┴───
 0b1110 kkkk kkkk
   ─┬──
    └─ Identifier (4 bit)

Here’s an example for ANDLW with a literal parameter:

ASM
ANDLW 0x0F  ; Perform AND operation between W and the literal 0x0F, store the result back in W

In this example, the value in register W is ANDed with the literal 0x0F, and the result is stored back in register W.

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

CALL k

Call subroutine.

The CALL instruction works with the STACK in the microcontroller, which we discuss in the emulator post. The CALL instruction takes an 8-bit literal k, changes the program counter to the address k, and saves the address of the next instruction after the CALL to the STACK. This opcode is useful for calling certain subroutines, and after finishing (reaching RETLW, which we will talk about), the program counter moves back to the instruction after the CALL, similar to a function call.

CALL:

             ┌─ Literal Value (8 bit)
        ─────┴───
 0b1001 kkkk kkkk
   ─┬──
    └─ Identifier (4 bit)

The program starts executing from the beginning, and in line 2, a CALL instruction is used on the label increase, which calls the increase subroutine. This causes the INCF instruction to execute. Since there is no RETLW (which we haven’t discussed yet), the CALL in line 2 functions somewhat like a GOTO (we will discuss GOTO further in the post).

ASM
start:
      CALL increase  ; Instead of a label name, an address can also be used.

skip:  ; Define label 'skip'
      NOP  ; No Operation
      NOP  ; No Operation

increase:  ; Define label 'increase'
      INCF 0x06, 0  ; Increment value of register 0x06 by 1 and store the result in register W

CALL does not affect any of the STATUS register bits.


CLRWDT

Clear Watchdog Timer.

This opcode, which takes no arguments (like NOP), is used to clear the watchdog timer.

CLRWDT:

 0b0000 0000 0100
   ─┬────────────
    └─ Identifier (12 bit)

There is no argument needed when using CLRWDT to clear the microcontroller’s watchdog timer.

ASM
CLRWDT  ; Clear Watchdog timer

Affected Bits

  • Bit TO in the STATUS register: When using CLRWDT, it sets the bit TO to 1, and if a watchdog timer time-out occurs, it sets it to 0.
  • Bit PD in the STATUS register: When using CLRWDT, it sets the bit PD to 1, or it is set to 1 after the microcontroller powers up. It is set to 0 when a SLEEP instruction is executed (which will be discussed later).

GOTO k

Unconditional Branch.

The GOTO opcode takes a 9-bit literal k and changes the microcontroller’s program counter (PC) to point to the given address k, causing the execution of the instructions at that address. This is very helpful for creating unconditional branches.

GOTO:

             ┌─ Literal Value (9 bit)
      ───────┴───
 0b101K kkkk kkkk
   ─┬──
    └─ Identifier (3 bit)

Let’s create an infinite loop using GOTO.

ASM
start:
    INCF 0x06, 1  ; Increment value of 0x06 by 1 and store the result in the same register
    GOTO start    ; Jump to 'start' and create an infinite loop

GOTO does not affect any of the STATUS register bits.


IORLW k

Inclusive OR Literal with W.

The contents of register W are OR’ed (|) with the 8-bit literal k. The result is placed in the W register.

IORLW:

             ┌─ Literal Value (8 bit)
        ─────┴───
 0b1101 kkkk kkkk
   ─┬──
    └─ Identifier (4 bit)

It is very easy to perform an inclusive OR (|) with the contents of register W and the literal k (0xFF).

ASM
IORLW 255 ; Perform inclusive OR between the contents of register W and literal 255 

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

MOVLW k

Move Literal to W.

Another useful instruction for creating a compiler is MOVLW, which takes an 8-bit literal value and places the result into the W register.

MOVLW:

             ┌─ Literal Value (8 bit)
        ─────┴───
 0b1100 kkkk kkkk
   ─┬──
    └─ Identifier (4 bit)

Let’s load register 0x06 with the value 0xFF using MOVLW.

ASM
MOVLW 0xFF   ; Load the W register with the value 0xFF
MOVWF 0x06   ; Move the value of W (0xFF) into register 0x06

MOVLW does not affect any of the STATUS register bits.


OPTION

Load OPTION register.

The content of register W is loaded into the OPTION register. The OPTION opcode does not take any arguments, just like NOP.

OPTION:

 0b0000 0000 0010
   ─┬────────────
    └─ Identifier (12 bit)

To use this opcode to load the OPTION register with the value of register W, simply use OPTION.

ASM
; OPTION = W
OPTION ; Loads the value of register W into the OPTION register

OPTION does not affect any of the STATUS register bits.


RETLW k

Return, Place Literal in W.

Another helpful opcode is RETLW. This opcode usually (but not always) works with the CALL opcode to perform function calls in Assembly. Just like CALL, RETLW interacts with the STACK. However, unlike CALL, this opcode pops one address off the stack, changes the program counter (PC) to the popped address (which is the address of the next instruction after the CALL that was pushed onto the stack), and loads the literal k into register W, similar to the MOVLW opcode. This makes RETLW very useful.

The W register is loaded with 8-bit literal k, The program counter is loaded from the top of the stack (the return address), this is a 2-cycle instruction.

RETLW:

             ┌─ Literal Value (8 bit)
        ─────┴───
 0b1000 kkkk kkkk
   ─┬──
    └─ Identifier (4 bit)

Let’s perform a function call using CALL and RETLW.

ASM
start:
    CALL myFunction    ; Call the subroutine 'myFunction'
    NOP                ; No operation (address of RETLW points to this)
    GOTO start         ; Loop back to 'start'

myFunction:
    INCF 0x06, 1       ; Increment value of register 0x06 and place the result back
    RETLW 0x00         ; Return from the function, load W with 0x00

RETLW does not affect any of the STATUS register bits.


SLEEP

Go into Standby mode.

When we use the SLEEP opcode (which takes no arguments), the CPU stops working (enters standby mode) and needs to be restarted to begin working again. When SLEEP is used, any instructions after it will not be executed.

SLEEP:

 0b0000 0000 0011
   ─┬────────────
    └─ Identifier (12 bit)

The program finishes when it reaches SLEEP.

ASM
MOVLW 128   ; Set value of register W to 128  
MOVWF 0x06  ; Move value of register W to register 0x06  
SLEEP       ; Enter sleep mode

Affected Bits

  • Bit TO in the STATUS register: When using CLRWDT, it sets the bit TO to 1, and if a watchdog timer time-out occurs, it sets it to 0.
  • Bit PD in the STATUS register: When using CLRWDT, it sets the bit PD to 1, or it is set to 1 after the microcontroller powers up. It is set to 0 when a SLEEP instruction is executed (which will be discussed later).

TRIS f

load TRIS register

The TRIS opcode takes one argument as an address (f = 0x06 or 0x07). The value of register W is loaded into the TRIS register. If a bit of the TRIS register is set to 0, it means that the corresponding pin is configured for input. Conversely, if a bit is set to 1, it indicates that the pin is configured for output.

TRIS:

               ┌─ Address (3 bit)
              ─┴─
 0b0000 0000 0fff
   ─┬─────────
    └─ Identifier (9 bit)

The first four bits are set to 0, meaning that the first four pins are configured for input, while the last four bits are set to 1, indicating that those pins are configured for output.

ASM
MOVLW 0b11110000     ; Set 0b11110000 (240 or 0xF0) to W register
TRIS 0x06            ; TRIS regiseter with address of 0x06 (GPIO)

TRIS does not affect any of the STATUS register bits.


XORLW k

Exclusive OR Literal to W

This opcode performs an Exclusive OR (XOR ^) between the value of register W and the given 8-bit literal k, and stores the result in the W register.

XORLW:

             ┌─ Literal Value (8 bit)
        ─────┴───
 0b1111 kkkk kkkk
   ─┬──
    └─ Identifier (4 bit)

You can simply use XORLW.

ASM
; W = W ^ 0xFF
XORLW 0xFF ; Exclusive OR between the value of register W and the literal (0xFF)

Affected Bits

  • Bit Z in the STATUS register: If the result is 0, it is set to 1; otherwise, Z is set to 0.

Examples

Now that we understand how the 33 opcode for this type of microcontroller works, let’s write a program using it.

Before writing the program, it’s important to know how a program starts executing. The microcontroller has a PC (Program Counter) or PCL register. The value of the PC can range from 0x00 to 0xFF (an 8-bit value), which points to the ROM address. For example, if the PC is set to 4, it means the microcontroller starts executing instructions at offset 4. After finishing executing this, it moves to the next instruction, or the PC changes because of 2-cycle instructions. Opcodes like GOTO, CALL, and RETLW can change the value of the PC (register PCL) to point to other places and start executing there. This is how a program starts working.

Let’s start by writing a simple Hello, World program.

To set an address for a label, you can use the EQU command. For example, let’s set GPIO to address 0x06.

ASM
GPIO EQU 0x06

Now, let’s print a character to the console:
The PIC10F200 microcontroller checks the first 7 bits (starting from 1) for a character value, and if bit number 8 (the last one) is set, it flushes the character to the console. Knowing this, let’s create a Hello, World program.

To print a character to the console, you need to do three things:

  1. Load the value of the character into the GPIO.
  2. Set bit 7 (the last one) of GPIO to 1.
  3. Clear the value of GPIO (to prevent printing the character again).
ASM
GPIO EQU 0x06    ; Set value of GPIO to 0x06
MOVLW 'H'        ; Move value of 'H' (0x48) to register W
MOVWF GPIO       ; Move value of register W to register GPIO (0x06)
BSF GPIO, 7      ; Set bit number 7 (last one, starting from 0) to 1
CLRF GPIO        ; Clear GPIO and set its value to 0x00

Now let’s improve this program by using CALL and RETLW to call a subroutine that is meant to flush one character at a time into the console.

ASM
GPIO EQU 0x06

main:
    MOVLW 'H'     ; Move value of 'H' (0x48) to register W
    CALL flush    ; Flush a character into the console

flush:
    MOVWF GPIO    ; Move value of register W to GPIO
    BSF GPIO, 7   ; Set 7th bit of register GPIO to 1 (flush the character)
    CLRF GPIO     ; Clear value of register GPIO and set it to 0x00
    RETLW 0       ; Return and load 0x00 to register W

In this block of code, we moved the value of 'H' into register W and called flush, which makes the character be written into the console. We can use this method to print “Hello, World”.

ASM
GPIO EQU 0x06

main:
    MOVLW 'H'   ; Move 'H' to register W
    CALL flush  ; Flush a character into the console

    MOVLW 'e'
    CALL flush

    MOVLW 'l'
    CALL flush

    MOVLW 'l'
    CALL flush

    MOVLW 'o'
    CALL flush

    MOVLW ' '
    CALL flush

    MOVLW 'W'
    CALL flush

    MOVLW 'o'
    CALL flush

    MOVLW 'r'
    CALL flush

    MOVLW 'l'
    CALL flush

    MOVLW 'd'
    CALL flush


flush:
    MOVWF GPIO    ; Move value of register W to GPIO
    BSF GPIO, 7   ; Set 7th bit of register GPIO to 1 (flush the character)
    CLRF GPIO     ; Clear value of register GPIO and set it to 0x00
    RETLW 0       ; Return and load 0x00 to register W

But this method is kind of inefficient and takes a lot of opcodes to write.
There is a better method, and we can use RETLW and the value of register PCL (PC) for it, which is much more efficient.

ASM
GPIO EQU 0x06   ; set address 0x06 for GPIO (Input/Output pin)
RAM EQU 0x0A    ; Use an address from RAM (random access memory)
PC EQU 0x02     ; Set PC to wit address register PCL

MOVWF GPIO      ; Move value of register W to register GPIO
BSF GPIO, 7     ; set 7th bit of register GPIO to 1
BCF GPIO, 7     ; clear the 7th bit of register GPIO


MOVLW 1         ; Move vlaue 0x01 to register W
INCF RAM, 1     ; Increment vlaue of RAM (store in RAM)
ADDWF RAM, 0    ; Add value of register W to RAM (store in register W)
ADDWF PC, 1     ; Add value of register W to PC (store in register PCL)

LOOP:
  NOP
  NOP
  RETLW 'H'     ; Return and Load register W with letter value of 'H'
  RETLW 'e'
  RETLW 'l'
  RETLW 'l'
  RETLW 'o'
  RETLW ','
  RETLW ' '
  RETLW 'W'
  RETLW 'o'
  RETLW 'r'
  RETLW 'l'
  RETLW 'd'
  RETLW '!'
  RETLW '\n'
  CLRF RAM     ; Clear 'RAM'
  CLRF GPIO    ; Clear register GPIO
  CLRW         ; Clear register W
  GOTO 0x00    ; Start Over again (infinite loop)

This code uses an array of characters to print Hello, World!\n. It’s interesting to know how we can implement an array with these opcodes. By changing the value of register PCL, we can cause the program counter (PC) of the microcontroller to point to other instructions. That’s what we are doing: using a sequence of RETLW with a value (letter) for each, and by using INCF and a temporary address (RAM), we can increment the value of the PC to point to one of these letters each time. Since we didn’t push anything to the microcontroller’s stack, RETLW has a value of 0, which causes the PC to start from the beginning (the place where we push one character at a time). Finally, when the PC points to the instruction after the last RETLW, we clear all the values and start over the loop. This creates an infinite loop that prints Hello, World!\n in the console.