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 ; Save W register swapf STATUS,W ; Swap status to be saved into W movwf s_temp ; 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 ;make sure in range of table call LED_Table andlw 0x7F ;set to correct LED movwf 7SEG_PORT goto INTX Do_tens movfw tens andlw 0x0F ;make sure in range of table call LED_Table iorlw 0x80 ;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 movlw 0x07 movwf CMCON ;turn comparators off (make it like a 16F84) 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 movlw b'00010110' ; Post scale /4, pre scale /16, TMR2 ON movwf T2CON 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 INTCON,PEIE ; Enable all peripheral interrupts bsf INTCON,GIE ; Global interrupt enable bcf STATUS, RP0 ;select bank 0 clrf tens clrf 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 Delay255 call Delay255 call Delay255 call Delay255 incf ones, f movf ones, w ;test first digit sublw 0x0A btfss STATUS, Z goto Main clrf ones incf tens, f movf tens, w ;test second digit sublw 0x0A btfss STATUS, Z goto Main clrf tens goto Main ;loop for ever
This tutorial implements a simple 0-99 counter.