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:
; [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 theSTATUS
register: If the result is 0, it is set to 1; otherwise,Z
is set to 0. - Bit
DC
in theSTATUS
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 theSTATUS
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
:
; [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 theSTATUS
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
:
; [0x06] = 0x00
CLRF 0x06 ; Clear the value of register 0x06
Affected Bits
- Bit
Z
in theSTATUS
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:
; W = 0
CLRW
Affected Bits
- Bit
Z
in theSTATUS
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
:
; [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 theSTATUS
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:
[0x06] = [0x06] - 1;
DECF 0x06, 1 ; Decrement the value of register 0x06 and store the result in register 0x06
Affected Bits
- Bit
Z
in theSTATUS
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:
; [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:
; [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 theSTATUS
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
:
; [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:
; 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 theSTATUS
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:
; W = [0x06]
MOVF 0x06, 0 ; Move the value of register 0x06 to register W
Affected Bits
- Bit
Z
in theSTATUS
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
:
; [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:
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.
; [0x06] = [0x06] << 1
RLF 0x06, 1 ; Rotate the contents of register 0x06 one bit to the left
Affected Bits
- Bit
C
in theSTATUS
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.
; [0x06] = [0x06] >> 1
RRF 0x06, 1 ; Rotate the contents of register 0x06 one bit to the right
Affected Bits
- Bit
C
in theSTATUS
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.
; [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 theSTATUS
register: If the result is 0, it is set to 1; otherwise,Z
is set to 0. - Bit
DC
in theSTATUS
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 theSTATUS
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
:
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.
; [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 theSTATUS
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:
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:
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.
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.
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:
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 theSTATUS
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).
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.
CLRWDT ; Clear Watchdog timer
Affected Bits
- Bit
TO
in theSTATUS
register: When usingCLRWDT
, it sets the bitTO
to 1, and if a watchdog timer time-out occurs, it sets it to 0. - Bit
PD
in theSTATUS
register: When usingCLRWDT
, it sets the bitPD
to 1, or it is set to 1 after the microcontroller powers up. It is set to 0 when aSLEEP
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
.
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).
IORLW 255 ; Perform inclusive OR between the contents of register W and literal 255
Affected Bits
- Bit
Z
in theSTATUS
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
.
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
.
; 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
.
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
.
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 theSTATUS
register: When usingCLRWDT
, it sets the bitTO
to 1, and if a watchdog timer time-out occurs, it sets it to 0. - Bit
PD
in theSTATUS
register: When usingCLRWDT
, it sets the bitPD
to 1, or it is set to 1 after the microcontroller powers up. It is set to 0 when aSLEEP
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.
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
.
; W = W ^ 0xFF
XORLW 0xFF ; Exclusive OR between the value of register W and the literal (0xFF)
Affected Bits
- Bit
Z
in theSTATUS
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
.
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:
- Load the value of the character into the GPIO.
- Set bit 7 (the last one) of GPIO to 1.
- Clear the value of GPIO (to prevent printing the character again).
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.
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”.
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.
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.