Sunteți pe pagina 1din 203

PIC Tutorial Zero - Programming

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

0x0000 0x07 CMCON STATUS, b'00000000' TRISB TRISA STATUS, RP0

;select bank 1 ;set PortB all outputs ;set PortA all outputs ;select bank 0

RP0

0xff PORTA PORTB

;set all bits on ;the nop's make up the time taken by the goto ;giving a square wave output

0x00 PORTA PORTB Loop

;set all bits off ;go back and do it again

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

goto decfsz goto decfsz goto retlw end

$+2 countb, f Delay_0 count1 ,f d1 0x00

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.

Tutorials below here require the LED board

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

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

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!

;this waits for a while!

;this waits for a while!

;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

;start of general purpose registers

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

;used in delay routine ;used in delay routine ;used in delay routine

;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

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

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!

;this waits for a while!

;this waits for a while!

;this waits for a while!

;this waits for a while!

;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

;delay 250 ms (4 MHz clock)

movwf Delay_0 decfsz goto decfsz goto decfsz goto retlw end

countb counta, f $+2 countb, f Delay_0 count1 ,f d1 0x00

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

;set counter register to zero ;put counter value in W

;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'

;data table for bit pattern

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 250 ms (4 MHz clock)

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.

PIC Tutorial Two - Switches


For the first parts of this tutorial you require the Main Board and the Switch Board, the later parts will also use the LED Board, as written the tutorials use the Switch Board on PortA and the LED Board on PortB. Download zipped tutorial files. Tutorial 2.1 - requires Main Board and Switch Board.

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

PORTA PORTA TRISA 7 6 5 4 3 2 1 0

;set constant LEDPORT = 'PORTA' ;set constant SWPORT = 'PORTA' ;set constant for TRIS register ;set constants for the switches

;and for the LED's

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

RP0

Loop

btfss call btfss call btfss call btfss call

SWPORT,SW1 Switch1 SWPORT,SW2 Switch2 SWPORT,SW3 Switch3 SWPORT,SW4 Switch4

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

;turn all LED's off ;turn LED2 on

;turn all LED's off ;turn LED3 on

;turn all LED's off ;turn LED4 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

__config 0x3D18 etc.) cblock 0x20 count1 counta countb

;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 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

;and for the LED's

;set the de-bounce delay (has to use 'Set' and

RP0

Loop

btfss call btfss call btfss call

SWPORT,SW1 Switch1 SWPORT,SW2 Switch2 SWPORT,SW3 Switch3

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

;turn LED1 on ;wait until button is released

LED1

;turn LED1 on ;wait until button is released

;give switch time to stop bouncing ;check it's still pressed ;return is not ;see if LED2 is already lit

;turn LED2 on ;wait until button is released

LED2

;turn LED2 on ;wait until button is released

;give switch time to stop bouncing ;check it's still pressed

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

;return is not ;see if LED3 is already lit

;turn LED3 on ;wait until button is released

LED3

;turn LED3 on ;wait until button is released

;give switch time to stop bouncing ;check it's still pressed ;return is not ;see if LED4 is already lit

;turn LED4 on ;wait until button is released

LED4

;turn LED4 on ;wait until button is released

;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

;set constants for the switches

;and for the LED's

;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'

;data table for bit pattern

Table2

;data table for bit pattern

retlw retlw retlw retlw retlw retlw retlw retlw Table3

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

;data table for bit pattern

ChkKeys btfss call btfss

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

;turn all LED's off ;turn LED2 on

;turn all LED's off ;turn LED3 on

;turn all LED's off ;turn LED4 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 250 ms (4 MHz clock) ;check the keys ;delay 1mS

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

;set constants for the switches

;and for the LED's

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'

;data table for bit pattern

retlw retlw retlw retlw retlw retlw retlw retlw Table2

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

;data table for bit pattern

ChkKeys btfss call btfss

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

;turn LED4 on ;set fast speed ;wait until button is released

;turn LED4 on ;set slow speed ;wait until button is released

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 set by Speed ;check the keys ;delay 1mS

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

PIC Tutorial Three - LCD Modules


For the first parts of this tutorial you require the Main Board and the LCD Board, the later parts will also use the Switch Board, as written the tutorials use the LCD Board on PortA and the Switch Board on PortB. Although the hardware diagram shows a 2x16 LCD, other sizes can be used, I've tested it with a 2x16, 2x20, and 2x40 - all worked equally well. The intention is to develop a useful set of LCD routines, these will be used in the later parts of the tutorials to display various information. Download zipped tutorial files. LCD Command Control Codes Command Clear Display Display and Cursor Home Character Entry Mode Display On/Off and Cursor Display/Cursor Shift Function Set Set CGRAM Address Set Display Address I/D: 1=Increment* S: 1=Display Shift On D: 1=Display On 1=Cursor Underline U: On B: 1=Cursor Blink On D/C: 1=Display Shift Binary D7 0 0 0 0 0 0 0 1 D6 0 0 0 0 0 0 1 A D5 0 0 0 0 0 1 A A D4 0 0 0 0 1 8/4 A A D3 0 0 0 1 D/C 2/1 A A D2 0 0 1 D R/L 10/7 A A D1 0 1 I/D U x x A A D0 1 x S B x x A A Hex 01 02 or 03 01 to 07 08 to 0F 10 to 1F 20 to 3F 40 to 7F 80 to FF

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

0x20 count count1 counta countb tmp1 tmp2 templcd templcd2

;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

;turn comparators off (make it like a

clrf clrf

PORTA PORTB

SetPorts bsf

STATUS, RP0 movlw 0x00 movwf LCD_TRIS bcf STATUS, call Delay100

;select bank 1 ;make all pins outputs RP0 ;select bank 0

;wait for LCD to settle

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

EndMessage Stop goto Stop ;endless loop

;Subroutines and text tables ;LCD routines ;Initialise LCD LCD_Init movlw

0x20 call movlw call movlw call movlw

;Set 4 bit mode LCD_Cmd 0x28 LCD_Cmd 0x06 LCD_Cmd 0x0d LCD_Cmd LCD_Clr 0x00 ;clear display ;Set display shift

;Set display character mode

;Set display on/off and cursor

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

;move to 2nd row, first column

LCD_Line1W

;move to 1st row, column W

LCD_Line2W

;move to 2nd row, column W

LCD_CurOn command

;Set display on/off and cursor

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

Delay255movlw Delay100movlw Delay50 Delay20 Delay5 d0 d1

Delay_0

Pulse_e

bsf nop bcf retlw

;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

addwf retlw retlw retlw retlw retlw retlw

PCL, f 'H' 'e' 'l' 'l' 'o' 0x00

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

0x20 count count1 counta countb tmp1 tmp2 templcd templcd2

;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

;Binary inputs for decimal convert routine

;Decimal outputs from convert routine

;LCD handshake lines

LCD_RW LCD_E

Equ Equ org movlw movwf

0x06 0x07 0x0000 0x07 CMCON

;turn comparators off (make it like a

16F84) Initialise clrf count clrf clrf clrf clrf

PORTA PORTB NumL NumH

SetPorts bsf

STATUS, RP0 movlw 0x00 movwf LCD_TRIS bcf STATUS, call LCD_Init

;select bank 1 ;make all pins outputs RP0 ;select bank 0

;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

;display a 'space' ;and counter in hexadecimal

;wait so you can see the digits change

;Subroutines and text tables ;LCD routines ;Initialise LCD LCD_Init call

Delay100 movlw call movlw call movlw call movlw

;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 shift

;Set display character mode

;Set display on/off and cursor ;Set cursor off ;clear display

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 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

;move to 2nd row, first column

LCD_Line1W

;move to 1st row, column W

LCD_Line2W

addlw call retlw movlw call retlw

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

;move to 2nd row, column W

LCD_CurOn command

;Set display on/off and cursor

LCD_CurOff command

movlw call retlw

;Set display on/off and cursor

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

Delay255movlw Delay100movlw Delay50 Delay20 Delay5 d0 d1

;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

addwf retlw retlw retlw retlw retlw retlw

PCL, f '1' '6' '' 'B' 'i' 't'

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

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

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

0x20 count count1 counta countb tmp1 tmp2 templcd templcd2

;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

;Binary inputs for decimal convert routine

;Decimal outputs from convert routine

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

'u' 'n' 't' 'e' 'r' '.' 0x00 0x07 CMCON

;turn comparators off (make it like a

PORTA PORTB NumL NumH

SetPorts bsf

STATUS, movlw movwf movwf bcf call

RP0 0x00 LCD_TRIS TRISB STATUS, LCD_Init

;select bank 1 ;make all pins outputs

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

;move to 2nd row, third column ;convert to decimal

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

;display decimal characters ;using LCD_CharD to convert to ASCII

;display a 'space' ;and counter in hexadecimal

;wait so you can see the digits change

;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

;wait for LCD to settle ;Set 4 bit mode

;Set display shift

;Set display character mode

;Set display on/off and cursor ;Set cursor off

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

;move to 2nd row, first column

LCD_Line1W

addlw call retlw addlw call retlw movlw call retlw

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

;move to 1st row, column W

LCD_Line2W

;move to 2nd row, column W

LCD_CurOn command

;Set display on/off and cursor

LCD_CurOff command

movlw call retlw

;Set display on/off and cursor

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

Delay255movlw Delay100movlw Delay50 Delay20 Delay5

;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

bsf nop bcf retlw

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

PIC Tutorial Four - Joysticks

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

include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18

;sets the configuration settings (oscillator type etc.)

cblock 0x20 count count1 counta countb

;start of general purpose registers ;used in looping routines ;used in delay routine ;used in delay routine ;used in delay routine

tmp1

;temporary storage tmp2

templcd

;temp store for 4 bit mode templcd2 HiX LoX HiY LoY Flags endc ;result for Y pot ;result for X pot

LCD_PORT LCD_TRIS LCD_RS Equ LCD_RW LCD_E 0x04

Equ Equ

PORTA TRISA ;LCD handshake lines Equ 0x06

Equ

0x07

JOY_PORT JOY_TRIS PotX Equ 0x06 PotY SW1

Equ Equ

PORTB TRISB ;input assignments for joystick

Equ Equ

0x03 0x07

SW2 SW1_Flag Equ 0x01 SW2_Flag

Equ

0x02 ;flags used for key presses

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

RETLW 0x43 RETLW 0x44 RETLW 0x45 RETLW 0x46

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

Start movwf CMCON

movlw 0x07 ;turn comparators off (make it like a

16F84)

Initialise

clrf clrf clrf

count PORTA PORTB ;clear button pressed flags

bcf

Flags, SW1_Flag bcf

Flags, SW2_Flag

SetPorts

bsf

STATUS,

RP0

;select bank 1

movlw 0x00

;make all LCD pins outputs movwf LCD_TRIS

movlw 0xff

;make all joystick pins inputs movwf JOY_TRIS

bcf

STATUS,

RP0

;select bank 0

call call

JOY_Init Delay100 LCD_Init

;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

LCD_Line1 XString w call LCD_HEX

;set to first line ;display Joy-X string ;display high byte

movf HiX,

movf LoX,

w call LCD_HEX movlw ' ' call call

;display low byte

LCD_Char DisplaySW1

call call

LCD_Line2 YString w 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

call goto Main

DisplaySW2 ;loop for ever

;Subroutines and text tables

DisplaySW1

btfsc Flags, SW1_Flag goto Press_Str

btfss Flags, SW1_Flag goto NoPress_Str

retlw 0x00

DisplaySW2

btfsc Flags, SW2_Flag goto Press_Str

btfss Flags, SW2_Flag goto NoPress_Str

retlw 0x00

XString Mess1

clrf

count

;set counter register to zero ;put counter value in W

movf count, w

call

Xtext xorlw 0x00

;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

YString Mess2 call

clrf

count

;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?

movf count, w Ytext xorlw 0x00

btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess2

Press_Str Mess3 call

clrf

count

;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?

movf count, w presstext xorlw 0x00

btfsc STATUS, Z retlw 0x00 call incf goto ;return when finished LCD_Char count, f Mess3

NoPress_Str clrf Mess4 call

count

;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?

movf count, w 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 0x28 call

;Set display shift LCD_Cmd

movlw 0x06 call

;Set display character mode LCD_Cmd

movlw 0x0d

;Set display on/off and cursor command call LCD_Cmd

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,

;send lower nibble

andlw 0x0f

;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

call

Delay5

retlw 0x00

LCD_CharD LCD_Char

addlw 0x30 movwf templcd swapf templcd, andlw 0x0f

;convert numbers to ASCII values ;display character in W register w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT

bsf call Pulse_e

LCD_PORT, LCD_RS

;RS line to 1 ;Pulse the E line high

movf templcd, andlw 0x0f

;send lower nibble ;clear upper 4 bits of W

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

movlw 0x80 call

;move to 1st row, first column LCD_Cmd

retlw 0x00

LCD_Line2

movlw 0xc0 call

;move to 2nd row, first column LCD_Cmd

retlw 0x00

LCD_CurOn

movlw 0x0d call

;Set block cursor on LCD_Cmd

retlw 0x00

LCD_CurOff

movlw 0x0c call

;Set block cursor off LCD_Cmd

retlw 0x00

LCD_Clr

movlw 0x01 call LCD_Cmd

;Clear display

retlw 0x00

LCD_HEX

movwf tmp1

;display W as hexadecimal byte

swapf tmp1, w andlw 0x0f call call HEX_Table LCD_Char

movf tmp1, w andlw 0x0f call call HEX_Table LCD_Char

retlw 0x00

Pulse_e

bsf

LCD_PORT, LCD_E nop

bcf

LCD_PORT, LCD_E retlw 0x00

;end of LCD routines

;joystick routines

JOY_Init bsf bcf STATUS, JOY_TRIS, PotX

;setup joystick port RP0 ;select bank 1

;make PotX an output

bcf bcf bcf bcf

JOY_PORT, JOY_TRIS, JOY_PORT, STATUS,

PotX PotY PotY

;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

;make PotX an input RP0 ;select bank 0

x1 btfsc JOY_PORT, PotX goto ;keep going until input high EndX

incfsz LoX,f goto x1

incfsz HiX,f goto x1

EndX bsf

STATUS,

RP0

;select bank 1

bcf bcf bcf

JOY_TRIS, JOY_PORT, STATUS,

PotX PotX

;make PotX an output ;discharge capacitor RP0 ;select bank 0

retlw 0x00

ReadY clrf HiY clrf call bsf bsf bcf STATUS, JOY_TRIS, STATUS, PotY ;reset counter registers LoY Delay5 RP0 ;select bank 1

;make PotY an input RP0 ;select bank 0

y1 btfsc JOY_PORT, PotY goto ;keep going until input high EndY

incfsz LoY,f goto y1

incfsz HiY,f goto y1

EndY bsf

STATUS,

RP0

;select bank 1

bcf bcf bcf

JOY_TRIS, JOY_PORT, STATUS,

PotY PotY

;make PotY an output ;discharge capacitor RP0 ;select bank 0

retlw 0x00

ReadSW

btfss JOY_PORT, call Sw1On

SW1

btfss JOY_PORT, call Sw2On

SW2

btfsc JOY_PORT, call Sw1Off

SW1

btfsc JOY_PORT, call Sw2Off

SW2

retlw 0x00

Sw1On bsf

Flags, SW1_Flag retlw 0x00

Sw2On bsf

Flags, SW2_Flag retlw 0x00

Sw1Off

bcf

Flags, SW1_Flag

retlw 0x00

Sw2Off

bcf

Flags, SW2_Flag

retlw 0x00

;end of joystick routines

;Delay routines

Delay255

movlw 0xff goto

;delay 255 mS d0 ;delay 100mS d0 ;delay 50mS d0 ;delay 20mS d0

Delay100

movlw d'100' goto

Delay50

movlw d'50' goto

Delay20

movlw d'20' goto

Delay5

movlw 0x05 d0

;delay 5.000 ms (4 MHz clock) movwf count1 ;delay 1mS movwf counta movlw 0x01

d1

movlw 0xC7

movwf countb Delay_0 decfsz counta, f goto $+2

decfsz countb, f goto Delay_0

decfsz count1 ,f goto d1

retlw 0x00

;end of Delay routines

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

include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18

;sets the configuration settings (oscillator type etc.)

cblock 0x20 count count1 counta countb tmp1

;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

LoY Timer_H Flags endc

LCD_PORT LCD_TRIS LCD_RS Equ LCD_RW LCD_E 0x04

Equ Equ

PORTA TRISA ;LCD handshake lines Equ 0x06

Equ

0x07

JOY_PORT JOY_TRIS PotX Equ 0x06 PotY SW1 SW2 SW1_Flag Equ 0x01 SW2_Flag

Equ Equ

PORTB TRISB ;input assignments for joystick

Equ Equ Equ

0x03 0x07 0x02 ;flags used for key presses

Equ

0x02

org goto

0x0000 Start

ORG BCF INTCON,

0x0004 T0IF f

INCF Timer_H, RETFIE

Start movwf CMCON

movlw 0x07 ;turn comparators off (make it like a 16F84)

Initialise

clrf clrf clrf

count PORTA PORTB ;clear button pressed flags

bcf

Flags, SW1_Flag bcf

Flags, SW2_Flag

SetPorts

bsf

STATUS,

RP0

;select bank 1

movlw 0x00

;make all LCD pins outputs

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

JOY_Init Delay100 LCD_Init

;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

LCD_Line1 XString w call LCD_HEX

;set to first line ;display Joy-X string ;display high byte

movf HiX,

movf LoX,

w call LCD_HEX movlw ' ' call call

;display low byte

LCD_Char DisplaySW1

call call

LCD_Line2 YString w 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 call goto Main

LCD_Char DisplaySW2 ;loop for ever

;Subroutines and text tables

DisplaySW1

btfsc Flags, SW1_Flag goto Press_Str

btfss Flags, SW1_Flag goto NoPress_Str

retlw 0x00

DisplaySW2

btfsc Flags, SW2_Flag goto Press_Str

btfss Flags, SW2_Flag goto NoPress_Str

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

btfsc STATUS, Z retlw 0x00 ;return when finished

call incf goto

LCD_Char count, f Mess3

NoPress_Str clrf Mess4

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 0x28 call

;Set display shift LCD_Cmd

movlw 0x06 call

;Set display character mode LCD_Cmd

movlw 0x0d

;Set display on/off and cursor command call LCD_Cmd

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,

;send lower nibble

andlw 0x0f

;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

call

Delay5

retlw 0x00

LCD_CharD LCD_Char

addlw 0x30 movwf templcd swapf templcd, andlw 0x0f

;convert numbers to ASCII values ;display character in W register w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT

bsf call Pulse_e

LCD_PORT, LCD_RS

;RS line to 1 ;Pulse the E line high

movf templcd, andlw 0x0f

;send lower nibble ;clear upper 4 bits of W

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

movlw 0x80 call

;move to 1st row, first column LCD_Cmd

retlw 0x00

LCD_Line2

movlw 0xc0 call

;move to 2nd row, first column LCD_Cmd

retlw 0x00

LCD_CurOn

movlw 0x0d call

;Set block cursor on LCD_Cmd

retlw 0x00

LCD_CurOff

movlw 0x0c call

;Set block cursor off LCD_Cmd

retlw 0x00

LCD_Clr

movlw 0x01 call LCD_Cmd

;Clear display

retlw 0x00

LCD_HEX

movwf tmp1

;display W as hexadecimal byte

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

LCD_PORT, LCD_E nop

bcf

LCD_PORT, LCD_E retlw 0x00

;end of LCD routines

;joystick routines

JOY_Init bsf bcf bcf bcf bcf bcf STATUS, JOY_TRIS, JOY_PORT, JOY_TRIS, JOY_PORT, STATUS, PotX PotX PotY PotY

;setup joystick port RP0 ;select bank 1

;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

;clear timer hi byte RP0 ;select bank 1

;make PotX an input RP0 ;select bank 0

clrf TMR0 bcf bsf INTCON, INTCON, T0IF GIE ;start timer ;start interrupts PotX

btfss JOY_PORT, goto $-1

;loop until input high clrw

iorwf TMR0,

f movf TMR0,

;stop timer (for 3 cycles) W

movwf LoX

;and read immediately movf Timer_H, W movwf HiX

bcf

INTCON,

GIE

;turn off interrupts GIE

btfsc INTCON, goto $-2 bsf bcf bcf bcf STATUS, JOY_TRIS, JOY_PORT, STATUS, retlw PotX PotX RP0

;select bank 1

;make PotX an output ;discharge capacitor RP0 0x00 ;select bank 0

ReadY clrf Timer_H bsf bsf bcf STATUS, JOY_TRIS, STATUS, PotY

;clear timer hi byte RP0 ;select bank 1

;make PotY an input RP0 ;select bank 0

clrf TMR0 bcf bsf INTCON, INTCON, T0IF GIE ;start timer ;start interrupts PotY

btfss JOY_PORT, goto $-1

;loop until input high clrw

iorwf TMR0,

f movf TMR0,

;stop timer (for 3 cycles) W ;and read immediately

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

;make PotY an output ;discharge capacitor RP0 0x00 ;select bank 0

ReadSW

btfss JOY_PORT, call Sw1On

SW1

btfss JOY_PORT, call Sw2On

SW2

btfsc JOY_PORT, call Sw1Off

SW1

btfsc JOY_PORT, call Sw2Off

SW2

retlw 0x00

Sw1On bsf

Flags, SW1_Flag retlw 0x00

Sw2On bsf

Flags, SW2_Flag retlw 0x00

Sw1Off

bcf

Flags, SW1_Flag

retlw 0x00

Sw2Off

bcf

Flags, SW2_Flag

retlw 0x00

;end of joystick routines

;Delay routines

Delay255

movlw 0xff goto

;delay 255 mS d0 ;delay 100mS d0

Delay100

movlw d'100' goto

Delay50

movlw d'50' goto d0

;delay 50mS

Delay20

movlw d'20' goto d0

;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

decfsz countb, f goto Delay_0

decfsz count1 ,f goto d1

retlw 0x00

;end of Delay routines

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

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

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'.

PIC Tutorial Five - Infrared Communication

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

include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18

;sets the configuration settings (oscillator type etc.)

cblock 0x20 count count1 counta countb

;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

;Binary inputs for decimal convert routine NumH

TenK

;Decimal outputs from convert routine Thou Hund Tens Ones

templcd

;temp store for 4 bit mode templcd2

endc

LCD_PORT LCD_TRIS LCD_RS Equ LCD_RW LCD_E 0x04

Equ Equ

PORTA TRISA ;LCD handshake lines Equ 0x06

Equ

0x07

IR_PORT IR_TRIS IR_In Equ 0x02

Equ Equ

PORTB TRISB

;input assignment for IR data

OUT_PORT LED

Equ Equ

PORTB 0x07

ErrFlag StartFlag Equ 0x01 One Zero

Equ

0x00 ;flags used for received bit

Equ Equ

0x02 0x03

New

Equ

0x07

;flag used to show key released

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

Start movwf CMCON

movlw 0x07 ;turn comparators off (make it like a 16F84)

Initialise

clrf clrf clrf clrf

count PORTA PORTB Flags

clrf Dev_Byte clrf Cmd_Byte

SetPorts

bsf

STATUS,

RP0

;select bank 1

movlw 0x00

;make all LCD pins outputs movwf LCD_TRIS

movlw b'01111111'

;make all IR port pins inputs (except RB7) movwf IR_TRIS

bcf

STATUS,

RP0

;select bank 0

call call

LCD_Init

;setup LCD module ;let IR receiver settle down

Delay255

Main call call LCD_Line1 String1 ;set to first line ;display IR title string

call

ReadIR movlw d'2'

;read IR signal

call

LCD_Line2W

;set cursor position

clrf movf Dev_Byte, w

NumH ;convert device byte

movwf NumL call Convert movf Tens, w call LCD_CharD

movf Ones, w call LCD_CharD

movlw d'11' call LCD_Line2W clrf movf Cmd_Byte, w ;set cursor position NumH ;convert data byte

movwf NumL call Convert

movf Hund, w call LCD_CharD movf Tens, w call LCD_CharD

movf Ones, w call LCD_CharD

call

ProcKeys

;do something with commands received

goto

Main

;loop for ever

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

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key1

;try next key if not correct code

movf OUT_PORT, movwf tmp3 btfss tmp3, LED bsf

;read PORTB (for LED status) ;and store in temp register ;and test LED bit for toggling LED ;turn on LED

OUT_PORT,

btfsc tmp3, LED

bcf bcf

OUT_PORT,

LED

;turn off LED

Flags2, New

;and cancel new flag retlw 0x00

Key1

movlw But2

;test for button 2 subwf Cmd_Byte, w btfss STATUS , Z

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

;and cancel new flag retlw 0x00

Key2

movlw But3

;test for button 3 subwf Cmd_Byte, w btfss STATUS , Z

retlw 0x00

;return if not correct code ;this time just turn it off

bcf bcf

OUT_PORT,

LED

;turn off LED

Flags2, New

;and cancel new flag retlw 0x00

String1 Mess1 call

clrf

count

;set counter register to zero ;put counter value in W ;get a character from the text table ;is it a zero?

movf count, w Xtext xorlw 0x00

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

;set up to read 7 bits movwf Bit_Cntr clrf Cmd_Byte

Next_RcvBit2 call btfsc Flags, StartFlag goto

Read_Pulse ;abort if another Start bit ReadIR

btfsc Flags, ErrFlag goto ReadIR

;abort if error

bcf

STATUS , C

btfss Flags, Zero bsf rrf STATUS , C Cmd_Byte , f

decfsz Bit_Cntr , f goto Next_RcvBit2

rrf

Cmd_Byte , f

;correct bit alignment for 7 bits

Get_Cmd

movlw 0x05 movwf Bit_Cntr clrf Dev_Byte Next_RcvBit call

;set up to read 5 bits

Read_Pulse ;abort if another Start bit ReadIR ;abort if error ReadIR

btfsc Flags, StartFlag goto btfsc Flags, ErrFlag goto

bcf

STATUS , C

btfss Flags, Zero bsf rrf STATUS , C Dev_Byte , f

decfsz Bit_Cntr , f goto Next_RcvBit

rrf

Dev_Byte , f rrf rrf

;correct bit alignment for 5 bits Dev_Byte , f Dev_Byte , f

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

Read_Pulse btfss IR_PORT,

clrf

LoX IR_In ;wait until high

goto $-1 clrf tmp1

movlw 0xC0 movwf tmp2

;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 goto Still_High ;set New flag if no button pressed Still_High

Next

nop nop nop nop

nop nop

;waste time to scale pulse ;width to 8 bits nop nop nop nop

nop nop incf btfss IR_PORT, goto Next LoX, f IR_In

;loop until input high again

; test if Zero, One, or Start (or error)

Chk_Pulse

clrf

Flags

TryError

movf LoX,

; check if pulse too small ; if LoX <= 20

addlw d'255' - d'20'

btfsc STATUS , C goto bsf Flags, ErrFlag TryZero ; Error found, set flag

retlw 0x00

TryZero

movf LoX,

; check if zero ; if LoX <= 60

addlw d'255' - d'60'

btfsc STATUS , C goto bsf Flags, Zero TryOne ; Zero found, set flag

retlw 0x00

TryOne

movf LoX,

; check if one ; if LoX <= 112

addlw d'255' - d'112'

btfsc STATUS , C goto bsf Flags, One TryStart ; One found, set flag

retlw 0x00

TryStart

movf LoX,

; check if start ; if LoX <= 180

addlw d'255' - d'180'

btfsc STATUS , C goto bsf NoMatch ; Start pulse found

Flags, StartFlag

retlw 0x00 NoMatch bsf Flags, ErrFlag ; pulse too long ; Error found, set flag retlw 0x00

;end of pulse measuring routines

;LCD routines

;Initialise LCD LCD_Init call LCD_Busy ;wait for LCD to settle

movlw 0x20 call

;Set 4 bit mode LCD_Cmd

movlw 0x28 call

;Set display shift LCD_Cmd

movlw 0x06 call

;Set display character mode LCD_Cmd

movlw 0x0c call LCD_Cmd

;Set display on/off and cursor command ;Set cursor off

call

LCD_Clr

;clear display

retlw 0x00

; command set routine LCD_Cmd movwf templcd

swapf templcd, andlw 0x0f

;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

;send lower nibble ;clear upper 4 bits of W

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

addlw 0x30 LCD_Char swapf templcd, andlw 0x0f

;add 0x30 to convert to ASCII movwf templcd w ;send upper nibble ;clear upper 4 bits of W movwf LCD_PORT

bsf call Pulse_e

LCD_PORT, LCD_RS

;RS line to 1 ;Pulse the E line high

movf templcd, andlw 0x0f

;send lower nibble ;clear upper 4 bits of W

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

movlw 0x80 call

;move to 1st row, first column LCD_Cmd

retlw 0x00

LCD_Line2

movlw 0xc0 call

;move to 2nd row, first column LCD_Cmd

retlw 0x00

LCD_Line1W addlw 0x80 call

;move to 1st row, column W LCD_Cmd

retlw 0x00

LCD_Line2W addlw 0xc0 call

;move to 2nd row, column W LCD_Cmd

retlw 0x00

LCD_CurOn

movlw 0x0d

;Set display on/off and cursor command call LCD_Cmd

retlw 0x00

LCD_CurOff

movlw 0x0c

;Set display on/off and cursor command call LCD_Cmd

retlw 0x00

LCD_Clr

movlw 0x01 call LCD_Cmd

;Clear display

retlw 0x00

LCD_HEX

movwf tmp1 swapf tmp1, w andlw 0x0f call call HEX_Table LCD_Char

movf tmp1, w andlw 0x0f call HEX_Table

call

LCD_Char

retlw 0x00

Pulse_e

bsf

LCD_PORT, LCD_E nop

bcf

LCD_PORT, LCD_E retlw 0x00

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

;set LCD for command mode

LCD_PORT, LCD_RW ;setup to read busy flag bsf LCD_PORT, LCD_E ;read upper nibble (busy flag)

swapf LCD_PORT, w bcf

LCD_PORT, LCD_E movwf templcd2

bsf

LCD_PORT, LCD_E bcf btfsc templcd2, 7

;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

movwf LCD_TRIS bcf STATUS, RP0 return ;set bank 0

;end of LCD routines

;Delay routines

Delay255

movlw 0xff goto

;delay 255 mS d0 ;delay 100mS d0 ;delay 50mS d0 ;delay 20mS d0

Delay100

movlw d'100' goto

Delay50

movlw d'50' goto

Delay20

movlw d'20' goto

Delay5

movlw 0x05

;delay 5.000 ms (4 MHz clock)

d0 d1

movwf count1 movlw 0xC7 movwf counta movlw 0x01 movwf countb

Delay_0

decfsz counta, f goto $+2

decfsz countb, f goto Delay_0

decfsz count1 ,f goto d1

retlw 0x00

;end of Delay routines

;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 NumL,w andlw 0X0F addwf Tens,f addwf Ones,f

rlf rlf

Tens,f 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

; 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.)

include "P16F628.inc" __config 0x3D18

cblock 0x20 count1 counta

;start of general purpose registers ;used in delay routine ;used in delay routine countb count Delay_Count

Bit_Cntr Data_Byte Dev_Byte Rcv_Byte Pulse endc

IR_PORT IR_TRIS

Equ Equ

PORTB TRISB 0x01 0x02 0x01

IR_Out Equ IR_In Equ Ser_Out

Equ

Ser_In Equ SW1 Equ 7 SW2 SW3 SW4

0x02 ;set constants for the switches

Equ Equ Equ

6 5 4

TV_ID

Equ

0x01

;TV device ID

But1

Equ

0x00

;numeric button ID's

But2 But3 But4 But5 But6 But7 But8 But9 ProgUp ProgDn VolUp VolDn

Equ Equ Equ Equ Equ Equ Equ Equ

0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 d'16' d'17'

Equ Equ Equ Equ

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

Start movwf CMCON

movlw 0x07 ;turn comparators off (make it like a 16F84)

clrf

IR_PORT

;make PortB outputs low

bsf movlw

STATUS, b'11111101' movwf

RP0

;select bank 1

;set PortB all inputs, except RB1 IR_TRIS

movlw 0xff movwf PORTA bcf STATUS, RP0 ;select bank 0

Read_Sw btfss PORTA, call Switch1 SW2 SW1

btfss PORTA, call Switch2

btfss PORTA, call Switch3

SW3

btfss PORTA, call call goto Switch4 Delay27 Read_Sw

SW4

Switch1 call

movlw ProgUp Xmit_RS232 retlw 0x00

Switch2 call

movlw ProgDn Xmit_RS232 retlw 0x00

Switch3 call

movlw VolUp Xmit_RS232 retlw 0x00

Switch4 call

movlw VolDn Xmit_RS232 retlw 0x00

TX_Start

movlw d'92' call IR_pulse

movlw d'23' call NO_pulse

retlw 0x00

TX_One call

movlw d'46' IR_pulse

movlw d'23' call NO_pulse

retlw 0x00

TX_Zero call

movlw d'23' IR_pulse

movlw d'23' call NO_pulse

retlw 0x00

IR_pulse MOVWF irloop count BSF ; Pulses the IR led at 38KHz IR_PORT, IR_Out ; ; ; ; ; ; ;

NOP NOP NOP NOP NOP NOP NOP

BCF

IR_PORT,

IR_Out ; ; ; ; ; ; ; ; ; ;

NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP DECFSZ

; ; count,F

GOTO irloop RETLW 0

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,

; ; ; ; ; ; ; ; ; ; ; IR_Out ; ; ; ; ; ; NOP NOP

NOP NOP NOP NOP NOP NOP

NOP NOP

; ;

DECFSZ

count,F

GOTO irloop2 RETLW 0

Xmit_RS232

MOVWF Data_Byte MOVLW 0x07

;move W to Data_Byte ;set 7 DATA bits out

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

GOTO Ser_Loop2 retlw 0x00

;Delay routines

Delay255

movlw 0xff goto

;delay 255 mS d0 ;delay 100mS d0 ;delay 50mS d0 ;delay 27mS d0 ;delay 20mS d0

Delay100

movlw d'100' goto

Delay50

movlw d'50' goto

Delay27

movlw d'27' goto

Delay20

movlw d'20' goto

Delay5

movlw 0x05

;delay 5.000 ms (4 MHz clock)

d0 d1

movwf count1 movlw 0xC7 movwf counta movlw 0x01 movwf countb

Delay_0

decfsz counta, f goto $+2

decfsz countb, f goto Delay_0

decfsz count1 ,f goto d1

retlw 0x00

;end of Delay routines

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

include "P16F628.inc" ERRORLEVEL 0, __config 0x3D18

;sets the configuration settings (oscillator type etc.)

cblock 0x20 count count1 counta countb

;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

Flags Flags2 tmp1 ;temporary storage tmp2 tmp3 lastdev lastkey

endc

LED_PORT LED_TRIS

Equ Equ

PORTB TRISB

IR_PORT IR_TRIS IR_In Equ 0x02

Equ Equ

PORTA TRISA

;input assignment for IR data

OUT_PORT LED0 LED1 LED2 LED3

Equ Equ Equ Equ Equ

PORTB 0x00 0x01 0x02 0x03

LED4 LED5 LED6 LED7

Equ Equ Equ Equ

0x04 0x05 0x06 0x07

EEPROM_Addr

Equ

0x00

;address of EEPROM byte used

ErrFlag StartFlag Equ 0x01 One Zero

Equ

0x00 ;flags used for received bit

Equ Equ

0x02 0x03

New

Equ

0x07

;flag used to show key released

TV_ID

Equ

0x01

;TV device ID

But1

Equ

0x00 But2 But3 But4 But5 But6 Equ Equ Equ Equ Equ

;numeric button ID's 0x01 0x02 0x03 0x04 0x05

But7 But8 But9

Equ Equ Equ

0x06 0x07 0x08

org goto

0x0000 Start

org

0x0004 retfie

Start movwf CMCON

movlw 0x07 ;turn comparators off (make it like a 16F84)

Initialise

clrf clrf clrf clrf

count PORTA PORTB Flags

clrf Dev_Byte clrf Cmd_Byte

SetPorts

bsf

STATUS,

RP0

;select bank 1

movlw 0x00

;make all LED pins outputs movwf LED_TRIS

movlw b'11111111'

;make all IR port pins inputs movwf IR_TRIS

bcf

STATUS,

RP0

;select bank 0

call

EE_Read

;restore previous settings

Main call call ProcKeys ReadIR ;read IR signal ;do something with commands received

goto

Main

;loop for ever

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

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key1

;try next key if not correct code

movf LED_PORT, movwf tmp3 btfss tmp3, LED0 bsf

;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

Flags2, New EE_Write

Key1

movlw But2

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key2

;try next key if not correct code

movf LED_PORT,

;read PORTB (for LED status)

movwf tmp3 btfss tmp3, LED1 bsf 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

Flags2, New EE_Write

Key2

movlw But3

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key3

;try next key if not correct code

movf LED_PORT, movwf tmp3 btfss tmp3, LED2 bsf

;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

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key4

;try next key if not correct code

movf LED_PORT, movwf tmp3 btfss tmp3, LED3 bsf

;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

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key5

;try next key if not correct code

movf LED_PORT, movwf tmp3 btfss tmp3, LED4 bsf

;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

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key6

;try next key if not correct code

movf LED_PORT, movwf tmp3 btfss tmp3, LED5 bsf

;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

;save the settings retlw 0x00

Key6

movlw But7

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z

goto

Key7

;try next key if not correct code

movf LED_PORT, movwf tmp3 btfss tmp3, LED6 bsf

;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

;test for button 1 subwf Cmd_Byte, w btfss STATUS , Z retlw 0X00

movf LED_PORT, movwf tmp3 btfss tmp3, LED7 bsf

;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

EEADR EECON1, W RP0 RD

; Address to read ; EE Read ; W = EEDATA ; Bank 0

movf EEDATA, bcf STATUS,

movwf LED_PORT

; restore previous value retlw 0x00

EE_Write

movf LED_PORT, bsf STATUS,

w RP0

; read current value ; Bank 1

bsf

EECON1,

WREN

; Enable write ; set EEPROM data

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,

; wait for write to complete. $-1

; 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

;set up to read 7 bits movwf Bit_Cntr clrf Cmd_Byte

Next_RcvBit2 call btfsc Flags, StartFlag goto

Read_Pulse ;abort if another Start bit ReadIR ;abort if error ReadIR

btfsc Flags, ErrFlag goto

bcf

STATUS , C

btfss Flags, Zero bsf rrf STATUS , C Cmd_Byte , f

decfsz Bit_Cntr , f goto Next_RcvBit2

rrf

Cmd_Byte , f

;correct bit alignment for 7 bits

Get_Cmd

movlw 0x05 movwf Bit_Cntr clrf Dev_Byte Next_RcvBit call

;set up to read 5 bits

Read_Pulse ;abort if another Start bit ReadIR ;abort if error ReadIR

btfsc Flags, StartFlag goto btfsc Flags, ErrFlag goto

bcf

STATUS , C

btfss Flags, Zero bsf rrf STATUS , C Dev_Byte , f

decfsz Bit_Cntr , f goto Next_RcvBit

rrf

Dev_Byte , f rrf rrf

;correct bit alignment for 5 bits Dev_Byte , f Dev_Byte , f

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

Read_Pulse btfss IR_PORT,

clrf

LoX IR_In ;wait until high

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 nop nop

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

;loop until input high again

; test if Zero, One, or Start (or error)

Chk_Pulse

clrf

Flags

TryError

movf LoX,

; check if pulse too small ; if LoX <= 20

addlw d'255' - d'20'

btfsc STATUS , C goto bsf Flags, ErrFlag TryZero ; Error found, set flag

retlw 0x00

TryZero

movf LoX,

; check if zero ; if LoX <= 60

addlw d'255' - d'60'

btfsc STATUS , C goto bsf Flags, Zero TryOne ; Zero found, set flag

retlw 0x00

TryOne

movf LoX,

; check if one ; if LoX <= 112

addlw d'255' - d'112'

btfsc STATUS , C goto bsf Flags, One TryStart ; One found, set flag

retlw 0x00

TryStart

movf LoX,

; check if start

addlw d'255' - d'180'

; if LoX <= 180

btfsc STATUS , C goto bsf NoMatch ; Start pulse found

Flags, StartFlag

retlw 0x00 NoMatch bsf Flags, ErrFlag ; pulse too long ; Error found, set flag retlw 0x00

;end of pulse measuring routines

;Delay routines

Delay255

movlw 0xff goto

;delay 255 mS d0 ;delay 100mS d0 ;delay 50mS d0 ;delay 20mS

Delay100

movlw d'100' goto

Delay50

movlw d'50' goto

Delay20

movlw d'20'

goto Delay5 movlw 0x05 d0 d1

d0

;delay 5.000 ms (4 MHz clock) movwf count1 movlw 0xC7 movwf counta movlw 0x01 movwf countb

Delay_0

decfsz counta, f goto $+2

decfsz countb, f goto Delay_0

decfsz count1 ,f goto d1

retlw 0x00

;end of Delay routines

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.

PIC Tutorial Six - I2C EEPROM Programming

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.

PIC Tutorial Eight - Using the PWM hardware

This tutorial is a little different, it's based on the 16F876 processor 2 board, but is designed specifically as a basic framework for a small differential drive robot, this means simply that it has two motors, one for left and one for right, with steering controlled in the same way as a tank. There seems a lot of confusion about how to use the hardware PWM, hence this simple tutorial. I've used the 16F876 as it has two PWM channels, the 16F628 only has one, the principals are exactly the same though, and the code could very simply be moved to the 16F628 if you only wanted a single PWM channel. The PWM hardware has up to 10 bit resolution, which means you can have 1024 different steps from zero to full power, for our purposes this is a little excessive, and I've decided on 128 steps forward (0-127), and 128 steps in reverse (128-255), using a single byte for the speed and direction, with the highest bit signifying reverse. It's actually very easy to use, once you've set the PWM up all you need to do is write to the CCPR1L and CCPR2L registers to set the speed, the listed routine uses an initialise subroutine which sets everything up, then subroutines to set the left and right motor speeds. The initialise subroutine sets various registers, they are commented in the code, but I'll explain them here as well: 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

cblock 0x20 count count1 counta countb temp

;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

Equ Equ Equ Equ

0x00 0x03 0x04 0x05

;pin for left motor reverse ;pin for left motor forward ;pin for right motor reverse ;pin for right motor forward

;pins 1 and 2 are the 2 PWM channels

ORG NOP

0x0000

;for bootloader compatibility

NOP NOP GOTO START ORG 0x0010

START CALL

Initialise

MainLoop: MOVLW CALL SpeedL CALL CALL d'64'

;both half speed forwards SpeedR Long_Delay

MOVLW CALL SpeedL MOVLW CALL SpeedR CALL

d'64'

;left half speed forwards d'192' ;right half speed reverse Long_Delay

MOVLW CALL SpeedL MOVLW

d'10' ;slow speed forwards d'228'

CALL

SpeedR CALL

;fast speed reverse Long_Delay

MOVLW CALL SpeedL MOVLW CALL SpeedR CALL

d'228' ;fast speed reverse d'10' ;slow speed forwards

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

CCP1CON,W ;set CCP1 as PWM

ANDLW 0xF0 IORLW 0x0C MOVWF CCP1CON

MOVF

CCP2CON,W ;set CCP2 as PWM ANDLW 0xF0 IORLW 0x0C MOVWF CCP2CON

MOVLW 126 BANKSEL PR2

;set highest PWM value ;over this (127) is permanently on MOVWF PR2 BANKSEL TMR2

MOVF

T2CON,W

;set prescaler to 16 ;PWM at 2500HZ

ANDLW 0xF8

IORLW 0x02 MOVWF T2CON

MOVF

T2CON,W

;set postscaler to 1

ANDLW 0x07 IORLW 0x00

MOVWF T2CON

CLRF

CCPR1L CLRF

;set PWM to zero CCPR2L

BSF

T2CON, TMR2ON

;and start the timer running RETURN

SpeedL:

;use value in W to set speed (0-127) MOVWF temp

BTFSC temp, 7 CALL ReverseL

;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

MOVWF CCPR1L RETURN

SpeedR: MOVWF temp

BTFSC temp, 7 CALL ReverseR

BTFSS temp, 7 CALL ANDLW ForwardR 0x7F

MOVWF CCPR2L RETURN

ReverseL: BSF PORTC, RL BCF ;set pins for reverse PORTC, FL RETURN

ReverseR: BSF BCF PORTC, RR PORTC, FR RETURN

ForwardL: BCF PORTC, RL BSF ;set pins for forward PORTC, FL RETURN

ForwardR:

BCF BSF

PORTC, RR PORTC, FR RETURN

;Delay routines

Long_Delay movlw d'50' call ;delay 5 seconds Delay100W return

Delay100W d2 call

movwf count Delay100

;delay W x 100mS ;maximum delay 25.5 seconds

decfsz count ,f goto d2

return

Delay255

movlw 0xff goto

;delay 255 mS d0 ;delay 100mS d0 ;delay 50mS

Delay100

movlw d'100' goto

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

d0 ;delay 20mS d0 ;delay 10mS d0 ;delay 1mS d0

;delay 5.000 ms (4 MHz clock) movwf count1 movlw 0xE7 movwf counta movlw 0x04 movwf countb

Delay_0

decfsz counta, f goto $+2

decfsz countb, f goto Delay_0

decfsz count1 ,f goto d1

return

;end of Delay routines

END

PIC Tutorial Nine - HEX Keypad

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

movlw 0x00 movwf KEY_PORT

;wait until no key pressed ;set all output pins low

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

Keys movlw 0x10

call Scan_Keys ;check for no key pressed subwf key, w btfss STATUS, Z goto Key_Found call goto Delay20 Keys

Key_Found

movf key, w andlw 0x0f

call

Key_Table

;lookup key in table ;save back in key

movwf key return

;key pressed now in W

Scan_Keys movlw 0xF0

clrf key ;set all output lines high

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

decfsz rows, f goto Scan Press return

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.

PIC Tutorial Ten - 7 Segment LED's

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

; Timer (TMR2) timeout every 16 milliseconds

bcf

PIR1,TMR2IF ; Clear the calling flag

btfss 7SEG_PORT, 7 ;check which LED was last goto Do_tens

movfw ones andlw 0x0F call andlw 0x7F ;make sure in range of table LED_Table ;set to correct LED

movwf 7SEG_PORT goto INTX

Do_tens andlw 0x0F call iorlw 0x80

movfw tens ;make sure in range of table LED_Table ;set to correct LED

movwf 7SEG_PORT

INTX movfw p_temp movwf PCLATH ; Restore PCLATH

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'

;Set port data directions, data output movwf 7SEG_TRIS

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

; Set up comparator movwf PR2

bsf

PIE1,TMR2IE

; Enable TMR2 interrupt

bcf

STATUS,

RP0

;select bank 0

; Global interrupt enable

bsf bsf

INTCON,PEIE INTCON,GIE

; Enable all peripheral interrupts ; Global interrupt enable

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

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

PIC Tutorial Eleven - Analogue Inputs

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

ADCON0 ADCS1 ADCS0 CHS2 CHS1 CHS0 GO/DONE ADCON1 ADFM -

PCFG3 PCFG2 PCFG1 PCFG0

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

1000 1001 1010 1011 1100 1101 1110 1111

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

A Vref+ Vref- A A Vref+ Vref- A D Vref+ Vref- A D D D D

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

btfsc ADCON0, GO_DONE goto $-1 ;wait for ADC to finish

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.

PIC Tutorial Twelve - RF remote control.

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

'packet', this consists of a number of different sections:


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.

PIC Tutorial Thirteen - Multiplexed LED's

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

btfss PIR1,TMR2IF ; Flag set if TMR2 interrupt

goto

INTX

; Jump if not timed out

; Timer (TMR2) timeout every 16 milliseconds

bcf

PIR1,TMR2IF ; Clear the calling flag

btfss row_pos, 0 goto Do_One

;check which ROW was last

btfss row_pos, 1 goto Do_Two

;check which ROW was last

btfss row_pos, 2 goto Do_Three

;check which ROW was last

btfss row_pos, 3 goto Do_Four

;check which ROW was last

btfss row_pos, 4 goto Do_Five

;check which ROW was last

btfss row_pos, 5 goto Do_Six

;check which ROW was last

btfss row_pos, 6 goto Do_Seven

;check which ROW was last

Do_Zero

movlw movwf movwf

0xFF ROW_PORT row_pos ;turn off all rows

movf zero, w movwf bcf bcf goto COL_PORT row_pos, 0 ROW_PORT, 0 INTX ;turn ON row zero ;load columns

Do_One movwf movwf

movlw

0xFF ROW_PORT row_pos ;turn off all rows

movf one, w movwf bcf bcf goto COL_PORT row_pos, 1 ROW_PORT, 1 INTX ;turn ON row one ;load columns

Do_Two movwf movwf

movlw

0xFF ROW_PORT row_pos ;turn off all rows

movf two, w movwf COL_PORT ;load columns

bcf bcf goto

row_pos, 2 ROW_PORT, 2 INTX ;turn ON row two

Do_Three

movlw movwf movwf

0xFF ROW_PORT row_pos ;turn off all rows

movf three, w movwf bcf bcf goto COL_PORT row_pos, 3 ROW_PORT, 3 INTX ;turn ON row three ;load columns

Do_Four

movlw movwf movwf movf four, w movwf bcf bcf goto

0xFF ROW_PORT row_pos ;turn off all rows

COL_PORT row_pos, 4 ROW_PORT, 4 INTX

;load columns

;turn ON row four

Do_Five

movlw

0xFF

movwf movwf movf five, w movwf bcf bcf goto

ROW_PORT row_pos

;turn off all rows

COL_PORT row_pos, 5 ROW_PORT, 5 INTX

;load columns

;turn ON row five

Do_Six movwf movwf

movlw

0xFF ROW_PORT row_pos ;turn off all rows

movf six, w movwf bcf bcf goto COL_PORT row_pos, 6 ROW_PORT, 6 INTX ;turn ON row six ;load columns

Do_Seven

movlw movwf movwf

0xFF ROW_PORT row_pos ;turn off all rows

movf seven, w movwf bcf COL_PORT row_pos, 7 ;load columns

bcf

ROW_PORT, 7

;turn ON row seven

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

movlw b'00000000' movwf movwf bcf ROW_TRIS COL_TRIS STATUS,

;Set port data directions, data output

RP0

;select bank 0

clrf movlw movwf

COL_PORT 0xFF ROW_PORT

;turn OFF all LED's

call

Clear

;clear display registers

Set up Timer 2.

;movlw

b'01111110'

; Post scale /16, pre scale /16, TMR2 ON ; Post scale /4, pre scale /16, TMR2 ON

movlw b'00010110' movwf T2CON

bsf

STATUS,

RP0

;select bank 1

movlw .249 movwf PR2

; Set up comparator

bsf

PIE1,TMR2IE

; Enable TMR2 interrupt

bcf

STATUS,

RP0

;select bank 0

; Global interrupt enable

bsf bsf

INTCON,PEIE INTCON,GIE

; Enable all peripheral interrupts ; Global interrupt enable

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

S-ar putea să vă placă și