pic tutorial 8-9-10-11

100
PIC TUTORIAL 8 - USING THE PWM HARDWARE This tutorial is a little different, it's based on the 16F876 processor 2 board, but is designed specifically as a basic framework for a small differential drive robot, this means simply that it has two motors, one for left and one for right, with steering controlled in the same way as a tank. There seems a lot of confusion about how to use the hardware PWM, hence this simple tutorial. I've used the 16F876 as it has two PWM channels, the 16F628 only has one, the principals are exactly the same though, and the code could very simply be moved to the 16F628 if you only wanted a single PWM channel. The PWM hardware has up to 10 bit resolution, which means you can have 1024 different steps from zero to full power, for our purposes this is a little excessive, and I've decided on 128 steps forward (0-127), and 128 steps in reverse (128-255), using a single byte for the speed and direction, with the highest bit signifying reverse. It's actually very easy to use, once you've set the PWM up all you need to do is write to the CCPR1L and CCPR2L registers to set the speed, the listed routine uses an initialise subroutine which sets everything up, then subroutines to set the left and right motor speeds. The initialise subroutine sets various registers, they are commented in the code, but I'll explain them here as well: First we turn off the analogue to digital converters for PortA, they default to analogue, so it's good practice to set them as digital I/O if they are not being used, if we need them later we can turn them back on (or simply remove the code which turns them off). Secondly we set all the pins of PortC as outputs, we'll be using six of the pins, pins 1 and 2 are the PWM outputs, and pins 0, 3, 4 and 5 will be used for direction switching. Next we set the CCP1CON and CCP2CON registers to operate as PWM, CCP1 and CCP2 can operate in various modes, so we need to specifically set them as PWM. Then we set the PR2 register, this is a step which often causes confusion, it basically sets the value of a comparison register which the actual PWM value will be compared against, in this case we set it to 126 which means the highest PWM value will be 126, if the PWM is 127 the comparator will never reach that value and the output will stay permanently high - just as we need for full power!. If the PWM value is zero, the comparator will always equal that value as it starts, so the output will remain permanently low - again, just as we need for zero power. The next step is to set T2CON, this sets the frequency of the PWM, as it's derived from the 20MHz system clock it runs too at too high a frequency, there are two possibilities here - setting the prescaler divides the frequency before the PWM section, and the postscaler afterwards. For this example we set the prescaler to divide by 16, this gives us a PWM frequency of 2500Hz. The next two lines set both PWM channels to zero, so both motors are off when it starts up. The last line actually starts the PWM system by turning Timer2 on, once this line runs the PWM is independent of the rest of the code, we can do pretty well whatever we like (unless we alter the register settings) and the PWM will carry on running regardless.

Upload: phuc-vu-viet-phuc

Post on 13-Apr-2015

65 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: PIC Tutorial 8-9-10-11

PIC TUTORIAL 8 - USING THE PWM HARDWARE

This tutorial is a little different, it's based on the 16F876 processor 2 board, but isdesigned specifically as a basic framework for a small differential drive robot, this meanssimply that it has two motors, one for left and one for right, with steering controlled inthe same way as a tank. There seems a lot of confusion about how to use the hardwarePWM, hence this simple tutorial. I've used the 16F876 as it has two PWM channels, the16F628 only has one, the principals are exactly the same though, and the code could verysimply be moved to the 16F628 if you only wanted a single PWM channel.

The PWM hardware has up to 10 bit resolution, which means you can have 1024different steps from zero to full power, for our purposes this is a little excessive, and I'vedecided on 128 steps forward (0-127), and 128 steps in reverse (128-255), using a singlebyte for the speed and direction, with the highest bit signifying reverse.

It's actually very easy to use, once you've set the PWM up all you need to do is writeto the CCPR1L and CCPR2L registers to set the speed, the listed routine uses an initialisesubroutine which sets everything up, then subroutines to set the left and right motorspeeds.

The initialise subroutine sets various registers, they are commented in the code, but I'llexplain them here as well:First we turn off the analogue to digital converters for PortA, they default to analogue, soit's good practice to set them as digital I/O if they are not being used, if we need themlater we can turn them back on (or simply remove the code which turns them off).Secondly we set all the pins of PortC as outputs, we'll be using six of the pins, pins 1 and2 are the PWM outputs, and pins 0, 3, 4 and 5 will be used for direction switching.Next we set the CCP1CON and CCP2CON registers to operate as PWM, CCP1 andCCP2 can operate in various modes, so we need to specifically set them as PWM.Then we set the PR2 register, this is a step which often causes confusion, it basically setsthe value of a comparison register which the actual PWM value will be compared against,in this case we set it to 126 which means the highest PWM value will be 126, if the PWMis 127 the comparator will never reach that value and the output will stay permanentlyhigh - just as we need for full power!. If the PWM value is zero, the comparator willalways equal that value as it starts, so the output will remain permanently low - again,just as we need for zero power.The next step is to set T2CON, this sets the frequency of the PWM, as it's derived fromthe 20MHz system clock it runs too at too high a frequency, there are two possibilitieshere - setting the prescaler divides the frequency before the PWM section, and thepostscaler afterwards. For this example we set the prescaler to divide by 16, this gives usa PWM frequency of 2500Hz.The next two lines set both PWM channels to zero, so both motors are off when it startsup.The last line actually starts the PWM system by turning Timer2 on, once this line runs thePWM is independent of the rest of the code, we can do pretty well whatever we like(unless we alter the register settings) and the PWM will carry on running regardless.

Page 2: PIC Tutorial 8-9-10-11

The main program itself is just a demonstration of how to use the PWM subroutines, itsimply sets four different PWM and direction settings with 5 second delays in betweenthem. It should be pretty self evident how to use it from your own programming. I'veincluded various delay routines, including a new one called 'Delay100W', this delays100mS multiplied by the value in W when the routine is called - in this example we loadW with 50 to give a 5 second delay.; 16F876 PWM example code;; Device 16F876

LIST P=16F876, W=2, X=ON, R=DEC#INCLUDE P16F876.INC__CONFIG 0x393A

cblock 0x20 ;start of general purpose registerscount ;used in delay routinecount1 ;used in delay routinecounta ;used in delay routinecountb ;used in delay routinetemp ;temp storage

endc

RL Equ 0x00 ;pin for left motor reverseFL Equ 0x03 ;pin for left motor forwardRR Equ 0x04 ;pin for right motor reverseFR Equ 0x05 ;pin for right motor forward

;pins 1 and 2 are the 2 PWM channels

ORG 0x0000NOP ;for bootloader compatibilityNOPNOPGOTO STARTORG 0x0010

START CALL Initialise

MainLoop:MOVLW d'64'CALL SpeedL ;both half speed forwardsCALL SpeedRCALL Long_Delay

Page 3: PIC Tutorial 8-9-10-11

MOVLW d'64'CALL SpeedL ;left half speed forwardsMOVLW d'192'CALL SpeedR ;right half speed reverseCALL Long_Delay

MOVLW d'10'CALL SpeedL ;slow speed forwardsMOVLW d'228'CALL SpeedR ;fast speed reverseCALL Long_Delay

MOVLW d'228'CALL SpeedL ;fast speed reverseMOVLW d'10'CALL SpeedR ;slow speed forwardsCALL Long_Delay

GOTO MainLoop

Initialise:BANKSEL ADCON1 ;turn off A2DMOVLW 0x06MOVWF ADCON1BANKSEL PORTABANKSEL TRISCMOVLW 0 ;set PORTC as all outputsMOVWF TRISCBANKSEL PORTC

MOVF CCP1CON,W ;set CCP1 as PWMANDLW 0xF0IORLW 0x0CMOVWF CCP1CON

MOVF CCP2CON,W ;set CCP2 as PWMANDLW 0xF0IORLW 0x0CMOVWF CCP2CON

MOVLW 126 ;set highest PWM valueBANKSEL PR2 ;over this (127) is permanently onMOVWF PR2BANKSEL TMR2

Page 4: PIC Tutorial 8-9-10-11

MOVF T2CON,W ;set prescaler to 16ANDLW 0xF8 ;PWM at 2500HZIORLW 0x02MOVWF T2CON

MOVF T2CON,W ;set postscaler to 1ANDLW 0x07IORLW 0x00MOVWF T2CON

CLRF CCPR1L ;set PWM to zeroCLRF CCPR2L

BSF T2CON, TMR2ON ;and start the timer runningRETURN

SpeedL: ;use value in W to set speed (0-127)MOVWF tempBTFSC temp, 7 ;if more than 128 set speed in reverseCALL ReverseL ;so '1' is very slow forwardBTFSS temp, 7 ;and '129' is very slow reverseCALL ForwardLANDLW 0x7FMOVWF CCPR1LRETURN

SpeedR:MOVWF tempBTFSC temp, 7CALL ReverseRBTFSS temp, 7CALL ForwardRANDLW 0x7FMOVWF CCPR2LRETURN

ReverseL:BSF PORTC, RL ;set pins for reverseBCF PORTC, FLRETURN

ReverseR:BSF PORTC, RRBCF PORTC, FRRETURN

Page 5: PIC Tutorial 8-9-10-11

ForwardL:BCF PORTC, RL ;set pins for forwardBSF PORTC, FLRETURN

ForwardR:BCF PORTC, RRBSF PORTC, FRRETURN

;Delay routines

Long_Delaymovlw d'50' ;delay 5 secondscall Delay100Wreturn

Delay100W movwf count ;delay W x 100mSd2 call Delay100 ;maximum delay 25.5 seconds

decfsz count ,fgoto d2return

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay10 movlw d'10' ;delay 10mSgoto d0

Delay1 movlw d'1' ;delay 1mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)d0 movwf count1d1 movlw 0xE7

movwf countamovlw 0x04movwf countb

Delay_0 decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

Page 6: PIC Tutorial 8-9-10-11

decfsz count1 ,fgoto d1return

;end of Delay routines

END

Page 7: PIC Tutorial 8-9-10-11

PIC TUTORIAL 9 - HEX KEYPAD

For these tutorials you require the Main Board, Keypad Board, LCD Board, IR Board andRS232 Board. Download zipped tutorial files.

These tutorials demonstrate how to read a HEX keypad, these are a standard device with16 keys connected in a 4x4 matrix, giving the characters 0-9 and A-F. You can also use a4x3 keypad, which gives the numbers 0-9, * and #.

This is how the HEX keypad is connected, each square with anumber or letter in it is a push to make switch, which connects thehorizontal wires (rows) with the vertical wires (columns). So ifyou press button 'A' it will connect COL1 with ROW4, orpressing button '6' will connect COL3 with ROW2. For a numeric(4x3) keypad COL4 will be missing, and 'A' and 'B' replaced with'*' and '#' but is otherwise the same. The sample programs use alookup table for the keys, this would need to be changed to insertthe correct values for the non-numeric characters.

As the switches are all interconnected, we need a way to differentiate between thedifferent ones - the four resistors on the interface board pull lines COL1 to COL4 high, thesefour lines are the ones which are read in the program. So in the absence of any switch beenpressed these lines will all read high. The four ROW connections are connected to outputpins, and if these are set high the switches will effectively do nothing - connecting a highlevel to a high level, results in a high level.

In order to detect a switch we need to take the ROW lines low, so if we take all the ROWlines low - what happens?. Assuming we press button 1, this joins COL1 with ROW1, asROW1 is now at a low level, this will pull COL1 down resulting in a low reading on COL1.Unfortunately if we press button 4, this joins COL1 with ROW2, as ROW2 is at a low levelthis also results in a low reading at COL1. This would only give us four possible choices,where each four buttons in a COL do exactly the same (e.g. 1, 4, 7, and A are the same).

The way round this is to only switch one ROW at a time low, so assuming we set ROW1low we can then read just the top row of buttons, button 1 will take COL1 low, button2 willtake COL2 low, and the same for buttons '3' and 'F' in COL3 and COL4. The twelve lowerbuttons won't have any effect as their respective ROW lines are still high. So to read theother buttons we need to take their respective ROW lines low, taking ROW2 low will allowus to read the second row of buttons (4, 5, 6, and E), again as the other three ROW lines arenow high the other 12 buttons have no effect. We can then repeat this for the last two ROW's

Page 8: PIC Tutorial 8-9-10-11

using ROW3 and ROW4, so we read four buttons at a time, taking a total of four readings toread the entire keypad - this is a common technique for reading keyboards, and is called'Keyboard Scanning'.

One obvious problem is what happens if you press more than one key at a time?, there area number of ways to deal with this, one way would be to check for multiple key presses andignore them, a simpler way (and that used in the examples) is to accept the first key you findwhich is pressed. You will find that various commercial products deal with this situation insimilar ways, some reject multiple key presses, and some just accept the first one.

As with previous tutorials, the idea is to give a tested working routine which can be usedelsewhere, the subroutine to be called is Chk_Keys, the first thing this does is check to see ifany of the 12 keys are pressed (by making all the ROW lines low) and waiting until no keysare pressed - this avoids problems with key presses repeating. Once no keys are pressed itjumps to the routine Keys which repeatedly scans the keyboard until a key is pressed, a callto one of my standard delay routines (call Delay20) provides a suitable de-bouncing delay.Once a key has been pressed the result is returned in the variable 'key', this is then logicallyANDed with 0x0F to make absolutely sure it's between 0 and 15. Next this value is passed toa look-up table which translates the key to it's required value (in this case the ASCII value ofthe labels on the keys). Finally the ASCII value is stored back in the 'key' variable (just incase you might need it storing) and the routine returns with the ASCII value in the Wregister.;Keypad subroutine

Chk_Keys movlw 0x00 ;wait until no key pressedmovwf KEY_PORT ;set all output pins lowmovf KEY_PORT, Wandlw 0x0F ;mask off high bytesublw 0x0Fbtfsc STATUS, Z ;test if any key pressedgoto Keys ;if none, read keyscall Delay20goto Chk_Keys ;else try again

Keys call Scan_Keysmovlw 0x10 ;check for no key pressedsubwf key, wbtfss STATUS, Zgoto Key_Found

call Delay20goto Keys

Key_Found movf key, wandlw 0x0fcall Key_Table ;lookup key in tablemovwf key ;save back in keyreturn ;key pressed now in W

Scan_Keys clrf key

Page 9: PIC Tutorial 8-9-10-11

movlw 0xF0 ;set all output lines highmovwf KEY_PORTmovlw 0x04movwf rows ;set number of rowsbcf STATUS, C ;put a 0 into carry

Scan rrf KEY_PORT, fbsf STATUS, C ;follow the zero with ones

;comment out next two lines for 4x3 numeric keypad.btfss KEY_PORT, Col4goto Pressincf key, fbtfss KEY_PORT, Col3goto Pressincf key, fbtfss KEY_PORT, Col2goto Pressincf key, fbtfss KEY_PORT, Col1goto Pressincf key, fdecfsz rows, fgoto Scan

Press return

For the full code, with the equates, variable declarations and look-up table, consult thecode for the examples below.Tutorial 9.1

Tutorial 9.1 simply reads the keyboard and displays the value of the key pressed on theLCD module, it shows this as both HEX and ASCII, so if you press key '0' it will display '300'.;Tutorial 9.1 - Nigel Goodwin 2003;Keypad reading with LCD display

LIST p=16F628 ;tell assembler what chip we are usinginclude "P16F628.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__config 0x3D18 ;sets the configuration settings (oscillator

type etc.)cblock 0x20 ;start of general purpose registers

count ;used in looping routinescount1 ;used in delay routinecounta ;used in delay routinecountb ;used in delay routinetmp1 ;temporary storagetmp2templcd ;temp store for 4 bit mode

Page 10: PIC Tutorial 8-9-10-11

templcd2

NumL ;Binary inputs for decimal convert routineNumH

TenK ;Decimal outputs from convert routineThouHundTensOnes

keyrowsindex

endc

LCD_PORT Equ PORTALCD_TRIS Equ TRISALCD_RS Equ 0x04 ;LCD handshake linesLCD_RW Equ 0x06LCD_E Equ 0x07KEY_PORT Equ PORTB ;keypad portKEY_TRIS Equ TRISBCol1 Equ 0 ;pins used for keypad inputsCol2 Equ 1Col3 Equ 2Col4 Equ 3

org 0x0000goto Start

Key_Table ADDWF PCL , fRETLW 0x43RETLW 0x42RETLW 0x30RETLW 0x41RETLW 0x44RETLW 0x39RETLW 0x38RETLW 0x37RETLW 0x45RETLW 0x36RETLW 0x35RETLW 0x34RETLW 0x46RETLW 0x33RETLW 0x32

Page 11: PIC Tutorial 8-9-10-11

RETLW 0x31

HEX_Table ADDWF PCL , fRETLW 0x30RETLW 0x31RETLW 0x32RETLW 0x33RETLW 0x34RETLW 0x35RETLW 0x36RETLW 0x37RETLW 0x38RETLW 0x39RETLW 0x41RETLW 0x42RETLW 0x43RETLW 0x44RETLW 0x45RETLW 0x46

Text addwf PCL, fretlw 'K'retlw 'e'retlw 'y'retlw 'p'retlw 'a'retlw 'd'retlw ' 'retlw 'T'retlw 'e'retlw 's'retlw 't'retlw '.'retlw 0x00

Start movlw 0x07movwf CMCON ;turn comparators off (make it like

a 16F84)

Initialise clrf countclrf PORTAclrf PORTBclrf NumLclrf NumH

Page 12: PIC Tutorial 8-9-10-11

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all pins outputsmovwf LCD_TRISmovlw 0x0F ;set keypad pinsmovwf KEY_TRIS ;half in, half outmovwf TRISBbcf STATUS, RP0 ;select bank 0

call LCD_Init ;setup LCD

clrf count ;set counter register to zeroMessage movf count, w ;put counter value in W

call Text ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zgoto Maincall LCD_Charincf count, fgoto Message

Main movlw 0x0Fmovwf KEY_PORT ;set all output pins low

Loop movlw d'1'call LCD_Line2W ;move to 2nd row, 2nd columncall Chk_Keys ;check keysmovlw 'H'call LCD_Charmovlw 'e'call LCD_Charmovlw 'x'call LCD_Charmovlw ' 'call LCD_Charmovf key, w ;restore key pressedcall LCD_HEX ;display in hexmovlw ' 'call LCD_Charmovlw 'A'call LCD_Charmovlw 'S'call LCD_Char

Page 13: PIC Tutorial 8-9-10-11

movlw 'C'call LCD_Charmovlw 'I'call LCD_Charmovlw 'I'call LCD_Charmovlw ' 'call LCD_Charmovf key, w ;restore key pressedcall LCD_Char ;display as ASCIIgoto Loop

;Keypad subroutine

Chk_Keys movlw 0x00 ;wait until no key pressedmovwf KEY_PORT ;set all output pins lowmovf KEY_PORT, Wandlw 0x0F ;mask off high bytesublw 0x0Fbtfsc STATUS, Z ;test if any key pressedgoto Keys ;if none, read keyscall Delay20goto Chk_Keys ;else try again

Keys call Scan_Keysmovlw 0x10 ;check for no key pressedsubwf key, wbtfss STATUS, Zgoto Key_Found

call Delay20goto Keys

Key_Found movf key, wandlw 0x0fcall Key_Table ;lookup key in tablemovwf key ;save back in keyreturn ;key pressed now in W

Scan_Keys clrf keymovlw 0xF0 ;set all output lines high

movwf KEY_PORTmovlw 0x04movwf rows ;set number of rowsbcf STATUS, C ;put a 0 into carry

Scan rrf KEY_PORT, fbsf STATUS, C ;follow the zero with ones

;comment out next two lines for 4x3 numeric keypad.

Page 14: PIC Tutorial 8-9-10-11

btfss KEY_PORT, Col4goto Pressincf key, fbtfss KEY_PORT, Col3goto Pressincf key, fbtfss KEY_PORT, Col2goto Pressincf key, fbtfss KEY_PORT, Col1goto Pressincf key, fdecfsz rows, fgoto Scan

Press return

;end of keypad subroutines.

;LCD routines

;Initialise LCDLCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 ;Set 4 bit modecall LCD_Cmd

movlw 0x28 ;Set display shiftcall LCD_Cmd

movlw 0x06 ;Set display character modecall LCD_Cmd

movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmd ;Set cursor off

call LCD_Clr ;clear display

retlw 0x00

; command set routineLCD_Cmd movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORT

Page 15: PIC Tutorial 8-9-10-11

bcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCIILCD_Char movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_Line1 movlw 0x80 ;move to 1st row, first columncall LCD_Cmdretlw 0x00

LCD_Line2 movlw 0xc0 ;move to 2nd row, first columncall LCD_Cmdretlw 0x00

LCD_Line1W addlw 0x80 ;move to 1st row, column Wcall LCD_Cmdretlw 0x00

LCD_Line2W addlw 0xc0 ;move to 2nd row, column Wcall LCD_Cmdretlw 0x00

LCD_CurOn movlw 0x0d ;Set display on/off and cursorcommand

call LCD_Cmd

Page 16: PIC Tutorial 8-9-10-11

retlw 0x00

LCD_CurOff movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_Clr movlw 0x01 ;Clear displaycall LCD_Cmdretlw 0x00

LCD_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charmovf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charretlw 0x00

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)d0 movwf count1d1 movlw 0xC7 ;delay 1mS

movwf countamovlw 0x01movwf countb

Delay_0decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

Page 17: PIC Tutorial 8-9-10-11

Pulse_e bsf LCD_PORT, LCD_Enopbcf LCD_PORT, LCD_Eretlw 0x00

LCD_Busybsf STATUS, RP0 ;set bank 1movlw 0x0f ;set Port for inputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0bcf LCD_PORT, LCD_RS ;set LCD for command modebsf LCD_PORT, LCD_RW ;setup to read busy flagbsf LCD_PORT, LCD_Eswapf LCD_PORT, w ;read upper nibble (busy flag)bcf LCD_PORT, LCD_Emovwf templcd2bsf LCD_PORT, LCD_E ;dummy read of lower

nibblebcf LCD_PORT, LCD_Ebtfsc templcd2, 7 ;check busy flag, high = busygoto LCD_Busy ;if busy check againbcf LCD_PORT, LCD_RWbsf STATUS, RP0 ;set bank 1movlw 0x00 ;set Port for outputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0return

;end of LCD routines

;This routine downloaded from http://www.piclist.comConvert: ; Takes number in NumH:NumL

; Returns decimal in; TenK:Thou:Hund:Tens:Ones

swapf NumH, wiorlw B'11110000'

movwf Thouaddwf Thou,faddlw 0XE2movwf Hundaddlw 0X32movwf Ones

movf NumH,w

Page 18: PIC Tutorial 8-9-10-11

andlw 0X0Faddwf Hund,faddwf Hund,faddwf Ones,faddlw 0XE9movwf Tensaddwf Tens,faddwf Tens,f

swapf NumL,wandlw 0X0Faddwf Tens,faddwf Ones,f

rlf Tens,frlf Ones,fcomf Ones,frlf Ones,f

movf NumL,wandlw 0X0Faddwf Ones,frlf Thou,f

movlw 0X07movwf TenK

; At this point, the original number is; equal to; TenK*10000+Thou*1000+Hund*100+Tens*10+Ones; if those entities are regarded as two's; complement binary. To be precise, all of; them are negative except TenK. Now the number; needs to be normalized, but this can all be; done with simple byte arithmetic.

movlw 0X0A ; TenLb1:

addwf Ones,fdecf Tens,fbtfss 3,0goto Lb1

Lb2:addwf Tens,fdecf Hund,fbtfss 3,0

Page 19: PIC Tutorial 8-9-10-11

goto Lb2Lb3:

addwf Hund,fdecf Thou,fbtfss 3,0goto Lb3

Lb4:addwf Thou,fdecf TenK,fbtfss 3,0goto Lb4

retlw 0x00

endTutorial 9.2

This tutorial implements a simple code lock, you enter a 4 digit code and it responds onthe LCD with either 'Correct Code' or 'Wrong Code', it uses all sixteen available I/O lines ona 16F628 which leaves no spare I/O line for opening an electrical lock. However, using a4x3 keypad would free one I/O line, or a 16F876 could be used giving plenty of free I/O.I've set the code length at 4 digits, it could very easily be altered from this. The secret code iscurrently stored within the program source code, as the 16F628 and 16F876 have internalEEPROM the code could be stored there and be changeable from the keypad.;Tutorial 9.2 - Nigel Goodwin 2003;4 digit keypad code entry with LCD display

LIST p=16F628 ;tell assembler what chip we are usinginclude "P16F628.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__config 0x3D18 ;sets the configuration settings (oscillator

type etc.)

cblock 0x20 ;start of general purpose registerscount ;used in looping routinescount1 ;used in delay routinecounta ;used in delay routinecountb ;used in delay routinetmp1 ;temporary storagetmp2templcd ;temp store for 4 bit modetemplcd2

Page 20: PIC Tutorial 8-9-10-11

key ;which key was pressedrows ;counter for number of rowscode1 ;registers for secret codecode2code3code4key1 ;registers for keyed attemptskey2key3key4

endc

LCD_PORT Equ PORTALCD_TRIS Equ TRISALCD_RS Equ 0x04 ;LCD handshake linesLCD_RW Equ 0x06LCD_E Equ 0x07KEY_PORT Equ PORTB ;keypad portKEY_TRIS Equ TRISBCol1 Equ 0 ;pins used for keypad inputsCol2 Equ 1Col3 Equ 2Col4 Equ 3

org 0x0000goto Start

Key_Table ADDWF PCL , f ;translation table for keypadRETLW 0x43RETLW 0x42RETLW 0x30RETLW 0x41RETLW 0x44RETLW 0x39RETLW 0x38RETLW 0x37RETLW 0x45RETLW 0x36RETLW 0x35RETLW 0x34RETLW 0x46RETLW 0x33RETLW 0x32RETLW 0x31

Page 21: PIC Tutorial 8-9-10-11

HEX_Table ADDWF PCL , f ;hex table for LCD routinesRETLW 0x30RETLW 0x31RETLW 0x32RETLW 0x33RETLW 0x34RETLW 0x35RETLW 0x36RETLW 0x37RETLW 0x38RETLW 0x39RETLW 0x41RETLW 0x42RETLW 0x43RETLW 0x44RETLW 0x45RETLW 0x46

Text addwf PCL, fretlw 'E'retlw 'n'retlw 't'retlw 'e'retlw 'r'retlw ' 'retlw 'C'retlw 'o'retlw 'd'retlw 'e'retlw '!'retlw '.'retlw 0x00

Wrong_Text addwf PCL, fretlw 'W'retlw 'r'retlw 'o'retlw 'n'retlw 'g'retlw ' 'retlw 'C'retlw 'o'retlw 'd'retlw 'e'retlw '.'

Page 22: PIC Tutorial 8-9-10-11

retlw 0x00

Correct_Text addwf PCL, fretlw 'C'retlw 'o'retlw 'r'retlw 'r'retlw 'e'retlw 'c'retlw 't'retlw ' 'retlw 'C'retlw 'o'retlw 'd'retlw 'e'retlw '.'retlw 0x00

Start movlw 0x07movwf CMCON ;turn comparators off (make it like

a 16F84)

Initialise movlw '2' ;set 4 digit secret codemovwf code1movlw '5'movwf code2movlw '8'movwf code3movlw '0'movwf code4

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all pins outputsmovwf LCD_TRISmovlw 0x0F ;set keypad pinsmovwf KEY_TRIS ;half in, half outmovwf TRISBbcf STATUS, RP0 ;select bank 0

Init call LCD_Init ;setup LCD

clrf count ;set counter register to zeroMessage movf count, w ;put counter value in W

Page 23: PIC Tutorial 8-9-10-11

call Text ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zgoto Maincall LCD_Charincf count, fgoto Message

Wrong call LCD_Clrclrf count ;set counter register to zero

Message1 movf count, w ;put counter value in Wcall Wrong_Text ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zgoto Wrong_Waitcall LCD_Charincf count, fgoto Message1

Wrong_Wait call Delay255call Delay255call Delay255call Delay255goto Init

Correct call LCD_Clrclrf count ;set counter register to zero

Message2 movf count, w ;put counter value in Wcall Correct_Text ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zgoto Correct_Waitcall LCD_Charincf count, fgoto Message2

Correct_Wait call Delay255call Delay255call Delay255call Delay255goto Init

Main movlw d'1'call LCD_Line2W ;move to 2nd row, 2nd columncall LCD_CurOncall Chk_Keys ;wait for keymovwf key1 ;store first digitcall LCD_Char

Page 24: PIC Tutorial 8-9-10-11

call Chk_Keys ;wait for keymovwf key2 ;store second digitcall LCD_Charcall Chk_Keys ;wait for keymovwf key3 ;store third digitcall LCD_Charcall Chk_Keys ;wait for keymovwf key4 ;store fourth digitcall LCD_Charcall LCD_CurOff

Chk_Code movf code1, w ;test first digitsubwf key1, wbtfss STATUS, Zgoto Wrongmovf code2, w ;test second digitsubwf key2, wbtfss STATUS, Zgoto Wrongmovf code3, w ;test third digitsubwf key3, wbtfss STATUS, Zgoto Wrongmovf code4, w ;test fourth digitsubwf key4, wbtfss STATUS, Zgoto Wronggoto Correct

;Keypad subroutines

Chk_Keys movlw 0x00 ;wait until no key pressedmovwf KEY_PORT ;set all output pins lowmovf KEY_PORT, Wandlw 0x0F ;mask off high bytesublw 0x0Fbtfsc STATUS, Z ;test if any key pressedgoto Keys ;if none, read keyscall Delay20goto Chk_Keys ;else try again

Keys call Scan_Keysmovlw 0x10 ;check for no key pressedsubwf key, wbtfss STATUS, Z

Page 25: PIC Tutorial 8-9-10-11

goto Key_Foundcall Delay20goto Keys

Key_Found movf key, wandlw 0x0fcall Key_Table ;lookup key in tablemovwf key ;save back in keyreturn ;key pressed now in W

Scan_Keys clrf keymovlw 0xF0 ;set all output lines high

movwf KEY_PORTmovlw 0x04movwf rows ;set number of rowsbcf STATUS, C ;put a 0 into carry

Scan rrf KEY_PORT, fbsf STATUS, C ;follow the zero with ones

;comment out next two lines for 4x3 numeric keypad.btfss KEY_PORT, Col4goto Pressincf key, fbtfss KEY_PORT, Col3goto Pressincf key, fbtfss KEY_PORT, Col2goto Pressincf key, fbtfss KEY_PORT, Col1goto Pressincf key, fdecfsz rows, fgoto Scan

Press return

;end of keypad subroutines.

;LCD routines

;Initialise LCDLCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 ;Set 4 bit modecall LCD_Cmd

movlw 0x28 ;Set display shiftcall LCD_Cmd

Page 26: PIC Tutorial 8-9-10-11

movlw 0x06 ;Set display character modecall LCD_Cmd

movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmd ;Set cursor off

call LCD_Clr ;clear display

retlw 0x00

; command set routineLCD_Cmd movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCIILCD_Char movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_Line1 movlw 0x80 ;move to 1st row, first columncall LCD_Cmd

Page 27: PIC Tutorial 8-9-10-11

retlw 0x00

LCD_Line2 movlw 0xc0 ;move to 2nd row, first columncall LCD_Cmdretlw 0x00

LCD_Line1W addlw 0x80 ;move to 1st row, column Wcall LCD_Cmdretlw 0x00

LCD_Line2W addlw 0xc0 ;move to 2nd row, column Wcall LCD_Cmdretlw 0x00

LCD_CurOn movlw 0x0d ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_CurOff movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_Clr movlw 0x01 ;Clear displaycall LCD_Cmdretlw 0x00

LCD_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charmovf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charretlw 0x00

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mS

Page 28: PIC Tutorial 8-9-10-11

goto d0Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)d0 movwf count1d1 movlw 0xC7 ;delay 1mS

movwf countamovlw 0x01movwf countb

Delay_0decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

Pulse_e bsf LCD_PORT, LCD_Enopbcf LCD_PORT, LCD_Eretlw 0x00

LCD_Busybsf STATUS, RP0 ;set bank 1movlw 0x0f ;set Port for inputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0bcf LCD_PORT, LCD_RS ;set LCD for command modebsf LCD_PORT, LCD_RW ;setup to read busy flagbsf LCD_PORT, LCD_Eswapf LCD_PORT, w ;read upper nibble (busy flag)bcf LCD_PORT, LCD_Emovwf templcd2bsf LCD_PORT, LCD_E ;dummy read of lower

nibblebcf LCD_PORT, LCD_Ebtfsc templcd2, 7 ;check busy flag, high = busygoto LCD_Busy ;if busy check againbcf LCD_PORT, LCD_RWbsf STATUS, RP0 ;set bank 1movlw 0x00 ;set Port for outputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0return

;end of LCD routines

Page 29: PIC Tutorial 8-9-10-11

endTutorial 9.3

Tutorial 9.3 reads the keypad and sends the ASCII code via RS232 at 9600 baud, you canuse HyperTerminal on a PC to display the data. Basically this is the same as 9.1 but uses aserial link to a PC instead of the LCD screen.;Tutorial 9.3 - Nigel Goodwin 2003;Keypad reading with RS232 output

LIST p=16F628 ;tell assembler what chip we are usinginclude "P16F628.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__config 0x3D18 ;sets the configuration settings (oscillator

type etc.)

cblock 0x20 ;start of general purpose registerscount ;used in looping routinescount1 ;used in delay routinecounta ;used in delay routinecountb ;used in delay routinetmp1 ;temporary storagetmp2

keyrowsindexXmit_Byte ;holds byte to xmit

Rcv_Byte ;holds received byteBit_Cntr ;bit counter for RS232Delay_Count ;delay loop counterendc

SER_PORT Equ PORTASER_TRIS Equ TRISASER_IN Equ 0x07SER_OUT Equ 0x06KEY_PORT Equ PORTB ;keypad portKEY_TRIS Equ TRISBCol1 Equ 0 ;pins used for keypad inputsCol2 Equ 1Col3 Equ 2Col4 Equ 3

Page 30: PIC Tutorial 8-9-10-11

org 0x0000goto Start

Key_Table ADDWF PCL , fRETLW 0x43RETLW 0x42RETLW 0x30RETLW 0x41RETLW 0x44RETLW 0x39RETLW 0x38RETLW 0x37RETLW 0x45RETLW 0x36RETLW 0x35RETLW 0x34RETLW 0x46RETLW 0x33RETLW 0x32RETLW 0x31

HEX_Table ADDWF PCL , fRETLW 0x30RETLW 0x31RETLW 0x32RETLW 0x33RETLW 0x34RETLW 0x35RETLW 0x36RETLW 0x37RETLW 0x38RETLW 0x39RETLW 0x41RETLW 0x42RETLW 0x43RETLW 0x44RETLW 0x45RETLW 0x46

Text addwf PCL, fretlw 'K'retlw 'e'retlw 'y'

Page 31: PIC Tutorial 8-9-10-11

retlw 'p'retlw 'a'retlw 'd'retlw ' 'retlw 'T'retlw 'e'retlw 's'retlw 't'retlw '.'retlw 0x0A ;send LFretlw 0x0D ;send CRretlw 0x00

Start movlw 0x07movwf CMCON ;turn comparators off (make it like

a 16F84)

Initialise clrf countclrf PORTAclrf PORTB

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all pins outputsmovwf SER_TRISmovlw 0x0F ;set keypad pinsmovwf KEY_TRIS ;half in, half outbcf STATUS, RP0 ;select bank 0

call SER_INIT ;setup serial portcall Delay255 ;a;;ow time to settle

clrf count ;set counter register to zeroMessage movf count, w ;put counter value in W

call Text ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zgoto Maincall XMIT_RS232incf count, fgoto Message

Main movlw 0x0F

Page 32: PIC Tutorial 8-9-10-11

movwf KEY_PORT ;set all output pins low

Loop call Chk_Keys ;check keysmovlw 'H'call XMIT_RS232movlw 'e'call XMIT_RS232movlw 'x'call XMIT_RS232movlw ' 'call XMIT_RS232movf key, w ;restore key pressedcall XMIT_HEX ;display in hexmovlw ' 'call XMIT_RS232movlw 'A'call XMIT_RS232movlw 'S'call XMIT_RS232movlw 'C'call XMIT_RS232movlw 'I'call XMIT_RS232movlw 'I'call XMIT_RS232movlw ' 'call XMIT_RS232movf key, w ;restore key pressedcall XMIT_RS232 ;display as ASCIImovlw 0x0A ;LFcall XMIT_RS232movlw 0x0D ;CRcall XMIT_RS232goto Loop

;Keypad subroutine

Chk_Keys movlw 0x00 ;wait until no key pressedmovwf KEY_PORT ;set all output pins lowmovf KEY_PORT, Wandlw 0x0F ;mask off high bytesublw 0x0Fbtfsc STATUS, Z ;test if any key pressedgoto Keys ;if none, read keyscall Delay20goto Chk_Keys ;else try again

Page 33: PIC Tutorial 8-9-10-11

Keys call Scan_Keysmovlw 0x10 ;check for no key pressedsubwf key, wbtfss STATUS, Zgoto Key_Found

call Delay20goto Keys

Key_Found movf key, wandlw 0x0fcall Key_Table ;lookup key in tablemovwf key ;save back in keyreturn ;key pressed now in W

Scan_Keys clrf keymovlw 0xF0 ;set all output lines high

movwf KEY_PORTmovlw 0x04movwf rows ;set number of rowsbcf STATUS, C ;put a 0 into carry

Scan rrf KEY_PORT, fbsf STATUS, C ;follow the zero with ones

;comment out next two lines for 4x3 numeric keypad.btfss KEY_PORT, Col4goto Pressincf key, fbtfss KEY_PORT, Col3goto Pressincf key, fbtfss KEY_PORT, Col2goto Pressincf key, fbtfss KEY_PORT, Col1goto Pressincf key, fdecfsz rows, fgoto Scan

Press return

;end of keypad subroutines.

;Serial routines

SER_INITBSF STATUS, RP0 ;select bank 1

Page 34: PIC Tutorial 8-9-10-11

BCF SER_TRIS, SER_OUT ;set B6 as an outputBSF SER_TRIS, SER_IN ;set B7 as an inputBCF STATUS, RP0 ;select bank 0BSF SER_PORT, SER_OUT ;set SER_OUT highRETURN

XMIT_RS232 MOVWF Xmit_Byte ;move W to Xmit_ByteMOVLW 0x08 ;set 8 bits outMOVWF Bit_CntrBCF SER_PORT, SER_OUTCALL Bit_Delay

Ser_Loop RRF Xmit_Byte , f ;send one bitBTFSS STATUS , CBCF SER_PORT, SER_OUTBTFSC STATUS , CBSF SER_PORT, SER_OUTCALL Bit_DelayDECFSZ Bit_Cntr , f ;test if all doneGOTO Ser_LoopBSF SER_PORT, SER_OUTCALL Bit_DelayRETURN

Rcv_RS232 BTFSC SER_PORT, SER_IN ;wait for start bitGOTO Rcv_RS232CALL Start_Delay ;do half bit time delayBTFSC SER_PORT, SER_IN ;check still in start bitGOTO Rcv_RS232MOVLW 0x08 ;set up to read 8 bitsMOVWF Bit_CntrCLRF Rcv_Byte

Next_RcvBit CALL Bit_DelayBTFSS SER_PORT, SER_INBCF STATUS , CBTFSC SER_PORT, SER_INBSF STATUS , CRRF Rcv_Byte , fDECFSZ Bit_Cntr , f ;test if all doneGOTO Next_RcvBitCALL Bit_DelayMOVF Rcv_Byte, WRETURN

Start_Delay MOVLW 0x0CMOVWF Delay_Count

Start_Wait NOP

Page 35: PIC Tutorial 8-9-10-11

DECFSZ Delay_Count , fGOTO Start_WaitRETURN

Bit_Delay MOVLW 0x18MOVWF Delay_Count

Bit_Wait NOPDECFSZ Delay_Count , fGOTO Bit_WaitRETURN

XMIT_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall XMIT_RS232movf tmp1, wandlw 0x0fcall HEX_Tablecall XMIT_RS232retlw 0x00

;End of serial routines

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)d0 movwf count1d1 movlw 0xC7 ;delay 1mS

movwf countamovlw 0x01movwf countb

Delay_0decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,f

Page 36: PIC Tutorial 8-9-10-11

goto d1retlw 0x00

endTutorial 9.4

A common use for keyboard scanning applications, this example reads the keypad andtransmits IR remote control signals to control a Sony TV, this works in a very similar way tothe circuit used in your TV remote control. For this application I've modified the keypadroutines slightly - as we want the keys to repeat I've removed the first part which waits for akey to be released, and as we don't need ASCII values back from the key presses, I'veremoved the look-up table which gave us the ASCII values. I've replaced that with a jumptable, this jumps to one of 16 different small routines which send the correct SIRC code foreach button pressed - it's a lot neater than checking which button was pressed. The controlsI've chosen to include are, 0-9, Prog Up, Prog Down, Vol Up, Vol Down, On Off, and Mute.If you want to change which button does what, you can either alter the equates for the buttonvalues, or change the order of the Goto's in the jump table, notice that these are currently in apeculiar order, this is to map the numeric buttons correctly.;Tutorial 9.4 - Nigel Goodwin 2003;Sony SIRC IR transmitter with HEX keypad

LIST p=16F628 ;tell assembler what chip we are usinginclude "P16F628.inc" ;include the defaults for the chip__config 0x3D18 ;sets the configuration settings (oscillator

type etc.)

cblock 0x20 ;start of general purpose registerscount1 ;used in delay routinecounta ;used in delay routinecountbcountkeyrowsindexDelay_CountBit_CntrData_ByteDev_ByteRcv_BytePulse

endc

IR_PORT Equ PORTAIR_TRISEqu TRISAIR_Out Equ 0x01IR_In Equ 0x02

Page 37: PIC Tutorial 8-9-10-11

KEY_PORT Equ PORTB ;keypad portKEY_TRIS Equ TRISBCol1 Equ 0 ;pins used for keypad inputsCol2 Equ 1Col3 Equ 2Col4 Equ 3

TV_ID Equ 0x01 ;TV device ID

But1 Equ 0x00 ;numeric button ID'sBut2 Equ 0x01But3 Equ 0x02But4 Equ 0x03But5 Equ 0x04But6 Equ 0x05But7 Equ 0x06But8 Equ 0x07But9 Equ 0x08But0 Equ 0x09ProgUp Equ d'16'ProgDn Equ d'17'VolUp Equ d'18'VolDn Equ d'19'Mute Equ d'20'Power Equ d'21'

org 0x0000 ;org sets the origin, 0x0000 for the 16F628,goto Start ;this is where the program starts running

org 0x005

Start movlw 0x07movwf CMCON ;turn comparators off (make it like

a 16F84)

clrf IR_PORT ;make IR Port outputs low

bsf STATUS, RP0 ;select bank 1movlw b'11111101' ;set IR Port all inputs, except RB1movwf IR_TRISmovlw 0x0F ;set keypad pinsmovwf KEY_TRIS ;half in, half outbcf STATUS, RP0 ;select bank 0movlw 0x00

Page 38: PIC Tutorial 8-9-10-11

movwf key ;set inital key to non repeating one

Loop call Keysandlw 0x0f ;make sure 0-15addwf PCL , f ;and jump to keygoto SwitchCgoto SwitchBgoto Switch0goto SwitchAgoto SwitchDgoto Switch9goto Switch8goto Switch7goto SwitchEgoto Switch6goto Switch5goto Switch4goto SwitchFgoto Switch3goto Switch2goto Switch1

Done call Delay27goto Loop

Switch1 movlw But1call Xmit_RS232Goto Done

Switch2 movlw But2call Xmit_RS232Goto Done

Switch3 movlw But3call Xmit_RS232Goto Done

Switch4 movlw But4call Xmit_RS232Goto Done

Switch5 movlw But5call Xmit_RS232Goto Done

Switch6 movlw But6call Xmit_RS232

Page 39: PIC Tutorial 8-9-10-11

Goto Done

Switch7 movlw But7call Xmit_RS232Goto Done

Switch8 movlw But8call Xmit_RS232Goto Done

Switch9 movlw But9call Xmit_RS232Goto Done

Switch0 movlw But0call Xmit_RS232Goto Done

SwitchA movlw VolUpcall Xmit_RS232Goto Done

SwitchB movlw VolDncall Xmit_RS232Goto Done

SwitchC movlw ProgUpcall Xmit_RS232Goto Done

SwitchD movlw ProgDncall Xmit_RS232Goto Done

SwitchE movlw Mutecall Xmit_RS232Goto Done

SwitchF movlw Powercall Xmit_RS232Goto Done

;Keypad subroutines

Keys call Scan_Keysmovlw 0x10 ;check for no key pressed

Page 40: PIC Tutorial 8-9-10-11

subwf key, wbtfss STATUS, Zgoto Key_Found

call Delay20goto Keys

Key_Found movf key, wreturn ;key pressed now in W

Scan_Keys clrf keymovlw 0xF0 ;set all output lines high

movwf KEY_PORTmovlw 0x04movwf rows ;set number of rowsbcf STATUS, C ;put a 0 into carry

Scan rrf KEY_PORT, fbsf STATUS, C ;follow the zero with ones

;comment out next two lines for 4x3 numeric keypad.btfss KEY_PORT, Col4goto Pressincf key, fbtfss KEY_PORT, Col3goto Pressincf key, fbtfss KEY_PORT, Col2goto Pressincf key, fbtfss KEY_PORT, Col1goto Pressincf key, fdecfsz rows, fgoto Scan

Press return

;end of keypad subroutines.

TX_Startmovlw d'92'call IR_pulsemovlw d'23'call NO_pulseretlw 0x00

TX_One movlw d'46'call IR_pulsemovlw d'23'call NO_pulseretlw 0x00

Page 41: PIC Tutorial 8-9-10-11

TX_Zero movlw d'23'call IR_pulsemovlw d'23'call NO_pulseretlw 0x00

IR_pulseMOVWF count ; Pulses the IR led at 38KHz

irloop BSF IR_PORT, IR_OutNOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOP ;BCF IR_PORT, IR_OutNOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOPNOPNOP ;NOP ;DECFSZcount,FGOTO irloopRETLW 0

NO_pulseMOVWF count ; Doesn't pulse the IR led

irloop2 BCF IR_PORT, IR_OutNOP ;NOP ;NOP ;NOP ;NOP ;NOP ;

Page 42: PIC Tutorial 8-9-10-11

NOP ;NOP ;NOP ;NOP ;NOP ;BCF IR_PORT, IR_OutNOP ;NOP ;NOP ;NOP ;NOP ;NOP ;NOPNOPNOP ;NOP ;DECFSZcount,FGOTO irloop2RETLW 0

Xmit_RS232 MOVWF Data_Byte ;move W to Data_ByteMOVLW 0x07 ;set 7 DATA bits outMOVWF Bit_Cntrcall TX_Start ;send start bit

Ser_Loop RRF Data_Byte , f ;send one bitBTFSC STATUS , Ccall TX_OneBTFSS STATUS , Ccall TX_ZeroDECFSZ Bit_Cntr , f ;test if all doneGOTO Ser_Loop

;now send device datamovlw D'1'movwf Dev_Byte ;set device to TVMOVLW 0x05 ;set 5 device bits out

MOVWF Bit_CntrSer_Loop2 RRF Dev_Byte , f ;send one bit

BTFSC STATUS , Ccall TX_OneBTFSS STATUS , Ccall TX_ZeroDECFSZ Bit_Cntr , f ;test if all doneGOTO Ser_Loop2retlw 0x00

Page 43: PIC Tutorial 8-9-10-11

;Delay routines

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay27 movlw d'27' ;delay 27mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)d0 movwf count1d1 movlw 0xC7

movwf countamovlw 0x01movwf countb

Delay_0 decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

;end of Delay routines

End

Page 44: PIC Tutorial 8-9-10-11

PIC Tutorial 10 - 7 Segment LED's

For these tutorials you require the Main Board and 7 Segment LED Board. Downloadzipped tutorial files.

These tutorials demonstrate how to use a multiplexed 7 segment LED display, thesetutorials use just a dual display (because it fits easily on a single port), but the technique caneasily be extended to use larger displays (and commonly is).

To reduce the pin count required, the two displays are multiplexed - this means we needseven I/O pins, one for each segment of the LED (ignoring the decimal point), and two toselect the individual sections - unfortunately this would require nine I/O pins - taking morethan one port. To over come this I've designed the 7 segment board to select the two sectionsusing just one pin - the two transistors are wired as inverters, this means that when Q1 isON, Q2 is OFF, and when Q1 is OFF, Q2 is ON - so by switching port pin 7 we canalternate between the two sections.

The technique used for the display is to show each section in turn, so we display section1, then section 2 - as long as we do this fast enough you can't see any flickering - thisobviously restricts what else the program can be doing, as we must refresh the displayregularly enough to prevent visible flicker. While this can be done in normal straight code, itdoes mean that the display code has to be an integral part of the program - precluding it's useas a reusable module. For this reason I've chosen to use interrupts!.

The interrupts are generated by Timer2, which is set to give an interrupt roughly every16mS. Interrupts in a PIC cause the program to stop what it's doing, save it's currentlocation, and jump to the 'Interrupt Vector' - which on a PIC is address 0x0004. The interruptroutine then does what it needs to do and exits using the 'REFIE' (REturn From IntErrupt)command - this works rather like a normal 'RETURN' in that it returns the program to whereit was when the interrupt was called. An important point to bear in mind when usinginterrupts is that you mustn't change anything the main program is using - for that reason thefirst thing we must do is save various resisters - the W register, the PCLATH register, andthe STATUS register - these are saved in data registers allocated for their use, and arerestored before the routine exits via 'RETFIE'.

The interrupt routine is shown below, the first 5 lines save the registers mentioned above,the 'swapf' command is used as it doesn't affect the STATUS register, there's not much pointsaving the register if we've already changed it!. Next we check is the interrupt was generatedby the timer or not, in this case we're only using one interrupt source (TIMER2) - but oftenyou may be using multiple interrupt sources, if it's not TIMER2 we simply jump to theEXIT routine, which restores the registers saved and exits via RETFIE.

Now we start the actual interrupt routine itself, the first thing we have to do is resetTIMER2 - once it's triggered an interrupt it turns itself off, so we turn it back on here (bcfPIR1,TMR2IF). Next we start our display routine, first we need to find out which displaywe're updating - in this particular case it's very simple, bit 7 of the port selects each display,so by testing bit 7 we can see which we need to update. The values for the two digits arestored in the variables 'ones' and 'tens', and can contain the value 0 to 15 (0 to 0x0F HEX),although only 0-9 are used here (A-F give a blank display) - so we first move the value into

Page 45: PIC Tutorial 8-9-10-11

the W register and AND it with 0x0F (to make sure it can't be any higher than 15), then 'callLED_Table' - this is a table which gives the correct segment pattern for each number - you'llnotice that on the circuit diagram I didn't label which segment was which, this was because Iwired them as simply as possible - we can sort out which is which in this table. Here the twodigits vary slightly, for the 'ones' digit we need to clear bit 7 - and do so with the 'andlw0x7F', for the 'tens' we need to set bit 7 - and do so with the 'iorlw 0x80', in both case wethen simply write the W register to the port and exit the interrupt routine.; Interrupt vector

ORG 0x0004

INTmovwf w_temp ; Save W registerswapf STATUS,W ; Swap status to be saved into Wmovwf s_temp ; Save STATUS registermovfw PCLATHmovwf p_temp ; Save PCLATH

btfss PIR1,TMR2IF ; Flag set if TMR2 interruptgoto INTX ; Jump if not timed out

; Timer (TMR2) timeout every 16 milliseconds

bcf PIR1,TMR2IF ; Clear the calling flag

btfss 7SEG_PORT, 7 ;check which LED was lastgoto Do_tensmovfw onesandlw 0x0F ;make sure in range of tablecall LED_Tableandlw 0x7F ;set to correct LEDmovwf 7SEG_PORTgoto INTX

Do_tens movfw tensandlw 0x0F ;make sure in range of tablecall LED_Tableiorlw 0x80 ;set to correct LEDmovwf 7SEG_PORT

INTX

Page 46: PIC Tutorial 8-9-10-11

movfw p_tempmovwf PCLATH ; Restore PCLATHswapf s_temp,Wmovwf STATUS ; Restore STATUS register - restores bankswapf w_temp,Fswapf w_temp,W ; Restore W registerretfie

The interrupt routine above is complete and working, but it requires TIMER2 setting upbefore it can work, this is done in the 'Initialise' section of the program, which is the firstsection called when the program runs. This first turns the comparators off, then sets thedirection register for the Port to all outputs, then sets TIMER2 to the correct time, you willnotice there's two lines (with one commented out) for setting the value of T2CON - if youcomment out the second one, and use the first one, it makes the interrupt routine timing tooslow - you can actually see the digits flickering, first one then the other - worth doing so youcan see what's going on. After that we set up PR2, this sets the value that the timer counts tobefore it times out - then we enable TIMER2 interrupts by setting the TMR2IE flag inregister PIE1. This still isn't enough, so finally we write to the INTCON register to enable'Peripheral Interrupts' and 'Global Interrupts'. The last two lines simply zero the two displayvariables, and aren't part of the TIMER2 setup routine.

Initialise movlw 0x07movwf CMCON ;turn comparators off (make it like

a 16F84)bsf STATUS, RP0 ;select bank 1movlw b'00000000' ;Set port data directions, data

outputmovwf 7SEG_TRISbcf STATUS, RP0 ;select bank 0

; Set up Timer 2.

;movlw b'01111110' ; Post scale /16, pre scale /16,TMR2 ON

movlw b'00010110' ; Post scale /4, pre scale /16, TMR2ON

movwf T2CON

bsf STATUS, RP0 ;select bank 1

movlw .249 ; Set up comparatormovwf PR2

bsf PIE1,TMR2IE ; Enable TMR2 interrupt

bcf STATUS, RP0 ;select bank 0

Page 47: PIC Tutorial 8-9-10-11

; Global interrupt enable

bsf INTCON,PEIE ; Enable all peripheral interruptsbsf INTCON,GIE ; Global interrupt enable

bcf STATUS, RP0 ;select bank 0

clrf tensclrf ones

This is the main section of the first program, as you can see there's not a great deal to it,all it does it delay about 1 second, then do a decimal increment of 'ones' and 'tens' - by adecimal increment I mean once the 'ones' digit gets to '10' we reset the 'ones' digit to zeroand increment the 'tens' digit'. We do the same with the 'tens' digit, except we don't have a'hundreds' digit so we throw the carry away (no 'incf'). The program simply increments 'ones'and 'tens' from '00' to '99' at roughly 1 second intervals, then loops back to '00' and startsagain. Notice that this section of the program has no connection to any kind of I/O or displayroutine, all it does is increment two variables - the display function is handled totallytransparently by the interrupt routine - all we need to do is place the values we want in 'ones'and 'tens'.

Maincall Delay255call Delay255call Delay255call Delay255incf ones, fmovf ones, w ;test first digitsublw 0x0Abtfss STATUS, Zgoto Mainclrf onesincf tens, fmovf tens, w ;test second digitsublw 0x0Abtfss STATUS, Zgoto Mainclrf tensgoto Main ;loop for ever

Tutorial 10.1This tutorial implements a simple 0-99 counter.

;7 segment LED display - count up from zero

LIST p=16F628 ;tell assembler what chip we are usinginclude "P16F628.inc" ;include the defaults for the chip

Page 48: PIC Tutorial 8-9-10-11

ERRORLEVEL 0, -302 ;suppress bank selection messages__config 0x3D18 ;sets the configuration settings (oscillator

type etc.)

cblock 0x20 ;start of general purpose registerscount ;used in looping routinescount1 ;used in delay routinecounta ;used in delay routinecountb ;used in delay routinetmp1 ;temporary storagetmp2p_temp ;save registers_temp ;save registerw_temp ;save registertens ;tens storageones ;ones storage

endc

SEG_PORT Equ PORTB ;7 segment portSEG_TRIS Equ TRISB

org 0x0000goto Initialise

;**************************************************************************; Interrupt routine;**************************************************************************

; Interrupt routine handles TMR2 which generates a 1ms tick

; Interrupt vector

ORG 0x0004

INTmovwf w_temp ; Save W registerswapf STATUS,W ; Swap status to be saved into Wmovwf s_temp ; Save STATUS registermovfw PCLATH

Page 49: PIC Tutorial 8-9-10-11

movwf p_temp ; Save PCLATH

btfss PIR1,TMR2IF ; Flag set if TMR2 interruptgoto INTX ; Jump if not timed out

; Timer (TMR2) timeout every 1 milli second

bcf PIR1,TMR2IF ; Clear the calling flag

btfss SEG_PORT, 7 ;check which LED was lastgoto Do_tensmovfw onesandlw 0x0F ;make sure in range of tablecall LED_Tableandlw 0x7F ;set to correct LEDmovwf SEG_PORTgoto INTX

Do_tens movfw tensandlw 0x0F ;make sure in range of tablecall LED_Tableiorlw 0x80 ;set to correct LEDmovwf SEG_PORT

INTXmovfw p_tempmovwf PCLATH ; Restore PCLATHswapf s_temp,Wmovwf STATUS ; Restore STATUS register - restores bankswapf w_temp,Fswapf w_temp,W ; Restore W registerretfie

LED_Table ADDWF PCL , fRETLW b'10001000' ;0RETLW b'10111011' ;1RETLW b'11000001' ;2RETLW b'10010001' ;3RETLW b'10110010' ;4RETLW b'10010100' ;5RETLW b'10000100' ;6RETLW b'10111001' ;7

Page 50: PIC Tutorial 8-9-10-11

RETLW b'10000000' ;8RETLW b'10110000' ;9RETLW b'11111111' ;blankRETLW b'11111111' ;blankRETLW b'11111111' ;blankRETLW b'11111111' ;blankRETLW b'11111111' ;blankRETLW b'11111111' ;blank

Initialise movlw 0x07movwf CMCON ;turn comparators off (make it like

a 16F84)bsf STATUS, RP0 ;select bank 1movlw b'00000000' ;Set port data directions, data

outputmovwf SEG_TRISbcf STATUS, RP0 ;select bank 0

; Set up Timer 2.

;movlw b'01111110' ; Post scale /16, pre scale /16,TMR2 ON

;uncomment previous line, and comment next line, to slow multiplexingspeed

;so you can see the multiplexing happeningmovlw b'00010110' ; Post scale /4, pre scale /16, TMR2

ONmovwf T2CON

bsf STATUS, RP0 ;select bank 1

movlw .249 ; Set up comparatormovwf PR2

bsf PIE1,TMR2IE ; Enable TMR2 interrupt

bcf STATUS, RP0 ;select bank 0

; Global interrupt enable

bsf INTCON,PEIE ; Enable all peripheral interruptsbsf INTCON,GIE ; Global interrupt enable

bcf STATUS, RP0 ;select bank 0

Page 51: PIC Tutorial 8-9-10-11

clrf tensclrf ones

Maincall Delay255call Delay255call Delay255call Delay255incf ones, fmovf ones, w ;test first digitsublw 0x0Abtfss STATUS, Zgoto Mainclrf onesincf tens, fmovf tens, w ;test second digitsublw 0x0Abtfss STATUS, Zgoto Mainclrf tensgoto Main ;loop for ever

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (4 MHz clock)d0 movwf count1d1 movlw 0xC7 ;delay 1mS

movwf countamovlw 0x01movwf countb

Delay_0decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

Page 52: PIC Tutorial 8-9-10-11

decfsz count1 ,fgoto d1retlw 0x00

END

Page 53: PIC Tutorial 8-9-10-11

PIC Tutorial 11 - Analogue Inputs

For these tutorials you require Main Board 2 or 3, the Analogue Board, the LCD Boardand the RS232 Board. Download zipped tutorial files.

The 16F876 and the 16F877 both include 10 bit analogue to digital converters, the 876provides a possible 5 inputs, and the 877 a possible 8 inputs, in both cases there's only oneactual converter - it's switched to each input pin as required by setting a register.

There's generally a lot of confusion about using the A2D inputs, but it's actually reallyvery simple - it's just a question of digging the information you need out of the datasheets.The main confusion arises from actually setting them up, in these tutorials I'll give provenworking examples, which you can easily modify to use as you wish.

There are four main registers associated with using the analogue inputs, these are listedin this table:

Main registers for analogue inputs.Name Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0ADRESH A2D Result Register - High ByteADRESL A2D Result Register - Low ByteADCON0 ADCS1 ADCS0 CHS2 CHS1 CHS0 GO/DONE - ADONADCON1 ADFM - - - PCFG3 PCFG2 PCFG1 PCFG0

ADRESH and ADRESL are fairly self explanatory, they are the registers that return theresult of the analogue to digital conversion, the only slightly tricky thing about them is thatthey are in different memory banks.ADCON0 Details

ADCON0 is split into four separate parts, the first part consists of the highest two bitsADCS1 and ADCS0, and sets the clock frequency used for the analogue to digitalconversion, this is divided down from the system clock (or can use an internal RCoscillator), as we are using a 20MHz clock on the tutorial boards, we have to use Fosc/32(as given in the table below). So that's one setup decision already solved:

ADCS1 ADCS0 A/D Conversion Clock Select bits. Max. Clock Freq.0 0 Fosc/2 1.25MHz0 1 Fosc/8 5MHz1 0 FOsc/32 20MHz1 1 Frc (Internal A2D RC Osc.) Typ. 4uS

The second part of ADCON0 consists of the next three bits, CHS2,CHS1 and CHS0,these are the channel select bits, and set which input pin is routed to the analogue to digitalconverter. Be aware that only the first five (AN0-AN4) are available on the 16F876, thenext three (AN5-AN7) are only available on the 16F877. Also notice that AN4 uses digitalpin RA5, and not RA4 as you would expect. As the tutorial analogue input board only hastwo inputs, connected to AN0 and AN1, this reduces the decision to two possibilities, either000 for AN0, or 001 for AN1, and we simply alter them as we switch between the twoinputs.

Page 54: PIC Tutorial 8-9-10-11

CHS2 CHS1 CHS0 Channel Pin0 0 0 Channel0 RA0/AN00 0 1 Channel1 RA1/AN10 1 0 Channel2 RA2/AN20 1 1 Channel3 RA3/AN31 0 0 Channel4 RA5/AN41 0 1 Channel5 RE0/AN51 1 0 Channel6 RE1/AN61 1 1 Channel7 RE2/AN7

The third part is a single bit (bit 2), GO/DONE, this bit has two functions, firstly bysetting the bit it initiates the start of analogue to digital conversion, secondly the bit iscleared when the conversion is complete - so we can check this bit to wait for theconversion to finish.

The fourth part is another single bit (bit 0), ADON, this simply turns the A2D On orOff, with the bit set it's On, with the bit cleared it's Off - thus saving the power it consumes.

So for our application the data required in ADCON0 is binary '10000001' to read fromAN0, and binary '10001001' to read from AN1. Bit 1 isn't used, and can be either '0' or '1' -in this case I've chosen to make it '0'. To initiate a conversion, we need to make Bit 2 high,we'll do this with a "BCF ADCON0, GO_DONE" line. Likewise, we'll use a "BTFSCADCON0, GO_DONE" line to check for the end of conversion.

Bits required in ADCON0.Input Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0AN0 1 0 0 0 0 0 - 1AN1 1 0 0 0 1 0 - 1

ADCON1 DetailsADCON1 is really a little more complicated, although it's only split into two sections.

The first section is a single bit, ADFM, this is the Result Format Selection Bit, and selectsif the output is Right Justified (bit set) or Left Justified (bit cleared). The advantage of thisis that it makes it very easy to use as an 8 bit converter (instead of ten bit) - by clearing thisbit, and reading just ADRESH, we get an 8 bit result, ignoring the two least significant bitsin ADRESL. For the purposes of these tutorials, I'm intending using the full ten bits - sothis bit will be set.

PCFG3-0 are probably the most complicated part of setting the A2D section, they set alot of different options, and also limit which pins can be analogue, and which can bedigital:

PCFG3:PCFG0

AN7RE2

AN6RE1

AN5RE0

AN4RA5

AN3RA3

AN2RA2

AN1RA1

AN0RA0 Vref+ Vref-

0000 A A A A A A A A Vdd Vss0001 A A A A Vref+ A A A RA3 Vss0010 D D D A A A A A Vdd Vss

Page 55: PIC Tutorial 8-9-10-11

0011 D D D A Vref+ A A A RA3 Vss0100 D D D D A D A A Vdd Vss0101 D D D D Vref+ D A A RA3 Vss0110 D D D D D D D D Vdd Vss0111 D D D D D D D D Vdd Vss1000 A A A A Vref+ Vref- A A RA3 RA21001 D D A A A A A A Vdd Vss1010 D D A A Vref+ A A A RA3 Vss1011 D D A A Vref+ Vref- A A RA3 RA21100 D D D A Vref+ Vref- A A RA3 RA21101 D D D D Vref+ Vref- A A RA3 RA21110 D D D D D D D A Vdd Vss1111 D D D D Vref+ Vref- D A RA3 RA2

As I mentioned above, this part looks rather complicated - but if we split it down, itstarts to make more sense. There are actually four different options being set here:Setting a pin to be an analogue input.Setting a pin to be a digital input.Setting the positive reference for the converter (Vref+).Setting the negative reference for the converter (Vref-).

For a start we need to decide what settings we actually require - first we are only usinganalogue inputs AN0 and AN1, which if you look down the columns eliminates four of thepossibilities (0110, 0111, 1110 and 1111, shaded in blue). Secondly we are using a VRef-of Vss (Ground), so that eliminates another four (1000, 1011, 1100 and 1101, shaded inyellow) - so now we've only got eight possibly choices left (down to 50% already). Thirdlywe are using an external VRef+ reference, so we need RA3 allocating to Vref+, thiseliminates a further four (0000, 0010, 0100 and 1001, shaded in green). This brings usdown to four possible choices and, to be honest, any of the four would work quite happily -however, one of our requirements was 'two analogue inputs', this eliminates a further threepossibilities (0001, 0011 and 1010, shaded in red) - which leaves the only option which fitsall our requirements '0101', so this is the value we will need to write to PCFG3:PCFG0.

So now we've decided what we need to set ADCON1 to, binary '10000101', with 0's inthe places of the unused bits, this gives us two analogue inputs, Vref+ set to RA3, andVref- set to Vdd.

Bits required in ADCON1.Name Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0ADCON1 1 - - - 0 1 0 1

Now we know the setting up details, it's time for a little more explaining about exactlywhat it all means, I've mentioned earlier that the A2D has '10 bit' resolution, this means theoutput of the A2D can vary from 0 (all bits '0') to 1023 (all bits '1'). A number from 0-1023probably isn't too helpful, because you don't know what they represent. This is where Vref+

Page 56: PIC Tutorial 8-9-10-11

and Vref- come in!. When the output of the A2D = 1023, the analogue input is at Vref+,when the A2D output = 0, then the analogue input is at Vref-. In our case, with the TL341reference on RA3, 1023 is about 2.5V, and with Vref- at Vdd, 0 from the A2D equals zerovolts on the input.

So, to recap, 1023 represents 2.5V, and 0 represents 0V. Numbers in between representvoltages in between, to work out what they actually represent, we need a little mathematics.Dividing 2.5V by 1023 gives 0.00244, this is the voltage resolution of out A2D, so areading of 1 represents 0.00244V (or 2.44mV), and a reading of 2 represents 0.00488V (or4.88mV), and so on. As you'll see later on (in the programming tutorials) it's important toknow the voltage resolution of the A2D, in order to calculate the voltage. Now while I saidthe voltage resolution is 0.00244, I was rounding the figure off, the actual value (fromWindows Calculator) is 0.00244379276637341153470185728250244 - multiplying 1023by 0.00244 only gives 2.49612V, and not the 2.5V it should be. Although this is only a0.388% error, it doesn't look good - I'd really like the display to read from 0 to themaximum input, with out silly limitations like that.

I considered this while designing the analogue input board, and made a decision to getover the problem in the hardware. The amplifiers on the inputs have attenuators feedingthem, this gives the overall amplifier an adjustable gain of around minus 4, so 10V (or so)input will give 1000 output from the A2D. So by adjusting the gain of the input amplifiers,we can use an input voltage range from 0-10.23V, which (by no coincidence at all) allowsus to get a 0-1023 reading from the A2D converter giving a resolution of 10mV, which is atheoretical accuracy of around 0.1% (obviously the system itself isn't this accurate, nor isthe meter we'll use to calibrate it). Another advantage of this is that it calibrates the voltagereference chip as well, this probably isn't going to be exactly 2.5V, by calibrating the inputsagainst a known meter we take care of this as well.

PIC Assembler routines.As usual, this tutorial will present simple reusable routines for using the analogue

inputs, as I mentioned at the beginning, it's actually really very easy to use, once you'veworked through the datasheets and found out how you need to set things. For the actualprogramming, this is reduced down to two small routines:Init_ADC; Set ADCON0

movlw b'10000001'movwf ADCON0

; Set ADCON1BANKSEL ADCON1movlw b'10000101'movwf ADCON1BANKSEL ADCON0return

This routine simply sets the values of ADCON0 and ADCON1 with the values wedecided on in the discussion earlier, the only point to note is the BANKSEL commandsbefore and after ADCON1. This register is in a different register bank to ADCON0, so weneed to select the correct bank before accessing the register, as a matter of course we return

Page 57: PIC Tutorial 8-9-10-11

to the original register bank afterwards.Read_ADC

bsf ADCON0, GO_DONE ;initiate conversionbtfsc ADCON0, GO_DONEgoto $-1 ;wait for ADC to finish

movf ADRESH,Wandlw 0x03movwf NumHBANKSEL ADRESLmovf ADRESL,WBANKSEL ADRESHmovwf NumL ;return result in NumL and NumHreturn

This second routine is the one which actually reads the analogue input, the first lineinitiates the conversion, and the second and third wait for it to complete. The secondsection simply reads the converted value from the two registers, and stores them invariables for later use. Notice the bank switching again around ADRESL, this is becauseADRESL is in a different register bank, it's important to notice that we move it's value toW, then switch banks back BEFORE storing it in the variable NumL - if we don't switchback it gets stored in the wrong register. Notice I'm AND'ing ADRESH with 0x03, this isbecause we only use the lower two bits of ADRESH, and although the upper six bits shouldall be zero - I like to make sure.

So all we need to do to use the A2D is to "call Init_ADC" during the programmesinitialisation, and then "call Read_ADC" whenever we wish to read a value. In order toselect the individual two analogue channels, I've simply expanded "Init_ADC" to tworoutines, "Init_ADC0" and "Init_ADC1", these simply set the analogue converter to thecorrect input pin.

These tutorials use the Analogue Board on PortA, the LCD Board on PortB, and theRS232 Board on PortC.Tutorial 11.1 - requires main Board Two or Three, Analogue Board, LCD Board.

This tutorial reads a single channel of the A2D converter and displays it on the LCD.First the program sets up the registers explained above, then the rest of the program runs inan endless loop. It reads the analogue input, which gives a value from 0-$3FF (inhexadecimal), then calls a routine which converts that to decimal, giving 0-1023(representing 0-10.23V). Lastly this decimal value is displayed on the LCD, followed by aspace, and the same value in hexadecimal. The program then wait for 100mS and jumpsback to the start of the loop and runs again, this gives roughly 10 readings a second.;Tutorial 11_1;Reading an analogue input, and displaying it on the LCD;Nigel Goodwin 2004; Device 16F876

LIST p=16F876, W=2, X=ON, R=DEC ;tell assembler what chip we are

Page 58: PIC Tutorial 8-9-10-11

usinginclude "P16F876.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__CONFIG 0x393A ;sets the configuration settings

(oscillator type etc.)

cblock 0x20 ;start of general purpose registerscountcount1countacountbLoXBit_CntrTimer_HFlagsFlags2tmp1tmp2tmp3NumLNumHTenKThouHundTensOnestemplcdtemplcd2Acc1L ;16 bit maths register 1Acc1HPoint ;position of decimal point

endc

LCD_PORT Equ PORTBLCD_TRIS Equ TRISBLCD_RS Equ 0x04 ;LCD handshake linesLCD_RW Equ 0x06LCD_E Equ 0x07

LEADZ Equ 0x00 ;set to display leading zeros

;start of program

ORG 0x0000NOP

Page 59: PIC Tutorial 8-9-10-11

BCF PCLATH, 3BCF PCLATH, 4GOTO Initialise

ORG 0x0004RETURN

ORG 0x0005

Initialise clrf countclrf PORTAclrf PORTBclrf PORTCclrf FlagsBANKSEL ADCON1 ;disable A2Dmovlw 0x06movwf ADCON1BANKSEL PORTA

;variables for decimal numbersdisplay

bsf Flags, LEADZ ;show leading zerosmovlw 0x00 ;set decimal point position to zero

(none)movwf Point

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all LCD pins outputsmovwf LCD_TRISbcf STATUS, RP0 ;select bank 0call LCD_Init ;setup LCD modulecall LCD_CurOff ;turn cursor offcall Init_ADC ;initialise analogue input

Maincall LCD_Line1 ;set to first linecall String1 ;display title stringcall LCD_Line2call Read_ADC ;read analogue inputcall LCD_Decimal ;and display on LCD (in decimal)movlw ' 'call LCD_Char ;display a spacemovf NumH, Wcall LCD_HEX ;and display in

Page 60: PIC Tutorial 8-9-10-11

hexadecimalmovf NumL, Wcall LCD_HEX

call Delay100 ;delay to give 10 readings persecond

goto Main ;loop for ever

Init_ADC; Set ADCON0

movlw b'10000001'movwf ADCON0

; Set ADCON1BANKSEL ADCON1movlw b'10000101'movwf ADCON1BANKSEL ADCON0return

Read_ADCbsf ADCON0, GO_DONE ;initiate conversionbtfsc ADCON0, GO_DONEgoto $-1 ;wait for ADC to finish

movf ADRESH,Wandlw 0x03movwf NumHBANKSEL ADRESLmovf ADRESL,WBANKSEL ADRESHmovwf NumL ;return result in NumL and NumHreturn

;TABLESHEX_Table addwf PCL , f

retlw 0x30retlw 0x31retlw 0x32retlw 0x33retlw 0x34retlw 0x35retlw 0x36retlw 0x37retlw 0x38

Page 61: PIC Tutorial 8-9-10-11

retlw 0x39retlw 0x41retlw 0x42retlw 0x43retlw 0x44retlw 0x45retlw 0x46

Xtext addwf PCL, fretlw 'T'retlw 'u'retlw 't'retlw 'o'retlw 'r'retlw 'i'retlw 'a'retlw 'l'retlw ' 'retlw '1'retlw '1'retlw '.'retlw '1'retlw 0x00

;end of tables

String1 clrf count ;set counter register to zeroMess1 movf count, w ;put counter value in W

call Xtext ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zretlw 0x00 ;return when finishedcall LCD_Charincf count, fgoto Mess1

;LCD routines

;Initialise LCDLCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 ;Set 4 bit modecall LCD_Cmd

movlw 0x28 ;Set display shift

Page 62: PIC Tutorial 8-9-10-11

call LCD_Cmd

movlw 0x06 ;Set display character modecall LCD_Cmd

movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmd ;Set cursor off

call LCD_Clr ;clear display

retlw 0x00

; command set routineLCD_Cmd movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCIILCD_Char movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_Line1 movlw 0x80 ;move to 1st row, first column

Page 63: PIC Tutorial 8-9-10-11

call LCD_Cmdretlw 0x00

LCD_Line2 movlw 0xc0 ;move to 2nd row, first columncall LCD_Cmdretlw 0x00

LCD_Line1W addlw 0x80 ;move to 1st row, column Wcall LCD_Cmdretlw 0x00

LCD_Line2W addlw 0xc0 ;move to 2nd row, column Wcall LCD_Cmdretlw 0x00

LCD_CurOn movlw 0x0d ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_CurOff movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_Clr movlw 0x01 ;Clear displaycall LCD_Cmdretlw 0x00

LCD_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charmovf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charretlw 0x00

Pulse_e bsf LCD_PORT, LCD_Enopbcf LCD_PORT, LCD_Eretlw 0x00

LCD_Busy

Page 64: PIC Tutorial 8-9-10-11

bsf STATUS, RP0 ;set bank 1movlw 0x0f ;set Port for inputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0bcf LCD_PORT, LCD_RS ;set LCD for command modebsf LCD_PORT, LCD_RW ;setup to read busy flagbsf LCD_PORT, LCD_Eswapf LCD_PORT, w ;read upper nibble (busy flag)bcf LCD_PORT, LCD_Emovwf templcd2bsf LCD_PORT, LCD_E ;dummy read of lower

nibblebcf LCD_PORT, LCD_Ebtfsc templcd2, 7 ;check busy flag, high = busygoto LCD_Busy ;if busy check againbcf LCD_PORT, LCD_RWbsf STATUS, RP0 ;set bank 1movlw 0x00 ;set Port for outputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0return

LCD_Decimalcall Convertbtfsc Flags, LEADZgoto LCD_TENKmovf TenK, wbtfss STATUS, Zgoto LCD_TENKmovf Thou, wbtfss STATUS, Zgoto LCD_THOUmovf Hund, wbtfss STATUS, Zgoto LCD_HUNDmovf Tens, wbtfss STATUS, Zgoto LCD_TENSgoto LCD_ONES

LCD_TENKmovlw 0x05 ;test if decimal point 5subwf Point, wbtfss STATUS , Zgoto NO_DP5movlw '.'call LCD_Char ;display decimal point

Page 65: PIC Tutorial 8-9-10-11

NO_DP5 movf TenK, wcall LCD_CharDmovlw 0x04 ;test if decimal point 4subwf Point, wbtfss STATUS , Zgoto LCD_THOUmovlw '.'call LCD_Char ;display decimal point

LCD_THOUmovf Thou, wcall LCD_CharDmovlw 0x03 ;test if decimal point 3subwf Point, wbtfss STATUS , Zgoto LCD_HUNDmovlw '.'call LCD_Char ;display decimal point

LCD_HUNDmovf Hund, wcall LCD_CharDmovlw 0x02 ;test if decimal point 2subwf Point, wbtfss STATUS , Zgoto LCD_TENSmovlw '.'call LCD_Char ;display decimal point

LCD_TENSmovf Tens, wcall LCD_CharDmovlw 0x01 ;test if decimal point 1subwf Point, wbtfss STATUS , Zgoto LCD_ONESmovlw '.'call LCD_Char ;display decimal point

LCD_ONESmovf Ones, wcall LCD_CharDreturn

;end of LCD routines

;Delay routines

Page 66: PIC Tutorial 8-9-10-11

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (20 MHz clock)d0 movwf count1d1 movlw 0xE7

movwf countamovlw 0x04movwf countb

Delay_0 decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

;end of Delay routines

;This routine downloaded from http://www.piclist.comConvert: ; Takes number in NumH:NumL

; Returns decimal in; TenK:Thou:Hund:Tens:Ones

swapf NumH, wiorlw B'11110000'

movwf Thouaddwf Thou,faddlw 0XE2movwf Hundaddlw 0X32movwf Ones

movf NumH,wandlw 0X0Faddwf Hund,faddwf Hund,faddwf Ones,faddlw 0XE9movwf Tensaddwf Tens,f

Page 67: PIC Tutorial 8-9-10-11

addwf Tens,f

swapf NumL,wandlw 0X0Faddwf Tens,faddwf Ones,f

rlf Tens,frlf Ones,fcomf Ones,frlf Ones,f

movf NumL,wandlw 0X0Faddwf Ones,frlf Thou,f

movlw 0X07movwf TenK

; At this point, the original number is; equal to; TenK*10000+Thou*1000+Hund*100+Tens*10+Ones; if those entities are regarded as two's; complement binary. To be precise, all of; them are negative except TenK. Now the number; needs to be normalized, but this can all be; done with simple byte arithmetic.

movlw 0X0A ; TenLb1:

addwf Ones,fdecf Tens,fbtfss 3,0goto Lb1

Lb2:addwf Tens,fdecf Hund,fbtfss 3,0goto Lb2

Lb3:addwf Hund,fdecf Thou,fbtfss 3,0goto Lb3

Lb4:addwf Thou,f

Page 68: PIC Tutorial 8-9-10-11

decf TenK,fbtfss 3,0goto Lb4

retlw 0x00

endTutorial 11.2 - requires as above.

Pretty much the same as Tutorial 11.1, except this time we read both channels anddisplay the two voltages, this demonstrates how to switch between the different channelsusing the two different "Init_ADC" routines. In order to keep 10 readings per second, thedelay has been reduced to 50mS, so we get 20 readings per second, but still only 10 on eachchannel.;Tutorial 11_2;Reading two analogue inputs, and display them on the LCD;Nigel Goodwin 2004; Device 16F876

LIST p=16F876, W=2, X=ON, R=DEC ;tell assembler what chip we areusing

include "P16F876.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__CONFIG 0x393A ;sets the configuration settings

(oscillator type etc.)cblock 0x20 ;start of general purpose registers

countcount1countacountbLoXBit_CntrTimer_HFlagsFlags2tmp1tmp2tmp3NumLNumHTenKThouHundTensOnestemplcd

Page 69: PIC Tutorial 8-9-10-11

templcd2Point ;position of decimal point

endc

LCD_PORT Equ PORTBLCD_TRIS Equ TRISBLCD_RS Equ 0x04 ;LCD handshake linesLCD_RW Equ 0x06LCD_E Equ 0x07

LEADZ Equ 0x00 ;set to display leading zeros

;start of program

ORG 0x0000NOPBCF PCLATH, 3BCF PCLATH, 4GOTO Initialise

ORG 0x0004RETURN

ORG 0x0005

Initialise clrf countclrf PORTAclrf PORTBclrf PORTCclrf FlagsBANKSEL ADCON1 ;disable A2Dmovlw 0x06movwf ADCON1BANKSEL PORTA

;variables for decimal numbersdisplay

bsf Flags, LEADZ ;show leading zerosmovlw 0x00 ;set decimal point position to zero

(none)movwf Point

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all LCD pins outputsmovwf LCD_TRISbcf STATUS, RP0 ;select bank 0

Page 70: PIC Tutorial 8-9-10-11

call LCD_Init ;setup LCD modulecall LCD_CurOff ;turn cursor offcall Init_ADC0 ;initialise analogue input

Maincall LCD_Line1 ;set to first linecall String1call Read_ADC ;read analogue inputcall LCD_Decimal ;and display on LCD (in decimal)movlw ' 'call LCD_Char ;display a spacemovf NumH, Wcall LCD_HEX ;and display in

hexadecimalmovf NumL, Wcall LCD_HEX

call Init_ADC1 ;set for second channelcall Delay50 ;delay to give 10 readings per

second

;read and display second input

call LCD_Line2call String2call Read_ADC ;read analogue inputcall LCD_Decimal ;and display on LCD (in decimal)movlw ' 'call LCD_Char ;display a spacemovf NumH, Wcall LCD_HEX ;and display in

hexadecimalmovf NumL, Wcall LCD_HEX

call Init_ADC0 ;set for first channelcall Delay50 ;delay to give 10 readings per

second

goto Main ;loop for ever

Init_ADC0 ;set for AN0; Set ADCON0

movlw b'10000001'movwf ADCON0

; Set ADCON1

Page 71: PIC Tutorial 8-9-10-11

BANKSEL ADCON1movlw b'10000101'movwf ADCON1BANKSEL ADCON0return

Init_ADC1 ;set for AD1; Set ADCON0

movlw b'10001001'movwf ADCON0

; Set ADCON1BANKSEL ADCON1movlw b'10000101'movwf ADCON1BANKSEL ADCON0return

Read_ADCbsf ADCON0, GO_DONE ;initiate conversionbtfsc ADCON0, GO_DONEgoto $-1 ;wait for ADC to finish

movf ADRESH,Wandlw 0x03movwf NumHBANKSEL ADRESLmovf ADRESL,WBANKSEL ADRESHmovwf NumL ;return result in NumL and NumHreturn

;TABLESHEX_Table addwf PCL , f

retlw 0x30retlw 0x31retlw 0x32retlw 0x33retlw 0x34retlw 0x35retlw 0x36retlw 0x37retlw 0x38retlw 0x39retlw 0x41retlw 0x42retlw 0x43retlw 0x44

Page 72: PIC Tutorial 8-9-10-11

retlw 0x45retlw 0x46

Xtext addwf PCL, fretlw 'C'retlw 'H'retlw '0'retlw ' 'retlw 0x00

Ytext addwf PCL, fretlw 'C'retlw 'H'retlw '1'retlw ' 'retlw 0x00

;end of tables

String1 clrf count ;set counter register to zeroMess1 movf count, w ;put counter value in W

call Xtext ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zretlw 0x00 ;return when finishedcall LCD_Charincf count, fgoto Mess1

String2 clrf count ;set counter register to zeroMess2 movf count, w ;put counter value in W

call Ytext ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zretlw 0x00 ;return when finishedcall LCD_Charincf count, fgoto Mess2

;LCD routines

;Initialise LCDLCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 ;Set 4 bit modecall LCD_Cmd

Page 73: PIC Tutorial 8-9-10-11

movlw 0x28 ;Set display shiftcall LCD_Cmd

movlw 0x06 ;Set display character modecall LCD_Cmd

movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmd ;Set cursor off

call LCD_Clr ;clear display

retlw 0x00

; command set routineLCD_Cmd movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCIILCD_Char movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

Page 74: PIC Tutorial 8-9-10-11

LCD_Line1 movlw 0x80 ;move to 1st row, first columncall LCD_Cmdretlw 0x00

LCD_Line2 movlw 0xc0 ;move to 2nd row, first columncall LCD_Cmdretlw 0x00

LCD_Line1W addlw 0x80 ;move to 1st row, column Wcall LCD_Cmdretlw 0x00

LCD_Line2W addlw 0xc0 ;move to 2nd row, column Wcall LCD_Cmdretlw 0x00

LCD_CurOn movlw 0x0d ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_CurOff movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_Clr movlw 0x01 ;Clear displaycall LCD_Cmdretlw 0x00

LCD_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charmovf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charretlw 0x00

Pulse_e bsf LCD_PORT, LCD_Enopbcf LCD_PORT, LCD_Eretlw 0x00

Page 75: PIC Tutorial 8-9-10-11

LCD_Busybsf STATUS, RP0 ;set bank 1movlw 0x0f ;set Port for inputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0bcf LCD_PORT, LCD_RS ;set LCD for command modebsf LCD_PORT, LCD_RW ;setup to read busy flagbsf LCD_PORT, LCD_Eswapf LCD_PORT, w ;read upper nibble (busy flag)bcf LCD_PORT, LCD_Emovwf templcd2bsf LCD_PORT, LCD_E ;dummy read of lower

nibblebcf LCD_PORT, LCD_Ebtfsc templcd2, 7 ;check busy flag, high = busygoto LCD_Busy ;if busy check againbcf LCD_PORT, LCD_RWbsf STATUS, RP0 ;set bank 1movlw 0x00 ;set Port for outputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0return

LCD_Decimalcall Convertbtfsc Flags, LEADZgoto LCD_TENKmovf TenK, wbtfss STATUS, Zgoto LCD_TENKmovf Thou, wbtfss STATUS, Zgoto LCD_THOUmovf Hund, wbtfss STATUS, Zgoto LCD_HUNDmovf Tens, wbtfss STATUS, Zgoto LCD_TENSgoto LCD_ONES

LCD_TENKmovlw 0x05 ;test if decimal point 5subwf Point, wbtfss STATUS , Zgoto NO_DP5

Page 76: PIC Tutorial 8-9-10-11

movlw '.'call LCD_Char ;display decimal point

NO_DP5 movf TenK, wcall LCD_CharDmovlw 0x04 ;test if decimal point 4subwf Point, wbtfss STATUS , Zgoto LCD_THOUmovlw '.'call LCD_Char ;display decimal point

LCD_THOUmovf Thou, wcall LCD_CharDmovlw 0x03 ;test if decimal point 3subwf Point, wbtfss STATUS , Zgoto LCD_HUNDmovlw '.'call LCD_Char ;display decimal point

LCD_HUNDmovf Hund, wcall LCD_CharDmovlw 0x02 ;test if decimal point 2subwf Point, wbtfss STATUS , Zgoto LCD_TENSmovlw '.'call LCD_Char ;display decimal point

LCD_TENSmovf Tens, wcall LCD_CharDmovlw 0x01 ;test if decimal point 1subwf Point, wbtfss STATUS , Zgoto LCD_ONESmovlw '.'call LCD_Char ;display decimal point

LCD_ONESmovf Ones, wcall LCD_CharDreturn

;end of LCD routines

;Delay routines

Page 77: PIC Tutorial 8-9-10-11

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (20 MHz clock)d0 movwf count1d1 movlw 0xE7

movwf countamovlw 0x04movwf countb

Delay_0 decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

;end of Delay routines;This routine downloaded from http://www.piclist.comConvert: ; Takes number in NumH:NumL

; Returns decimal in; TenK:Thou:Hund:Tens:Ones

swapf NumH, wiorlw B'11110000'

movwf Thouaddwf Thou,faddlw 0XE2movwf Hundaddlw 0X32movwf Ones

movf NumH,wandlw 0X0Faddwf Hund,faddwf Hund,faddwf Ones,faddlw 0XE9movwf Tensaddwf Tens,faddwf Tens,f

Page 78: PIC Tutorial 8-9-10-11

swapf NumL,wandlw 0X0Faddwf Tens,faddwf Ones,f

rlf Tens,frlf Ones,fcomf Ones,frlf Ones,f

movf NumL,wandlw 0X0Faddwf Ones,frlf Thou,f

movlw 0X07movwf TenK

; At this point, the original number is; equal to; TenK*10000+Thou*1000+Hund*100+Tens*10+Ones; if those entities are regarded as two's; complement binary. To be precise, all of; them are negative except TenK. Now the number; needs to be normalized, but this can all be; done with simple byte arithmetic.

movlw 0X0A ; TenLb1:

addwf Ones,fdecf Tens,fbtfss 3,0goto Lb1

Lb2:addwf Tens,fdecf Hund,fbtfss 3,0goto Lb2

Lb3:addwf Hund,fdecf Thou,fbtfss 3,0goto Lb3

Lb4:addwf Thou,fdecf TenK,fbtfss 3,0

Page 79: PIC Tutorial 8-9-10-11

goto Lb4retlw 0x00

endTutorial 11.3 - requires as above.

Similar to the previous Tutorials, but this time we add a decimal point and a 'V' forvoltage at the end of the decimal display, and we don't bother showing the hexadecimalvalue - just as you would for a simple voltmeter.;Tutorial 11_3;Reading an analogue input, and displaying it on the LCD;Nigel Goodwin 2004; Device 16F876

LIST p=16F876, W=2, X=ON, R=DEC ;tell assembler what chip we areusing

include "P16F876.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__CONFIG 0x393A ;sets the configuration settings

(oscillator type etc.)

cblock 0x20 ;start of general purpose registerscountcount1countacountbLoXBit_CntrTimer_HFlagsFlags2tmp1tmp2tmp3NumLNumHTenKThouHundTensOnestemplcdtemplcd2Point ;position of decimal point

endcLCD_PORT Equ PORTBLCD_TRIS Equ TRISBLCD_RS Equ 0x04 ;LCD handshake linesLCD_RW Equ 0x06

Page 80: PIC Tutorial 8-9-10-11

LCD_E Equ 0x07LEADZ Equ 0x00 ;set to display leading zeros;start of program

ORG 0x0000NOPBCF PCLATH, 3BCF PCLATH, 4GOTO InitialiseORG 0x0004RETURN

ORG 0x0005Initialise clrf count

clrf PORTAclrf PORTBclrf PORTCclrf FlagsBANKSEL ADCON1 ;disable A2Dmovlw 0x06movwf ADCON1BANKSEL PORTA

;variables for decimal numbersdisplay

bsf Flags, LEADZ ;show leading zerosmovlw 0x02 ;set decimal point position to twomovwf Point

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all LCD pins outputsmovwf LCD_TRISbcf STATUS, RP0 ;select bank 0call LCD_Init ;setup LCD modulecall LCD_CurOff ;turn cursor offcall Init_ADC ;initialise analogue input

Maincall LCD_Line1 ;set to first linecall String1 ;display title stringcall LCD_Line2call Read_ADC ;read analogue inputcall LCD_Decimal ;and display on LCD (in decimal)movlw 'V'call LCD_Char ;display a 'V'

call Delay100 ;delay to give 10 readings persecond

goto Main ;loop for ever

Page 81: PIC Tutorial 8-9-10-11

Init_ADC; Set ADCON0

movlw b'10000001'movwf ADCON0

; Set ADCON1BANKSEL ADCON1movlw b'10000101'movwf ADCON1BANKSEL ADCON0return

Read_ADCbsf ADCON0, GO_DONE ;initiate conversionbtfsc ADCON0, GO_DONEgoto $-1 ;wait for ADC to finish

movf ADRESH,Wandlw 0x03movwf NumHBANKSEL ADRESLmovf ADRESL,WBANKSEL ADRESHmovwf NumL ;return result in NumL and NumHreturn

;TABLESHEX_Table addwf PCL , f

retlw 0x30retlw 0x31retlw 0x32retlw 0x33retlw 0x34retlw 0x35retlw 0x36retlw 0x37retlw 0x38retlw 0x39retlw 0x41retlw 0x42retlw 0x43retlw 0x44retlw 0x45retlw 0x46

Xtext addwf PCL, fretlw 'T'

Page 82: PIC Tutorial 8-9-10-11

retlw 'u'retlw 't'retlw 'o'retlw 'r'retlw 'i'retlw 'a'retlw 'l'retlw ' 'retlw '1'retlw '1'retlw '.'retlw '3'retlw 0x00

;end of tablesString1 clrf count ;set counter register to zeroMess1 movf count, w ;put counter value in W

call Xtext ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zretlw 0x00 ;return when finishedcall LCD_Charincf count, fgoto Mess1

;LCD routines

;Initialise LCDLCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 ;Set 4 bit modecall LCD_Cmd

movlw 0x28 ;Set display shiftcall LCD_Cmd

movlw 0x06 ;Set display character modecall LCD_Cmd

movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmd ;Set cursor off

call LCD_Clr ;clear display

Page 83: PIC Tutorial 8-9-10-11

retlw 0x00

; command set routineLCD_Cmd movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCIILCD_Char movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_Line1 movlw 0x80 ;move to 1st row, first columncall LCD_Cmdretlw 0x00

LCD_Line2 movlw 0xc0 ;move to 2nd row, first columncall LCD_Cmdretlw 0x00

LCD_Line1W addlw 0x80 ;move to 1st row, column Wcall LCD_Cmdretlw 0x00

Page 84: PIC Tutorial 8-9-10-11

LCD_Line2W addlw 0xc0 ;move to 2nd row, column Wcall LCD_Cmdretlw 0x00

LCD_CurOn movlw 0x0d ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_CurOff movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_Clr movlw 0x01 ;Clear displaycall LCD_Cmdretlw 0x00

LCD_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charmovf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charretlw 0x00

Pulse_e bsf LCD_PORT, LCD_Enopbcf LCD_PORT, LCD_Eretlw 0x00

LCD_Busybsf STATUS, RP0 ;set bank 1movlw 0x0f ;set Port for inputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0bcf LCD_PORT, LCD_RS ;set LCD for command modebsf LCD_PORT, LCD_RW ;setup to read busy flagbsf LCD_PORT, LCD_Eswapf LCD_PORT, w ;read upper nibble (busy flag)bcf LCD_PORT, LCD_Emovwf templcd2bsf LCD_PORT, LCD_E ;dummy read of lower

Page 85: PIC Tutorial 8-9-10-11

nibblebcf LCD_PORT, LCD_Ebtfsc templcd2, 7 ;check busy flag, high = busygoto LCD_Busy ;if busy check againbcf LCD_PORT, LCD_RWbsf STATUS, RP0 ;set bank 1movlw 0x00 ;set Port for outputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0return

LCD_Decimalcall Convertbtfsc Flags, LEADZgoto LCD_TENKmovf TenK, wbtfss STATUS, Z

goto LCD_TENKmovf Thou, wbtfss STATUS, Zgoto LCD_THOUmovf Hund, wbtfss STATUS, Zgoto LCD_HUNDmovf Tens, wbtfss STATUS, Zgoto LCD_TENSgoto LCD_ONES

LCD_TENKmovlw 0x05 ;test if decimal point 5subwf Point, wbtfss STATUS , Zgoto NO_DP5movlw '.'call LCD_Char ;display decimal point

NO_DP5 movf TenK, wcall LCD_CharDmovlw 0x04 ;test if decimal point 4subwf Point, wbtfss STATUS , Zgoto LCD_THOUmovlw '.'call LCD_Char ;display decimal point

LCD_THOUmovf Thou, wcall LCD_CharD

Page 86: PIC Tutorial 8-9-10-11

movlw 0x03 ;test if decimal point 3subwf Point, wbtfss STATUS , Zgoto LCD_HUNDmovlw '.'call LCD_Char ;display decimal point

LCD_HUNDmovf Hund, wcall LCD_CharDmovlw 0x02 ;test if decimal point 2subwf Point, wbtfss STATUS , Zgoto LCD_TENSmovlw '.'call LCD_Char ;display decimal point

LCD_TENSmovf Tens, wcall LCD_CharDmovlw 0x01 ;test if decimal point 1subwf Point, wbtfss STATUS , Zgoto LCD_ONESmovlw '.'call LCD_Char ;display decimal point

LCD_ONESmovf Ones, wcall LCD_CharDreturn

;end of LCD routines

;Delay routines

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (20 MHz clock)d0 movwf count1d1 movlw 0xE7

Page 87: PIC Tutorial 8-9-10-11

movwf countamovlw 0x04movwf countb

Delay_0 decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

;end of Delay routines

;This routine downloaded from http://www.piclist.comConvert: ; Takes number in NumH:NumL

; Returns decimal in; TenK:Thou:Hund:Tens:Ones

swapf NumH, wiorlw B'11110000'

movwf Thouaddwf Thou,faddlw 0XE2movwf Hundaddlw 0X32movwf Ones

movf NumH,wandlw 0X0Faddwf Hund,faddwf Hund,faddwf Ones,faddlw 0XE9movwf Tensaddwf Tens,faddwf Tens,f

swapf NumL,wandlw 0X0Faddwf Tens,faddwf Ones,f

rlf Tens,frlf Ones,fcomf Ones,frlf Ones,f

Page 88: PIC Tutorial 8-9-10-11

movf NumL,wandlw 0X0Faddwf Ones,frlf Thou,f

movlw 0X07movwf TenK

; At this point, the original number is; equal to; TenK*10000+Thou*1000+Hund*100+Tens*10+Ones; if those entities are regarded as two's; complement binary. To be precise, all of; them are negative except TenK. Now the number; needs to be normalized, but this can all be; done with simple byte arithmetic.

movlw 0X0A ; TenLb1:

addwf Ones,fdecf Tens,fbtfss 3,0goto Lb1

Lb2:addwf Tens,fdecf Hund,fbtfss 3,0goto Lb2

Lb3:addwf Hund,fdecf Thou,fbtfss 3,0goto Lb3

Lb4:addwf Thou,fdecf TenK,fbtfss 3,0goto Lb4

retlw 0x00

endTutorial 11.4 - requires RS232 board as well.

This time we add an extra board, the RS232 Board. The program is essentially Tutorial

Page 89: PIC Tutorial 8-9-10-11

11.3, but this time we add a serial output routine to send the data via RS232 to a PC. Forthe RS232 output we don't send the 'V' at the end, but we do send CR/LF in order to displayeach reading on a separate line. I've implemented this by modifying the LCD_Char routineand adding a couple of Flags - LCD and RS232 - by setting these flags ON or OFF we canprint to either the LCD, the RS232 output, both, or neither.;Tutorial 11_4;Reading an analogue input, and displaying it on the LCD and RS232;Nigel Goodwin 2004; Device 16F876

LIST p=16F876, W=2, X=ON, R=DEC ;tell assembler what chip we areusing

include "P16F876.inc" ;include the defaults for the chipERRORLEVEL 0, -302 ;suppress bank selection messages__CONFIG 0x393A ;sets the configuration settings

(oscillator type etc.)

cblock 0x20 ;start of general purpose registerscountcount1countacountbLoXBit_CntrTimer_HFlagsFlags2tmp1tmp2tmp3NumLNumHTenKThouHundTensOnestemplcdtemplcd2Point ;position of decimal point

endc

LCD_PORT Equ PORTBLCD_TRIS Equ TRISBLCD_RS Equ 0x04 ;LCD handshake lines

Page 90: PIC Tutorial 8-9-10-11

LCD_RW Equ 0x06LCD_E Equ 0x07

LEADZ Equ 0x00 ;set to display leading zerosLCD Equ 0x01 ;set to print to LCDRS232 Equ 0x02 ;set to print to RS232

;start of program

ORG 0x0000NOPBCF PCLATH, 3BCF PCLATH, 4GOTO Initialise

ORG 0x0004RETURN

ORG 0x0005

Initialise clrf countclrf PORTAclrf PORTBclrf PORTCclrf FlagsBANKSEL ADCON1 ;disable A2Dmovlw 0x06movwf ADCON1BANKSEL PORTA

;variables for decimal numbersdisplay

bsf Flags, LEADZ ;show leading zerosbsf Flags, LCD ;set to print to LCDbsf Flags, RS232 ;and also to RS232movlw 0x02 ;set decimal point position to twomovwf Point

SetPorts bsf STATUS, RP0 ;select bank 1movlw 0x00 ;make all LCD pins outputsmovwf LCD_TRISbcf STATUS, RP0 ;select bank 0call SER_INIT ;initialise serial portcall LCD_Init ;setup LCD module

Page 91: PIC Tutorial 8-9-10-11

call LCD_CurOff ;turn cursor offcall Init_ADC ;initialise analogue input

Mainbcf Flags, RS232 ;turn off RS232call LCD_Line1 ;set to first linecall String1 ;display title stringbsf Flags, RS232 ;turn RS232 back on

call LCD_Line2call Read_ADC ;read analogue inputcall LCD_Decimal ;and display (in decimal)

bcf Flags, RS232 ;turn off RS232movlw 'V'call LCD_Char ;display a 'V' on LCD only

bsf Flags, RS232 ;turn RS232 back onbcf Flags, LCD ;and turn off LCDmovlw 0x0Acall LCD_Char ;display a LFmovlw 0x0Dcall LCD_Char ;display a CRbsf Flags, LCD ;turn LCD back on

call Delay100 ;delay to give 10 readings persecond

goto Main ;loop for ever

Init_ADC; Set ADCON0

movlw b'10000001'movwf ADCON0

; Set ADCON1BANKSEL ADCON1movlw b'10000101'movwf ADCON1BANKSEL ADCON0return

Read_ADCbsf ADCON0, GO_DONE ;initiate conversionbtfsc ADCON0, GO_DONEgoto $-1 ;wait for ADC to finish

Page 92: PIC Tutorial 8-9-10-11

movf ADRESH,Wandlw 0x03movwf NumHBANKSEL ADRESLmovf ADRESL,WBANKSEL ADRESHmovwf NumL ;return result in NumL and NumHreturn

;TABLESHEX_Table addwf PCL , f

retlw 0x30retlw 0x31retlw 0x32retlw 0x33retlw 0x34retlw 0x35retlw 0x36retlw 0x37retlw 0x38retlw 0x39retlw 0x41retlw 0x42retlw 0x43retlw 0x44retlw 0x45retlw 0x46

Xtext addwf PCL, fretlw 'T'retlw 'u'retlw 't'retlw 'o'retlw 'r'retlw 'i'retlw 'a'retlw 'l'retlw ' 'retlw '1'retlw '1'retlw '.'retlw '4'retlw 0x00

;end of tables

Page 93: PIC Tutorial 8-9-10-11

String1 clrf count ;set counter register to zeroMess1 movf count, w ;put counter value in W

call Xtext ;get a character from the text tablexorlw 0x00 ;is it a zero?btfsc STATUS, Zretlw 0x00 ;return when finishedcall LCD_Charincf count, fgoto Mess1

;USART Serial routines

SER_INITBSF STATUS, RP0 ;select bank 1

MOVLW d'129' ;9600 baud @ 20 Mhz Fosc +0.16 errMOVWF SPBRGMOVLW b'00100100' ;brgh = 1MOVWF TXSTA ;enable Async Transmission, set brgh

BCF STATUS, RP0 ;select bank 0MOVLW b'10010000'MOVWF RCSTA ;enable Async Reception

RETURN

XMIT_RS232 btfss PIR1, TXIF ;xmit buffer empty?GOTO XMIT_RS232 ;no, waitMOVWF TXREG ;now send

RETURN

Rcv_RS232 BTFSS PIR1, RCIF ; check for received dataGOTO Rcv_RS232

MOVF RCREG, WRETURN

;End of serial routines

;LCD routines

;Initialise LCDLCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 ;Set 4 bit modecall LCD_Cmd

movlw 0x28 ;Set display shiftcall LCD_Cmd

Page 94: PIC Tutorial 8-9-10-11

movlw 0x06 ;Set display character modecall LCD_Cmd

movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmd ;Set cursor off

call LCD_Clr ;clear display

retlw 0x00

; command set routineLCD_Cmd movwf templcd

swapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbcf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busyretlw 0x00

LCD_CharD addlw 0x30 ;add 0x30 to convert to ASCIILCD_Char btfsc Flags, RS232

call XMIT_RS232 ;do RS232 if flag setbtfss Flags, LCDreturn ;return if LCD not setmovwf templcdswapf templcd, w ;send upper nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line high

movf templcd, w ;send lower nibbleandlw 0x0f ;clear upper 4 bits of Wmovwf LCD_PORTbsf LCD_PORT, LCD_RS ;RS line to 1call Pulse_e ;Pulse the E line highcall LCD_Busy

Page 95: PIC Tutorial 8-9-10-11

retlw 0x00

LCD_Line1 movlw 0x80 ;move to 1st row, first columncall LCD_Cmdretlw 0x00

LCD_Line2 movlw 0xc0 ;move to 2nd row, first columncall LCD_Cmdretlw 0x00

LCD_Line1W addlw 0x80 ;move to 1st row, column Wcall LCD_Cmdretlw 0x00

LCD_Line2W addlw 0xc0 ;move to 2nd row, column Wcall LCD_Cmdretlw 0x00

LCD_CurOn movlw 0x0d ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_CurOff movlw 0x0c ;Set display on/off and cursorcommand

call LCD_Cmdretlw 0x00

LCD_Clr movlw 0x01 ;Clear displaycall LCD_Cmdretlw 0x00

LCD_HEX movwf tmp1swapf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charmovf tmp1, wandlw 0x0fcall HEX_Tablecall LCD_Charretlw 0x00

Pulse_e bsf LCD_PORT, LCD_Enopbcf LCD_PORT, LCD_E

Page 96: PIC Tutorial 8-9-10-11

retlw 0x00

LCD_Busybsf STATUS, RP0 ;set bank 1movlw 0x0f ;set Port for inputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0bcf LCD_PORT, LCD_RS ;set LCD for command modebsf LCD_PORT, LCD_RW ;setup to read busy flagbsf LCD_PORT, LCD_Eswapf LCD_PORT, w ;read upper nibble (busy flag)bcf LCD_PORT, LCD_Emovwf templcd2bsf LCD_PORT, LCD_E ;dummy read of lower

nibblebcf LCD_PORT, LCD_Ebtfsc templcd2, 7 ;check busy flag, high = busygoto LCD_Busy ;if busy check againbcf LCD_PORT, LCD_RWbsf STATUS, RP0 ;set bank 1movlw 0x00 ;set Port for outputmovwf LCD_TRISbcf STATUS, RP0 ;set bank 0return

LCD_Decimalcall Convertbtfsc Flags, LEADZgoto LCD_TENKmovf TenK, wbtfss STATUS, Zgoto LCD_TENKmovf Thou, wbtfss STATUS, Zgoto LCD_THOUmovf Hund, wbtfss STATUS, Zgoto LCD_HUNDmovf Tens, wbtfss STATUS, Zgoto LCD_TENSgoto LCD_ONES

LCD_TENKmovlw 0x05 ;test if decimal point 5subwf Point, wbtfss STATUS , Z

Page 97: PIC Tutorial 8-9-10-11

goto NO_DP5movlw '.'call LCD_Char ;display decimal point

NO_DP5 movf TenK, wcall LCD_CharDmovlw 0x04 ;test if decimal point 4subwf Point, wbtfss STATUS , Zgoto LCD_THOUmovlw '.'call LCD_Char ;display decimal point

LCD_THOUmovf Thou, wcall LCD_CharDmovlw 0x03 ;test if decimal point 3subwf Point, wbtfss STATUS , Zgoto LCD_HUNDmovlw '.'call LCD_Char ;display decimal point

LCD_HUNDmovf Hund, wcall LCD_CharDmovlw 0x02 ;test if decimal point 2subwf Point, wbtfss STATUS , Zgoto LCD_TENSmovlw '.'call LCD_Char ;display decimal point

LCD_TENSmovf Tens, wcall LCD_CharDmovlw 0x01 ;test if decimal point 1subwf Point, wbtfss STATUS , Zgoto LCD_ONESmovlw '.'call LCD_Char ;display decimal point

LCD_ONESmovf Ones, wcall LCD_CharDreturn

;end of LCD routines

Page 98: PIC Tutorial 8-9-10-11

;Delay routines

Delay255 movlw 0xff ;delay 255 mSgoto d0

Delay100 movlw d'100' ;delay 100mSgoto d0

Delay50 movlw d'50' ;delay 50mSgoto d0

Delay20 movlw d'20' ;delay 20mSgoto d0

Delay5 movlw 0x05 ;delay 5.000 ms (20 MHz clock)d0 movwf count1d1 movlw 0xE7

movwf countamovlw 0x04movwf countb

Delay_0 decfsz counta, fgoto $+2decfsz countb, fgoto Delay_0

decfsz count1 ,fgoto d1retlw 0x00

;end of Delay routines

;This routine downloaded from http://www.piclist.comConvert: ; Takes number in NumH:NumL

; Returns decimal in; TenK:Thou:Hund:Tens:Ones

swapf NumH, wiorlw B'11110000'

movwf Thouaddwf Thou,faddlw 0XE2movwf Hundaddlw 0X32movwf Ones

movf NumH,wandlw 0X0Faddwf Hund,faddwf Hund,faddwf Ones,f

Page 99: PIC Tutorial 8-9-10-11

addlw 0XE9movwf Tensaddwf Tens,faddwf Tens,f

swapf NumL,wandlw 0X0Faddwf Tens,faddwf Ones,f

rlf Tens,frlf Ones,fcomf Ones,frlf Ones,f

movf NumL,wandlw 0X0Faddwf Ones,frlf Thou,f

movlw 0X07movwf TenK

; At this point, the original number is; equal to; TenK*10000+Thou*1000+Hund*100+Tens*10+Ones; if those entities are regarded as two's; complement binary. To be precise, all of; them are negative except TenK. Now the number; needs to be normalized, but this can all be; done with simple byte arithmetic.

movlw 0X0A ; TenLb1:

addwf Ones,fdecf Tens,fbtfss 3,0goto Lb1

Lb2:addwf Tens,fdecf Hund,fbtfss 3,0goto Lb2

Lb3:addwf Hund,fdecf Thou,f

Page 100: PIC Tutorial 8-9-10-11

btfss 3,0goto Lb3

Lb4:addwf Thou,fdecf TenK,fbtfss 3,0goto Lb4

retlw 0x00

end