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

		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

		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


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

		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

Tutorial 10.1

This tutorial implements a simple 0-99 counter.