External Links

Creative Science Centre

 

7 Segment Display

This is not really an advanced topic but a discussion of an alternative way to do multiplexing and driving the 7 segment display. It does not use any of the functions provided in 'rookie'

http://byvac.com/mBlib/flb/Tutorial/PIC32MX1_Family/25_LED_Display/Project_1.bas

#option echo off
#option only on   // only load functions that don't already exist
// Load only the registers needed for this project
// I/O Port B 
constant TRISB           0xBF886110
constant TRISBCLR        0xBF886114
constant TRISBSET        0xBF886118
constant LATB            0xBF886130
constant LATBCLR         0xBF886134
constant LATBSET         0xBF886138
constant ANSELB          0xBF886100
// Timer registers for 2/3
constant T2CON           0xBF800800
constant TMR2            0xBF800810
constant PR2             0xBF800820
// Interrupt registers
constant IFS0CLR         0xBF881034
constant IPC3SET         0xBF8810C8
constant IEC0SET         0xBF881068

// derived constants
constant TIMERON 1 << 15 // on bit
constant T3IF 1 << 14  


// ==================== DISPLAY SECTION ========================================

// 4 digit 7 segment display wired as follows
// port		pin-IC 	pin-dosplay		segment
// RB0		4		11				A
// RB1		5		7				B
// RB2		6		4				C
// RB3		7		2				D
// RB4		11		1				E
// RB5		14		10				F
// RB6		15		5				G
// RB7		16		3				DP
// Digits connected to N-Channel FET so high switches on
// RB12		23		Common Cathode digit 1
// RB13		24		Common Cathode digit 2
// RB14		25		Common Cathode digit 1
// RB15		26		Common Cathode digit 2

constant SEGa	1 << 0
constant SEGb	1 << 1
constant SEGc	1 << 2
constant SEGd	1 << 3
constant SEGe	1 << 4
constant SEGf	1 << 5
constant SEGg	1 << 6
constant SEGdp	1 << 7
constant DIG1	1 << 12
constant DIG2	1 << 13
constant DIG3	1 << 14
constant DIG4	1 << 15
// The following are values that when sent to the port will turn the segments off
// they evaluate to a single value and so son't take up much space
constant DIGOFF DIG1 | DIG2 | DIG3 | DIG4
constant SEGOFF SEGa | SEGb | SEGc | SEGd | SEGe | SEGf | SEGg | SEGdp
//
// 0,1,2,3,4,5,6,7,8,9,dp,off
constant DIGIT$ "\63\48\91\79\102\109\124\7\127\103\128\0"
dim segS(5)

// *****************************************************************************
// Initialise ports
// *****************************************************************************
function seg_portInit()
dim j
	// good practice to switch off ports before setting to o/p
	@LATB = 0	// all 16 bits low
	// Note that RB10 and 11 are used for serial so dont set these
	@TRISBCLR = 0xf3ff // all o/p except bits 10 & 11
	// AN9 to 12 are also used by RB15 to RB12
	@ANSELB = 0xf000
	for j = 0 to 4
		segS(j)=11
	next
	segS(0)=1
endf


// *****************************************************************************
// Uses a table to set the segments to represent a number
// *****************************************************************************
function seg_number(digit,n)
print digit,n
	select(digit)
		case 1
			@LATBSET = DIG1
		case 2
			@LATBSET = DIG2
		case 3
			@LATBSET = DIG3
		case 4
			@LATBSET = DIG4
	endselect			
	@LATBSET = asc(DIGIT$,n)
endf

// *****************************************************************************
// This is needed to turn the segments off and clear any previous digits, could
// use @LATBCLR = 0xff but if there were any changes to the i/o port then
// that might not work.
// *****************************************************************************
function seg_allClear()
	@LATBCLR = DIGOFF | SEGOFF
endf

// *****************************************************************************
// scans
// *****************************************************************************
function x()
	segS(1)=1;segS(2)=2;segS(3)=3;segS(4)=4 // removed from final version
	seg_allClear()
	seg_number(segS(0),segS(segS(0)))
	segS(0)=segS(0) + 1
	if segS(0) > 4 then
		segS(0)=1 // reset
	endif
endf

Have a look at this project and notice that instead of pulling in all of the register names from the libraries, only the names that are used are defined. This saves quite a bit of space as the libraries are quite large. Other than that it is almost a copy of Project_2e.bas from the Output section.

There is an added function called x() (at the endof the code) for the moment. This or something similar will be called over an over again by the interrupt. Try it out first by loading the project, typing seg_portInit and then x a few times and finish with seg_allClear().

How it Works

There is a variable array defined called segS(5) with 5 elements. The first element segS(0) keeps track of the current segment and the other elements keep track of what is being displayed on the digit. The top lines sets the segments to display 1,2,3 and 4. segS(0) is incremented each time the function is called to illuminate the next digit, when it reaches 4 it is reset back to 1. You will have seen that calling this function over and over displays the value in segS(1-4) in turn. Do this quick enough (every 5mS) and we have what appears to be a steady display.

http://byvac.com/mBlib/flb/Tutorial/PIC32MX1_Family/25_LED_Display/Project_2.bas

#option echo off
#option only on   // only load functions that don't already exist
// Load only the registers needed for this project
// I/O Port B 
constant TRISB           0xBF886110
constant TRISBCLR        0xBF886114
constant TRISBSET        0xBF886118
constant LATB            0xBF886130
constant LATBCLR         0xBF886134
constant LATBSET         0xBF886138
constant ANSELB          0xBF886100
// Timer registers for 2/3
constant T2CON           0xBF800800
constant TMR2            0xBF800810
constant PR2             0xBF800820
// Interrupt registers
constant IFS0CLR         0xBF881034
constant IPC3SET         0xBF8810C8
constant IEC0SET         0xBF881068
constant IFS0            0xBF881030
// derived constants
constant TIMERON 1 << 15 // on bit
constant T3IF 1 << 14  


// ==================== DISPLAY SECTION ========================================

// 4 digit 7 segment display wired as follows
// port		pin-IC 	pin-dosplay		segment
// RB0		4		11				A
// RB1		5		7				B
// RB2		6		4				C
// RB3		7		2				D
// RB4		11		1				E
// RB5		14		10				F
// RB6		15		5				G
// RB7		16		3				DP
// Digits connected to N-Channel FET so high switches on
// RB12		23		Common Cathode digit 1
// RB13		24		Common Cathode digit 2
// RB14		25		Common Cathode digit 1
// RB15		26		Common Cathode digit 2

constant SEGa	1 << 0
constant SEGb	1 << 1
constant SEGc	1 << 2
constant SEGd	1 << 3
constant SEGe	1 << 4
constant SEGf	1 << 5
constant SEGg	1 << 6
constant SEGdp	1 << 7
constant DIG1	1 << 12
constant DIG2	1 << 13
constant DIG3	1 << 14
constant DIG4	1 << 15
// The following are values that when sent to the port will turn the segments off
// they evaluate to a single value and so son't take up much space
constant DIGOFF DIG1 | DIG2 | DIG3 | DIG4
constant SEGOFF SEGa | SEGb | SEGc | SEGd | SEGe | SEGf | SEGg | SEGdp
//
// 0,1,2,3,4,5,6,7,8,9,dp,off
constant DIGIT$ "\63\48\91\79\102\109\124\7\127\103\128\0"
dim segS(5)

// *****************************************************************************
// Initialise ports
// *****************************************************************************
function seg_portsInit()
dim j
	// good practice to switch off ports before setting to o/p
	@LATB = 0	// all 16 bits low
	// Note that RB10 and 11 are used for serial so dont set these
	@TRISBCLR = 0xf3ff // all o/p except bits 10 & 11
	// AN9 to 12 are also used by RB15 to RB12
	@ANSELB = 0xf000
	for j = 0 to 4
		segS(j)=11
	next
	segS(0)=1
endf


// *****************************************************************************
// Uses a table to set the segments to represent a number
// *****************************************************************************
function seg_number(digit,n)
	select(digit)
		case 1
			@LATBSET = DIG1
		case 2
			@LATBSET = DIG2
		case 3
			@LATBSET = DIG3
		case 4
			@LATBSET = DIG4
	endselect			
	@LATBSET = asc(DIGIT$,n)
endf

// *****************************************************************************
// This is needed to turn the segments off and clear any previous digits, could
// use @LATBCLR = 0xff but if there were any changes to the i/o port then
// that might not work.
// *****************************************************************************
function seg_allClear()
	@LATBCLR = DIGOFF | SEGOFF
endf

// *****************************************************************************
// each time this is called it will illuminate a digit in turn
// *****************************************************************************
function seg_isr()
	seg_allClear()
	seg_number(segS(0),segS(segS(0)))
	segS(0)=segS(0) + 1
	if segS(0) > 4 then
		segS(0)=1 // reset
	endif
endf

// ==================== INTERRUPT SECTION ======================================
// *****************************************************************************
// clocked with PCLK that is running at 40MHz, prescale 256 = 156.25kHz (6.4uS)
// setting PR2 1/6.4uS = 156250 should see the counter reset every second so 
// using 1562500 is 10 seconds.
// This also sets the interrupt
// *****************************************************************************
function seg_init()
	// initialsie ports used for display
	seg_portsInit()
	// set timer 
	@TMR2=0  // clear counter
	// timer_on | prescale_256 | 32_bit_on | Internal_clock
	@T2CON = 0x8078
	@PR2=781  // 5mS
	// Set interrupts
	@IFS0CLR = T3IF  // clear interrupt
	@IPC3SET = (3 << 2)
	@IEC0SET = T3IF
endf 

// just to look at timer values
function seg_test()
	irset(IFS0,T3IF,"seg_isr")
	seg_init()
endf

This is the full (unimproved) version. Load the project and type seg_test(). Nothing will appear to happen but now type segS(1)=5 and ‘5’ will appear on digit 1.

Because of the interrupt and timer this goes on in the background so all you have to do is set the variable to the correct value. DIGIT$ also has an added 0 to the end so segS(1)=10 is the decimal point and 11 is off.

Now for the improvements; the first thing is that to get a 5mS delay only 781 needs to be applied to PR2, this can be handled by a 16 bit timer and so just one of the timers are needed.

@T2CON = 0×8078 to @T2CON = 0×8070
constant IPC3SET 0xBF8810C8 to constant IPC2SET 0xBF8810B8
@IPC3SET = (3 << 2) to @IPC2SET = (3 << 2)
constant T3IF 1 << 14 to constant T2IF 1 << 9
irset(IFS0,T3IF,"seg_isr") to irset(IFS0,T2IF,"seg_isr")

Quite a few changes to use just the one timer.

Another improvement would be able to alter the brightness. At the moment seg_allClear() is being used at the beginning of the scan, this means that each display is on for a full 5ms. I will leave it up to you how to adjust this but as a hint, try moving seg_allClear() to the end of the function and see what happens.

Special Note:
It may have come to your attention that loading programs when the interrupt is working is not possible, a reset is required first. This is because the serial interface does not have any handshaking lines and so when an interrupt occurs the serial interface has no idea of this and still sends data which of course cannot be serviced. If some kind of handshaking were available then this could inform the serial interface to pause whilst the interrupt occurs.

http://byvac.com/mBlib/flb/Tutorial/PIC32MX1_Family/25_LED_Display/Project_2a.bas

#option echo off
#option only on   // only load functions that don't already exist
// Load only the registers needed for this project
// I/O Port B 
constant TRISB           0xBF886110
constant TRISBCLR        0xBF886114
constant TRISBSET        0xBF886118
constant LATB            0xBF886130
constant LATBCLR         0xBF886134
constant LATBSET         0xBF886138
constant ANSELB          0xBF886100
// Timer registers for 2/3
constant T2CON           0xBF800800
constant TMR2            0xBF800810
constant PR2             0xBF800820
// Interrupt registers
constant IFS0CLR         0xBF881034
constant IPC2SET         0xBF8810B8
constant IEC0SET         0xBF881068
constant IFS0            0xBF881030
// derived constants
constant TIMERON 1 << 15 // on bit
constant T2IF 1 << 9 


// ==================== DISPLAY SECTION ========================================

// 4 digit 7 segment display wired as follows
// port		pin-IC 	pin-dosplay		segment
// RB0		4		11				A
// RB1		5		7				B
// RB2		6		4				C
// RB3		7		2				D
// RB4		11		1				E
// RB5		14		10				F
// RB6		15		5				G
// RB7		16		3				DP
// Digits connected to N-Channel FET so high switches on
// RB12		23		Common Cathode digit 1
// RB13		24		Common Cathode digit 2
// RB14		25		Common Cathode digit 1
// RB15		26		Common Cathode digit 2

constant SEGa	1 << 0
constant SEGb	1 << 1
constant SEGc	1 << 2
constant SEGd	1 << 3
constant SEGe	1 << 4
constant SEGf	1 << 5
constant SEGg	1 << 6
constant SEGdp	1 << 7
constant DIG1	1 << 12
constant DIG2	1 << 13
constant DIG3	1 << 14
constant DIG4	1 << 15
// The following are values that when sent to the port will turn the segments off
// they evaluate to a single value and so son't take up much space
constant DIGOFF DIG1 | DIG2 | DIG3 | DIG4
constant SEGOFF SEGa | SEGb | SEGc | SEGd | SEGe | SEGf | SEGg | SEGdp
//
// 0,1,2,3,4,5,6,7,8,9,dp,off
constant DIGIT$ "\63\48\91\79\102\109\124\7\127\103\128\0"
dim segS(5)


// *****************************************************************************
// Initialise ports
// *****************************************************************************
function seg_portsInit()
dim j
	// good practice to switch off ports before setting to o/p
	@LATB = 0	// all 16 bits low
	// Note that RB10 and 11 are used for serial so dont set these
	@TRISBCLR = 0xf3ff // all o/p except bits 10 & 11
	// AN9 to 12 are also used by RB15 to RB12
	@ANSELB = 0xf000
	for j = 0 to 4
		segS(j)=11 // 11 is off
	next
	segS(0)=1
endf


// *****************************************************************************
// Uses a table to set the segments to represent a number
// *****************************************************************************
function seg_number(digit,n)
	select(digit)
		case 1
			@LATBSET = DIG1
		case 2
			@LATBSET = DIG2
		case 3
			@LATBSET = DIG3
		case 4
			@LATBSET = DIG4
	endselect			
	@LATBSET = asc(DIGIT$,n)
endf

// *****************************************************************************
// This is needed to turn the segments off and clear any previous digits, could
// use @LATBCLR = 0xff but if there were any changes to the i/o port then
// that might not work.
// *****************************************************************************
function seg_allClear()
	@LATBCLR = DIGOFF | SEGOFF
endf

// *****************************************************************************
// each time this is called it will illuminate a digit in turn
// *****************************************************************************
function seg_isr()
	seg_allClear()
	seg_number(segS(0),segS(segS(0)))
	segS(0)=segS(0) + 1
	if segS(0) > 4 then
		segS(0)=1 // reset
	endif
endf

// ==================== INTERRUPT SECTION ======================================
// *****************************************************************************
// clocked with PCLK that is running at 40MHz, prescale 256 = 156.25kHz (6.4uS)
// setting PR2 5mS/6.4uS = 781
// *****************************************************************************
function seg_init()
	// initialsie ports used for display
	seg_portsInit()
	// set timer 
	@TMR2=0  // clear counter
	// timer_on | prescale_256 | 16_bit_on | Internal_clock
	@T2CON = 0x8070
	@PR2=781  // 5mS
	// Set interrupts
	@IFS0CLR = T2IF  // clear interrupt
	@IPC2SET = (3 << 2)
	@IEC0SET = T2IF
endf 

// *****************************************************************************
// starts timer running and inteerupt.
// *****************************************************************************
function seg_start()
	irset(IFS0,T2IF,"seg_isr")
	seg_init()
endf

Load this project and type flsave(“”). The name has been changed to seg_start. This set of functions are now part of flash so that at any time we can type seg_start and place a value into one for the segS() elements to get the display to work.

http://byvac.com/mBlib/flb/Tutorial/PIC32MX1_Family/25_LED_Display/Project_3.bas

// 7 segment 4 LED display
// This needs the interrupt driven display to be in flash
//

// *****************************************************************************
// Initialise the library, check first
// *****************************************************************************
function num_init()
	if lookup("segS") = 0 then
		print "segment library not loaded"
	else
		seg_start()
	endif	
endf

// *****************************************************************************
// displays a 4 digit number with leading zeroes blanked
// *****************************************************************************
function num_disp4(number)
dim zero=0
	segS(1)=11;segS(2)=11;segS(3)=11 // blank leading 0
	if number >= 1000 then
		segS(1) = number / 1000
		number = number % 1000
		zero=1
	endif
	if zero = 1 then
		segS(2)=0;segS(3)=0;segS(4)=0;
	endif
	if number >= 100 then
		segS(2) = number / 100
		number = number % 100
		zero=1
	endif
	if zero = 1 then
		segS(3)=0;segS(4)=0;
	endif
	if number >= 10 then
		segS(3) = number / 10
	endif
		segS(4) = number % 10
endf

// *****************************************************************************
// test
// *****************************************************************************
function x()
dim j
	for j = 90 to 120
		num_disp4(j)
		wait(300)
	next
endf

This project uses the extended language and is therefore quite simple. Load the project type num_init and then x to see the results. The counter (display) should go from 90 to 120. The wait(300) is so you can see it happen.

The project starts with num_init() so that the functions, timer and interrupt now in Flash can be initialised. See also how a check is made that the library has been loaded by seeing if segS exists. It could be that this set of functions is used at a later date when the Flash has been cleared and so it is a good idea to check. Looking for segS is just convenient, any element for the library program could have been checked for. It is assumed that if segS exists then so do the other functions.

num_disp4(number) is a modification of the previous function from project_2e section 10_Input_output. It displays the specified number as a 4 digit value but blanks any unused digits so 55 will display as 55 not 0055.

We now have a method of displaying a number on a 4 digit display and we can also get access to the individual digits by reading or setting segS().

http://byvac.com/mBlib/flb/Tutorial/PIC32MX1_Family/25_LED_Display/Project_3a.bas

// 7 segment 4 LED display
// This needs the interrupt driven display to be in flash which is 
// section 25_LED_Display project_2a.bas

// *****************************************************************************
// displays a 4 digit number with leading zeroes blanked
// *****************************************************************************
function num_disp4(number)
dim zero=0
	segS(1)=11;segS(2)=11;segS(3)=11 // blank leading 0
	if number >= 1000 then
		segS(1) = number / 1000
		number = number % 1000
		zero=1
	endif
	if zero = 1 then
		segS(2)=0;segS(3)=0;segS(4)=0;
	endif
	if number >= 100 then
		segS(2) = number / 100
		number = number % 100
		zero=1
	endif
	if zero = 1 then
		segS(3)=0;segS(4)=0;
	endif
	if number >= 10 then
		segS(3) = number / 10
	endif
		segS(4) = number % 10
endf

Load and save this project (flsave(“”)). It is the same as Project_3 with x removed. Normally we would save to flash more useful items but as we will be using this quite a bit it may be worthwhile. On this device at least 1k of flash is taken up on each save and there isn’t a great deal spare, approximately 20k.

We can now use the display by first seg_start (but only once per reset) and then num_disp4(n) will place a number on the display.

Recap

CPU Use: Although this is a seemingly simple task of displaying numbers on a 4 digit display it is taking up quite a bit of CPU resource as it is being called every 5ms. It doesn’t take much for one or two more similar takes to take up nearly all of the CPU time. Another consideration is that if two or more interrupts are running together then; when the interrupts are serviced becomes less clear.

Although it looks attractive to be able to simply place a value in a variable and it will be displayed, this may not be the best use of resources. All programs eventually boil down to a single continuous loop, for a non-critical task like this it would be probably better to call the refresh within this loop and use the timers and interrupts for something that actually needs them.