Documente Academic
Documente Profesional
Documente Cultură
Obviously one of the first things we need to know how to do is how to get the program into the chip!. There are various programmers and software available, some free, some shareware, and some commercial products. The idea behind this series is of a low cost tutorial, so it will be based on my free WinPicProg software, and any suitable parallel port hardware - usually based on the original David Tait design. Personally I'm using the P16PRO40 hardware, although this series will only be using an 18 pin chip, we may introduce another chip or two later on, so it's worthwhile going for the P16PRO40 rather than a programmer that only accepts 18 pin chips. The circuit for the programmer is freely available (so you can build it yourself if you like), and can be downloaded from my WinPicProg page, there are various links there where you can buy kits for the programmer as well.
Having got the hardware, we now need to install the software, this requires two downloads, one for the program itself, and one for the 32 bit driver DLL, 'port95nt.exe', download both programs from the link above and save them to your hard drive. The driver should be installed first, simply run the exe file and it will be installed on your computer, then unzip the 'winpicprog.zip' file and store the two files in a suitable directory on your computer - I haven't added an install program as it increases the size of the download by a huge amount, and there's nothing complicated about installing it manually - just put the two files in any directory you like. Now, make sure the programmer hardware is connected up and turned on, then run WinPicProg, this will auto-detect which port you are connected to and display the port address it uses. If this doesn't happen you may have a hardware fault, or no power to the programmer, or a different programmer that requires the I/O pins configuring differently. This can be done from the 'Options/Hardware' menu option - however, if you use the P16PRO40 hardware the defaults that the program uses are correct. Once you have the programmer hardware connected and working, you need to try programming a chip, to do this you select 'Open', either from the 'file' menu or from the speedbutton just below - this produces a file-requester where you can browse for the file you want, these files end in '.HEX' and are files produced by MPASM, the MicroChip assembler. Simply select the file you wish to load and click on the 'Open' button, the file will be loaded and displayed in the HEX Buffer mode. To program the chip all you have to do now if press 'Write', WinPicProg will give you a few messages as it proceeds, and will display error messages and abort if it finds any problems. Return to main tutorial page. PIC Tutorial One - LED's
For the first parts of this tutorial you can use the Main Board LED, with jumper J1 set, or you can use the LED board on PortB, the later parts use more than one LED and the LED board will be required. Download zipped tutorial files. Tutorial 1.1 This simple program repeatedly switches all the output pins high and low. ;Tutorial 1.1 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings ;(oscillator type etc.) ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84)
org movlw movwf bsf movlw movwf movwf bcf Loop movlw movwf movwf nop nop movlw movwf movwf goto end
;select bank 1 ;set PortB all outputs ;set PortA all outputs ;select bank 0
RP0
;set all bits on ;the nop's make up the time taken by the goto ;giving a square wave output
The first three lines are instructions to the assembler, and not really part of the program at all, they should be left as they are during these tutorials - the __Config line sets the various configuration fuses in the chip, in this case it selects the internal 4MHz oscillator. The next line
'org 0x0000' sets the start address, it does vary across the PIC range, but most modern ones start from the lowest address - zero. Lines 5 and 6 are specific to the 16F628, 'movlw 0x07' means 'MOVe the Literal value 7 into the W register', the W register is the main working register, 'movwf CMCON' means 'MOV the value in W to File CMCON', CMCON is a register in the 16F628 that is used to select the operation of the comparator hardware. So these two lines set CMCON to 7, this disables the comparator, and makes their I/O lines available for general use. The next five lines set the direction of the I/O pins, first we have to select 'bank 1', some registers are in 'bank 0' and some in 'bank 1', to select 'bank 1' we need to set the bit RP0 in the STATUS register to '1' - the 'bsf' (Bit Set File) command sets a bit to one. The 'bcf' (Bit Clear File) at the end of these five lines, sets RP0 back to '0' and returns to 'bank 0'. The 'movlw', as before, moves a literal value into the W register, although this time the value passed is a binary value (instead of the hexadecimal 0x00), signified by the 'b' at the start of the value, in this case it's simply zero, and this value is then transferred to the two TRIS registers (TRIState) A and B. This sets the direction of the pins, a '0' sets a pin as an Output, and a '1' sets a pin as an Input so b'00000000' (eight zeros) sets all the pins to outputs, b'10000000' would set I/O pin 7 as an input, and all the others as outputs - by using a binary value it's easy to see which pins are inputs (1) and which are outputs (0). This completes the setting up of the chip, we can now start the actual 'running' part of the program, this begins with a label 'Loop', the last command 'goto Loop' returns the program to here, and it loops round for ever. The first instruction in this section 'movlw 0xff' moves the hexadecimal number 0xff (255 decimal, 11111111 binary) to the W register, the second and third then transfer this to the PortA and PortB I/O ports - this 'tries' to set all 16 pins high (I'll explain more later!). The next two instructions are 'nop' 'NO Operation', these simply take 1uS to execute, and do nothing, they are used to keep the outputs high for an extra 2uS. Following that we have a 'movlw 0x00' which moves 0x00 (0 decimal, 00000000 binary) to the W register, then we transfer them to the ports as before, this sets all 16 outputs low. The last 'goto Loop' instruction goes back and runs this section of the program again, and thus continues switching the port pins high then low. Tutorial 1.2 As you will have noticed from the first part, the LED's don't flash!. This isn't strictly true, they do flash - but much too quickly to be visible. As the PIC runs at 4MHz each instruction only takes 1uS to complete (except for 'jump' instructions, which take 2uS), this causes the LED's to flash tens of thousands of times per second - much too quick for our eyes!. This is a common 'problem' with PIC programming, it runs far faster than the world we are used to, and often we need to slow things down!. This second program also repeatedly switches all the output pins high and low, but this time introduces a time delay in between switching.
;Tutorial 1.2 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc org movlw movwf bsf movlw movwf movwf bcf Loop movlw movwf movwf nop nop call movlw movwf movwf call goto Delay d1 movlw movwf movlw movwf movlw movwf decfsz 0xff PORTA PORTB ;set all bits on ;the nop's make up the time taken by the goto ;giving a square wave output ;this waits for a while! 0x0000 0x07 CMCON STATUS, b'00000000' TRISB TRISA STATUS, RP0 ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;set PortA all outputs ;select bank 0
RP0
Delay 0x00 PORTA PORTB Delay Loop d'250' count1 0xC7 counta 0x01 countb counta, f
;set all bits off ;go back and do it again ;delay 250 ms (4 MHz clock)
Delay_0
This simply adds a couple of extra lines in the main program, 'call Delay', this is a call to a subroutine, a part of the program which executes and then returns to where it was called from. The routine is called twice, once after the LED's are turned on, and again after they are turned off. All the 'Delay' subroutine does is waste time, it loops round and round counting down until it finishes and returns. The extra part added at the beginning of the program (cblock to endc) allocates a couple of variables (count1 and count2) to two of the 'general purpose file registers', these start at address 0x20 - the cblock directive allocates the first variable to 0x20, and subsequent ones to sequential addresses. The 'Delay' routine delays 250mS, set in it's first line (movlw d'250') - the 'd' signifies a decimal number, easier to understand in this case - so we turn on the LED's, wait 250mS, turn off the LED's, wait another 250mS, and then repeat. This makes the LED's flash 2 times per second, and is now clearly visible. By altering the value d'250' you can alter the flash rate, however as it's an eight bit value it can't go any higher than d'255' (0xff hexadecimal). This routine introduces a new command 'decfsz' 'Decrement File and Skip on Zero', this decrements the file register specified (in this case either count2, or count1) and if the result equals zero skips over the next line. So this first section using it, d2 decfsz goto d2 count2 ,f
decrements count2, checks if it equals zero, and if not continues to the 'goto d2' line, which jumps back and decrements count2 again, this continues until count2 equals zero, then the 'goto d2' is skipped over and count1 is decrements in the same way, this time looping back to the start of the count2 loop, so it runs again. This is called a 'nested loop', the inner loop takes 1mS to run, and the outer loop calls the inner loop the number of times specified in count1 - so if you load 0x01 into count1 the entire Delay routine will take 1mS, in the example used we load d'250' (hexadecimal 0xfa) into count1, so it takes 250mS (1/4 of a second). The other new command introduced is 'retlw' 'RETurn from subroutine with Literal in W', this returns to where the subroutine was called from, and returns an optional value in the W register (it's not used to return a value here, so we assign 0x00 to it).
I mentioned above that the routine (as written) can only delay a maximum of 255mS, if we wanted a longer delay we could introduce another outer loop which calls 'Delay' the required number of times, but if we just wanted to make it flash once per second (instead of twice) we could simply duplicate the 'call Delay' lines, nop call call movlw Delay Delay 0x00 ;giving a square wave output ;this waits for a while! ;second delay call added
this gives a 500mS delay, leaving the LED on for 1/2 a second, by adding a second 'call Delay' to the 'off time' the LED will stay off for 1/2 a second as well. There's no requirement to keep these symmetrical, by using one 'call Delay' for the 'on time', and three for the 'off time' the LED will still flash once per second, but only stay on for 1/4 of the time (25/75) - this will only use 50% of the power that a 50/50 flash would consume. There are huge advantages in using subroutines, a common routine like this may be required many times throughout the program, by storing it once as a subroutine we save lots of space. Also, if you need to alter the routine for any reason, you only need to alter it in one place, and the change will affect all the calls to it. As your PIC programming skills develop you will find you create a library of useful little routines, these can be 'stitched together' to create larger programs - you'll see the 'Delay' subroutine appearing quite a lot in later tutorials, and other subroutines will also make many appearances - why keep reinventing the wheel?. Tutorial 1.3 The previous two examples simply turn all pins high or low, often we only want to affect a single pin, this is easily achieved with the 'bcf' and 'bsf' commands, 'bcf' 'Bit Clear File' clears a bit (sets it to 0), and 'bsf' 'Bit Set File' sets a bit (sets it to 1), the bit number ranges from 0 (LSB) to 7 (MSB). The following example flashes PortB, bit 7 (RB7) only, the rest of the pins remain at 0. ;Tutorial 1.3 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc
org movlw movwf bsf movlw movwf movwf bcf clrf clrf Loop bsf call bcf call goto Delay d1 movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end
0x0000 0x07 CMCON STATUS, b'00000000' TRISB TRISA STATUS, PORTA PORTB RP0
;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;set PortA all outputs ;select bank 0 ;set all outputs low
RP0
PORTB, 7 Delay PORTB, 7 Delay Loop d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
;turn on RB7 only! ;this waits for a while! ;turn off RB7 only!. ;go back and do it again ;delay 250 ms (4 MHz clock) ;delay 1mS
Delay_0
The 'movwf PORTA' and 'movwf PORTB' lines have been replaced by the single line 'bsf PORTB, 7' (to turn the LED on), and 'bcf PORTB, 7' (to turn the LED off). The associated 'movlw 0xff' and 'movlw 0x00' have also been removed, as they are no longer required, the two 'nop'
commands have also been removed, they are pretty superfluous - it's not worth adding 2uS to a routine that lasts 250mS!. Tutorial 1.4 If you want to use a different pin to RB7, you could simply alter the '7' on the relevant two lines to whichever pin you wanted, or if you wanted to use PortA, alter the PortB to PortA however, this requires changing two lines (and could be many more in a long program). So there's a better way! - this example (functionally identical to the previous one) assigns two constants at the beginning of the program, LED, and LEDPORT - these are assigned the values '7' and 'PORTB' respectively, and these constant names are used in the 'bsf' and 'bcf' lines. When the assembler is run it replaces all occurrences of the constant names with their values. By doing this is makes it MUCH! easier to change pin assignments, and it will be used more and more in the following tutorials. In fact, if you look at the 'P16F628.INC' which sets the defaults for the chip, this is simply a list of similar assignments which take a name and replace it with a number (PORTB is actually 0x06). ;Tutorial 1.4 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc LED Equ LEDPORT Equ org movlw movwf bsf movlw movwf movwf bcf clrf clrf 0x0000 0x07 CMCON STATUS, b'00000000' TRISB TRISA STATUS, PORTA PORTB RP0 7 PORTB ;set constant LED = 7 ;set constant LEDPORT = 'PORTB' ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;set PortA all outputs ;select bank 0 ;set all outputs low
RP0
Loop bsf call bcf call goto Delay d1 movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end LEDPORT, Delay LEDPORT, Delay Loop d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00 LED LED ;turn on RB7 only! ;this waits for a while! ;turn off RB7 only!. ;go back and do it again ;delay 250 ms (4 MHz clock) ;delay 1mS
Delay_0
This works exactly the same as the previous version, and if you compare the '.hex' files produced you will see that they are identical. Suggested exercises:
Alter the number of Delay calls, as suggested above, to produce asymmetrical flashing, both short flashes and long flashes. Change the pin assignments to use pins other than RB7 (requires LED board). Flash more than one (but less than 8) LED's at the same time - TIP: add extra 'bsf' and 'bcf' lines. Introduce extra flashing LED's, using different flashing rates -TIP: flash one on/off, then a different one on/off, adding different numbers of calls to Delay in order to have different flashing rates. If required change the value (d'250') used in the Delay subroutine.
Tutorial 1.5 This uses the LED board, and runs a single LED across the row of eight. ;Tutorial 1.5 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ LEDTRIS Equ org movlw movwf bsf movlw movwf bcf clrf Loop movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw b'10000000' LEDPORT Delay b'01000000' LEDPORT Delay b'00100000' LEDPORT Delay b'00010000' LEDPORT Delay b'00001000' 0x0000 0x07 CMCON STATUS, b'00000000' LEDTRIS STATUS, LEDPORT RP0 PORTB TRISB ;set constant LEDPORT = 'PORTB' ;set constant for TRIS register ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;select bank 0 ;set all outputs low
RP0
movwf call movlw movwf call movlw movwf call movlw movwf call goto Delay d1 movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end
LEDPORT Delay b'00000100' LEDPORT Delay b'00000010' LEDPORT Delay b'00000001' LEDPORT Delay Loop d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
;this waits for a while! ;go back and do it again ;delay 250 ms (4 MHz clock)
Delay_0
Tutorial 1.6 We can very easily modify this routine so the LED bounces from end to end, just add some more 'movlw' and 'movwf' with the relevant patterns in them - plus the 'call Delay' lines. ;Tutorial 1.6 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
count1 counta countb endc LEDPORT Equ LEDTRIS Equ org movlw movwf bsf movlw movwf bcf clrf Loop movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw movwf call b'10000000' LEDPORT Delay b'01000000' LEDPORT Delay b'00100000' LEDPORT Delay b'00010000' LEDPORT Delay b'00001000' LEDPORT Delay b'00000100' LEDPORT Delay b'00000010' LEDPORT Delay b'00000001' LEDPORT Delay 0x0000 0x07 CMCON STATUS, b'00000000' LEDTRIS STATUS, LEDPORT RP0 PORTB TRISB
;set constant LEDPORT = 'PORTB' ;set constant for TRIS register ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;select bank 0 ;set all outputs low
RP0
movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw movwf call movlw movwf call goto Delay d1 movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end
b'00000010' LEDPORT Delay b'00000100' LEDPORT Delay b'00001000' LEDPORT Delay b'00010000' LEDPORT Delay b'00100000' LEDPORT Delay b'01000000' LEDPORT Delay Loop d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
;this waits for a while! ;go back and do it again ;delay 250 ms (4 MHz clock)
Delay_0
Tutorial 1.7 Now while the previous two routines work perfectly well, and if this was all you wanted to do would be quite satisfactory, they are rather lengthy, crude and inelegant!. Tutorial 1.5 uses
24 lines within the loop, by introducing another PIC command we can make this smaller, and much more elegant. ;Tutorial 1.7 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ LEDTRIS Equ org movlw movwf bsf movlw movwf bcf clrf Start movlw movwf bcf call rrf btfss goto goto movlw movwf movlw movwf movlw 0x0000 0x07 CMCON STATUS, b'00000000' LEDTRIS STATUS, LEDPORT b'10000000' LEDPORT STATUS, C Delay LEDPORT, STATUS, C Loop Start d'250' count1 0xC7 counta 0x01 RP0 PORTB TRISB ;set constant LEDPORT = 'PORTB' ;set constant for TRIS register ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;select bank 0 ;set all outputs low ;set first LED lit
RP0
Loop
;clear carry bit ;this waits for a while! f ;check if last bit (1 rotated into Carry) ;go back and do it again
Delay d1
movwf Delay_0 decfsz goto decfsz goto decfsz goto retlw end
This introduces the 'rrf' 'Rotate Right File' command, this rotates the contents of the file register to the right (effectively dividing it by two). As the 'rrf' command rotates through the 'carry' bit we need to make sure that is cleared, we do this with the 'bcf STATUS, C' line. To check when we reach the end we use the line 'btfss STATUS, C' to check when the CARRY bit is set, this then restarts the bit sequence at bit 7 again. Tutorial 1.8 We can apply this to tutorial 1.6 as well, this time adding the 'rlf' 'Rotate Left File' command, this shifts the contents of the register to the left (effectively multiplying by two). ;Tutorial 1.8 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ LEDTRIS Equ org movlw movwf 0x0000 0x07 CMCON PORTB TRISB ;set constant LEDPORT = 'PORTB' ;set constant for TRIS register ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84)
bsf movlw movwf bcf clrf Start movlw movwf bcf call rrf btfss goto movlw movwf bcf call rlf btfss goto goto movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end
STATUS, b'00000000' LEDTRIS STATUS, LEDPORT b'10000000' LEDPORT STATUS, C Delay LEDPORT, STATUS, C Loop b'00000001' LEDPORT STATUS, C Delay LEDPORT, STATUS, C Loop2 Start d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
RP0
;select bank 1 ;set PortB all outputs ;select bank 0 ;set all outputs low ;set first LED lit
RP0
Loop
;clear carry bit ;this waits for a while! f ;check if last bit (1 rotated into Carry) ;go back and do it again ;set last LED lit
Loop2
;clear carry bit ;this waits for a while! f ;check if last bit (1 rotated into Carry) ;go back and do it again ;finished, back to first loop ;delay 250 ms (4 MHz clock)
Delay d1
Delay_0
Tutorial 1.9 So far we have used two different methods to produce a 'bouncing' LED, here's yet another version, this time using a data lookup table. ;Tutorial 1.9 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in table read routine ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ LEDTRIS Equ org movlw movwf bsf movlw movwf bcf clrf 0x0000 0x07 CMCON STATUS, b'00000000' LEDTRIS STATUS, LEDPORT RP0 PORTB TRISB ;set constant LEDPORT = 'PORTB' ;set constant for TRIS register ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;select bank 0 ;set all outputs low
RP0
Start Read
clrf movf call movwf call incf xorlw btfsc goto incf goto
count count, w Table LEDPORT Delay count, w d'14' STATUS, Z Start count, f Read
;check for last (14th) entry ;if start from beginning ;else do next
Table
ADDWF PCL, f retlw b'10000000' retlw b'01000000' retlw b'00100000' retlw b'00010000' retlw b'00001000' retlw b'00000100' retlw b'00000010' retlw b'00000001' retlw b'00000010' retlw b'00000100' retlw b'00001000' retlw b'00010000' retlw b'00100000' retlw b'01000000'
Delay d1
movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end
d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
Delay_0
This version introduces another new command 'addwf' 'ADD W to File', this is the command used to do adding on the PIC, now with the PIC only having 35 commands in it's RISC architecture some usual commands (like easy ways of reading data from a table) are absent, but there's always a way to do things. Back in tutorial 1.2 we introduced the 'retlw' command, and mentioned that we were not using the returned value, to make a data table we make use of this returned value. At the Label 'Start' we first zero a counter we are going to use 'clrf Count', this is then moved to the W register 'movf Count, w', and the Table subroutine called.
The first line in the subroutine is 'addwf PCL, f', this adds the contents of W (which has just been set to zero) to the PCL register, the PCL register is the Program Counter, it keeps count of where the program is, so adding zero to it moves to the next program line 'retlw b'10000000' which returns with b'10000000' in the W register, this is then moved to the LED's. Count is then incremented and the program loops back to call the table again, this time W holds 1, and this is added to the PCL register which jumps forward one more and returns the next entry in the table - this continues for the rest of the table entries. It should be pretty obvious that it wouldn't be a good idea to overrun the length of the table, it would cause the program counter to jump to the line after the table, which in this case is the Delay subroutine, this would introduce an extra delay, and return zero, putting all the LED's out. To avoid this we test the value of count, using 'xorlw d14' 'eXclusive Or Literal with W', this carries out an 'exclusive or' with the W register (to which we've just transferred the value of count) and the value 14, and sets the 'Z' 'Zero' flag if they are equal, we then test the Z flag, and if it's set jump back to the beginning and reset count to zero again. The big advantage of this version is that we can have any pattern we like in the table, it doesn't have to be just one LED, and we can easily alter the size of the table to add or remove entries, remembering to adjust the value used to check for the end of the table!. A common technique in this tables is to add an extra entry at the end, usually zero, and check for that rather than maintain a separate count, I haven't done this because doing it via a counter allows you to have a zero entry in the table - although this example doesn't do that you may wish to alter the patterns, and it could make a simple light sequencer for disco lights, and you may need a zero entry. Suggested exercises:
Using the table technique of 1.9, alter the pattern to provide a moving dark LED, with all others lit. Alter the length of the table, and add your own patterns, for a suggestion try starting with LED's at each end lit, and move them both to the middle and back to the outside.
This simple program turns the corresponding LED on, when the button opposite it is pressed, extinguishing all other LED's. ;Tutorial 2.1 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.)
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
LEDPORT Equ SWPORT Equ LEDTRIS Equ SW1 Equ SW2 Equ SW3 Equ SW4 Equ LED1 Equ LED2 Equ LED3 Equ LED4 Equ ;end of defines org movlw movwf bsf movlw movwf bcf clrf
;set constant LEDPORT = 'PORTA' ;set constant SWPORT = 'PORTA' ;set constant for TRIS register ;set constants for the switches
;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortA 4 inputs, 4 outputs ;select bank 0 ;set all outputs low
RP0
Loop
goto Switch1 clrf bsf retlw Switch2 clrf bsf retlw Switch3 clrf bsf retlw Switch4 clrf bsf retlw
Loop LEDPORT SWPORT,LED1 0x00 LEDPORT SWPORT,LED2 0x00 LEDPORT SWPORT,LED3 0x00 LEDPORT SWPORT,LED4 0x00 ;turn all LED's off ;turn LED1 on
end
As with the previous tutorials we first set things up, then the main program runs in a loop, the first thing the loop does is check switch SW1 with the 'btfss SWPORT, SW1' line, if the switch isn't pressed the input line is held high by the 10K pull-up resistor and it skips the next line. This takes it to the 'btfss SWPORT, SW2' line, where SW2 is similarly checked - this continues down checking all the switches and then loops back and checks them again. If a key is pressed, the relevant 'btfss' doesn't skip the next line, but instead calls a sub-routine to process the key press, each switch has it's own sub-routine. These sub-routines are very simple, they first 'clrf' the output port, turning all LED's off, and then use 'bsf' to turn on the corresponding LED, next the sub-routine exits via the 'retlw' instruction. As the switch is likely to be still held down, the same routine will be run again (and again, and again!) until you release the key, however for this simple application that isn't a problem and you can't even tell it's happening. Tutorial 2.2 - requires Main Board and Switch Board. This program toggles the corresponding LED on and off, when the button opposite it is pressed. It introduces the concept of 'de-bouncing' - a switch doesn't close immediately, it 'bounces' a little before it settles, this causes a series of fast keypresses which can cause chaos in operating a device. ;Tutorial 2.2 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc"
;tell assembler what chip we are using ;include the defaults for the chip
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ SWPORT Equ LEDTRIS Equ SW1 Equ SW2 Equ SW3 Equ SW4 Equ LED1 Equ LED2 Equ LED3 Equ LED4 Equ SWDel Set not 'Equ') ;end of defines org movlw movwf bsf movlw movwf bcf clrf 0x0000 0x07 CMCON STATUS, b'11110000' LEDTRIS STATUS, LEDPORT RP0 ;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortA 4 inputs, 4 outputs ;select bank 0 ;set all outputs low PORTA PORTA TRISA 7 6 5 4 3 2 1 0 Del50 ;set constant LEDPORT = 'PORTA' ;set constant SWPORT = 'PORTA' ;set constant for TRIS register ;set constants for the switches
RP0
Loop
btfss call goto Switch1 call btfsc retlw btfss goto goto LED1ON bsf call btfsc retlw goto LED1OFF bcf call btfsc retlw goto Switch2 call btfsc retlw btfss goto goto LED2ON bsf call btfsc retlw goto LED2OFF bcf call btfsc retlw goto Switch3 call btfsc
SWPORT,SW4 Switch4 Loop SWDel SWPORT,SW1 0x00 SWPORT,LED1 LED1ON LED1OFF LEDPORT, SWDel SWPORT,SW1 0x00 LED1ON LEDPORT, SWDel SWPORT,SW1 0x00 LED1OFF SWDel SWPORT,SW2 0x00 SWPORT,LED2 LED2ON LED2OFF LEDPORT, SWDel SWPORT,SW2 0x00 LED2ON LEDPORT, SWDel SWPORT,SW2 0x00 LED2OFF SWDel SWPORT,SW3 LED2 LED1 ;give switch time to stop bouncing ;check it's still pressed ;return is not ;see if LED1 is already lit
LED1
;give switch time to stop bouncing ;check it's still pressed ;return is not ;see if LED2 is already lit
LED2
retlw btfss goto goto LED3ON bsf call btfsc retlw goto LED3OFF bcf call btfsc retlw goto Switch4 call btfsc retlw btfss goto goto LED4ON bsf call btfsc retlw goto LED4OFF bcf call btfsc retlw goto
0x00 SWPORT,LED3 LED3ON LED3OFF LEDPORT, SWDel SWPORT,SW3 0x00 LED3ON LEDPORT, SWDel SWPORT,SW3 0x00 LED3OFF SWDel SWPORT,SW4 0x00 SWPORT,LED4 LED4ON LED4OFF LEDPORT, SWDel SWPORT,SW4 0x00 LED4ON LEDPORT, SWDel SWPORT,SW4 0x00 LED4OFF LED4 LED3
LED3
;give switch time to stop bouncing ;check it's still pressed ;return is not ;see if LED4 is already lit
LED4
;modified Delay routine, direct calls for specified times ;or load W and call Delay for a custom time. Del0 Del1 Del5 retlw movlw goto movlw goto 0x00 d'1' Delay d'5' Delay ;delay 0mS - return immediately ;delay 1mS ;delay 5mS
Del10
movlw goto Del20 movlw goto Del50 movlw goto Del100 movlw goto Del250 movlw Delay movwf d1 movlw movwf movlw movwf Delay_0 decfsz goto decfsz goto decfsz goto retlw end
d'10' Delay d'20' Delay d'50' Delay d'100' Delay d'250' count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
;delay 10mS ;delay 20mS ;delay 50mS ;delay 100mS ;delay 250 ms ;delay 1mS
In order to de-bounce the keypresses we delay for a short time, then check that the button is still pressed, the time delay is set by the variable SWDel, which is defined as Del50 in the defines section at the start of the program. I've extended the Delay routine to provide a selection of different delays (from 0mS to 250mS), called by simple 'call' instructions, the Delay routine itself can also be called directly - simply load the required delay into the W register and 'call Delay'. We then check to see if the corresponding LED is lit, with 'btfss SWPORT, LEDx', and jump to either 'LEDxON' or 'LEDxOFF', these routines are almost identical, the only difference being that the first turns the LED on, and the second turns it off. They first switch the LED, on or off, depending on which routine it is, and then delay again (calling SWDel as before), next they check to see if the button is still pressed, looping back around if it is. Once the key has been released the routine exits via the usual 'retlw' and returns to waiting for a keypress. I've used the variable SWDel (and provided the various delay times) so that you can easily try the effect of different delay times - in particular try setting SWDel to Del0, and see how the button pressing isn't reliable, you will probably find one of the buttons is worse than the others particularly if you use old switches, wear makes them bounce more. Tutorial 2.3 - requires Main Board, Switch Board, and LED Board.
Now for a more realistic example - this combines Tutorial 2.1 with Tutorial 1.9, the result is an LED sequencing program with four different patterns, selected by the four keys, with the key selected indicated by the corresponding LED. ;Tutorial 2.3 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count count1 counta countb
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in table read routine ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ LEDTRIS Equ SWPORT Equ SWTRIS Equ SW1 Equ SW2 Equ SW3 Equ SW4 Equ LED1 Equ LED2 Equ LED3 Equ LED4 Equ org movlw movwf bsf movlw movwf movlw movwf bcf clrf clrf bsf PORTB TRISB PORTA TRISA 7 6 5 4 3 2 1 0 0x0000 0x07 CMCON STATUS, b'00000000' LEDTRIS b'11110000' SWTRIS STATUS, LEDPORT SWPORT SWPORT,LED1 RP0 ;set constant LEDPORT = 'PORTB' ;set constant for TRIS register
;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;set PortA 4 inputs, 4 outputs RP0 ;select bank 0 ;set all outputs low ;set initial pattern
Start Read
clrf movf btfsc call btfsc call btfsc call btfsc call movwf call incf xorlw btfsc goto incf goto
count count, w SWPORT,LED1 Table1 SWPORT,LED2 Table2 SWPORT,LED3 Table3 SWPORT,LED4 Table4 LEDPORT Delay count, w d'14' STATUS, Z Start count, f Read
;set counter register to zero ;put counter value in W ;check which LED is lit ;and read the associated table
;check for last (14th) entry ;if start from beginning ;else do next
Table1
ADDWF PCL, f retlw b'10000000' retlw b'01000000' retlw b'00100000' retlw b'00010000' retlw b'00001000' retlw b'00000100' retlw b'00000010' retlw b'00000001' retlw b'00000010' retlw b'00000100' retlw b'00001000' retlw b'00010000' retlw b'00100000' retlw b'01000000' ADDWF PCL, f retlw b'11000000' retlw b'01100000' retlw b'00110000' retlw b'00011000' retlw b'00001100' retlw b'00000110'
Table2
b'00000011' b'00000011' b'00000110' b'00001100' b'00011000' b'00110000' b'01100000' b'11000000' ;data table for bit pattern
ADDWF PCL, f retlw b'01111111' retlw b'10111111' retlw b'11011111' retlw b'11101111' retlw b'11110111' retlw b'11111011' retlw b'11111101' retlw b'11111110' retlw b'11111101' retlw b'11111011' retlw b'11110111' retlw b'11101111' retlw b'11011111' retlw b'10111111' ADDWF PCL, f retlw b'00111111' retlw b'10011111' retlw b'11001111' retlw b'11100111' retlw b'11110011' retlw b'11111001' retlw b'11111100' retlw b'11111100' retlw b'11111001' retlw b'11110011' retlw b'11100111' retlw b'11001111' retlw b'10011111' retlw b'00111111' SWPORT,SW1 Switch1 SWPORT,SW2
Table4
call btfss call btfss call retlw Switch1 clrf bsf retlw Switch2 clrf bsf retlw Switch3 clrf bsf retlw Switch4 clrf bsf retlw
Switch2 SWPORT,SW3 Switch3 SWPORT,SW4 Switch4 0x00 SWPORT SWPORT,LED1 0x00 SWPORT SWPORT,LED2 0x00 SWPORT SWPORT,LED3 0x00 SWPORT SWPORT,LED4 0x00 ;turn all LED's off ;turn LED1 on
Delay d1
movlw movwf call movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw end
d'250' count1 ChkKeys 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
Delay_0
The main differences here are in the Delay routine, which now has a call to check the keys every milli-second, and the main loop, where it selects one of four tables to read, depending on the settings of flag bits which are set according to which key was last pressed. Tutorial 2.4 - requires Main Board, Switch Board, and LED Board. Very similar to the last tutorial, except this one combines Tutorials 2.2 and 2.3 with Tutorial 1.9, the result is an LED sequencing program with three different patterns, selected by three of the keys, with the key selected indicated by the corresponding LED - the difference comes with the fourth switch, this selects slow or fast speeds, with the fast speed being indicated by a toggled LED. ;Tutorial 2.4 - Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" __config 0x3D18 etc.) cblock 0x20 count count1 count2 counta countb countc countd speed
;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type
;start of general purpose registers ;used in table read routine ;used in delay routine ;used in delay routine ;used in delay routine
endc LEDPORT Equ LEDTRIS Equ SWPORT Equ SWTRIS Equ SW1 Equ SW2 Equ SW3 Equ SW4 Equ LED1 Equ LED2 Equ LED3 Equ LED4 Equ PORTB TRISB PORTA TRISA 7 6 5 4 3 2 1 0 ;set constant LEDPORT = 'PORTB' ;set constant for TRIS register
SWDel
Set
Del50
org movlw movwf bsf movlw movwf movlw movwf bcf clrf clrf bsf movlw movwf
0x0000 0x07 CMCON STATUS, b'00000000' LEDTRIS b'11110000' SWTRIS STATUS, LEDPORT SWPORT SWPORT,LED1 d'250' speed RP0
;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running ;turn comparators off (make it like a 16F84) ;select bank 1 ;set PortB all outputs ;set PortA 4 inputs, 4 outputs RP0 ;select bank 0 ;set all outputs low ;make sure all LED's are off ;and turn initial LED on ;set initial speed
Start Read
clrf movf btfsc call btfsc call btfsc call movwf call incf xorlw btfsc goto incf goto
count count, w SWPORT,LED1 Table1 SWPORT,LED2 Table2 SWPORT,LED3 Table3 LEDPORT DelVar count, w d'14' STATUS, Z Start count, f Read
;set counter register to zero ;put counter value in W ;check which LED is on ;and call the associated table
;check for last (14th) entry ;if start from beginning ;else do next
Table1
ADDWF PCL, f retlw b'10000000' retlw b'01000000' retlw b'00100000' retlw b'00010000' retlw b'00001000' retlw b'00000100'
b'00000010' b'00000001' b'00000010' b'00000100' b'00001000' b'00010000' b'00100000' b'01000000' ;data table for bit pattern
ADDWF PCL, f retlw b'11000000' retlw b'01100000' retlw b'00110000' retlw b'00011000' retlw b'00001100' retlw b'00000110' retlw b'00000011' retlw b'00000011' retlw b'00000110' retlw b'00001100' retlw b'00011000' retlw b'00110000' retlw b'01100000' retlw b'11000000' ADDWF PCL, f retlw b'01111111' retlw b'10111111' retlw b'11011111' retlw b'11101111' retlw b'11110111' retlw b'11111011' retlw b'11111101' retlw b'11111110' retlw b'11111101' retlw b'11111011' retlw b'11110111' retlw b'11101111' retlw b'11011111' retlw b'10111111' SWPORT,SW1 Switch1 SWPORT,SW2
Table3
call btfss call btfss call retlw Switch1 bcf bcf bsf retlw Switch2 bcf bcf bsf retlw Switch3 bcf bcf bsf retlw Switch4 call btfsc retlw btfss goto goto FASTON bsf movlw movwf call btfsc retlw goto FASTOFF bcf movlw movwf call btfsc retlw goto
Switch2 SWPORT,SW3 Switch3 SWPORT,SW4 Switch4 0x00 SWPORT,LED2 SWPORT,LED3 SWPORT,LED1 0x00 SWPORT,LED1 SWPORT,LED3 SWPORT,LED2 0x00 SWPORT,LED1 SWPORT,LED2 SWPORT,LED3 0x00 SWDel SWPORT,SW4 0x00 SWPORT,LED4 FASTON FASTOFF SWPORT,LED4 d'80' speed SWDel SWPORT,SW4 0x00 FASTON SWPORT,LED4 d'250' speed SWDel SWPORT,SW4 0x00 FASTOFF ;turn unselected LED's off ;turn unselected LED's off ;turn LED1 on
;turn unselected LED's off ;turn unselected LED's off ;turn LED2 on
;turn unselected LED's off ;turn unselected LED's off ;turn LED3 on
;give switch time to stop bouncing ;check it's still pressed ;return is not ;see if LED4 is already lit
DelVar d1
movfw movwf call movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw
speed count1 ChkKeys 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00
Delay_0
;use separate delay routines, as Del50 is called from ChkKeys ;which is called from within DelVar Del50 d3 movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw d'50' count2 0xC7 countc 0x01 countd countc, f $+2 countd, f Delay_1 count2 ,f d3 0x00 ;delay 50mS ;delay 1mS
Delay_1
end
0=Decrement 0=Display Shift off* 0=Display Off* 0=Cursor Underline Off* 0=Cursor Blink Off* 0=Cursor Move
R/L: 1=Right Shift 8/4: 1=8 bit interface* 2/1: 1=2 line mode 10/7: 1=5x10 dot format
0=Left Shift 0=4 bit interface 0=1 line mode* 0=5x7 dot format*
*=initialisation setting
x=don't care
This table shows the command codes for the LCD module, it was taken from an excellent LCD tutorial that was published in the UK magazine 'Everyday Practical Electronics' February 1997 - it can be downloaded as a PDF file from the EPE website. The following routines are an amalgamation of a number of routines from various sources (including the previously mentioned tutorial), plus various parts of my own, the result is a set of reliable, easy to use,
routines which work well (at least in my opinion!). Tutorial 3.1 - requires Main Board and LCD Board. This program displays a text message on the LCD module, it consists mostly of subroutines for using the LCD module. ;LCD text demo - 4 bit mode ;Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18 etc.) ;tell assembler what chip we are using ;include the defaults for the chip ;suppress bank selection messages ;sets the configuration settings (oscillator type
-302
cblock
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine ;temporary storage ;temp store for 4 bit mode
endc LCD_PORT LCD_TRISEqu LCD_RS LCD_RW LCD_E Equ TRISA Equ Equ Equ org movlw movwf 16F84) Initialise clrf count PORTA 0x04 0x06 0x07 0x0000 0x07 CMCON ;LCD handshake lines
clrf clrf
PORTA PORTB
SetPorts bsf
STATUS, RP0 movlw 0x00 movwf LCD_TRIS bcf STATUS, call Delay100
call
LCD_Init
;setup LCD
Message
clrf movf call xorlw btfsc goto call call incf goto call clrf movf call xorlw btfsc goto call incf goto
count count, w Text 0x00 STATUS, Z NextMessage LCD_Char Delay255 count, f Message LCD_Line2 count count, w Text2 0x00 STATUS, Z EndMessage LCD_Char count, f Message2
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
NextMessage
;move to 2nd row, first column ;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
Message2
;Subroutines and text tables ;LCD routines ;Initialise LCD LCD_Init movlw
;Set 4 bit mode LCD_Cmd 0x28 LCD_Cmd 0x06 LCD_Cmd 0x0d LCD_Cmd LCD_Clr 0x00 ;clear display ;Set display shift
command call call retlw ; command set routine LCD_Cmd swapf andlw movwf bcf call movf andlw movwf bcf call call retlw LCD_CharD LCD_Char addlw movwf
movwf templcd templcd, w ;send upper nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 0 Pulse_e ;Pulse the E line high templcd, w ;send lower nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 0 Pulse_e ;Pulse the E line high Delay5 0x00 0x30 templcd
swapf andlw movwf bsf call movf andlw movwf bsf call call retlw LCD_Line1 movlw call retlw movlw call retlw addlw call retlw addlw call retlw movlw call retlw LCD_CurOff command movlw call retlw LCD_Clr movlw call retlw
templcd, w ;send upper nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 1 Pulse_e ;Pulse the E line high templcd, w ;send lower nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 1 Pulse_e ;Pulse the E line high Delay5 0x00 0x80 LCD_Cmd 0x00 0xc0 LCD_Cmd 0x00 0x80 LCD_Cmd 0x00 0xc0 LCD_Cmd 0x00 0x0d LCD_Cmd 0x00 0x0c LCD_Cmd 0x00 0x01 LCD_Cmd 0x00 ;Clear display ;Set display on/off and cursor ;move to 1st row, first column
LCD_Line2
LCD_Line1W
LCD_Line2W
LCD_CurOn command
LCD_HEX
movwf swapf andlw call call movf andlw call call retlw 0xff goto d'100' goto movlw goto movlw goto movlw movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw
tmp1 tmp1, w 0x0f HEX_Table LCD_Char tmp1, w 0x0f HEX_Table LCD_Char 0x00 ;delay 255 mS d0 ;delay 100mS d0 d'50' d0 d'20' d0 0x05 count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00 LCD_PORT, LCD_E LCD_PORT, LCD_E 0x00 ;delay 50mS ;delay 20mS ;delay 5.000 ms (4 MHz clock) ;delay 1mS
Delay_0
Pulse_e
HEX_Table RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW
ADDWF PCL 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x41 0x42 0x43 0x44 0x45 0x46
,f
Text
Text2 RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW
ADDWF PCL, f 'R' 'e' 'a' 'd' 'y' '.' '.' '.' 0x00
end
As usual, first we need to set things up, after the normal variable declarations and port
setting we reach 'call LCD_Init', this sets up the LCD module. It first waits for 100mS to give the module plenty of time to settle down, we then set it to 4 bit mode (0x20) and set the various options how we want them - in this case, Display Shift is On (0x28), Character Entry Mode is Increment (0x06), and Block Cursor On (0x0D). Once the LCD is setup, we can then start to send data to it, this is read from a table, exactly the same as the LED sequencer in the earlier tutorials - except this time we send the data to the LCD module (using LCD_Char) and use a 0x00 to mark the end of the table, thus removing the need to maintain a count of the characters printed. Once the first line is displayed we then sent a command to move to the second line (using call LCD_Line2), and then print the second line from another table. After that we enter an endless loop to leave the display as it is. This program introduces a new use of the 'goto' command, 'goto $+2' - '$' is an MPASM arithmetic operator, and uses the current value of the program counter, so 'goto $+2' means jump to the line after the next one - 'goto $+1' jumps to the next line, and may seem pretty useless (as the program was going to be there next anyway), but it can be extremely useful. A program branch instruction (like goto) uses two instruction cycles, whereas other instructions only take one, so if you use a 'nop' in a program it takes 1uS to execute, and carries on from the next line - however, if you use 'goto $+1' it still carries on from the next line, but now takes 2uS. You'll notice more use of the 'goto $' construction in later tutorials, if you are checking an input pin and waiting for it to change state you can use 'goto $-1' to jump back to the previous line, this saves allocating a label to the line that tests the condition. This is a table of the LCD subroutines provided in these programs, you can easily add more if you wish - for instance to set a line cursor rather than a block one, if you find you are using a particular feature a lot you may as well make a subroutine for it. LCD Subroutines LCD_Init LCD_Cmd LCD_CharD LCD_Char LCD_Line1 LCD_Line2 Initialise LCD Module Sent a command to the LCD Add 0x30 to a byte and send to the LCD (to display numbers as ASCII) Send the character in W to the LCD Go to start of line 1 Go to start of line 2
LCD_Line1W Go to line 1 column W LCD_Line2W Go to line 2 column W LCD_CurOn Turn block cursor on LCD_CurOff Turn block cursor off LCD_Clr LCD_HEX Clear the display Display the value in W as Hexadecimal
Tutorial 3.2 - requires Main Board and LCD Board. This program displays a text message on the top line and a running 16 bit counter on the bottom line, with the values displayed in both decimal and hexadecimal , it consists mostly of the previous subroutines for using the LCD module, plus an extra one for converting from 16 bit hexadecimal to decimal. ;LCD 16 bit counter ;Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18 etc.) ;tell assembler what chip we are using ;include the defaults for the chip ;suppress bank selection messages ;sets the configuration settings (oscillator type
-302
cblock
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine ;temporary storage ;temp store for 4 bit mode
NumL NumH TenK Thou Hund Tens Ones endc LCD_PORT LCD_TRISEqu LCD_RS Equ TRISA Equ PORTA 0x04
LCD_RW LCD_E
SetPorts bsf
STATUS, RP0 movlw 0x00 movwf LCD_TRIS bcf STATUS, call LCD_Init
;setup LCD
Message
clrf movf call xorlw btfsc goto call incf goto call call movf call movf call movf
count count, w Text 0x00 STATUS, Z NextMessage LCD_Char count, f Message LCD_Line2 Convert TenK, w LCD_CharD Thou, w LCD_CharD Hund, w
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
NextMessage
;move to 2nd row, first column ;convert to decimal ;display decimal characters ;using LCD_CharD to convert to ASCII
Next
call movf call movf call movlw call movf call movf call incfsz goto incf call goto
LCD_CharD Tens, w LCD_CharD Ones, w LCD_CharD '' LCD_Char NumH, w LCD_HEX NumL, w LCD_HEX NumL, f Next NumH, f Delay255 NextMessage
;Subroutines and text tables ;LCD routines ;Initialise LCD LCD_Init call
;wait for LCD to settle 0x20 LCD_Cmd 0x28 LCD_Cmd 0x06 LCD_Cmd 0x0c LCD_Cmd LCD_Clr 0x00 ;Set 4 bit mode
;Set display on/off and cursor ;Set cursor off ;clear display
LCD_Cmd swapf andlw movwf bcf call movf andlw movwf bcf call call retlw LCD_CharD LCD_Char addlw movwf swapf andlw movwf bsf call movf andlw movwf bsf call call retlw LCD_Line1 movlw call retlw movlw call retlw addlw call retlw
movwf templcd templcd, w ;send upper nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 0 Pulse_e ;Pulse the E line high templcd, w ;send lower nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 0 Pulse_e ;Pulse the E line high Delay5 0x00 0x30 ;add 0x30 to convert to ASCII templcd templcd, w ;send upper nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 1 Pulse_e ;Pulse the E line high templcd, w ;send lower nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 1 Pulse_e ;Pulse the E line high Delay5 0x00 0x80 LCD_Cmd 0x00 0xc0 LCD_Cmd 0x00 0x80 LCD_Cmd 0x00 ;move to 1st row, first column
LCD_Line2
LCD_Line1W
LCD_Line2W
0xc0 LCD_Cmd 0x00 0x0d LCD_Cmd 0x00 0x0c LCD_Cmd 0x00 0x01 LCD_Cmd 0x00 tmp1 tmp1, w 0x0f HEX_Table LCD_Char tmp1, w 0x0f HEX_Table LCD_Char 0x00 ;delay 255 mS d0 ;delay 100mS d0 d'50' d0 d'20' d0 0x05 count1 0xC7 counta 0x01 countb
LCD_CurOn command
LCD_CurOff command
LCD_Clr
movlw call retlw movwf swapf andlw call call movf andlw call call retlw 0xff goto d'100' goto movlw goto movlw goto movlw movwf movlw movwf movlw movwf
;Clear display
LCD_HEX
;delay 50mS ;delay 20mS ;delay 5.000 ms (4 MHz clock) ;delay 1mS
Delay_0 decfsz goto decfsz goto decfsz goto retlw Pulse_e bsf nop bcf retlw counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00 LCD_PORT, LCD_E LCD_PORT, LCD_E 0x00
;end of LCD routines HEX_Table RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW ADDWF PCL 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x41 0x42 0x43 0x44 0x45 0x46 ,f
Text
retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
'' 'C' 'o' 'u' 'n' 't' 'e' 'r' '.' 0x00
;This routine downloaded from http://www.piclist.com Convert: ; Takes number in NumH:NumL ; Returns decimal in ; TenK:Thou:Hund:Tens:Ones swapf NumH, w iorlw B'11110000' movwf Thou addwf Thou,f addlw 0XE2 movwf Hund addlw 0X32 movwf Ones movf NumH,w andlw 0X0F addwf Hund,f addwf Hund,f addwf Ones,f addlw 0XE9 movwf Tens addwf Tens,f addwf Tens,f swapf andlw addwf addwf NumL,w 0X0F Tens,f Ones,f
movf NumL,w andlw 0X0F addwf Ones,f rlf Thou,f movlw 0X07 movwf 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 Lb1: addwf Ones,f decf Tens,f btfss 3,0 goto Lb1 Lb2: addwf Tens,f decf Hund,f btfss 3,0 goto Lb2 Lb3: addwf Hund,f decf Thou,f btfss 3,0 goto Lb3 Lb4: addwf Thou,f decf TenK,f btfss 3,0 goto Lb4 retlw 0x00 ; Ten
end
Tutorial 3.3 - requires Main Board and LCD Board. This program displays a text message on the top line and a running 16 bit counter on the bottom line, just as the last example, however, instead of using the Delay calls this version waits until the LCD Busy flag is clear. The LCD module takes time to carry out commands, these times vary, and the previous tutorials used a delay more than long enough to 'make sure' however, the modules have the capability of signalling when they are ready, this version uses that facility and avoids any unnecessary delays. I've also used the LCD_Line2W routine to position the numbers further to the right and demonstrate the use of the routine, another slight change is that the tables have been moved to the beginning of program memory, this was done because it's important that tables don't cross a 256 byte boundary, so putting them at the start avoids this. ;LCD 16 bit counter - using LCD Busy line ;Nigel Goodwin 2002 LIST p=16F628 include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18 etc.) ;tell assembler what chip we are using ;include the defaults for the chip ;suppress bank selection messages ;sets the configuration settings (oscillator type
-302
cblock
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine ;temporary storage ;temp store for 4 bit mode
Hund Tens Ones endc LCD_PORT LCD_TRISEqu LCD_RS LCD_RW LCD_E Equ TRISA Equ Equ Equ org goto HEX_Table RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW RETLW PORTA 0x04 0x06 0x07 0x0000 Start ,f ;LCD handshake lines
ADDWF PCL 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x41 0x42 0x43 0x44 0x45 0x46
Text
addwf retlw retlw retlw retlw retlw retlw retlw retlw retlw
PCL, f '1' '6' '' 'B' 'i' 't' '' 'C' 'o'
retlw retlw retlw retlw retlw retlw retlw Start 16F84) Initialise clrf count clrf clrf clrf clrf movlw movwf
SetPorts bsf
RP0
;select bank 0
;setup LCD
Message
clrf movf call xorlw btfsc goto call incf goto movlw call call
count count, w Text 0x00 STATUS, Z NextMessage LCD_Char count, f Message d'2' LCD_Line2W Convert
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
NextMessage
Next
movf call movf call movf call movf call movf call movlw call movf call movf call incfsz goto incf call goto
TenK, w LCD_CharD Thou, w LCD_CharD Hund, w LCD_CharD Tens, w LCD_CharD Ones, w LCD_CharD '' LCD_Char NumH, w LCD_HEX NumL, w LCD_HEX NumL, f Next NumH, f Delay255 NextMessage
;Subroutines and text tables ;LCD routines ;Initialise LCD LCD_Init call
LCD_Busy movlw call movlw call movlw call movlw 0x20 LCD_Cmd 0x28 LCD_Cmd 0x06 LCD_Cmd 0x0c LCD_Cmd
command call
call retlw ; command set routine LCD_Cmd swapf andlw movwf bcf call movf andlw movwf bcf call call retlw LCD_CharD LCD_Char addlw movwf swapf andlw movwf bsf call movf andlw movwf bsf call call retlw LCD_Line1 movlw call retlw movlw call retlw
LCD_Clr 0x00
;clear display
movwf templcd templcd, w ;send upper nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 0 Pulse_e ;Pulse the E line high templcd, w ;send lower nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 0 Pulse_e ;Pulse the E line high LCD_Busy 0x00 0x30 ;add 0x30 to convert to ASCII templcd templcd, w ;send upper nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 1 Pulse_e ;Pulse the E line high templcd, w ;send lower nibble 0x0f ;clear upper 4 bits of W LCD_PORT LCD_PORT, LCD_RS ;RS line to 1 Pulse_e ;Pulse the E line high LCD_Busy 0x00 0x80 LCD_Cmd 0x00 0xc0 LCD_Cmd 0x00 ;move to 1st row, first column
LCD_Line2
LCD_Line1W
0x80 LCD_Cmd 0x00 0xc0 LCD_Cmd 0x00 0x0d LCD_Cmd 0x00 0x0c LCD_Cmd 0x00 0x01 LCD_Cmd 0x00 tmp1 tmp1, w 0x0f HEX_Table LCD_Char tmp1, w 0x0f HEX_Table LCD_Char 0x00
LCD_Line2W
LCD_CurOn command
LCD_CurOff command
LCD_Clr
movlw call retlw movwf swapf andlw call call movf andlw call call retlw 0xff goto d'100' goto movlw goto movlw goto movlw
;Clear display
LCD_HEX
;delay 255 mS d0 ;delay 100mS d0 d'50' d0 d'20' d0 0x05 ;delay 50mS ;delay 20mS ;delay 5.000 ms (4 MHz clock)
d0 d1
movwf movlw movwf movlw movwf decfsz goto decfsz goto decfsz goto retlw
count1 0xC7 counta 0x01 countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00 LCD_PORT, LCD_E LCD_PORT, LCD_E 0x00
;delay 1mS
Delay_0
Pulse_e
LCD_Busy bsf movlw movwf bcf bcf bsf bsf swapf bcf movwf bsf bcf btfsc goto bcf bsf movlw movwf bcf return ;end of LCD routines STATUS, RP0 0x0f LCD_TRIS STATUS, RP0 LCD_PORT, LCD_RS LCD_PORT, LCD_RW LCD_PORT, LCD_E LCD_PORT, w LCD_PORT, LCD_E templcd2 LCD_PORT, LCD_E LCD_PORT, LCD_E templcd2, 7 LCD_Busy LCD_PORT, LCD_RW STATUS, RP0 0x00 LCD_TRIS STATUS, RP0 ;set bank 1 ;set Port for input ;set bank 0 ;set LCD for command mode ;setup to read busy flag ;read upper nibble (busy flag)
;dummy read of lower nibble ;check busy flag, high = busy ;if busy check again ;set bank 1 ;set Port for output ;set bank 0
;This routine downloaded from http://www.piclist.com Convert: ; Takes number in NumH:NumL ; Returns decimal in ; TenK:Thou:Hund:Tens:Ones swapf NumH, w iorlw B'11110000' movwf Thou addwf Thou,f addlw 0XE2 movwf Hund addlw 0X32 movwf Ones movf NumH,w andlw 0X0F addwf Hund,f addwf Hund,f addwf Ones,f addlw 0XE9 movwf Tens addwf Tens,f addwf Tens,f swapf andlw addwf addwf NumL,w 0X0F Tens,f Ones,f
rlf Tens,f rlf Ones,f comf Ones,f rlf Ones,f movf NumL,w andlw 0X0F addwf Ones,f rlf Thou,f movlw 0X07 movwf 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 Lb1: addwf Ones,f decf Tens,f btfss 3,0 goto Lb1 Lb2: addwf Tens,f decf Hund,f btfss 3,0 goto Lb2 Lb3: addwf Hund,f decf Thou,f btfss 3,0 goto Lb3 Lb4: addwf Thou,f decf TenK,f btfss 3,0 goto Lb4 retlw 0x00 ; Ten
end
For this tutorial you require the Main Board, LCD Board and the Joystick Board, as written
the tutorials use the LCD Board on PortA and the Joystick Board on PortB - although these could easily be swapped over, as the Joystick Board doesn't use either of the two 'difficult' pins for PortA, pins 4 and 5. Download zipped tutorial files. Tutorial 4.1 - requires Main Board, LCD Board and Joystick Board. This program uses the LCD module to give a hexadecimal display of the values of the X and Y resistors in the joystick, and an indication if the buttons are pressed or not. It uses the LCD subroutines from the previous tutorial, and subroutines written to read the joystick. ;Joystick routines with LCD display ;Nigel Goodwin 2002
LIST
p=16F628
;tell assembler what chip we are using ;include the defaults for the chip -302 ;suppress bank selection messages
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine
tmp1
templcd
;temp store for 4 bit mode templcd2 HiX LoX HiY LoY Flags endc ;result for Y pot ;result for X pot
Equ Equ
Equ
0x07
Equ Equ
Equ Equ
0x03 0x07
Equ
Equ
0x02
org goto
0x0000 Start
;TABLES - moved to start of program memory to avoid paging problems, ;a table must not cross a 256 byte boundary. HEX_Table ADDWF PCL RETLW 0x30 RETLW 0x31 RETLW 0x32 RETLW 0x33 RETLW 0x34 RETLW 0x35 RETLW 0x36 RETLW 0x37 RETLW 0x38 RETLW 0x39 RETLW 0x41 RETLW 0x42 ,f
Xtext
addwf PCL, f retlw 'J' retlw 'o' retlw 'y' retlw '-' retlw 'X' retlw ' ' retlw 0x00
Ytext
addwf PCL, f retlw 'J' retlw 'o' retlw 'y' retlw '-' retlw 'Y' retlw ' '
retlw 0x00
presstext
addwf PCL, f retlw 'C' retlw 'l' retlw 'o' retlw 's' retlw 'e' retlw 0x00
nopresstext
addwf PCL, f retlw 'O' retlw 'p' retlw 'e' retlw 'n' retlw ' ' retlw 0x00
;end of tables
16F84)
Initialise
bcf
Flags, SW2_Flag
SetPorts
bsf
STATUS,
RP0
;select bank 1
movlw 0x00
movlw 0xff
bcf
STATUS,
RP0
;select bank 0
call call
;discharge timing capacitors ;wait for LCD to settle ;setup LCD module
call
Main
call call
movf HiX,
movf LoX,
LCD_Char DisplaySW1
call call
;set to second line ;display Joy-Y string ;display high byte LCD_HEX ;display low byte LCD_HEX movlw ' '
movf HiY,
movf LoY,
w call
call
LCD_Char
DisplaySW1
retlw 0x00
DisplaySW2
retlw 0x00
XString Mess1
clrf
count
movf count, w
call
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess1
clrf
count
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess2
clrf
count
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess3
count
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess4
;LCD routines
;Initialise LCD LCD_Init movlw 0x20 call ;Set 4 bit mode LCD_Cmd
movlw 0x0d
call
LCD_Clr
;clear display
retlw 0x00
; command set routine LCD_Cmd swapf templcd, andlw 0x0f movwf templcd w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT bcf call Pulse_e LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high
movf templcd,
andlw 0x0f
LCD_PORT, LCD_RS
call
Delay5
retlw 0x00
LCD_CharD LCD_Char
;convert numbers to ASCII values ;display character in W register w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT
LCD_PORT, LCD_RS
movwf LCD_PORT bsf call Pulse_e call Delay5 LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high
retlw 0x00
LCD_Line1
retlw 0x00
LCD_Line2
retlw 0x00
LCD_CurOn
retlw 0x00
LCD_CurOff
retlw 0x00
LCD_Clr
;Clear display
retlw 0x00
LCD_HEX
movwf tmp1
retlw 0x00
Pulse_e
bsf
bcf
;joystick routines
;discharge capacitor ;make PotY an output ;discharge capacitor RP0 ;select bank 0
retlw 0x00
ReadX clrf HiX clrf bsf bsf bcf STATUS, JOY_TRIS, STATUS, PotX ;reset counter registers LoX RP0 ;select bank 1
x1 btfsc JOY_PORT, PotX goto ;keep going until input high EndX
EndX bsf
STATUS,
RP0
;select bank 1
PotX PotX
retlw 0x00
ReadY clrf HiY clrf call bsf bsf bcf STATUS, JOY_TRIS, STATUS, PotY ;reset counter registers LoY Delay5 RP0 ;select bank 1
y1 btfsc JOY_PORT, PotY goto ;keep going until input high EndY
EndY bsf
STATUS,
RP0
;select bank 1
PotY PotY
retlw 0x00
ReadSW
SW1
SW2
SW1
SW2
retlw 0x00
Sw1On bsf
Sw2On bsf
Sw1Off
bcf
Flags, SW1_Flag
retlw 0x00
Sw2Off
bcf
Flags, SW2_Flag
retlw 0x00
;Delay routines
Delay255
Delay100
Delay50
Delay20
Delay5
movlw 0x05 d0
;delay 5.000 ms (4 MHz clock) movwf count1 ;delay 1mS movwf counta movlw 0x01
d1
movlw 0xC7
retlw 0x00
end
Tutorial 4.2 - requires Main Board, LCD Board and Joystick Board. This second program is very similar to the previous one, except it uses a different method of counting the time taken to charge the capacitor. Whereas the first example used a simple software counter, this one uses a hardware timer for the lower byte, and an interrupt driven routine for the upper byte. This has the major advantage of being more accurate, giving 1uS resolution. ;Joystick routines with LCD display ;Nigel Goodwin 2002
LIST
p=16F628
;tell assembler what chip we are using ;include the defaults for the chip -302 ;suppress bank selection messages
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine ;temporary storage tmp2
templcd
;temp store for 4 bit mode templcd2 HiX LoX HiY ;result for Y pot ;result for X pot
Equ Equ
Equ
0x07
JOY_PORT JOY_TRIS PotX Equ 0x06 PotY SW1 SW2 SW1_Flag Equ 0x01 SW2_Flag
Equ Equ
Equ
0x02
org goto
0x0000 Start
0x0004 T0IF f
Initialise
bcf
Flags, SW2_Flag
SetPorts
bsf
STATUS,
RP0
;select bank 1
movlw 0x00
movwf LCD_TRIS movlw 0xff ;make all joystick pins inputs movwf JOY_TRIS MOVLW 0x88 ;assign prescaler to watchdog MOVWF OPTION_REG bcf STATUS, CLRF BSF INTCON , T0IE RP0 INTCON ;enable timer interrupts ;select bank 0
call call
;discharge timing capacitors ;wait for LCD to settle ;setup LCD module
call
Main call call call ReadX ReadY ReadSW ;read X joystick ;read Y joystick ;read switches
call call
movf HiX,
movf LoX,
LCD_Char DisplaySW1
call call
;set to second line ;display Joy-Y string ;display high byte LCD_HEX ;display low byte LCD_HEX movlw ' '
movf HiY,
movf LoY,
w call
DisplaySW1
retlw 0x00
DisplaySW2
retlw 0x00
XString Mess1
clrf
count
;set counter register to zero ;put counter value in W bsf PCLATH, 1 ;get a character from the text table bcf PCLATH, 1 ;is it a zero?
movf count, w
call
Xtext
xorlw 0x00
btfsc STATUS, Z retlw 0x00 call incf ;return when finished LCD_Char count, f
goto
Mess1
YString Mess2
clrf
count
;set counter register to zero ;put counter value in W bsf PCLATH, 1 ;get a character from the text table bcf PCLATH, 1 ;is it a zero?
movf count, w
call
Ytext
xorlw 0x00
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess2
Press_Str Mess3
clrf
count
;set counter register to zero ;put counter value in W bsf PCLATH, 1 ;get a character from the text table bcf PCLATH, 1 ;is it a zero?
movf count, w
call
presstext
xorlw 0x00
count
;set counter register to zero ;put counter value in W bsf PCLATH, 1 ;get a character from the text table bcf PCLATH, 1 ;is it a zero?
movf count, w
call
nopresstext
xorlw 0x00
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess4
;LCD routines
;Initialise LCD LCD_Init movlw 0x20 call ;Set 4 bit mode LCD_Cmd
movlw 0x0d
call
LCD_Clr
;clear display
retlw 0x00
; command set routine LCD_Cmd swapf templcd, andlw 0x0f movwf templcd w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT bcf call Pulse_e LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high
movf templcd,
andlw 0x0f
LCD_PORT, LCD_RS
call
Delay5
retlw 0x00
LCD_CharD LCD_Char
;convert numbers to ASCII values ;display character in W register w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT
LCD_PORT, LCD_RS
movwf LCD_PORT bsf call Pulse_e call Delay5 LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high
retlw 0x00
LCD_Line1
retlw 0x00
LCD_Line2
retlw 0x00
LCD_CurOn
retlw 0x00
LCD_CurOff
retlw 0x00
LCD_Clr
;Clear display
retlw 0x00
LCD_HEX
movwf tmp1
swapf tmp1, w andlw 0x0f bsf call bcf call PCLATH, 1 HEX_Table PCLATH, 1 LCD_Char
movf tmp1, w andlw 0x0f bsf call bcf call PCLATH, 1 HEX_Table PCLATH, 1 LCD_Char
retlw 0x00
Pulse_e
bsf
bcf
;joystick routines
JOY_Init bsf bcf bcf bcf bcf bcf STATUS, JOY_TRIS, JOY_PORT, JOY_TRIS, JOY_PORT, STATUS, PotX PotX PotY PotY
;make PotX an output ;discharge capacitor ;make PotY an output ;discharge capacitor RP0 ;select bank 0
retlw 0x00
ReadX clrf Timer_H bsf bsf bcf STATUS, JOY_TRIS, STATUS, PotX
clrf TMR0 bcf bsf INTCON, INTCON, T0IF GIE ;start timer ;start interrupts PotX
iorwf TMR0,
f movf TMR0,
movwf LoX
bcf
INTCON,
GIE
btfsc INTCON, goto $-2 bsf bcf bcf bcf STATUS, JOY_TRIS, JOY_PORT, STATUS, retlw PotX PotX RP0
;select bank 1
ReadY clrf Timer_H bsf bsf bcf STATUS, JOY_TRIS, STATUS, PotY
clrf TMR0 bcf bsf INTCON, INTCON, T0IF GIE ;start timer ;start interrupts PotY
iorwf TMR0,
f movf TMR0,
movwf LoY
movf Timer_H, W movwf HiY bcf INTCON, GIE ;turn off interrupts GIE
btfsc INTCON, goto $-2 bsf bcf bcf bcf STATUS, JOY_TRIS, JOY_PORT, STATUS, retlw PotY PotY RP0
;select bank 1
ReadSW
SW1
SW2
SW1
SW2
retlw 0x00
Sw1On bsf
Sw2On bsf
Sw1Off
bcf
Flags, SW1_Flag
retlw 0x00
Sw2Off
bcf
Flags, SW2_Flag
retlw 0x00
;Delay routines
Delay255
Delay100
Delay50
;delay 50mS
Delay20
;delay 20mS
Delay5
movlw 0x05 d0
;delay 5.000 ms (4 MHz clock) movwf count1 ;delay 1mS movwf counta movlw 0x01 movwf countb Delay_0 decfsz counta, f goto $+2
d1
movlw 0xC7
retlw 0x00
ORG
0x0200
;TABLES - moved to avoid paging problems, ;a table must not cross a 256 byte boundary. HEX_Table ADDWF PCL RETLW 0x30 RETLW 0x31 RETLW 0x32 RETLW 0x33 RETLW 0x34 RETLW 0x35 RETLW 0x36 RETLW 0x37 RETLW 0x38 RETLW 0x39 RETLW 0x41 RETLW 0x42 RETLW 0x43 RETLW 0x44 RETLW 0x45 RETLW 0x46 ,f
Xtext
addwf PCL, f retlw 'J' retlw 'o' retlw 'y' retlw '-' retlw 'X' retlw ' ' retlw 0x00
Ytext
addwf PCL, f retlw 'J' retlw 'o' retlw 'y' retlw '-' retlw 'Y' retlw ' ' retlw 0x00
presstext
nopresstext
addwf PCL, f retlw 'O' retlw 'p' retlw 'e' retlw 'n' retlw ' ' retlw 0x00
;end of tables
end
The first change in this program is the main program start address, previously we started as immediately after the reset vector (0x0000) as possible, but the interrupt vector is located at 0x0004 so we need to skip over this with a 'goto Start' command. The small interrupt routine itself is located an 0x0004 and simply increments the high byte counter every time the hardware low byte counter overflows. The other two lines in the interrupt routine reenable interrupts (they are cancelled automatically when called) and the return from the routine, this time using 'retfie' (Return From Interrupt) rather than 'retlw'.
To complete all of these tutorials you will require two Main Boards, two IR Boards, the LCD Board, the Switch Board, and the LED Board, as written the first two tutorials use the LCD Board and Switch Board on PortA and the IR Boards on PortB - although these could easily be swapped over, as the IR Board doesn't use either of the two 'difficult' pins for PortA, pins 4 and 5. The third tutorial uses the IR Board on PortA and the LED Board on PortB (as we require all 8 pins to be outputs). Download zipped tutorial files. IR transmission has limitations, the most important one (for our purposes) being that the receiver doesn't give out the same width pulses that we transmit, so we can't just use a normal, RS232 type, serial data stream, where we simply sample the data at fixed times - the length of the received data varies with the number of ones sent - making receiving it accurately very difficult. Various different schemes are used by the manufacturers of IR remote controls, and some are much more complicated than others. I've chosen to use the Sony SIRC (Sony Infra Red Control) remote control system, many of you may already have a suitable Sony remote at home you can use, and it's reasonably easy to understand and implement. Basically it uses a pulse width system, with a start bit of 2.4mS, followed by 12 data bits, where a '1' is 1.2mS wide, and a '0' is 0.6mS wide, the bits are all separated by gaps of 0.6mS. The data itself consists of a 7 bit 'command' code, and a 5 bit 'device' code - where a command is Channel 1, Volume Up etc. and a device is TV, VCR etc. This is how the same remote system can be used for different appliances, the same command for 'Power On' is usually used by all devices, but by transmitting a device ID only a TV will respond to 'TV Power On' command. The table to the right shows the data Start Command Code Device Code format, after the Start bit the command code is S D0 D1 D2 D3 D4 D5 D6 C0 C1 C2 C3 C4 send, lowest bit first, then the device code, 1.2 or 0.6mS 1.2 or 0.6mS again lowest bit first. The entire series is sent 2.4mS repeatedly while the button is held down, every 45mS. In order to decode the transmissions we need to measure the width of the pulses, first looking for the long 'start' pulse, then measuring the next 12 pulses and deciding if they are 1's or 0's. To do this I'm using a simple software 8 bit counter, with NOP's in the loop to make sure we don't overflow the counter. After measuring one pulse we then test it to see if it's a valid pulse, this routine provides four possible responses 'Start Pulse', 'One', 'Zero', or 'Error', we initially loop until we get a 'Start Pulse' reply, then read the next 12 bits - if the reply to any of these 12 is other than 'One' or 'Zero' we abort the read and go back to waiting for a 'Start Pulse'. Device ID's TV VTR1 Text 1 2 3
The device codes used specify the particular device, but with a few exceptions!, while a TV uses device code 1, some of the Teletext buttons use MDP 6 code 3, as do the Fastext coloured keys - where a separate Widescreen button VTR2 7 is fitted, this uses code 4. The table to the left shows some of the Device ID VTR3 11 codes I found on a sample of Sony remotes. Five bits gives a possible 32 different device ID's, and some devices respond to more than one device ID, Effect 12 for example some of the current Sony VCR's have the Play button in a 'cursor' Audio 16 type of design, surrounded by 'Stop', 'Pause', 'Rewind', and 'Fast Forward' Pro-Logic 18 the ones I tested actually send a DVD ID code when these keys are pressed DVD 26 (along with a different command ID to that used normally used for 'Play' etc.). However, they still respond to an older Sony remote which sends the VTR3 device ID, which despite being labelled VTR3 on TV remotes seems to be the normal standard Sony VCR device ID. It's quite common for Sony remotes to use more than one device ID, a Surround Sound Amplifier Remote I tried used four different device ID's. Widescreen 4 If you don't have a Sony remote you can use, I've also built a transmitter, using the second Main Board, second IR Board, and the Switch Board, the four buttons allow you to send four different command codes - I've chosen TV as the device, and Volume Up, Volume Down, Program Up, and Program Down as my four commands, I've confirmed this works on various Sony TV's. Transmitting the SIRC code is quite simple to do, I generate the 38KHz modulation directly in software, and to reduce current consumption don't use a 50/50 on/off ratio - by using a longer off than on time we still get the 38KHz, but with a reduced power requirement. I've recently discovered that Sony DVD players DON'T use the standard 12 bit SIRC's system, it's extended to comprise 20 bits instead. It still has the same 5 bit device code and 7 bit command code, but it's followed by an extra 8 bit code at the end. In the ones I've tested these 8 bits were always the same, hexadecimal 0x49. It's simple to add this to the transmitter, just add an extra section after the device code section 'Ser_Loop2' that sends 8 bits with the value 0x49. Apparently there's also a third variant of SIRC's that uses 15 bits, a 7 bit command code, and an 8 bit device code. So, all together, three versions, 12 bit, 15 bit, and 20 bit, although 12 bit seems by far the most common, DVD players seem to use 20 bit, and I've yet to see a 15 bit remote. Another interesting Sony point is that some remotes can be configured to act as 'service remotes', this changes one of the buttons to become a 'test' button, pressing it once displays 'T' on the screen, pressing it twice displays 'TT' and enters test mode - pressing 'Menu' at this point displays the service menu. In order to make yourself a 'service remote' you just need to send the Device ID '1' and the command '127'. Tutorial 5.1 - requires one Main Board (with LED set to RB7), one IR Board and LCD Board. This program uses the LCD module to give a decimal display of the values of the Device and Command bytes transmitted by a Sony SIRC remote control, it can be easily altered to operate port pins to control external devices, as an example the main board LED is turned on
by pressing button 2, turned off by pressing button 3, and toggled on and off by pressing button 1 (all on a TV remote, you can change the device ID for a different remote if you need to). As it stands it's very useful for displaying the data transmitted by each button on your Sony remote control - the Device ID's table above was obtained using this design. ;Tutorial 5_1 ;Read SIRC IR with LCD display ;Nigel Goodwin 2002
LIST
p=16F628
;tell assembler what chip we are using ;include the defaults for the chip -302 ;suppress bank selection messages
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine LoX Bit_Cntr
Cmd_Byte Dev_Byte Timer_H Flags Flags2 tmp1 ;temporary storage tmp2 tmp3 lastdev lastkey
NumL
TenK
templcd
endc
Equ Equ
Equ
0x07
Equ Equ
PORTB TRISB
OUT_PORT LED
Equ Equ
PORTB 0x07
Equ
Equ Equ
0x02 0x03
New
Equ
0x07
TV_ID
Equ
0x01
;TV device ID
But1
Equ
0x00 But2 But3 But4 But5 But6 But7 But8 But9 Equ Equ Equ Equ Equ Equ Equ Equ
;numeric button ID's 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08
org goto
0x0000 Start
org
0x0004 retfie
;TABLES - moved to start of page to avoid paging problems, ;a table must not cross a 256 byte boundary. HEX_Table addwf PCL retlw 0x30 ,f
retlw 0x31 retlw 0x32 retlw 0x33 retlw 0x34 retlw 0x35 retlw 0x36 retlw 0x37 retlw 0x38 retlw 0x39 retlw 0x41 retlw 0x42 retlw 0x43 retlw 0x44 retlw 0x45 retlw 0x46
Xtext
addwf PCL, f retlw 'D' retlw 'e' retlw 'v' retlw 'i' retlw 'c'
retlw 'e' retlw ' ' retlw ' ' retlw ' ' retlw 'C' retlw 'o' retlw 'm' retlw 'm' retlw 'a' retlw 'n' retlw 'd' retlw 0x00
;end of tables
Initialise
SetPorts
bsf
STATUS,
RP0
;select bank 1
movlw 0x00
movlw b'01111111'
bcf
STATUS,
RP0
;select bank 0
call call
LCD_Init
Delay255
Main call call LCD_Line1 String1 ;set to first line ;display IR title string
call
;read IR signal
call
LCD_Line2W
movlw d'11' call LCD_Line2W clrf movf Cmd_Byte, w ;set cursor position NumH ;convert data byte
call
ProcKeys
goto
Main
ProcKeys btfss Flags2, New retlw 0x00 movlw TV_ID ;return if not new keypress ;check for TV ID code subwf Dev_Byte, btfss STATUS , Z retlw 0x00 ;return if not correct code w
movlw But1
goto
Key1
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED ;turn on LED
OUT_PORT,
bcf bcf
OUT_PORT,
LED
Flags2, New
Key1
movlw But2
goto
Key2
;try next key if not correct code ;this time just turn it on
bsf bcf
OUT_PORT,
LED
;turn on LED
Flags2, New
Key2
movlw But3
retlw 0x00
bcf bcf
OUT_PORT,
LED
Flags2, New
clrf
count
;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?
btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess1
;IR routines
ReadIR
call
Read_Pulse
btfss Flags, StartFlag goto ReadIR ;wait for start pulse (2.4mS)
Get_Data
movlw 0x07
;abort if error
bcf
STATUS , C
rrf
Cmd_Byte , f
Get_Cmd
bcf
STATUS , C
rrf
retlw 0x00
;end of ReadIR
;read pulse width, return flag for StartFlag, One, Zero, or ErrFlag ;output from IR receiver is normally high, and goes low when signal received
clrf
Still_High
btfss IR_PORT,
IR_In ;and wait until goes low goto Next incfsz tmp1,f goto Still_High
incfsz tmp2,f goto bsf Flags2, New goto Still_High ;set New flag if no button pressed Still_High
Next
nop nop
;waste time to scale pulse ;width to 8 bits nop nop nop nop
Chk_Pulse
clrf
Flags
TryError
movf LoX,
btfsc STATUS , C goto bsf Flags, ErrFlag TryZero ; Error found, set flag
retlw 0x00
TryZero
movf LoX,
btfsc STATUS , C goto bsf Flags, Zero TryOne ; Zero found, set flag
retlw 0x00
TryOne
movf LoX,
btfsc STATUS , C goto bsf Flags, One TryStart ; One found, set flag
retlw 0x00
TryStart
movf LoX,
Flags, StartFlag
retlw 0x00 NoMatch bsf Flags, ErrFlag ; pulse too long ; Error found, set flag retlw 0x00
;LCD routines
call
LCD_Clr
;clear display
retlw 0x00
movwf LCD_PORT bcf call Pulse_e LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high
movwf LCD_PORT bcf call Pulse_e call LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high LCD_Busy
retlw 0x00
LCD_CharD
;add 0x30 to convert to ASCII movwf templcd w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT
LCD_PORT, LCD_RS
movwf LCD_PORT bsf call Pulse_e call LCD_PORT, LCD_RS ;RS line to 1 ;Pulse the E line high LCD_Busy
retlw 0x00
LCD_Line1
retlw 0x00
LCD_Line2
retlw 0x00
retlw 0x00
retlw 0x00
LCD_CurOn
movlw 0x0d
retlw 0x00
LCD_CurOff
movlw 0x0c
retlw 0x00
LCD_Clr
;Clear display
retlw 0x00
LCD_HEX
movwf tmp1 swapf tmp1, w andlw 0x0f call call HEX_Table LCD_Char
call
LCD_Char
retlw 0x00
Pulse_e
bsf
bcf
LCD_Busy bsf STATUS, RP0 ;set bank 1 ;set Port for input movwf LCD_TRIS bcf bcf bsf STATUS, RP0 ;set bank 0
movlw 0x0f
LCD_PORT, LCD_RS
LCD_PORT, LCD_RW ;setup to read busy flag bsf LCD_PORT, LCD_E ;read upper nibble (busy flag)
bsf
;dummy read of lower nibble LCD_PORT, LCD_E ;check busy flag, high = busy
goto
LCD_Busy bcf
;if busy check again LCD_PORT, LCD_RW RP0 ;set bank 1 ;set Port for output
bsf
STATUS,
movlw 0x00
;Delay routines
Delay255
Delay100
Delay50
Delay20
Delay5
movlw 0x05
d0 d1
movwf count1 movlw 0xC7 movwf counta movlw 0x01 movwf countb
Delay_0
retlw 0x00
;This routine downloaded from http://www.piclist.com Convert: ; Takes number in NumH:NumL ; Returns decimal in ; TenK:Thou:Hund:Tens:Ones swapf NumH, w iorlw B'11110000'
movwf Thou addwf Thou,f addlw 0XE2 movwf Hund addlw 0X32 movwf Ones
movf NumH,w andlw 0X0F addwf Hund,f addwf Hund,f addwf Ones,f addlw 0XE9 movwf Tens addwf Tens,f addwf Tens,f
rlf rlf
Tens,f Ones,f
; 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 Lb1: addwf Ones,f decf Tens,f btfss 3,0 goto Lb1 Lb2: addwf Tens,f decf Hund,f btfss 3,0 goto Lb2 Lb3: addwf Hund,f decf Thou,f btfss 3,0 goto Lb3 Lb4: addwf Thou,f decf TenK,f btfss 3,0 goto Lb4
; Ten
retlw
0x00
end
Tutorial 5.2 - requires one Main Board, one IR Board and Switch Board. This program implements a Sony SIRC IR transmitter, pressing one of the four buttons sends the corresponding code, you can alter the codes as you wish, for this example I chose Volume Up and Down, and Program Up and Down. In order to use this with the LED switching above, I would suggest setting the buttons to transmit '1', '2', '3' and '4', where '4' should have no effect on the LED - the codes are 0x00, 0x01, 0x02, 0x03 respectively (just to confuse us, the number keys start from zero, not from one). ;Tutorial 5.2 - Nigel Goodwin 2002 ;Sony SIRC IR transmitter LIST p=16F628 ;tell assembler what chip we are using ;include the defaults for the chip ;sets the configuration settings (oscillator type etc.)
;start of general purpose registers ;used in delay routine ;used in delay routine countb count Delay_Count
IR_PORT IR_TRIS
Equ Equ
Equ
6 5 4
TV_ID
Equ
0x01
;TV device ID
But1
Equ
0x00
But2 But3 But4 But5 But6 But7 But8 But9 ProgUp ProgDn VolUp VolDn
0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 d'16' d'17'
d'18' d'19'
org goto
0x0000 Start
;org sets the origin, 0x0000 for the 16F628, ;this is where the program starts running
org
0x005
clrf
IR_PORT
bsf movlw
RP0
;select bank 1
SW3
SW4
Switch1 call
Switch2 call
Switch3 call
Switch4 call
TX_Start
retlw 0x00
TX_One call
retlw 0x00
TX_Zero call
retlw 0x00
IR_pulse MOVWF irloop count BSF ; Pulses the IR led at 38KHz IR_PORT, IR_Out ; ; ; ; ; ; ;
BCF
IR_PORT,
IR_Out ; ; ; ; ; ; ; ; ; ;
NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP DECFSZ
; ; count,F
NO_pulse MOVWF irloop2 count BCF ; Doesn't pulse the IR led IR_Out
IR_PORT,
NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP BCF IR_PORT,
NOP NOP
; ;
DECFSZ
count,F
Xmit_RS232
MOVWF Bit_Cntr call TX_Start Ser_Loop RRF Data_Byte , f BTFSC STATUS , C call TX_One BTFSS STATUS , C call TX_Zero DECFSZ Bit_Cntr , f ;test if all done ;send start bit ;send one bit
GOTO Ser_Loop ;now send device data movlw D'1' movwf Dev_Byte MOVLW 0x05 ;set device to TV ;set 5 device bits out
MOVWF Bit_Cntr Ser_Loop2 RRF Dev_Byte , f BTFSC STATUS , C ;send one bit
call TX_One BTFSS STATUS , C call TX_Zero DECFSZ Bit_Cntr , f ;test if all done
;Delay routines
Delay255
;delay 255 mS d0 ;delay 100mS d0 ;delay 50mS d0 ;delay 27mS d0 ;delay 20mS d0
Delay100
Delay50
Delay27
Delay20
Delay5
movlw 0x05
d0 d1
movwf count1 movlw 0xC7 movwf counta movlw 0x01 movwf countb
Delay_0
retlw 0x00
end
Tutorial 5.3 - requires one Main Board, one IR Board and LED Board. This program implements toggling the 8 LED's on the LED board with the buttons 1 to 8 on a Sony TV remote control, you can easily change the device ID and keys used for the LED's. I've also used a (so far unused) feature of the 16F628, the EEPROM data memory - by using this the program remembers the previous settings when unplugged - when you reconnect the power it restores the last settings by reading them from the internal non-volatile memory. The 16F628 provides 128 bytes of this memory, we only use one here (address 0x00, set in
the EEPROM_Addr constant). ;Tutorial 5_3 ;Read SIRC IR and toggle LED display, save settings in EEPROM data memory. ;Nigel Goodwin 2002
LIST
p=16F628
;tell assembler what chip we are using ;include the defaults for the chip -302 ;suppress bank selection messages
;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine LoX Bit_Cntr Cmd_Byte Dev_Byte
endc
LED_PORT LED_TRIS
Equ Equ
PORTB TRISB
Equ Equ
PORTA TRISA
EEPROM_Addr
Equ
0x00
Equ
Equ Equ
0x02 0x03
New
Equ
0x07
TV_ID
Equ
0x01
;TV device ID
But1
Equ
0x00 But2 But3 But4 But5 But6 Equ Equ Equ Equ Equ
org goto
0x0000 Start
org
0x0004 retfie
Initialise
SetPorts
bsf
STATUS,
RP0
;select bank 1
movlw 0x00
movlw b'11111111'
bcf
STATUS,
RP0
;select bank 0
call
EE_Read
Main call call ProcKeys ReadIR ;read IR signal ;do something with commands received
goto
Main
ProcKeys btfss Flags2, New retlw 0x00 movlw TV_ID ;return if not new keypress ;check for TV ID code subwf Dev_Byte, btfss STATUS , Z retlw 0x00 ;return if not correct code w
movlw But1
goto
Key1
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED0 ;turn on LED
LED_PORT,
btfsc tmp3, LED0 bcf bcf call LED_PORT, LED0 ;turn off LED ;and cancel new flag ;save the settings retlw 0x00
Key1
movlw But2
goto
Key2
movf LED_PORT,
;and store in temp register ;and test LED bit for toggling LED1 ;turn on LED
btfsc tmp3, LED1 bcf bcf call LED_PORT, LED1 ;turn off LED ;and cancel new flag ;save the settings retlw 0x00
Key2
movlw But3
goto
Key3
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED2 ;turn on LED
LED_PORT,
btfsc tmp3, LED2 bcf bcf call LED_PORT, Flags2, New LED2 ;turn off LED ;and cancel new flag ;save the settings
EE_Write
retlw 0x00
Key3
movlw But4
goto
Key4
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED3 ;turn on LED
LED_PORT,
btfsc tmp3, LED3 bcf bcf call LED_PORT, Flags2, New LED3 ;turn off LED ;and cancel new flag ;save the settings retlw 0x00
EE_Write
Key4
movlw But5
goto
Key5
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED4 ;turn on LED
LED_PORT,
btfsc tmp3, LED4 bcf bcf call LED_PORT, Flags2, New LED4 ;turn off LED ;and cancel new flag ;save the settings retlw 0x00
EE_Write
Key5
movlw But6
goto
Key6
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED5 ;turn on LED
LED_PORT,
btfsc tmp3, LED5 bcf bcf LED_PORT, Flags2, New LED5 ;turn off LED ;and cancel new flag
call
EE_Write
Key6
movlw But7
goto
Key7
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED6 ;turn on LED
LED_PORT,
btfsc tmp3, LED6 bcf bcf call LED_PORT, Flags2, New LED6 ;turn off LED ;and cancel new flag ;save the settings retlw 0x00
EE_Write
Key7
movlw But8
;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED7 ;turn on LED
LED_PORT,
btfsc tmp3, LED7 bcf bcf call LED_PORT, Flags2, New LED7 ;turn off LED ;and cancel new flag ;save the settings retlw 0x00
EE_Write
EE_Read
bsf
STATUS, movlw
RP0 EEPROM_Addr
; Bank 1
movwf bsf
movwf LED_PORT
EE_Write
w RP0
bsf
EECON1,
WREN
movwf EEDATA
movlw EEPROM_Addr movwf EEADR ; set EEPROM address movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1, WR ; Write AAh ; Set WR bit ; begin write bcf STATUS, RP0 ; Bank 0 ; Write 55h
btfss PIR1, EEIF goto bcf PIR1, EEIF bsf bcf bcf STATUS, EECON1, STATUS,
; and clear the 'write complete' flag RP0 WREN RP0 retlw 0x00 ; Bank 1 ; Disable write ; Bank 0
;IR routines
ReadIR
call
Read_Pulse
btfss Flags, StartFlag goto ReadIR ;wait for start pulse (2.4mS)
Get_Data
movlw 0x07
bcf
STATUS , C
rrf
Cmd_Byte , f
Get_Cmd
bcf
STATUS , C
rrf
retlw 0x00
;end of ReadIR
;read pulse width, return flag for StartFlag, One, Zero, or ErrFlag ;output from IR receiver is normally high, and goes low when signal received
clrf
goto $-1 clrf movlw 0xC0 movwf tmp2 tmp1 ;delay to decide new keypress ;for keys that need to toggle
Still_High
btfss IR_PORT,
IR_In ;and wait until goes low goto Next incfsz tmp1,f goto Still_High
incfsz tmp2,f goto bsf Flags2, New Still_High ;set New flag if no button pressed
goto
Still_High
Next
nop nop
;waste time to scale pulse ;width to 8 bits nop nop nop nop nop nop incf btfss IR_PORT, LoX, f IR_In
goto Next
Chk_Pulse
clrf
Flags
TryError
movf LoX,
btfsc STATUS , C goto bsf Flags, ErrFlag TryZero ; Error found, set flag
retlw 0x00
TryZero
movf LoX,
btfsc STATUS , C goto bsf Flags, Zero TryOne ; Zero found, set flag
retlw 0x00
TryOne
movf LoX,
btfsc STATUS , C goto bsf Flags, One TryStart ; One found, set flag
retlw 0x00
TryStart
movf LoX,
; check if start
Flags, StartFlag
retlw 0x00 NoMatch bsf Flags, ErrFlag ; pulse too long ; Error found, set flag retlw 0x00
;Delay routines
Delay255
Delay100
Delay50
Delay20
movlw d'20'
d0
;delay 5.000 ms (4 MHz clock) movwf count1 movlw 0xC7 movwf counta movlw 0x01 movwf countb
Delay_0
retlw 0x00
end
The EEPROM data is accessed by two new routines, EE_Read and EE_Write, the EE_Read routine is called as the program powers up, before we enter the main loop, and the EE_Write
routine is called after every LED change. The EE_Read routine is very straightforward, we simply set the address we wish to read in the EEADR register, set the RD flag in the EECON1 register, and then read the data from the EEDATA register. Writing is somewhat more complicated, for a couple of reasons: 1. Microchip have taken great care to prevent accidental or spurious writes to the data EEPROM. In order to write to it we first have to set the 'Write Enable' bit in the EECON1 register, and then make two specific writes (0x55 and 0xAA) to the EECON2 register, only then can we set the WR bit in EECON1 and start the actual writing. One of the most common problems in domestic electronics today is data EEPROM corruption, hopefully the efforts of Microchip will prevent similar problems with the 16F628. 2. Writing to EEPROM takes time, so we have to wait until the 'Write Complete' flag is set, it doesn't really matter in this application as the time spent waiting for the next IR command gives more than enough time to write to the data EEPROM, but it's good practice to do it anyway. The extra work involved makes the EE_Write routine a lot longer than the EE_Read routine, it also doesn't help that we need to access registers in different banks, so we do a fair bit of bank switching.
These tutorials require the Main Board, the LCD Board, and various of the I2C Boards, as written the tutorials use the LCD Board on PortA and the I2C Boards on PortB - although these could easily be swapped over, as the I2C Boards don't use either of the two 'difficult' pins for PortA, pins 4 and 5, as outputs. Download zipped tutorial files. As with the LCD Tutorial, the idea is to implement a reusable set of I2C routines. Rather than showing the routines on the page as with earlier tutorials (they are getting quite lengthy now), I'm only going to store them in the pages download ZIP file so you will need to download them. As the I2C tutorials use a number of different boards, each section is headed by the I2C boards required in bold type. I2C is a protocol designed by Philips Semiconductors, and is for communications between I/C's over a two wire synchronous serial bus, devices are classed as either 'Master' or 'Slave', for our purposes the Main Board processor is the 'Master', and any other devices are 'Slaves'. The initial tutorials use a 24C04, a 512 byte EEPROM memory chip, commonly used for storing the settings in modern TV's and VCR's, where they are used to store all the customer settings
(tuning, volume, brightness etc.) and the internal calibration values for the set, which are normally accessed through a special 'service mode'. These chips provide non-volatile memory as a series of 256 byte 'pages', so the 24C04 provides two 'pages' giving 512 bytes of memory. The 24C02 uses 'standard addressing', which gives the 256 byte page limit, other larger chips use 'extended addressing', giving a possible 16 bit address space, I've used a 24C256 which uses a 15 bit address space, giving 32,768 of memory space. To address these you require two bytes of address data, the programs already include these (but commented out), I've uncommented them for copies of the first two tutorials and called them 6_1a and 6_2a, if you want to use an EEPROM larger than a 24C16 you will need to use these extended addressing versions. I2C EEPROM Board The first tutorial writes sequential numbers through one entire page of memory, and then reads them back, 4 bytes at a time, displaying them on the LCD, separated by about half a second between updates. The second tutorial demonstrates 'sequential writing', the first tutorial uses 'byte writing' (which is why it displays 'Writing..' for a couple of seconds) - a write is fairly slow to EEPROM, taking around 10mS to complete - 'sequential writing' allows you to store a number of bytes in RAM inside the EEPROM chip (a maximum of 8 for the 24C04) and then write them all to EEPROM with a single write delay. Tutorial 2 writes 4 bytes at once, to demonstrate how this is done. The third tutorial is simply a cut-down version of the first, I've included it as a useful tool, it simply reads one page of the EEPROM and displays it as tutorial 1 does - useful for checking the contents of an EEPROM. You can deal with the EEPROM write delay in a couple of ways, firstly you can introduce a software delay, and this option is included in the tutorials (but commented out), or you can keep checking until the chip is ready, this is the method I've used in these tutorials, although if you want you can comment that line out and un-comment the 'call delay10' line instead. I2C Clock Board, and I2C Switch Board Now we move onto the I2C Clock board, basically we use exactly the same I2C routines, the only difference being in the way we manipulate the data, we need to read the clock registers from the chip (using a sequential read), apply a little processing, and then display them on the LCD. Actually setting the clock is somewhat more complicated, and the biggest difference is the routines for reading the switch board, and setting the clock chip values which are then written back to the chip with a sequential write. The four buttons used are (from left to right), 'Set', 'Up', 'Down', and 'Next' - in the initial display mode the only button which has an effect is the 'Set' button, this jumps to the 'Clock Set' mode, and starts a flashing cursor on the tens of hours. From this point all four buttons work, pressing 'Set' again will return to display mode, updating the clock values (and zeroing the seconds). Pressing 'Up' will increase the value under the cursor, and 'Down' will decrease the value, with '0' being the lower limit, and '9' being the upper one - I don't currently take account of the different maximum values for particular digits (i.e. tens of hours doesn't go higher than 2), but rely on setting them sensibly. The 'Next' button moves on to the next digit, and if pressed while on the last digit (years units) will return to display mode, just like pressing the 'Set' button. I also
don't currently take any account of the correct years, the PCF8583 only provides 0-3 for the years, with 0 being a leap year - extra software routines will be required to do this, with the actual values stored in spare PCF8583 EEPROM memory, and updated when the year changes (remembering that the year might change while the processor is powered down, and the clock is running on it's back-up battery). I2C A2D Board, and I2C Switch Board Again, the A2D board uses the same basic I2C routines as before (but with a different chip address for the PCF8591) as with the I2C Clock Board the differences come in the manipulation of the data. As the board also includes an EEPROM socket this can be used to store samples from the A2D chip - with a single 24C256 we can store up to 32,768 eight bit samples - this introduces a slight 'snag', the 24C256 uses 'extended addressing', while the PCF8591 only uses 'standard addressing', however we can still use the same I2C routines by using a flag to tell the routines which addressing mode to use, simply switching the flag for the different chips - this flag switching becomes part of the reusable I2C routines.
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: 1. 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
2. 3. 4.
5.
6. 7.
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.
The main program itself is just a demonstration of how to use the PWM subroutines, it simply sets four different PWM and direction settings with 5 second delays in between them. It should be pretty self evident how to use it from your own programming. I've included various delay routines, including a new one called 'Delay100W', this delays 100mS multiplied by the value in W when the routine is called - in this example we load W 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
;start of general purpose registers ;used in delay routine ;used in delay routine ;used in delay routine ;used in delay routine ;temp storage endc
RL FL RR FR
;pin for left motor reverse ;pin for left motor forward ;pin for right motor reverse ;pin for right motor forward
ORG NOP
0x0000
START CALL
Initialise
d'64'
;left half speed forwards d'192' ;right half speed reverse Long_Delay
CALL
SpeedR CALL
Long_Delay
GOTO MainLoop
Initialise: BANKSEL ADCON1 MOVLW 0x06 MOVWF ADCON1 BANKSEL PORTA BANKSEL TRISC MOVLW 0 ;set PORTC as all outputs MOVWF TRISC BANKSEL PORTC ;turn off A2D
MOVF
MOVF
CCP2CON,W ;set CCP2 as PWM ANDLW 0xF0 IORLW 0x0C MOVWF CCP2CON
;set highest PWM value ;over this (127) is permanently on MOVWF PR2 BANKSEL TMR2
MOVF
T2CON,W
ANDLW 0xF8
MOVF
T2CON,W
;set postscaler to 1
MOVWF T2CON
CLRF
CCPR1L CLRF
BSF
T2CON, TMR2ON
SpeedL:
;if more than 128 set speed in reverse ;so '1' is very slow forward ;and '129' is very slow reverse CALL ANDLW ForwardL 0x7F
BTFSS temp, 7
ReverseL: BSF PORTC, RL BCF ;set pins for reverse PORTC, FL RETURN
ForwardL: BCF PORTC, RL BSF ;set pins for forward PORTC, FL RETURN
ForwardR:
BCF BSF
;Delay routines
Delay100W d2 call
return
Delay255
Delay100
Delay50
movlw d'50'
goto Delay20 movlw d'20' goto Delay10 movlw d'10' goto Delay1 movlw d'1' goto Delay5 movlw 0x05 d0 d1
;delay 5.000 ms (4 MHz clock) movwf count1 movlw 0xE7 movwf counta movlw 0x04 movwf countb
Delay_0
return
END
For these tutorials you require the Main Board, Keypad Board, LCD Board, IR Board and RS232 Board. Download zipped tutorial files. These tutorials demonstrate how to read a HEX keypad, these are a standard device with 16 keys connected in a 4x4 matrix, giving the characters 0-9 and A-F. You can also use a 4x3 keypad, which gives the numbers 0-9, * and #.
This is how the HEX keypad is connected, each square with a number or letter in it is a push to make switch, which connects the horizontal wires (rows) with the vertical wires (columns). So if you press button 'A' it will connect COL1 with ROW4, or pressing 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 a lookup table for the keys, this would need to be changed to insert the correct values for the non-numeric characters.
As the switches are all interconnected, we need a way to differentiate between the different ones - the four resistors on the interface board pull lines COL1 to COL4 high, these four lines are the ones which are read in the program. So in the absence of any switch been pressed these lines will all read high. The four ROW connections are connected to output
pins, and if these are set high the switches will effectively do nothing - connecting a high level 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 ROW lines low - what happens?. Assuming we press button 1, this joins COL1 with ROW1, as ROW1 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 level this 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 ROW1 low we can then read just the top row of buttons, button 1 will take COL1 low, button2 will take COL2 low, and the same for buttons '3' and 'F' in COL3 and COL4. The twelve lower buttons won't have any effect as their respective ROW lines are still high. So to read the other buttons we need to take their respective ROW lines low, taking ROW2 low will allow us to read the second row of buttons (4, 5, 6, and E), again as the other three ROW lines are now high the other 12 buttons have no effect. We can then repeat this for the last two ROW's using ROW3 and ROW4, so we read four buttons at a time, taking a total of four readings to read 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 are a number of ways to deal with this, one way would be to check for multiple key presses and ignore them, a simpler way (and that used in the examples) is to accept the first key you find which is pressed. You will find that various commercial products deal with this situation in similar 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 used elsewhere, the subroutine to be called is Chk_Keys, the first thing this does is check to see if any of the 12 keys are pressed (by making all the ROW lines low) and waiting until no keys are pressed - this avoids problems with key presses repeating. Once no keys are pressed it jumps to the routine Keys which repeatedly scans the keyboard until a key is pressed, a call to 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 logically ANDed with 0x0F to make absolutely sure it's between 0 and 15. Next this value is passed to a lookup table which translates the key to it's required value (in this case the ASCII value of the labels on the keys). Finally the ASCII value is stored back in the 'key' variable (just in case you might need it storing) and the routine returns with the ASCII value in the W register. ;Keypad subroutine
Chk_Keys
movf KEY_PORT, W andlw 0x0F ;mask off high byte sublw 0x0F btfsc STATUS, Z goto Keys call goto Chk_Keys ;test if any key pressed ;if none, read keys Delay20 ;else try again
call Scan_Keys ;check for no key pressed subwf key, w btfss STATUS, Z goto Key_Found call goto Delay20 Keys
Key_Found
call
Key_Table
movwf KEY_PORT movlw 0x04 movwf rows bcf Scan bsf STATUS, C STATUS, C rrf ;set number of rows ;put a 0 into carry KEY_PORT, f ;follow the zero with ones
;comment out next two lines for 4x3 numeric keypad. btfss KEY_PORT, Col4 goto Press incf key, f btfss KEY_PORT, Col3 goto Press incf key, f btfss KEY_PORT, Col2 goto Press incf key, f btfss KEY_PORT, Col1 goto Press incf key, f
For the full code, with the equates, variable declarations and look-up table, consult the code for the examples below. Tutorial 9.1 Tutorial 9.1 simply reads the keyboard and displays the value of the key pressed on the LCD module, it shows this as both HEX and ASCII, so if you press key '0' it will display '30 0'. Tutorial 9.2 This tutorial implements a simple code lock, you enter a 4 digit code and it responds on the LCD with either 'Correct Code' or 'Wrong Code', it uses all sixteen available I/O lines on a 16F628 which leaves no spare I/O line for opening an electrical lock. However, using a 4x3 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 is currently stored within the program source code, as the 16F628 and 16F876 have internal EEPROM the code could be stored there and be changeable from the keypad. Tutorial 9.3 Tutorial 9.3 reads the keypad and sends the ASCII code via RS232 at 9600 baud, you can use HyperTerminal on a PC to display the data. Basically this is the same as 9.1 but uses a serial link to a PC instead of the LCD screen. Tutorial 9.4 A common use for keyboard scanning applications, this example reads the keypad and transmits IR remote control signals to control a Sony TV, this works in a very similar way to the circuit used in your TV remote control. For this application I've modified the keypad routines slightly - as we want the keys to repeat I've removed the first part which waits for a key to be released, and as we don't need ASCII values back from the key presses, I've removed the look-up table which gave us the ASCII values. I've replaced that with a jump table, this jumps to one of 16 different small routines which send the correct SIRC code for each button pressed - it's a lot neater than checking which button was pressed. The controls I'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 button
values, or change the order of the Goto's in the jump table, notice that these are currently in a peculiar order, this is to map the numeric buttons correctly.
For these tutorials you require the Main Board and 7 Segment LED Board. Download zipped tutorial files. These tutorials demonstrate how to use a multiplexed 7 segment LED display, these tutorials use just a dual display (because it fits easily on a single port), but the technique can easily be extended to use larger displays (and commonly is). To reduce the pin count required, the two displays are multiplexed - this means we need seven I/O pins, one for each segment of the LED (ignoring the decimal point), and two to select the individual sections - unfortunately this would require nine I/O pins - taking more than one port. To over come this I've designed the 7 segment board to select the two sections using just one pin - the two transistors are wired as inverters, this means that when Q1 is ON, Q2 is OFF, and when Q1 is OFF, Q2 is ON - so by switching port pin 7 we can alternate between the two sections. The technique used for the display is to show each section in turn, so we display section 1, then section 2 - as long as we do this fast enough you can't see any flickering - this obviously restricts what else the program can be doing, as we must refresh the display regularly enough to prevent visible flicker. While this can be done in normal straight code, it does mean that the display code has to be an integral part of the program - precluding it's use as 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 every 16mS. Interrupts in a PIC cause the program to stop what it's doing, save it's current location, and jump to the 'Interrupt Vector' - which on a PIC is address 0x0004. The interrupt routine 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 where it was when the interrupt was called. An important point to bear in mind when using interrupts is that you mustn't change anything the main program is using - for that reason the first thing we must do is save various resisters - the W register, the PCLATH register, and the STATUS register these are saved in data registers allocated for their use, and are restored 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 point
saving the register if we've already changed it!. Next we check is the interrupt was generated by the timer or not, in this case we're only using one interrupt source (TIMER2) - but often you may be using multiple interrupt sources, if it's not TIMER2 we simply jump to the EXIT 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 reset TIMER2 - once it's triggered an interrupt it turns itself off, so we turn it back on here (bcf PIR1,TMR2IF). Next we start our display routine, first we need to find out which display we'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 are stored 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 the W register and AND it with 0x0F (to make sure it can't be any higher than 15), then 'call LED_Table' - this is a table which gives the correct segment pattern for each number - you'll notice that on the circuit diagram I didn't label which segment was which, this was because I wired them as simply as possible - we can sort out which is which in this table. Here the two digits vary slightly, for the 'ones' digit we need to clear bit 7 - and do so with the 'andlw 0x7F', for the 'tens' we need to set bit 7 - and do so with the 'iorlw 0x80', in both case we then simply write the W register to the port and exit the interrupt routine. ; Interrupt vector
ORG
0x0004
INT movwf w_temp swapf STATUS,W movwf s_temp ; Save W register ; Swap status to be saved into W ; Save STATUS register movfw PCLATH movwf p_temp ; Save PCLATH
btfss PIR1,TMR2IF ; Flag set if TMR2 interrupt goto INTX ; Jump if not timed out
bcf
movfw ones andlw 0x0F call andlw 0x7F ;make sure in range of table LED_Table ;set to correct LED
movfw tens ;make sure in range of table LED_Table ;set to correct LED
movwf 7SEG_PORT
swapf s_temp,W movwf STATUS ; Restore STATUS register - restores bank swapf w_temp,F swapf w_temp,W ; Restore W register
retfie
The interrupt routine above is complete and working, but it requires TIMER2 setting up before it can work, this is done in the 'Initialise' section of the program, which is the first section called when the program runs. This first turns the comparators off, then sets the direction register for the Port to all outputs, then sets TIMER2 to the correct time, you will notice there's two lines (with one commented out) for setting the value of T2CON - if you comment out the second one, and use the first one, it makes the interrupt routine timing too slow - you can actually see the digits flickering, first one then the other - worth doing so you can see what's going on. After that we set up PR2, this sets the value that the timer counts to before it times out - then we enable TIMER2 interrupts by setting the TMR2IE flag in register 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 display variables, and aren't part of the TIMER2 setup routine. Initialise movwf CMCON 16F84) movlw 0x07 ;turn comparators off (make it like a
bsf
STATUS,
RP0
;select bank 1
movlw b'00000000'
bcf
STATUS,
RP0
;select bank 0
Set up Timer 2.
;movlw
b'01111110'
; Post scale /16, pre scale /16, TMR2 ON ; Post scale /4, pre scale /16, TMR2 ON movwf T2CON
movlw b'00010110'
bsf
STATUS,
RP0
;select bank 1
movlw .249
bsf
PIE1,TMR2IE
bcf
STATUS,
RP0
;select bank 0
bsf bsf
INTCON,PEIE INTCON,GIE
bcf
STATUS,
RP0
;select bank 0
clrf clrf
tens 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 a decimal increment I mean once the 'ones' digit gets to '10' we reset the 'ones' digit to zero and 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 starts again. Notice that this section of the program has no connection to any kind of I/O or display routine, all it does is increment two variables - the display function is handled totally transparently by the interrupt routine - all we need to do is place the values we want in 'ones' and 'tens'. Main call call call call incf movf ones, w Delay255 Delay255 Delay255 Delay255 ones, f ;test first digit
sublw 0x0A btfss STATUS, Z goto clrf incf movf tens, w Main ones tens, f ;test second digit
sublw 0x0A btfss STATUS, Z goto clrf goto Main Main tens ;loop for ever
For these tutorials you require Main Board 2 or 3, the Analogue Board, the LCD Board and the RS232 Board. Download zipped tutorial files. The 16F876 and the 16F877 both include 10 bit analogue to digital converters, the 876 provides a possible 5 inputs, and the 877 a possible 8 inputs, in both cases there's only one actual 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 really very 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 proven working
examples, which you can easily modify to use as you wish. There are four main registers associated with using the analogue inputs, these are listed in this table:
Main registers for analogue inputs. Name ADRESH ADRESL Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
A2D Result Register - High Byte A2D Result Register - Low Byte ADON
ADRESH and ADRESL are fairly self explanatory, they are the registers that return the result of the analogue to digital conversion, the only slightly tricky thing about them is that they are in different memory banks. ADCON0 Details ADCON0 is split into four separate parts, the first part consists of the highest two bits ADCS1 and ADCS0, and sets the clock frequency used for the analogue to digital conversion, this is divided down from the system clock (or can use an internal RC oscillator), 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 1 1 0 1 0 1 Fosc/2 Fosc/8 FOsc/32 Frc (Internal A2D RC Osc.) 1.25MHz 5MHz 20MHz 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 digital converter. Be aware that only the first five (AN0-AN4) are available on the 16F876, the next three (AN5AN7) are only available on the 16F877. Also notice that AN4 uses digital pin RA5, and not RA4 as you would expect. As the tutorial analogue input board only has two inputs, connected to AN0 and AN1, this reduces the decision to two possibilities, either 000 for AN0, or 001 for AN1, and we simply alter them as we switch between the two inputs. CHS2 CHS1 CHS0 Channel 0 0 0 0 1 1 1 1 0 0 1 1 0 0 1 1 0 1 0 1 0 1 0 1 Pin
Channel0 RA0/AN0 Channel1 RA1/AN1 Channel2 RA2/AN2 Channel3 RA3/AN3 Channel4 RA5/AN4 Channel5 RE0/AN5 Channel6 RE1/AN6 Channel7 RE2/AN7
The third part is a single bit (bit 2), GO/DONE, this bit has two functions, firstly by setting the bit it initiates the start of analogue to digital conversion, secondly the bit is cleared when the conversion is complete - so we can check this bit to wait for the conversion to finish. The fourth part is another single bit (bit 0), ADON, this simply turns the A2D On or Off, 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 from AN0, 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 "BTFSC ADCON0, 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 0 AN0 AN1 1 1 0 0 0 0 0 0 0 1 0 0 1 1
ADCON1 Details ADCON1 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 selects if the output is Right Justified (bit set) or Left Justified (bit cleared). The advantage of this is that it makes it very easy to use as an 8 bit converter (instead of ten bit) - by clearing this bit, and reading just ADRESH, we get an 8 bit result, ignoring the two least significant bits in ADRESL. For the purposes of these tutorials, I'm intending using the full ten bits - so this bit will be set. PCFG3-0 are probably the most complicated part of setting the A2D section, they set a lot of different options, and also limit which pins can be analogue, and which can be digital: PCFG3: AN7 AN6 AN5 AN4 AN3 AN2 AN1 AN0 Vref+ VrefPCFG0 RE2 RE1 RE0 RA5 RA3 RA2 RA1 RA0 0000 0001 0010 0011 0100 0101 0110 0111 A A D D D D D D A A D D D D D D A A D D D D D D A A A A A A A A A D D A A A A A A D D Vdd Vss RA3 Vss Vdd Vss RA3 Vss Vdd Vss RA3 Vss Vdd Vss Vdd Vss
A Vref+ A A A A
A Vref+ A D A D
D Vref+ D D D D D D D
A D D D D D D D
A D D D D D D D
A A A A D D D D
A Vref+ Vref- A A A A A A
A A A A A A A A
RA3 RA2 Vdd Vss RA3 Vss RA3 RA2 RA3 RA2 RA3 RA2 Vdd Vss RA3 RA2
A Vref+ A
D Vref+ Vref- D
As I mentioned above, this part looks rather complicated - but if we split it down, it starts to make more sense. There are actually four different options being set here: 1. 2. 3. 4. 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 using analogue inputs AN0 and AN1, which if you look down the columns eliminates four of the possibilities (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 in yellow) - so now we've only got eight possibly choices left (down to 50% already). Thirdly we are using an external VRef+ reference, so we need RA3 allocating to Vref+, this eliminates a further four (0000, 0010, 0100 and 1001, shaded in green). This brings us down 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 three possibilities (0001, 0011 and 1010, shaded in red) - which leaves the only option which fits all 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 in the places of the unused bits, this gives us two analogue inputs, Vref+ set to RA3, and Vref- set to Vdd.
Bits required in ADCON1. Name Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0 ADCON1 1 0 1 0 1
Now we know the setting up details, it's time for a little more explaining about exactly what it all means, I've mentioned earlier that the A2D has '10 bit' resolution, this means the output of the A2D can vary from 0 (all bits '0') to 1023 (all bits '1'). A number from 0-1023 probably isn't too helpful, because you don't know what they represent. This is where Vref+ 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 TL341 reference on RA3, 1023 is about 2.5V, and with Vref- at Vdd, 0 from the A2D equals zero volts on the input. So, to recap, 1023 represents 2.5V, and 0 represents 0V. Numbers in between represent voltages 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 a reading of 1 represents 0.00244V (or 2.44mV), and a reading of 2 represents 0.00488V (or 4.88mV), and so on. As you'll see later on (in the programming tutorials) it's important to know the voltage resolution of the A2D, in order to calculate the voltage. Now while I said the voltage resolution is 0.00244, I was rounding the figure off, the actual value (from Windows Calculator) is 0.00244379276637341153470185728250244 - multiplying 1023 by 0.00244 only gives 2.49612V, and not the 2.5V it should be. Although this is only a 0.388% error, it doesn't look good - I'd really like the display to read from 0 to the maximum input, with out silly limitations like that. I considered this while designing the analogue input board, and made a decision to get over the problem in the hardware. The amplifiers on the inputs have attenuators feeding them, 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) allows us to get a 0-1023 reading from the A2D converter giving a resolution of 10mV, which is a theoretical accuracy of around 0.1% (obviously the system itself isn't this accurate, nor is the meter we'll use to calibrate it). Another advantage of this is that it calibrates the voltage reference chip as well, this probably isn't going to be exactly 2.5V, by calibrating the inputs against 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've worked through the datasheets and found out how you need to set things. For the actual programming, this is reduced down to two small routines: Init_ADC ; Set ADCON0 movlw b'10000001' movwf ADCON0 ; Set ADCON1 BANKSEL ADCON1 movlw b'10000101' movwf ADCON1 BANKSEL ADCON0 return
This routine simply sets the values of ADCON0 and ADCON1 with the values we decided on in the discussion earlier, the only point to note is the BANKSEL commands before and after ADCON1. This register is in a different register bank to ADCON0, so we need to select the correct bank before accessing the register, as a matter of course we return to the original register bank afterwards. Read_ADC bsf ADCON0, GO_DONE ;initiate conversion
movf ADRESH,W andlw 0x03 movwf NumH BANKSEL ADRESL movf ADRESL,W BANKSEL movwf NumL ADRESH ;return result in NumL and NumH return
This second routine is the one which actually reads the analogue input, the first line initiates the conversion, and the second and third wait for it to complete. The second section simply reads the converted value from the two registers, and stores them in variables for later use. Notice the bank switching again around ADRESL, this is because ADRESL is in a different register bank, it's important to notice that we move it's value to W, then switch banks back BEFORE storing it in the variable NumL - if we don't switch back it gets stored in the wrong register. Notice I'm AND'ing ADRESH with 0x03, this is because we only use the lower two bits of ADRESH, and although the upper six bits should all be zero - I like to make sure. So all we need to do to use the A2D is to "call Init_ADC" during the programmes initialisation, and then "call Read_ADC" whenever we wish to read a value. In order to select the individual two analogue channels, I've simply expanded "Init_ADC" to two routines, "Init_ADC0" and "Init_ADC1", these simply set the analogue converter to the correct input pin.
These tutorials use the Analogue Board on PortA, the LCD Board on PortB, and the RS232 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 in an endless loop. It reads the analogue input, which gives a value from 0-$3FF (in hexadecimal), 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 a space, and the same value in hexadecimal. The program then wait for 100mS and jumps back to the start of the loop and runs again, this gives roughly 10 readings a second. Tutorial 11.2 - requires as above. Pretty much the same as Tutorial 11.1, except this time we read both channels and display the two voltages, this demonstrates how to switch between the different channels using the two different "Init_ADC" routines. In order to keep 10 readings per second, the delay has been reduced to 50mS, so we get 20 readings per second, but still only 10 on each channel. Tutorial 11.3 - requires as above. Similar to the previous Tutorials, but this time we add a decimal point and a 'V' for voltage at the end of the decimal display, and we don't bother showing the hexadecimal value - just as you would for a simple voltmeter. Tutorial 11.4 - requires RS232 board as well. This time we add an extra board, the RS232 Board. The program is essentially Tutorial 11.3, but this time we add a serial output routine to send the data via RS232 to a PC. For the RS232 output we don't send the 'V' at the end, but we do send CR/LF in order to display each reading on a separate line. I've implemented this by modifying the LCD_Char routine and adding a couple of Flags - LCD and RS232 - by setting these flags ON or OFF we can print to either the LCD, the RS232 output, both, or neither.
For these tutorials you require two Main Boards (16F628), one main board (16F876), LED Board, Switch Board, Joystick Board, RS232 Board, Analogue Board and LCD Board. Download zipped tutorial files. This tutorial is about using licence free radio modules, most countries now have such modules available at low cost, although the exact frequencies may vary between different countries. If you've tried to send plain RS232 via these modules, you've probably found that it doesn't work?, I found this a number of years ago, and a quick check with a scope soon showed why - the modules are AC coupled, so you can't send a long continuous HIGH level, after a fairly short period it breaks up and falls LOW. The standard RS232 sends a continuous HIGH level when there's no data being sent, with a drop to LOW being the START bit, so this method soon falls foul of the AC coupling - a crude solution, (which I used with my original modules years ago) is to invert the RS232 - hence the normal condition is LOW, and a START bit goes HIGH. To do this using the hardware USART requires an external hardware inverter, but for a software UART you can simply do the inversion in the software (which is what I did). As I suggested, this is rather crude, and while it worked well for the very short range I was using it for, it's not really recommended, but there are a number of ways to deal with the situation - the most common of which is 'Manchester coding', and it's this that we'll be dealing with in this tutorial. While RS232 works on the width of individual pulses (9600 baud being 105uS wide for each pulse) Manchester coding works on transitions from HIGH to LOW (bit 0), or LOW to HIGH (bit 1), the actual width of the pulses doesn't matter particularly (within reason) - this is good, because neither IR or wireless preserve the exact width of the transmitted pulses. The popular Philips RC5 IR remote control systems actually use this same coding scheme.
This is the way the bits are actually sent, a bit 1 is a transition from 0 to 1, and a bit 0 is a transition from 1 to 0.
I'm basing these tutorials on the routines by Peter JAKAB from http://jap.hu/electronic/codec.html, incorporating his basic encoding/decoding code within these tutorials, please visit his site and check his remote controls based around them. The first thing we need to discuss is the actual transmission format, we can't just send a single byte like we do for RS232, radio transmission isn't reliable enough, and if it misses the start bit then the complete byte will be useless. So it's normal to use a PACKET system, where a number of different pieces of information are transmitted after each other in the form of a
Header section - used to synchronise to the beginning of the packet. Address byte - this is a specific byte that the receiver checks, to make sure it's intended for this receiver. Data - at least one byte of data, but could be more - using more data bytes in larger packets speeds things up. Checksum - a checksum byte, used to make sure that the data is received correctly, if it fails the data should be discarded.
Header - This consists of a row of 20 bit one's, followed by a single bit zero. The long sequence of 1's gives the receiver time to settle, and the decoding software time to synchronise. This is what the header looks like, with the signal level originally being sat at zero, and the first transition HIGH being the first bit ONE. It's the actual transitions between levels that signify the data bit, NOT the width of the pulses. Notice that the first 20 ONE's are a really nice single frequency,
this property of Manchester coding allows you to recover the original clock speed from an asynchrono us data stream. Address - This consists of the 8 bits of data in the byte, followed by a single bit one, and a single bit zero.
This is what the Address byte (and all the following bytes) looks like, this particular byte has the binary value '11100110', the two extra bits at the end are always ONE and ZERO.
Data - This is the same as Address, 8 bits followed by a 1 and a 0. Checksum - This is also the same as Address, 8 bits followed by a 1 and a 0. This gives a total of 52 bits to send just a single 8 bit data byte, but by transmitting more than one byte in a packet this can be improved, two bytes will take only 62 bits, or four bytes only 82 bits.
Tutorial 12.1 This tutorial comprises a transmitter unit (requiring one 16F628 main board and the wireless transmitter board - Tut12_1T), and two receiver units, the first outputs the received bytes via RS232 to your PC (requires one 16F628 main board, one RS232 board and the wireless receiver board - Tut12_1R), the second is essentially the same, but rather than using the PC it uses an LCD display (requires one 16F628 main board, one LCD board and the wireless receiver board - Tut12_1RL). This tutorial uses a single byte packet, so only one byte
is sent at a time - the transmitter simply sends a short text string one byte at a time, separated by a short delay. The Wireless Boards connect to PortA, and the LCD or RS232 Boards connect to PortB. Tutorial 12.2 This tutorial comprises a transmitter unit (requiring one 16F628 main board and the wireless transmitter board - Tut12_2T), and two receiver units, the first outputs the received bytes via RS232 to your PC (requires one 16F628 main board, one RS232 board and the wireless receiver board - Tut12_2R), the second is essentially the same, but rather than using the PC it uses an LCD display (requires one 16F628 main board, one LCD board and the wireless receiver board - Tut12_2RL). This tutorial uses a four byte packet, so four bytes are sent at the same time - the transmitter simply sends a four byte string, which repeats after a short delay. The Wireless Boards connect to PortA, and the LCD or RS232 Boards connect to PortB. Notice that the first two tutorials are pretty well identical, with only the packet size being changed, this is done by simply altering the buffer size, and changing the packet size defined near the beginning of each program (packet_len EQU 2) - please note, the packet_len constant includes the address byte, so needs to be one greater than the data packet size. Tutorial 12.3 This tutorial comprises a transmitter unit (requiring one 16F628 main board, a switch board and the wireless transmitter board - TUT12_3T), and one receiver unit (requires one main board, one LED board and the wireless receiver board - TUT12_3R). This tutorial uses a single byte packet, so only one byte is sent at a time - the transmitter simply sends the packet while a transmit button is pressed, different buttons send different data bytes, and you can turn the receiver LED's ON and OFF, two buttons turn them ON, and the other two turn them OFF - a simple radio remote control. The Wireless Boards connect to PortA, the Switch and LED Boards connect to PortB. Tutorial 12.4 This tutorial comprises a transmitter unit (requiring one 16F628 main board, a switch board and the wireless transmitter board - TUT12_4T), and one receiver unit (requires one main board, one LED board and the wireless receiver board - TUT12_4R). This tutorial uses a single byte packet, so only one byte is sent at a time - the transmitter simply sends the packet while a transmit button is pressed, different buttons send different data bytes, but this time
you can toggle the receiver LED's ON and OFF - a simple toggling radio remote control. This gave me a few problems though, initially I tired to use exactly the same transmitter code as the previous example, but I was struggling to identify a new key press of the same button (particularly as they repeat whilst held down). Eventually I hit on the idea of modifying the transmitter code as well - so while you hold the button down the same code repeats, but when you let it go, a different code (actually 0x00) is sent to inform the receiver the button has been released. Incidentally, I mentioned the Philips RC5 IR remote system above, this actually uses an extra bit to signify a new button press or just a repeat signal while the button is held down - rather than implement that type of system I decided on a 'release' transmission instead. As in the previous tutorial, the Wireless Boards connect to PortA, the Switch and LED Boards connect to PortB. Tutorial 12.5 This tutorial comprises a transmitter unit (requiring one 16F876 main board, the analogue board and the wireless transmitter board - TUT12_5T), and one receiver unit, that outputs the received bytes via RS232 to your PC (requires one 16F628 main board, one RS232 board and the wireless receiver board - TUT12_5R). The transmitter board reads the two analogue inputs, then transmits the two 10 bit readings as four hexadecimal bytes (just as the A2D provides them), it does this repeatedly, with a short delay between each transmission. The receiver converts the two 10 bit values into decimal, and sends them to the PC over RS232 as ASCII characters, separated by a comma, and ended with a CR/LF - this will display nicely in HyperTerminal, and is easily imported into many PC programs. This tutorial uses a four byte packet, so the four bytes are sent in a single packet. On the transmitter the Wireless Board connects to PortB, and the Analogue Board to PortA (because the Analogue Board MUST use PortA), on the receiver the Wireless Board connects to PortA, and the RS232 Board to PortB.
For these tutorials you require the 876 or 877 Main Board and Multiplex LED Board.
Download zipped tutorial files. These tutorials demonstrate how to multiplex 64 LED's, arranged as a matrix of 8x8, we use one port to 'source' the columns, and another port to 'sink' the rows. So the Multiplex LED board requires two port connections, one for rows and one for columns - notice that NEITHER of these have the 0V or 5V pins connected, they aren't used at all. The technique of multiplexing allows you to individually access any one LED, or any combination of LED's, with just sixteen output pins. It's also worth noting that we're driving the LED's entirely from the PIC, which is why I chose 150 ohm current limiting resistors, this keeps the current down to within the PIC's individual pin limits, and also within the overall chip limits - but both are pushed pretty close!, this is done to get as much brightness as possible from the LED's, whilst minimising the component count on the board. Because of the current requirements the processor board MUST have a 7805 regulator, a 78L05 won't provide enough current. The technique used for the display is to show each section in turn, so we display row 1, then row 2, then row 3, and continue through the other five rows - as long as we do this fast enough you can't see any flickering - this obviously restricts what else the program can be doing, as we must refresh the display regularly enough to prevent visible flicker. As with the previous 7 segment multiplexing tutorial, I'm again using timer driven interrupts in order to transparently refresh the LED matrix - and the code is basically very similar (and simply modified from it). The interrupts are generated by Timer2, which is set to give an interrupt roughly every 16mS. Interrupts in a PIC cause the program to stop what it's doing, save it's current location, and jump to the 'Interrupt Vector' - which on a PIC is address 0x0004. The interrupt routine 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 where it was when the interrupt was called. An important point to bear in mind when using interrupts is that you mustn't change anything the main program is using - for that reason the first thing we must do is save various resisters - the W register, the PCLATH register, and the STATUS register these are saved in data registers allocated for their use, and are restored 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 point saving the register if we've already changed it!. Next we check is the interrupt was generated by the timer or not, in this case we're only using one interrupt source (TIMER2) - but often you may be using multiple interrupt sources, if it's not TIMER2 we simply jump to the EXIT
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 reset TIMER2 - once it's triggered an interrupt it turns itself off, so we turn it back on here (bcf PIR1,TMR2IF). Next we start our display routine, first we need to find out which display we're updating - in this case we're selecting one of eight display ROWS in turn, we do this by simply checking which was the previous display (using seven 'btfss' instructions, with each one jumping to a different routine to set the new ROW to be displayed) - I originally tried to do this directly using the PORT register, but it didn't really work, so I added a GPR (row_pos) which I use to store the position. We only need seven tests, because if those seven fail it MUST be the eighth one that was the previous row. The actual values for the eight columns are stored in the variables 'zero', 'one', 'two' etc. Each of the eight row display routines (Do_Zero etc.) first turns OFF the previous row, which will blank the complete display, then preload the column data from the respective register. Lastly it turns that row ON, to display the data for that row. There's probably no need to blank the display before displaying the new row?, but it ensures that only one row at a time is ever displayed - and it only costs 400nS extra time with a 20MHz clock. ; Interrupt vector
ORG
0x0004
INT movwf w_temp swapf STATUS,W movwf s_temp movfw PCLATH movwf p_temp ; Save PCLATH ; Save W register ; Swap status to be saved into W ; Save STATUS register
goto
INTX
bcf
Do_Zero
movf zero, w movwf bcf bcf goto COL_PORT row_pos, 0 ROW_PORT, 0 INTX ;turn ON row zero ;load columns
movlw
movf one, w movwf bcf bcf goto COL_PORT row_pos, 1 ROW_PORT, 1 INTX ;turn ON row one ;load columns
movlw
Do_Three
movf three, w movwf bcf bcf goto COL_PORT row_pos, 3 ROW_PORT, 3 INTX ;turn ON row three ;load columns
Do_Four
;load columns
Do_Five
movlw
0xFF
ROW_PORT row_pos
;load columns
movlw
movf six, w movwf bcf bcf goto COL_PORT row_pos, 6 ROW_PORT, 6 INTX ;turn ON row six ;load columns
Do_Seven
bcf
ROW_PORT, 7
INTX movfw p_temp movwf PCLATH swapf s_temp,W movwf STATUS swapf w_temp,F swapf w_temp,W retfie ; Restore W register ; Restore STATUS register - restores bank ; Restore PCLATH
The interrupt routine above is complete and working, but it requires TIMER2 setting up before it can work, this is done in the 'Initialise' section of the program, which is the first section called when the program runs. This first turns the analogue inputs off, then sets the direction registers for the Ports to all outputs, then sets TIMER2 to the correct time, you will notice there's two lines (with one commented out) for setting the value of T2CON - if you comment out the second one, and use the first one, it makes the interrupt routine timing too slow - you can actually see the digits flickering, first one then the other - worth doing so you can see what's going on. After that we set up PR2, this sets the value that the timer counts to before it times out - then we enable TIMER2 interrupts by setting the TMR2IE flag in register PIE1. This still isn't enough, so finally we write to the INTCON register to enable 'Peripheral Interrupts' and 'Global Interrupts'. Initialise BANKSEL ADCON1 movlw movwf 0x06 ADCON1 ;disable analogue inputs
BANKSEL PORTA
bsf
STATUS,
RP0
;select bank 1
RP0
;select bank 0
call
Clear
Set up Timer 2.
;movlw
b'01111110'
; Post scale /16, pre scale /16, TMR2 ON ; Post scale /4, pre scale /16, TMR2 ON
bsf
STATUS,
RP0
;select bank 1
; Set up comparator
bsf
PIE1,TMR2IE
bcf
STATUS,
RP0
;select bank 0
bsf bsf
INTCON,PEIE INTCON,GIE
bcf
STATUS,
RP0
;select bank 0
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 display a 'square' using the outer layer of LED's, delay 1 second, then 'invert' the display (make lit ones dark, and dark ones lit), and delay a further second, then loop back, this give a simple flashing square pattern on the display. As with the previous interrupt multiplexing tutorial, the display function is handled totally transparently by the interrupt routine - all we need to do is place the values required in the eight display data registers. Main call call call call goto Square Delay1Sec Invert Delay1Sec Main ;endless loop ;and clear it ;display a square
Tutorial 13.1 This tutorial flashes an inverting 'square' at 1 second intervals. To display a pattern, we
simply write it to the eight data registers, zero is the bottom line, and seven is the top. To invert the display we simple XOR the display registers with 0xFF. Tutorial 13.2 Where the previous tutorial used just eight display registers, this second one uses two sets of eight - one is the same as before (the actual registers which get displayed), the second is a duplicate set - labelled zero1 to seven 1. This allows a range of simple effects by changing from one to the other, and this tutorial gives routines for scrolling in all four directions, and displaying numeric digits. The code presented scrolls the digits 0 to 9 from left to right, right to left, bottom to top, and top to bottom, on the display. It's pretty obvious how the other routines can be used, just load the second set of registers, and call them as you wish. Here's a 20 second video clip of the scrolling in action LED1.WMV Tutorial 13.3 Following on from the previous tutorial, this one adds the rest of the ASCII character set, including upper and lower case letters. The fonts used is based on the Hitachi text LCD character set - but as I've an extra line I've added true descenders on those characters which have them. Rather then the previous simple method of storing the character maps, this tutorial stores them in a large table - as the table exceeds the normal 256 byte table limit, we use an extended 16 bit table to provide sufficient room for all the data, this also overcomes the 256 byte boundary problem. The tutorial itself simply scrolls the entire character set (starting with a space) from left to right. Tutorial 13.4 This is tutorial 13.3 with an extra table added, which is used to store a string to be displayed - the string scrolls across the display from right to left, the string can easily be changed, and as before is stored as a large table - you just need to add a 0x00 at the end to signify the end of the string. The string is standard ASCII, and the display routine subtracts 0x20 from it to match the character set. Again, you can see a short video of this tutorial in action TUT13_4.WMV