line

Jake's Electronics

a place to document

line

Interrupt Driven 7 Segment Display

Many projects require the outputting of a result for human interface. Numbers can easily be displayed using a 7 Segment Display but they require a little bit of software and some smart programming to run them efficiently. If not coded efficiently you run the risk of using up an excessive amount of I/O pins from your controller, and possibly dedicating too much processing time to just running the display and not the other tasks the Microcontroller should be carrying out.

On this page I explain in depth one of the methods I use for running a 7 Segment Display. I have broken each code section down and explained it, as well as compiling a program that brings everything together and simply counts from 0 to 9999 at 10Hz.

Breadboard:

Interrupt Driven 7 Segment Display Breadboard

Schematic:

Circuit Extract. See below for full schematic.

Interrupt Driven 7 Segment Display Schematic
  • Asterisk Can be rearranged, although the Lookup Table in software must be altered to suit
  • Exclamation Mark Must connect to stated bit of PORT

How It Works:

Let's assume a 4 Digit display will be used with a processing speed of 4MHz.

In short, during the main program loop, numbers to be displayed are stored in General Purpose Registers, for instance after a temperature conversion. Timer 2 is used to generate an interrupt which cycles each digit of the display on and off for a specified interval. During the interrupt a lookup table is used to get the 7 Segment LED arrangement for the currently active digit. More details on how and why each section of code is needed are given under each piece of source code below. Note that only one digit of the display is on at any one time, but because all 4 digits of the display are being cycled through about 250 times per second the human brain is too slow to realise and it looks like a continuously lit display. Keep in mind that with 4 digits, each digit is only on 25% of the time. This is fine most of the time in terms of brightness, but if more digits are added you will experience a dimer display due to reduction in duty cycle. Changes in value for the current limiting resister can compensate for this. More information for adding or subtracting digits is explained below under 'More or Less Digits'.

Source Code Extracts:

» Define General Purpose Registers. Registers must be in this order due to indirect addressing! Place register labels in Constant Block.

For ease of understanding throughout the program, register labels are used instead of actual RAM address. When analysing the source code the label 'ONES_DIGIT' means makes a lot more sense than 0x25.

CBLOCK  H'20'                           ;
INDF_DIGIT_POINTER                      ; Used within Interrupt Service Routine to tell program which RAM address to pull BCD value from
ACTIVE_DIGIT                            ; Used to indicate which digit of the display is active
THOUS_DIGIT                             ; Holds BCD number, used within Interrupt Service Routine
HUNS_DIGIT                              ; Holds BCD number, used within Interrupt Service Routine
TENS_DIGIT                              ; Holds BCD number, used within Interrupt Service Routine
ONES_DIGIT                              ; Holds BCD number, used within Interrupt Service Routine

THOUS                                   ; Holds BCD number to be displayed, manipulate during normal program cycle
HUNS                                    ; Holds BCD number to be displayed, manipulate during normal program cycle
TENS                                    ; Holds BCD number to be displayed, manipulate during normal program cycle
ONES                                    ; Holds BCD number to be displayed, manipulate during normal program cycle
ENDC                                    ;

» Initialise General Purpose Registers. (Routine taken from datasheet). Place in device setup area, just after PORTs have been initialised.

On power up, RAM address locations are not always initialised to 0 (00000000). This routine runs through all RAM address locations in bank 1 setting them all to 0. If this is not done, erroneous data in various sections of code is used, causing bugs. Multiple CLRF instructions just for the General Purpose Registers that are used could be executed. But for the sake of a few hundred instruction cycles on power up, it takes just as much program code to initialise the entire bank 1 RAM.

We also need to turn the first (left most) digit of the display on to start the on off cycle for the Interrupt Service Routine (ISR). If this is not done, no digit of the display is ever turned on. See the 'Prepare for next interrupt' section of the ISR.

  ; Clear ALL Bank 0 RAM from 0X20 - 0X7F (96 bytes)
        BCF     STATUS, IRP             ; Indirect addressing Bank 0/1
        MOVLW   H'20'                   ; Initialise pointer to RAM
        MOVWF   FSR                     ; (File Select Register)
        CLRF    INDF                    ; Clear Register indirectly
        INCF    FSR, F                  ; Increment pointer
        BTFSS   FSR, 7                  ; All done?
        GOTO    $-D'3'                  ; No, Clear next byte
        
  ; Initialise Column Select
        BSF     ACTIVE_DIGIT, 3         ; Set column select to THOU_COL (1XXX) (00001000)

» Configure Interrupts and TIMER 2. Place in device setup area, just after PORTs have been initialised.

Firstly, interrupts are configured. Interrupt requirements will be program specific but for this example only Timer2 Match Interrupt is enabled and used. Set TMR2IE bit of PIE1 (TiMeR 2 Interrupt Enable bit of Peripheral Interrupt Enable Register 1) to enable the Timer 2 Interrupt.

Next we configure the Timer2 Match Interrupt. Basically, Timer 2 has a count register (TMR2) and a match register (PR2). In theory, the count register is incremented and when it matches the value in the match register the Timer 2 Interrupt Flag (TMR2IF) is set and if the Peripheral Interrupt is enabled (TMR2IE) an interrupt will be triggered. Here is the data sheets explantion for the Timer 2 operation:

The clock input to the Timer2 module is the system instruction clock (FOSC/4). The clock is fed into the Timer2 prescaler, which has prescale options of 1:1, 1:4 or 1:16. The output of the prescaler is then used to increment the TMR2 register. The values of TMR2 and PR2 are constantly compared to determine when they match. TMR2 will increment from 0x00 until it matches the value in PR2. When a match occurs, two things happen:

  • TMR2 is reset to 0x00 on the next increment cycle
  • The Timer2 postscaler is incremented

The match output of the Timer2/PR2 comparator is then fed into the Timer2 postscaler. The postscaler has postscale options of 1:1 to 1:16 inclusive. The output of the Timer2 postscaler is used to set the TMR2IF interrupt flag bit in the PIR1 register.

So to have the display cycle through all 4 digits without flicker but not take up too much processing time, I have configured Timer 2 to interrupt every 1mS. To do this, the Prescaler is set to 1:4, the Postscaler is set to 1:1 and the PR2 register is loaded with 250. With a processing speed of 4MHz, instruction cycles execute at FOSC/4, or every 1uS. 1uS fed into the 1:4 prescaler effectively causes the TMR2 (count) register increment every 4 instruction cycles. When TMR2 (count) matches PR2 (250) an overflow occurs and increments the 1:1 postscaler. Simplified as 1 * 4 * 250 * 1 = 1000uS/1mS

  ; Configure Interrupts
        BSF     STATUS, RP0             ; Bank 1
        MOVLW   B'00000010'             ;   00000010
        MOVWF   PIE1                    ;   -------0  Disable TMR1IE - TMR1 Overflow Interrupt Enable bit
                                        ;   ------1-  Enable  TMR2IE - TMR2 to PR2 Match Interrupt Enable bit
                                        ;   -----0--  Disable CCP1IE - CCP1 Interrupt Enable bit
                                        ;   ----X---  Unused
                                        ;   ---0----  Disable TXIE - USART Transmit Interrupt Enable bit
                                        ;   --0-----  Disable RCIE - USART Receive Interrupt Enable bit
                                        ;   -0------  Disable CMIE - Comparator Interrupt Enable bit
                                        ;   0-------  Disable EEIE - EE Write Complete Interrupt Enable bit
        BCF     STATUS, RP0             ; Bank 0
        CLRF    PIR1                    ; Clear Peripheral Interrupt Flags
        
  ; Configure Timer 2
    ; TMR2 Prescaler 1:4,
    ; TMR2 Postscale 1:1,
    ; TMR2 PR2       250     = 0.001S or 1mS Interrupts
        CLRF    TMR2                    ; Clear TMR2 register
        MOVLW   B'00000001'             ;   00000001
        MOVWF   T2CON                   ;   ------01  Pre-scale 1:4
                                        ;   -----0--  TMR2 OFF
                                        ;   -0000---  Post-scale 1:1
                                        ;   0-------  Unused
        BSF     STATUS, RP0             ; Bank 1
        MOVLW   D'250'                  ;
        MOVWF   PR2                     ; Interrupt every 0.001s or 1mS
        BCF     STATUS, RP0             ; Bank 0
        
  ; Enable Interrupts and start TMR2
        BSF     INTCON, GIE             ; Enable all un-masked interrupts
        BSF     INTCON, PEIE            ; Enable all un-masked peripheral interrupts
        BSF     T2CON, TMR2ON           ; Start TMR2

» LED display segments arrangement lookup table. Place in subroutine area (after device setup and before main program block).

The program calls this table during the ISR to retrieve the LED display segments arrangement based on the number held within the jump pointer registers (ONES_DIGIT etc). For example, if a 2 is held in the jump pointer, 2 is added to the program counter and the segment arrangement for the digit 2 is returned in the W register.

The arrangement can be adjusted to suit the hardware/wiring of the LED segments to the PORT bits as shown in the schematic.

SEGMENT_TABLE
  ; 7 Segment Display segments arrangement lookup table
  ; This routine assigns on/off arrangement to the 7 Segment Display Segments
        ADDWF   PCL, F
             ;    pGFEDCBA    JUMP      A|B|C|D|E|F|G|p   DISPLAY
        RETLW   B'00111111' ;   0       A|B|C|D|E|F|-|-      0
        RETLW   B'00000110' ;   1       -|B|C|-|-|-|-|-      1
        RETLW   B'01011011' ;   2       A|B|-|D|E|-|G|-      2
        RETLW   B'01001111' ;   3       A|B|C|D|-|-|G|-      3
        RETLW   B'01100110' ;   4       -|B|C|-|-|F|G|-      4
        RETLW   B'01101101' ;   5       A|-|C|D|-|F|G|-      5
        RETLW   B'01111101' ;   6       A|-|C|D|E|F|G|-      6
        RETLW   B'00000111' ;   7       A|B|C|-|-|-|-|-      7
        RETLW   B'01111111' ;   8       A|B|C|D|E|F|G|-      8
        RETLW   B'01101111' ;   9       A|B|C|D|-|F|G|-      9
        RETLW   B'00000000' ;  10       -|-|-|-|-|-|-|-    BLANK
        RETLW   B'00001000' ;  11       -|-|-|G|-|-|-|-      -
        RETURN

» Interrupt Service Routine. Place near top of program code, after ORG H'004' has been specified.

I have included the save and restore code in this code block, but the important part I will be referring to is the TMR2_INT section. This is probably the trickiest part of the whole display function to understand, but basically it achieves this;

  • Turn all segments that are currently on, off.
  • Turn currently active digit off and turn next digit on. Keep in mind at this point there are no segments turned on.
  • Retrieve 7 segment arrangement for currently active display and turn segments on.
  • Prepare for next interrupt before exiting.

A detailed step by step walk through is given in the comments of the source code, read them for a better understanding.

        ORG     H'004'                  ; Interrupt vector location. When an Interrupt occurs, the program jumps here
SAVE
  ; Save W, Status, Program Counter & File Select Registers
        MOVWF   W_ISR                   ; Save W to W_ISR
        SWAPF   STATUS, W               ; Use SWAPF instruction so status bits don't change
        MOVWF   S_ISR                   ; Save Status to S_ISR
        CLRF    STATUS                  ; Switch to Bank 0
        MOVF    PCLATH, W               ; Move PCLATH to W register
        MOVWF   P_ISR                   ; Save PCLATH to P_ISR
        CLRF    PCLATH                  ; Force page 0
        MOVF    FSR, W                  ; Move FSR to W register
        MOVWF   F_ISR                   ; Save FSR to F_ISR

TMR2_INT
  ; Refresh display
    ; Turn currently active segments off while maintaining the state of the any unused PORT pins (i.e., if no Decimal Point LED segment is used)
        MOVF    SEGMENT_PORT, W         ; Move the PORT state of which the LED display segments are connected to W
        ANDLW   B'10000000'             ; Turn off ONLY display segment PORT pins & leave unused PORT pins unchanged (i.e., place a 1 where PORT pins need to be left unchanged)
        MOVWF   SEGMENT_PORT            ; Write back to SEGMENT_PORT
        
    ; Turn currently active display digit off and turn next digit on while maintaining the state of the bits of the PORT that may be used for other functions
        MOVF    COLUMN_PORT, W          ; Move the PORT state of which the LED display columns are connected to W
        ANDLW   B'11110000'             ; Clear ONLY column select bits & leave upper nibble unchanged
        IORWF   ACTIVE_DIGIT, W         ; Inclusive OR above with ACTIVE_DIGIT register to turn next digit on
        MOVWF   COLUMN_PORT             ; Leave upper nibble of PORT unchanged & activate (turn on) next digit
        
    ; Get 7 segment arrangement for corresponding active digit. At this point all segments are off so it doesn't matter that the next digit has already been made active
        MOVF    INDF_DIGIT_POINTER, W   ; INDF_DIGIT_POINTER is initialised to 0. Used for Indirect Addressing
        ADDLW   THOUS_DIGIT             ; Add 'INDF_DIGIT_POINTER' to 'THOUS_DIGIT' GPR address to specify which display digit jump pointer to look for
        MOVWF   FSR                     ; FSR = THOUS_DIGIT + (0forTHOUS, 1forHUNS, 2forTENS & 3forONES) GPR'S MUST BE DEFINED SEQUENTIALLY IN CBLOCK!
        MOVF    INDF, W                 ; Effectively moves contents of THOUS_DIGIT/HUN_DIGIT/etc to W register. This is used as the jump pointer for the corresponding digit to be displayed (INDF register actually accesses data pointed to by the File Select Register (FSR))
        CALL    SEGMENT_TABLE           ; Get 7 segment arrangement from SEGMENT_TABLE for corresponding value to be displayed
        XORWF   SEGMENT_PORT, F         ; Segment pins are all already 0's but remaining pin if no DP used must be left unchanged. If PORT pins not used for segments they will be 0 in the lookup table so XOR to ensure PORT pin not used for segments are unchanged
        
    ; Prepare for next interrupt
        INCF    INDF_DIGIT_POINTER, F   ; Increment INDF_DIGIT_POINTER jump pointer to tell previous code block which display digit to return (used when indirect addressing above)
        BCF     STATUS, C               ; Clear C bit of STATUS register
        RRF     ACTIVE_DIGIT, F         ; Advance ACTIVE_DIGIT bit one digit to the right
        BTFSS   STATUS,C                ; Was the last ACTIVE_DIGIT the ONES_DIGIT, i.e., is Carry Flag Set?
        GOTO    $+D'3'                  ; NO, do not reset INDF_DIGIT_POINTER and ACTIVE_DIGIT
        CLRF    INDF_DIGIT_POINTER      ; Reset INDF_DIGIT_POINTER jump pointer
        BSF     ACTIVE_DIGIT, 3         ; Reset ACTIVE_DIGIT to THOUS_DIGIT (1XXX) (00001000)
        
    ; Clear TMR2 interrupt flag
        BCF     PIR1, TMR2IE            ; Clear TMR2 interrupt flag while still in Bank 0

RESTORE
  ; Restore W, Status, Program Counter & File Select Registers
        MOVF    F_ISR, W                ; MOVE F_ISR to W
        MOVWF   FSR                     ; Restore FSR
        MOVF    P_ISR, W                ; MOVE P_ISR to W
        MOVWF   PCLATH                  ; Restore PCLATH
        SWAPF   S_ISR, W                ; Undo previous SWAPF, place result in W
        MOVWF   STATUS                  ; Restore STATUS
        SWAPF   W_ISR, F                ; Use SWAPF instruction so status bits don't change
        SWAPF   W_ISR, W                ; Undo previous SWAPF and restore W register
        RETFIE                          ; Return From Interrupt

» Leading Zero Suppression. Place immediately prior to the following 'DISABLE_TMR2_INTERRUPT' routine.

The following is only required for aesthetic functionality. Just like on a calculator if you enter a single digit, all the 0's to the left (leading) are not visible. What I mean is 00000004 on an 8 digit calculator is simply displayed as 4. So to replicate this feature any 0's that are immediately to the left of any digit is turned off. This is achieved by testing each digit of the display starting from the left and checking if it holds a BCD 0. If so, the register is amended with the jump pointer for a blank digit and the next digit is tested. As soon as a digit that is not 0 is encountered, the sub-routine is exited to prevent other 0's from being suppressed, such as the 0 in 102.

LEADING_ZERO_SUPPRESSION
  ; Leading zero suppression to blank leading zeros, e.g., 0056 is displayed as 56
        MOVLW   B'00001010'             ; Jump Pointer for blank arrangement (See SEGMENT_TABLE)

        MOVF    THOUS, F                ; MOVF instruction affects Z bit of STATUS register
        BTFSS   STATUS, Z               ; Is BCD digit 0?
        GOTO    DISABLE_TMR2_INTERRUPT  ; No, do not black display
        MOVWF   THOUS                   ; Yes, blank this digit by overriding digit with jump pointer (B'00001010') held in W register, continue to check next digit for 0

        MOVF    HUNS, F                 ; Same method used above
        BTFSS   STATUS, Z               ; 
        GOTO    DISABLE_TMR2_INTERRUPT  ; 
        MOVWF   HUNS                    ; 

        MOVF    TENS, F                 ; Same method used above
        BTFSS   STATUS, Z               ; 
        GOTO    DISABLE_TMR2_INTERRUPT  ; 
        MOVWF   TENS                    ; 

» Disable TMR2 Interrupt while BCD jump pointers are modified.

To prevent a glitchy display, registers that will hold the BCD numbers that are used within the (ISR) cannot be modified during normal program execution. This is because it is almost impossible to know when these register will be modified in relation to the interrupt being triggered. It could be calculated at what point in the program the interrupt would be triggered if the program was in a continuous fixed time loop, but having a program that doesn't branch off, call routines & vary in execution time due to various circumstances is highly unlikely. So, if for arguments sake the registers that hold the digits to be displayed are modified during normal program flow, and the interrupt is triggered before all registers are modified, erroneous data will be displayed. To work around this, two sets of General Purpose Register labels are used. One set (ONES, TENS, HUNS & THOUS) that can be modified throughout the main program at any time and the other set (ONES_DIGIT, TEN_DIGIT, HUNS_DIGIT & THOUS_DIGIT) that is used by the ISR that should only be modified if interrupts are disabled.

To summarise, during normal program execution, BCD numbers can be moved to the ONE, TENS, HUNS & THOUS registers at any time. A number held within the ONES register will inevitably be displayed on the 'ones' column (right most digit) on the display. At the bottom of the main program block, the TMR2 interrupt is disabled and the BCD jump pointers held within ONES etc, are copied to ONE_DIGITS etc for use within the ISR.

DISABLE_TMR2_INTERRUPT
  ; Disable Timer 2 Interrupt to prevent interrupt from occurring during the modification of table jump pointer registers  
        BCF     PIE1, TMR2IE            ; Disable Timer 2 Interrupts
        MOVF    ONES, W                 ; 
        MOVWF   ONES_DIGIT              ; 
        MOVF    TENS, W                 ; 
        MOVWF   TENS_DIGIT              ; 
        MOVF    HUNS, W                 ; 
        MOVWF   HUNS_DIGIT              ; 
        MOVF    THOUS, W                ; 
        MOVWF   THOU_DIGIT              ; 
        BSF     PIE1, TMR2IE            ; Enable Timer 2 Interrupts

        GOTO    MAIN

line

Complete Working Program

Build the circuit, burn the chip, and watch the amazingness.

Schematic:

Interrupt Driven 7 Segment Display Schematic

Source Code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                                                                                                       ;
;       Program:        Interrupt Driven 7 Segment Display                                              ;
;       Author:         Jake Sutherland                                                                 ;
;       Start Date:     Thursday 8th May 2014                                                           ;
;       First Working:  Thursday 8th May 2014                                                           ;
;       Comments:       This is a example program to demonstrate an Interrupt Driven 7 Segment Display. ;
;                       The LOOP_THIS & STORE_VALUES sub-routines are included so the display is        ;
;                       actually doing something, they are not required to drive the display.           ;
;                                                                                                       ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        LIST            P=PIC16F628A
        INCLUDE         P16F628A.INC
        __CONFIG        _CP_OFF & _CPD_OFF & _LVP_OFF & _BOREN_OFF & _MCLRE_OFF & _PWRTE_ON & _WDTE_OFF & _INTOSC_OSC_NOCLKOUT 
        ERRORLEVEL      -302    ;Eliminate bank warning

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                                                                                                       ;
;                                      PIC16F628A Microcontroller                                       ;
;                                               ____ ____                                               ;
; LED 7 Segment 100's CC         VREF/AN2/RA2 -| 1  - 18 |- RA1/AN1               LED 7 Segment 10's CC ;
; LED 7 Segment 1000's CC        CPM1/AN3/RA3 -| 2    17 |- RA0/AN0                LED 7 Segment 1's CC ;
;                              CMP2/T0CKI/RA4 -| 3    16 |- RA7/OSC1/CLKIN                              ;
;                                VPP/MCLR/RA5 -| 4    15 |- RA6/OSC2/CLKOUT                             ;
;                                         VSS -| 5    14 |- VDD                                         ;
; Segment A                           INT/RB0 -| 6    13 |- RB7/T1OSC1/ICSPDAT               Segment DP ;
; Segment B                         DT/RX/RB1 -| 7    12 |- RB6/T1OSCO/T1CLKI/ICSPCLK         Segment G ;
; Segment C                         CK/TX/RB2 -| 8    11 |- RB5                               Segment F ;
; Segment D                          CCP1/RB3 -|_9____10_|- RB4/PGM                           Segment E ;
;                                                                                                       ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CBLOCK  H'20'                           ; Commence defining following constant block at RAM address X
DELAYGPR1                               ; Variable used for temporary information storage in the delay subroutines
DELAYGPR2                               ; Variable used for temporary information storage in the delay subroutines

INDF_DIGIT_POINTER                      ; Used within Interrupt Service Routine to tell program which RAM address to pull BCD value from
ACTIVE_DIGIT                            ; Used to indicate which digit of the display is active
THOUS_DIGIT                             ; Holds BCD number, used within Interrupt Service Routine
HUNS_DIGIT                              ; Holds BCD number, used within Interrupt Service Routine
TENS_DIGIT                              ; Holds BCD number, used within Interrupt Service Routine
ONES_DIGIT                              ; Holds BCD number, used within Interrupt Service Routine

THOUS                                   ; Holds BCD number to be displayed, manipulate during normal program cycle
HUNS                                    ; Holds BCD number to be displayed, manipulate during normal program cycle
TENS                                    ; Holds BCD number to be displayed, manipulate during normal program cycle
ONES                                    ; Holds BCD number to be displayed, manipulate during normal program cycle

DUMMY_ONES                              ; Holds BCD number for manipulation during normal program cycle, register not used outside of program loop therefore values are preserved for re-use
DUMMY_TENS                              ; Holds BCD number for manipulation during normal program cycle, register not used outside of program loop therefore values are preserved for re-use
DUMMY_HUNS                              ; Holds BCD number for manipulation during normal program cycle, register not used outside of program loop therefore values are preserved for re-use
DUMMY_THOUS                             ; Holds BCD number for manipulation during normal program cycle, register not used outside of program loop therefore values are preserved for re-use
ENDC

W_ISR           EQU     H'70'           ; For context saving. Ensures GPR is accessible from any Bank. See Memory Map of 628A
S_ISR           EQU     H'71'           ; For context saving. Ensures GPR is accessible from any Bank. See Memory Map of 628A
P_ISR           EQU     H'72'           ; For context saving. Ensures GPR is accessible from any Bank. See Memory Map of 628A
F_ISR           EQU     H'73'           ; For context saving. Ensures GPR is accessible from any Bank. See Memory Map of 628A

#define SEGMENT_PORT    PORTB
#define COLUMN_PORT     PORTA



        ORG     H'000'                  ; Processor reset vector location. On power up, the program jumps here
        GOTO    SETUP                   ;

        ORG     H'004'                  ; Interrupt vector location. When an Interrupt occurs, the program jumps here
SAVE
  ; Save W, Status, Program Counter & File Select Registers
        MOVWF   W_ISR                   ; Save W to W_ISR
        SWAPF   STATUS, W               ; Use SWAPF instruction so status bits don't change
        MOVWF   S_ISR                   ; Save Status to S_ISR
        CLRF    STATUS                  ; Switch to Bank 0
        MOVF    PCLATH, W               ; Move PCLATH to W register
        MOVWF   P_ISR                   ; Save PCLATH to P_ISR
        CLRF    PCLATH                  ; Force page 0
        MOVF    FSR, W                  ; Move FSR to W register
        MOVWF   F_ISR                   ; Save FSR to F_ISR

TMR2_INT
  ; Refresh display
    ; Turn currently active segments off while maintaining the state of the any unused PORT pins (i.e., if no Decimal Point LED segment is used)
        MOVF    SEGMENT_PORT, W         ; Move the PORT state of which the LED display segments are connected to W
        ANDLW   B'10000000'             ; Turn off ONLY display segment PORT pins & leave unused PORT pins unchanged (i.e., place a 1 where PORT pins need to be left unchanged)
        MOVWF   SEGMENT_PORT            ; Write back to SEGMENT_PORT
        
    ; Turn currently active display digit off and turn next digit on while maintaining the state of the bits of the PORT that may be used for other functions
        MOVF    COLUMN_PORT, W          ; Move the PORT state of which the LED display columns are connected to W
        ANDLW   B'11110000'             ; Clear ONLY column select bits & leave upper nibble unchanged
        IORWF   ACTIVE_DIGIT, W         ; Inclusive OR above with ACTIVE_DIGIT register to turn next digit on
        MOVWF   COLUMN_PORT             ; Leave upper nibble of PORT unchanged & activate (turn on) next digit
        
    ; Get 7 segment arrangement for corresponding active digit. At this point all segments are off so it doesn't matter that the next digit has already been made active
        MOVF    INDF_DIGIT_POINTER, W   ; INDF_DIGIT_POINTER is initialised to 0. Used for Indirect Addressing
        ADDLW   THOUS_DIGIT             ; Add 'INDF_DIGIT_POINTER' to 'THOUS_DIGIT' GPR address to specify which display digit jump pointer to look for
        MOVWF   FSR                     ; FSR = THOUS_DIGIT + (0forTHOUS, 1forHUNS, 2forTENS & 3forONES) GPR'S MUST BE DEFINED SEQUENTIALLY IN CBLOCK!
        MOVF    INDF, W                 ; Effectively moves contents of THOUS_DIGIT/HUN_DIGIT/etc to W register. This is used as the jump pointer for the corresponding digit to be displayed (INDF register actually accesses data pointed to by the File Select Register (FSR))
        CALL    SEGMENT_TABLE           ; Get 7 segment arrangement from SEGMENT_TABLE for corresponding value to be displayed
        XORWF   SEGMENT_PORT, F         ; Segment pins are all already 0's but remaining pin if no DP used must be left unchanged. If PORT pins not used for segments they will be 0 in the lookup table so XOR to ensure PORT pin not used for segments are unchanged
        
    ; Prepare for next interrupt
        INCF    INDF_DIGIT_POINTER, F   ; Increment INDF_DIGIT_POINTER jump pointer to tell previous code block which display digit to return (used when indirect addressing above)
        BCF     STATUS, C               ; Clear C bit of STATUS register
        RRF     ACTIVE_DIGIT, F         ; Advance ACTIVE_DIGIT bit one digit to the right
        BTFSS   STATUS,C                ; Was the last ACTIVE_DIGIT the ONES_DIGIT, i.e., is Carry Flag Set?
        GOTO    $+D'3'                  ; NO, do not reset INDF_DIGIT_POINTER and ACTIVE_DIGIT
        CLRF    INDF_DIGIT_POINTER      ; Reset INDF_DIGIT_POINTER jump pointer
        BSF     ACTIVE_DIGIT, 3         ; Reset ACTIVE_DIGIT to THOUS_DIGIT (1XXX) (00001000)
        
    ; Clear TMR2 interrupt flag
        BCF     PIR1, TMR2IE            ; Clear TMR2 interrupt flag while still in Bank 0

RESTORE
  ; Restore W, Status, Program Counter & File Select Registers
        MOVF    F_ISR, W                ; MOVE F_ISR to W
        MOVWF   FSR                     ; Restore FSR
        MOVF    P_ISR, W                ; MOVE P_ISR to W
        MOVWF   PCLATH                  ; Restore PCLATH
        SWAPF   S_ISR, W                ; Undo previous SWAPF, place result in W
        MOVWF   STATUS                  ; Restore STATUS
        SWAPF   W_ISR, F                ; Use SWAPF instruction so status bits don't change
        SWAPF   W_ISR, W                ; Undo previous SWAPF and restore W register
        RETFIE                          ; Return From Interrupt
        
        
        
SEGMENT_TABLE
  ; 7 Segment Display segments arrangement lookup table
  ; This routine assigns on/off arrangement to the 7 Segment Display Segments
        ADDWF   PCL, F
             ;    pGFEDCBA    JUMP      A|B|C|D|E|F|G|p   DISPLAY
        RETLW   B'00111111' ;   0       A|B|C|D|E|F|-|-      0
        RETLW   B'00000110' ;   1       -|B|C|-|-|-|-|-      1
        RETLW   B'01011011' ;   2       A|B|-|D|E|-|G|-      2
        RETLW   B'01001111' ;   3       A|B|C|D|-|-|G|-      3
        RETLW   B'01100110' ;   4       -|B|C|-|-|F|G|-      4
        RETLW   B'01101101' ;   5       A|-|C|D|-|F|G|-      5
        RETLW   B'01111101' ;   6       A|-|C|D|E|F|G|-      6
        RETLW   B'00000111' ;   7       A|B|C|-|-|-|-|-      7
        RETLW   B'01111111' ;   8       A|B|C|D|E|F|G|-      8
        RETLW   B'01101111' ;   9       A|B|C|D|-|F|G|-      9
        RETLW   B'00000000' ;  10       -|-|-|-|-|-|-|-    BLANK
        RETLW   B'00001000' ;  11       -|-|-|G|-|-|-|-      -
        RETURN
        
        
        
  ; Program Orgiginates Here
SETUP
  ; This rountine sets up the Microcontroller's Inputs and Outputs
        MOVLW   H'07'                   ; Turn Comparators off and enable pins for I/O functions
        MOVWF   CMCON                   ;
        BSF     STATUS, RP0             ; Bank 1
        MOVLW   B'01010000'             ; RA<7><5><3:0> Output, RA<6><4> Input
        MOVWF   TRISA                   ;
        CLRF    TRISB                   ; RB<7:0> Outputs
        BCF     STATUS, RP0             ; Bank 0
        
        CLRF    PORTA                   ; 
        CLRF    PORTB                   ; 
        
  ; Clear ALL Bank 0 RAM from 0X20 - 0X7F (96 bytes)
        BCF     STATUS, IRP             ; Indirect addressing Bank 0/1
        MOVLW   H'20'                   ; Initialise pointer to RAM
        MOVWF   FSR                     ; (File Select Register)
        CLRF    INDF                    ; Clear Register indirectly
        INCF    FSR, F                  ; Increment pointer
        BTFSS   FSR, 7                  ; All done?
        GOTO    $-D'3'                  ; No, Clear next byte
        
  ; Initialise Column Select
        BSF     ACTIVE_DIGIT, 3         ; Set column select to THOU_COL (1XXX) (00001000)
        
  ; Configure Interrupts
        BSF     STATUS, RP0             ; Bank 1
        MOVLW   B'00000010'             ;   00000010
        MOVWF   PIE1                    ;   -------0  Disable TMR1IE - TMR1 Overflow Interrupt Enable bit
                                        ;   ------1-  Enable  TMR2IE - TMR2 to PR2 Match Interrupt Enable bit
                                        ;   -----0--  Disable CCP1IE - CCP1 Interrupt Enable bit
                                        ;   ----X---  Unused
                                        ;   ---0----  Disable TXIE - USART Transmit Interrupt Enable bit
                                        ;   --0-----  Disable RCIE - USART Receive Interrupt Enable bit
                                        ;   -0------  Disable CMIE - Comparator Interrupt Enable bit
                                        ;   0-------  Disable EEIE - EE Write Complete Interrupt Enable bit
        BCF     STATUS, RP0             ; Bank 0
        CLRF    PIR1                    ; Clear Peripheral Interrupt Flags
        
  ; Configure Timer 2
    ; TMR2 Prescaler 1:4,
    ; TMR2 Postscale 1:1,
    ; TMR2 PR2       250     = 0.001S or 1mS Interrupts
        CLRF    TMR2                    ; Clear TMR2 register
        MOVLW   B'00000001'             ;   00000001
        MOVWF   T2CON                   ;   ------01  Pre-scale 1:4
                                        ;   -----0--  TMR2 OFF
                                        ;   -0000---  Post-scale 1:1
                                        ;   0-------  Unused
        BSF     STATUS, RP0             ; Bank 1
        MOVLW   D'250'                  ;
        MOVWF   PR2                     ; Interrupt every 0.001s or 1mS
        BCF     STATUS, RP0             ; Bank 0
        
  ; Enable Interrupts and start TMR2
        BSF     INTCON, GIE             ; Enable all un-masked interrupts
        BSF     INTCON, PEIE            ; Enable all un-masked peripheral interrupts
        BSF     T2CON, TMR2ON           ; Start TMR2
        
        
        
LOOP_THIS
        CALL    DELAY_100mS             ; Delay the software so the incrementing display is visible
        
        INCF    DUMMY_ONES, W           ; Increment the dummy ones register and store in W so not to adjust original value
        ADDLW   D'246'                  ; Add 245 to it
        BTFSC   STATUS, Z               ; Test zero bit of status register to check if original number is....
        GOTO    $+3                     ; Equal to 10. If it was 10 + 246 = 256, or an overflow to 0. So jump 'here + 3' instructions to 'MOVWF DUMMY_ONES' and store 0 in the ones register, continue and increment tens register
        INCF    DUMMY_ONES, F           ; Less than 10, just repeat the increment instruction but store it back to itself
        GOTO    STORE_VALUES            ; Continue without having to check tens huns or thous as ones did not overflow
        MOVWF   DUMMY_ONES              ; 
        
        INCF    DUMMY_TENS, W           ; Same as code block above
        ADDLW   D'246'                  ; 
        BTFSC   STATUS, Z               ; 
        GOTO    $+3                     ; 
        INCF    DUMMY_TENS, F           ; 
        GOTO    STORE_VALUES            ; 
        MOVWF   DUMMY_TENS              ; 
        
        INCF    DUMMY_HUNS, W           ; Same as code block above
        ADDLW   D'246'                  ; 
        BTFSC   STATUS, Z               ; 
        GOTO    $+3                     ; 
        INCF    DUMMY_HUNS, F           ; 
        GOTO    STORE_VALUES            ; 
        MOVWF   DUMMY_HUNS              ; 
        
        INCF    DUMMY_THOUS, W          ; Same as code block above
        ADDLW   D'246'                  ; 
        BTFSC   STATUS, Z               ; 
        GOTO    $+3                     ; 
        INCF    DUMMY_THOUS, F          ; 
        GOTO    STORE_VALUES            ; 
        MOVWF   DUMMY_THOUS             ; 
        
  ; Leading 0's are suppressed by adjusting the ONES, TENS, HUNS & THOUS registers, so we cant use the 
  ; values for the incrementing loop iteration above as they will be overridden if the leading number 
  ; held is a zero. This is why Dummy registers are used for the incrementing loop but we still need to 
  ; move these values to the ONES, TENS, HUNS & THOUS registers for the standard interrupt routines. 
  ; If using this Interrupt Driven Display program, the ONES, 
  ; TENS, HUNS & THOUS registers can just be adjusted in the normal program loop but the value can't be
  ; reused from loop to loop as they may be altered in the LEADING_ZERO_SUPPRESSION routine. If the 
  ; normal program loop simply overrides the values in the ONES, TENS, HUNS & THOUS registers then using 
  ; dummy registers in not required
STORE_VALUES
        MOVF    DUMMY_ONES, W           ; Move number held in DUMMY_ONES to working register
        MOVWF   ONES                    ; Store it in ONES register
        MOVF    DUMMY_TENS, W           ; Repeat
        MOVWF   TENS                    ; 
        MOVF    DUMMY_HUNS, W           ; 
        MOVWF   HUNS                    ; 
        MOVF    DUMMY_THOUS, W          ; 
        MOVWF   THOUS                   ; 


LEADING_ZERO_SUPPRESSION
  ; Leading zero suppression to blank leading zeros, e.g., 0056 is displayed as 56
        MOVLW   B'00001010'             ; Jump Pointer for blank arrangement (See SEGMENT_TABLE)

        MOVF    THOUS, F                ; MOVF instruction affects Z bit of STATUS register
        BTFSS   STATUS, Z               ; Is BCD digit 0?
        GOTO    DISABLE_TMR2_INTERRUPT  ; No, do not black display
        MOVWF   THOUS                   ; Yes, blank this digit by overriding digit with jump pointer (B'00001010') held in W register, continue to check next digit for 0

        MOVF    HUNS, F                 ; Same method used above
        BTFSS   STATUS, Z               ; 
        GOTO    DISABLE_TMR2_INTERRUPT  ; 
        MOVWF   HUNS                    ; 

        MOVF    TENS, F                 ; Same method used above
        BTFSS   STATUS, Z               ; 
        GOTO    DISABLE_TMR2_INTERRUPT  ; 
        MOVWF   TENS                    ; 

        
DISABLE_TMR2_INTERRUPT
  ; Disable Timer 2 Interrupt to prevent interrupt from occurring during the modification of table jump pointer registers  
        BCF     PIE1, TMR2IE            ; Disable Timer 2 Interrupts
        MOVF    ONES, W                 ; 
        MOVWF   ONES_DIGIT              ; 
        MOVF    TENS, W                 ; 
        MOVWF   TENS_DIGIT              ; 
        MOVF    HUNS, W                 ; 
        MOVWF   HUNS_DIGIT              ; 
        MOVF    THOUS, W                ; 
        MOVWF   THOUS_DIGIT             ; 
        BSF     PIE1, TMR2IE            ; Enable Timer 2 Interrupts

        GOTO    LOOP_THIS

        
DELAY_100mS  ; Actual delay = 0.1 seconds = 100000 cycles               1/10 Second
        MOVLW   0X1F                    ;
        MOVWF   DELAYGPR1               ;
        MOVLW   0X4F                    ;
        MOVWF   DELAYGPR2               ;
DELAY_100mS_0
        DECFSZ  DELAYGPR1, F            ;
        GOTO    $+2                     ;
        DECFSZ  DELAYGPR2, F            ;
        GOTO    DELAY_100mS_0           ; 
        GOTO    $+1                     ; 99998 CYCLES
        RETURN                          ; 4 cycles (including call)

END                                     ; Directive 'end of program'

.asm File Icon .hex File Icon

Parts List:

Part Quantity Schematic Designator
Semiconductors    
PIC16F628A Microcontroller 1 U1
LED 7 Segment (Common Cathode) 4 D1 - D4
BC548 NPN Transistor 4 Q1 - Q4
     
Resistors    
330Ω 1 R12
470Ω 7 R5 - R11
10KΩ 5 R1 - R4
     
Capacitors    
.1μF Multilayer Ceramic 1 C1
     
Hardware    
Breadboard 1  
Jumper Wire    
     
Miscellaneous    
-    
     
Power Requirements    
5 Volt DC Regulated, 100mAh    
     

line

More or Less Digits:

It's not all the time that we need 4 digits, sometimes we need less, sometimes more. A perfect example of this would be a 16 bit Binary to Decimal converter where 5 digits would be required to display 0 up to 65535 in decimal (B'11111111 11111111'). Listed below are the things that need adding or adjusting to increase the display from 4 to 5 digits.

Software. I will refer to the complete source code above and reference the line number before outlining what must be done. Tip, duplicate this window and view side by side to compare dot points to the source code line numbers.

  • Line 40 - (Add) Specify/insert fifth GPR address here, label it TENS_THOUS_DIGIT
  • Line 46 - (Add) Specify/insert fifth GPR address here, label it TENS_THOUS
  • Line 91 - (Adjust) Change B'11110000' to B'11100000'
  • Line 97 - (Adjust) THOUS_DIGIT must be changed to TENS_THOUS_DIGIT as this will be the new most significant digit
  • Line 110 - (Adjust) 3 must be changed to 4
  • Line 174 - (Adjust) 3 must be changed to 4
  • Line 271-274 - (Add) Duplicate 4 lines of code to accommodate for TENS_THOUS_DIGIT
  • Line 296-298 - (Add) Duplicate 2 lines of code to accommodate for TENS_THOUS_DIGIT

Hardware. Simply add a fifth to the existing 4 transistor drivers and feed it from the next sequential bit on the column port. In the case of the above schematic, RA4.

For fewer digits, instead off adding things, remove them and when adjusting things, well, it should be fairly self explanatory by now.

line