Below is a piece of assembly language for the MicroChip PIC processor. This particular program will flash some LED's and activate some relays based on the status of some control-inputs. The target MCU was the PIC 16C54, one of the most simple chips in that range.
To give some indication of what we're upto:
RAM 25 bytes
ROM 512 words (of 12 bits each)
I/O 12 bits
Clockspeed 8 kHz (this project, max = 4 MHz)
Instructions 33
On-Chip-Stack 2 levels
Compare this to a modern PC clone....
RISC and Harvard architecture.
The PIC line of MCU's are RISC chips, so they use the Harvard architecture,
and one of the results is that they have different code- and data-memories.
Higher PIC's have more features, like INTerrupt sources on 4 or more pins,
internal interrupts etcetera. All models have a watchdogtimer (WDT) which
needs to be reset regularly (if enabled) else the MCU will reset itself.
The PIC registers.
The register architecture of the PIC is somewhat odd to Intel programmer's but
programming resembles that of the Hewlett Packard HP 11 range of calculators.
Here is an overview of the registerset. Microchip refers to this as the
"register file".
file address name comment
------------ -------------- --------------------
00 indirect calls not a real register!
01 RTCC timer counter
02 PC (or IP) lower 8 bits of it
03 STATUS flags register
04 FSR bank select of PIC 16C57
05 Port A has 4 I/O lines
06 Port B has 8 I/O lines
07 Port C 8 I/O, only 16C55 and 16C57
GP register on 'C54 and 'C56
08 GP register General purpose register
.. .. ..
1F GP register General purpose register
Besides these "transparant registers" there are also some hidden registers
(which also are write only...) for processor control. These are:
TRISA The "tristate A/B/C" registers determine the status
TRISB of each pin of the I/O ports.
TRISC A "1" makes it "input" and a "0" makes it an output.
OPTION is for controlling the WDT and the RTCC
And there's the ubiquitous "W" register. This is the "Working register" and is
used to haul data back and forth. PIC registers (or "files") cannot process
constants (or "literals"). This can only be done with the W-file. It takes
some getting used to, but the concept is simple and straightforward and
eventually you will get used to it and learn to appreciate it.
From that moment on, you will only have to get used to the fact that data is
nbot always ending up where you would like to have it. All instructions
between W and F (any register or file) end with a "d" option. If "d" is a "1",
the destination is the file F, if "d" is "0", the result will be stored in the
W file...
This took me some time to get used to and still is the main source of errors.
Apart from having selected the wrong osciallator and not disabling the WDT....
The PIC instructions.
The instructions for the PIC 16C54 are as follows:
mnemonic description
ADDWF F, d d := W + F
ANDLW k W := W AND k
ANDWF F, d d := W AND F
BCF F, b bit b in F is cleared (i.e. made zero)
BSF F, b bit b in F is set (i.e. made one)
BTFSC F, b if bit b in F is CLEAR, skip next instruction
BTFSS F, b if bit b in F is SET, skip next instruction
CALL k push PC, PC := k
CLRF F Clear file F
CLRW Clear file W
CLRWDT Clear Watchdogtimer
COMF F F := NOT F (1's complement)
DECF F, d d := F - 1
DECFSZ F, d d := F - 1; If 0 => skip next instruction
GOTO k PC = k
INCF F, d d := F + 1
INCFSZ F, d d := F + 1; If 0 => skip next instruction
IORLW k W := W OR k
IORWF F, d d := W OR F
MOVF F, d d := F (zero flag affected)
MOVLW k W := k
MOVWF F F := W
NOP No operation
OPTION OPTION := W
RETLW k W := k, pop PC
RLF F, d d := rotate left through carry (F)
RRF F, d d := rotate right through carry (F)
SLEEP enter powerdown mode
SUBWF F, d d := F - W (2's complement)
SWAPF F, d d := swap-nibbles (F)
TRIS F TRIState information for I/O pins
XORLW k W := W XOR k
XORWF F, d d := W XOR F
Especially the "F, d" construct takes some getting used to.
Below is the source for the "LEGO controller":
title "LEGO 003"
subtitl "control LEGO technic devices"
LIST P=16C54, R=HEX, F=INHX8M, C=120, E=0, N=80
PIC54 equ 1FFH ; Define Reset Vectors
RTCC equ 1h ; define register designators
PC equ 2h ; the program counter is a register as well
STATUS equ 3h ; F3 Reg is STATUS Reg.
PORT_A equ 5h
PORT_B equ 6h ; I/O Port Assignments
RTCC_tc equ 0Dh ; time constant for RTCC
count_1 equ 0Eh ; delay counters and GP registers
count_2 equ 0Fh
file equ 1
w equ 0
flag_0 equ 0 ; input bits in RA port
flag_1 equ 1
flag_2 equ 2
flag_3 equ 3
LED_0 equ 0 ; status led 1, in RB Port
LED_1 equ 1 ; status led 2
RL_1 equ 2 ; relays 1 - 3
RL_2 equ 3
RL_3 equ 4
s_clk equ 5 ; s_clk input
s_data equ 6 ; s_data input
go equ 7
delay movlw .100 ; mov W with 100 decimal
movwf count_1 ; xfer W to register
dela_1 clrf count_2 ; count_2 = 0
dela_2 decfsz count_2, file ; count_2 = count_2 - 1
goto dela_2 ; skip this instruction if count_2 = 0, ...
decfsz count_1, file ; ... ending here: count_1 = count_1 - 1
goto dela_1 ; skip this instruction when count_1 = 0
retlw 0 ; ending here, if so.
flash bcf PORT_B, LED_1 ; flash LED's 0 and 1 as an acknowledgement
bsf PORT_B, LED_0 ; activate the LED's.
call delay ; wait a while
bcf PORT_B, LED_0 ; toggle the LED's
bsf PORT_B, LED_1
call delay ; wait a second!
bcf PORT_B, LED_1 ; turn LED_1 off as well.
retlw 0 ; return to caller with W = 0
RT_chk clrwdt ; clear the watchdog timer
btfsc RTCC, 7 ; RELAY_3 follows bit7 of RTCC
bcf PORT_B, RL_3
btfss RTCC, 7
bsf PORT_B, RL_3
movf RTCC, w
skpz ; internal macro for BTFSS STATUS, 2
retlw 0
movf RTCC_tc, w ; if
movwf RTCC
retlw 0
start clrf RTCC
clrf RTCC_tc ; clear RTCC and RTCC time constant
movlw B'00001111'
tris PORT_A ; define port A as inputs
movlw B'11100000'
tris PORT_B ; define port B as I/O
movlw B'00110111'
option ; define state of WDT, RTCC and prescaler
movlw B'00011100'
movwf PORT_B ; initialize port B
call flash ; signal READY
call flash
btfss PORT_B, s_clk ; if s_clkline low, check for mode 2 request
goto m_chk
repeat clrwdt ; clear watchdog timer
call flash
movf PORT_A, w ; read port A into W
andlw 3 ; mask off sensor inputs
skpnz ; skip next instruction if NonZero
goto set_tc ; flag_0 and 1 zero => define RTCC time constant
btfsc PORTA, flag_0
goto t_left
btfsc PORT_A, flag_1
goto t_right
movf PORT_B, w
andlw s_clk + s_data + go
skpnz ; if no RESET condition, skip
goto start
call RT_chk
goto repeat
t_left btfsc PORT_A, flag_2 ; if in end position, do not turn at all
goto l_exit
bcf PORT_B, RL_1 ; else set direction for Turn Left
bsf PORT_B, RL_2
bsf PORT_B, LED_0 ; show direction with LED's
bcf PORT_B, LED_1
chk_fl2 btfsc PORT_A, flag_2 ; wait until home-position is reached
goto l_exit ; if so, get out
call RT_chk ; if not, check again
goto chk_fl2 ; until done
l_exit bsf PORT_B, RL_1 ; release relay 1
bcf PORT_B, LED_0 ; extinguish light 0
goto repeat ; jump back
t_right btfsc PORT_A, flag_3 ; if in end position, do not turn at all
goto r_exit
bcf PORT_B, RL_2 ; else set direction for Turn Right
bsf PORT_B, RL_1
bsf PORT_B, LED_1 ; show direction with LED's
bcf PORT_B, LED_0
chk_fl3 btfsc PORT_A, flag_3 ; wait until home position reached
goto r_exit
call RT_chk
goto chk_fl3
r_exit bsf PORT_B, RL_2 ; deactivate lights and relays
bcf PORT_B, LED_1
goto repeat
m_chk clrf count_1 ; check inputs and make sure there's no glitch
clrf count_2
m_chk_1 btfss PORT_B, s_clk
decf count_1, file ; count pulses s_clkline = low
decfsz count_2, file
goto m_chk_1
movf count_1, w ; w = low-pulses
subwf count_2, w ; if count_1 <> count_2, glitch occurred
skpz
goto start
set_tc movf RTCC, w ; move current value of RTCC
movwf RTCC_tc ; to time constant register
goto repeat
org PIC54 ; goto highest word in code space
goto start ; and place the reset vector.
end
If you ever programmed an HP 11 (or 12, 15 or 16) calculator, the conditional
jumps may ring a bell. I don't know how the HP machines handle these jumps,
but the PIC line does the following:
condition action by PIC
--------- -----------------------------------
FALSE execute next instruction
TRUE replace next instruction with a NOP
This enables the programmer to make 100% accurate timingloops since there is
no difference between a FALSE and a TRUE condition.
The size of this piece of code is easy to calculate: each line with an
mnemonic is one instructionword. This makes 115 words from the 512 word
program memoryspace, so we have nearly 400 instructionwords wasted.
The PIC's are marvelous chips to bridge the gap between lots and lots of TTL
chips and the overkill of a microcontroller unit with separate RAM, ROM and
I/O. If you want to find out more of this kind of CPU's, visit the website at
{http://www.microchip.com}
for PDF datasheets and more. Scenix also has a range of clones out, right now.
They are software compatible but offer more hardware features. Which is not
difficult since the codeword in the design of the PIC's seemed to have been
KISS.
|