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.