# 74HC595 Shift Register with 4 Dig Com Anode 7 segment display

#### Mosaic

##### Well-Known Member
Here is a complete Proteus sim with asm code running a number of useful routines.

1) A simple debouncer in asm, easily understood, not as eff. as a parallel debouncer.
2) Binary to decimal conversion for counting.
3) 7 segment lookup table to display digits.
4) Shift register serial data transfer with column select and strobe for each digit.
5) ASM delay in main loop to effect a precise strobe time, as opposed to an interrupt.
6) Sampling of a couple tactile switches.

Almost every line is commented.

I used a 16f886 which I find to be a very flexible chip, although there are better ones now.

Clicking Sw1 counts up, Sw2 reset the count.
You can swap in other Shift registers, just keep track of the pinouts as they are not necessarily pin for pin replacements. The TPIC6C595 Shift reg. packs a lot more power and can drive LEDS quite bright which is necessary when strobing for best effect.

Change the 'bin' extension to DSN to use in Proteus.

#### colin55

##### Well-Known Member
How is 74HC04 going to sink or source a 7-segment display??????

#### Mosaic

##### Well-Known Member
Colin:
The hex gate is just for the simple functioning of ISIS....if u look at the bitmap pic u see that it is indicated that the gates will be replaced by PNP or P channel transistors. ISIS had sim probs with the actual transistors.

#### Jon Wilder

##### Active Member
I use the 16F886 and the F887 extensively. The F886 fit into the design and PCB layout of my MIDI controller pedals quite nicely. Very nice PIC to work with.

#### Mosaic

##### Well-Known Member
Update

Here is a somewhat improved version of this topic.

The asm code is cleaned up and commented to enable the user to easily switch between 2 or 4 digit, CC or CA displays and also use tactile switches running off the column select lines to minimise pin usage.

The new schematic is also posted as a BIN file, rename to DSN extension for Proteus.

The switch sampling GPR etc will dovetail with my other post on parallel debouncing for handling multiple switches efficiently.

The final improvement makes it simple to use any Dig. output pin in any port for the Shift Register and column select and switch I/O. Mix and match pins and ports freely now. This will make it easier to layout a congested PCB or work with MCU firmware that has only scattered free pins available.

Last edited:

#### Mike - K8LH

##### Well-Known Member
Your method looks remarkably similar to one of my MacMux designs from long ago (below) except you haven't implemented the PWM brightness control.

The MacMux design is relatively flexible and expandable. You can use NPN or NFET drivers for CC displays along with a 74HC595 or an MIC5891 as a 'source' driver for the segments, or you can use PNP or PFET column drivers for CA displays along with a 74HC595, an MIC5821, a TPIC6C595, or similar 'sinking' driver IC for the cathode segments. Re-tasking the column/digit driver lines for use as <clk> and <dat> lines to load the driver shift registers is a hallmark of the MacMux method, as well as the ability to load multiple driver shift registers in parallel in as little as 24 instruction cycles (bit-banged multi-channel SPI).

Regards...

#### Attachments

• 17.9 KB Views: 8,200
• 31.7 KB Views: 7,901
• 11.1 KB Views: 476
Last edited:

#### Mike - K8LH

##### Well-Known Member
Here's a design variation that only uses four pins (not expandable, no PWM brightness control). After all this time I still haven't tested it, but you're welcome to give it a try if you like.

Regards, Mike

Last edited:

#### Mosaic

##### Well-Known Member
Thanks Mike!

As an application of this thread it can be useful to retask an ICSP programming header to provide a pair of pushbuttons and a 2 Digit 7 segment display.

All that needs to be done is any gp output pin must be tied to the MCLR (which is configured as an input) via 1K resistor.

Now this GP pin plus the ICSP clk pin can be used to drive the serial data and the serial clock (respectively) of a 74hc595. The ICSP DAT pin can be used to drive the 74hc595 OE & RCK (byte) lines.

I did this using a 2 digit CC display and 2n3904 NPN CC drivers with 1k base resistors, plus pushbuttons. The pushbuttons will corrupt the display when pressed as they drive the 2n3904's on when pressed.Releasing the buttons returns the display to normal.

The 7 segment Display, 74HC595 and buttons go onto a daughter board with a female 5 pin receptacle to mate to the 5 pin ICSP header. Pwr and gnd are obtained from the ICSP header as well.

This is useful to get interactive feedback from a simple MCu circuit which does not warrant having it's own dedicated display. Including the code to handle the display on custom Mcu boards as shown before in this thread permits you to have a standard portable Display daughterboard that 'jacks' in to your MCu PCBs ICSP header and can make config changes via the pushbuttons and display whatever u need on the 2 digits available.

Last edited:

#### Mike - K8LH

##### Well-Known Member
Wouldn't a DIY serial backpack be easier?

Last edited:

#### Mosaic

##### Well-Known Member
Can't say I know what that is.

#### ARTUZAC

##### New Member
Hi, to everyone. On Apr 8,2012 there is thread called
"74HC595 Shift Register with 4 Dig Com Anode 7 segment display".

Mosaic wrote "Here is a complete Proteus sim with asm code ....".
I am a new member in this forum. Would someone please tell me
what should I do to get those Proteus files ?

artuzac

#### Mosaic

##### Well-Known Member
Here are the zipped files.

#### Attachments

• 84.3 KB Views: 440

#### Mike - K8LH

##### Well-Known Member
Hi Mosaic:

May I ask about the purpose of this section of code, please? It appears you're calling the GETSWITCH and Strobe subroutines at approximately ~768 usec intervals (plus the overhead of the subroutines) but you're not doing anything at the longer ~199 msec interval. I'm just curious what you may have had planned for that ~199 msec interval? Comments suggest you're using the least significant two bits of the Delay2 variable in the Strobe subroutine but I haven't found that in the code. Am I missing something?

Code:
Longloop
CLRF    Delay1          ;
CLRF    Delay2          ;
LOOP
DECFSZ  Delay1,F        ; 1 inst cycle, when  delay1=0 => test = true skip next line.
GOTO    LOOP            ; 2 inst cycles, this  is looped 256 times before skipping to next line => 768 microsec at 1 MIP
CALL    GETSWITCH       ; approx ~768 cycle intervals
CALL    Strobe          ; strobe the display based on 2 lsb's of Delay2  <-- where is this used?
DECFSZ  Delay2,F        ; 1 inst cycle
GOTO    LOOP            ; 2 inst cyles, by adding the loops = 256 * 768 + (256*3) = 257* 768 = 197376 microsec = .2 sec.  Simple Substitute for  ISR time control.
Goto    Longloop        ; CONTINUE
If you're not using the ~199 msec interval, why not simplify the "main loop"? You wouldn't even need to preset the Delay1 variable;

Code:
;*******************************************************************
;  main loop                                                       *
;*******************************************************************
loop
decfsz  Delay1,F        ; delay ~768 usecs (4-MHz)         |B0
goto    loop            ;                                  |B0
call    GETSWITCH       ; sample & debounce inputs         |B0
call    Strobe          ; refresh the display              |B0
goto    loop            ;                                  |B0

Last edited:

##### Active Member
Could i suggest you look at MAX7219 , for 8 CC ss leds saves a heap of code , no flicker , no decode , one resistor , no transistors, addressable digits, sw brightness , dip package , 1hz to 1Mhz serial, bit bangable , daisy chain , 5volt... expencive !!! .

Last edited:

#### Mosaic

##### Well-Known Member
\$13 ea! If you have to avoid transistors and current limiting resistors. Maybe if the form factor limits PCB space.

#### Mosaic

##### Well-Known Member
Hi Mosaic:

May I ask about the purpose of this section of code, please? It appears you're calling the GETSWITCH and Strobe subroutines at approximately ~768 usec intervals (plus the overhead of the subroutines) but you're not doing anything at the longer ~199 msec interval. I'm just curious what you may have had planned for that ~199 msec interval? Comments suggest you're using the least significant two bits of the Delay2 variable in the Strobe subroutine but I haven't found that in the code. Am I missing something?

If you're not using the ~199 msec interval, why not simplify the "main loop"? You wouldn't even need to preset the Delay1 variable;
The 'delay2' comment seems to be a mistake it should be 'Digitstrobe', I had prev. used Delay2 in an older variant of the code (which made the code dependent on the delay loop) and the comment slipped by getting updated.

The rest of this post is meant for the general readership, I know you know all these things already MK.

199 msec is essentially available time resource for an application to execute other tasks, whatever they may be, at the discretion of the coder.
Also the 768 usec interval in the delay loop can also be considered available time resource for doing other useful things in code.
It really depends on the coder, based on the refresh rate for the desired display updates.
The delay loop here just a tool that demos the rest of the display & key sampling code .

If display update speeds are critical then an interrupt driven display update cycle may be justified. I have found that to be generally not required.
Usually, when I code i build in the display & switch control (HMI) first to make the application interactive and also help debugging by displaying chosen characters/ GPR values/msgs on the fly during code progression.
Then as code develops , more processing overheads slows the display updates. In bigger applications this can start to impact the display quality.
Sometimes writing EPROM data can introduce excessive delays in general code based on the sample coding supplied by Microchip in 16F PIC datasheets. Proper coding can permit multi-tasked EPROM writing permitting general code flow and avoiding such delays. Similar techniques for ADC oversampling (avoiding dedicated sampling loops)can be used to permit code execution while such sampling is being done.
Use of flags (bits) can optimize subroutine execution and reduce unnecessary overheads as well.

#### Mike - K8LH

##### Well-Known Member
The 'delay2' comment seems to be a mistake it should be 'Digitstrobe', I had prev. used Delay2 in an older variant of the code (which made the code dependent on the delay loop) and the comment slipped by getting updated.
Well, the program certainly deserves a cleanup. It's difficult to figure out what it's doing with the sloppy formatting and several extraneous code fragments.

I found an interesting algorithm in the PICLIST Source Code Library for an alternate binary-to-decimal subroutine. While it takes a bit more time to execute, it's much smaller and it's isochronous. Note that the 'Rem' variable is equated to the 'Thousands' variable which is taken advantage of during the last trip through the Div10 routine.

Code:
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;                                 739 cycles (isochronous)        ~
Bin2Dec
movf    ClickL,W        ; click count lo                  |B0
movwf   Blo             ;                                 |B0
movf    ClickH,W        ; click count hi                  |B0
movwf   Bhi             ;                                 |B0
call    Div10           ;                                 |B0
movwf   Units           ; save 'ones'                     |B0
call    Div10           ;                                 |B0
movwf   Tens            ; save 'tens'                     |B0
call    Div10           ;                                 |B0
movwf   Hundreds        ; save 'hundreds'                 |B0
Div10
movlw   16              ;                                 |B0
movwf   Ctr             ; repeat for 16 bits              |B0
clrf    Rem             ; clear 'remainder'               |B0
DivLoop
rlf     Blo,W           ;                                 |B0
rlf     Bhi,F           ;                                 |B0
rlf     Rem,F           ; move MSB of number into Rem     |B0
movlw   10              ;                                 |B0
subwf   Rem,W           ; does 10 go in?                  |B0
skpnc                   ; no, skip, else                  |B0
movwf   Rem             ; update remainder                |B0
rlf     Blo,F           ; shift in the borrow bit         |B0
decfsz  Ctr,F           ; all 16 bits? yes, skip, else    |B0
goto    DivLoop         ; loop                            |B0
movf    Rem,W           ; note Rem equates to Thousands   |B0
return                  ;                                 |B0
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here's your binary-to-decimal routine for comparison;
Code:
Bindec    ;calculate decimal# for each 7seg led block
clrf Thousands
clrf Hundreds
clrf Tens
clrf Units
movf ClickL,w
movwf TempL
movf ClickH,w
movwf TempH
call Thou ; get thousands in decimal
movlw .10 ; check for 10,000 or more
subwf Thousands,w;  if Thousands < 10, carry is clr.
btfsc STATUS,C ;
goto Overrange ;
Call Hundred ; get hundreds
call Ten ; get tens & units.
return
Overrange
movlw 0x79 ; E pattern
movwf Thousands
movlw b'01010000' ; r pattern
movwf Hundreds
Movwf Tens
movlw 0x06 ; '1' pattern (error 1)
movwf Units
return

Thou    movlw .3 ; high byte set to 3 = 768
movwf FixedH ; high byte of # to be subtracted.
movlw .232 ; low byte
movwf FixedL ; 768 + 232 = 1000's column

ThouCount
call Subtract
xorlw .1 ; check for returned wreg value. A  zero occurs if a 1 was returned
btfss STATUS,Z ; branch if 1 was returned, else
goto Fixremainder ; due to last subtraction the remainder went -ve, so we fix this.
incf Thousands,f
goto ThouCount

Fixremainder ;restore remainder to continue calculating for next decimal column.
movf    FixedL,w ; low byte
addwf   TempL,f ; TempL=TempL + FixedL
movf    FixedH,w ; hi byte
btfsc   STATUS,C ; check for carry, set if  TempL + FixedL >255
incfsz  FixedH,w ; Increment due to carry set ,if zero skip next.
addwf   TempH,f    ; TempH=TempH+FixedH (+1 if carry was set)
return

Hundred    movlw 0 ; high byte set to 0
movwf FixedH ; high byte of # to be subtracted.
movlw .100 ; low byte
movwf FixedL ; 0 + 100 = 100's column

HdrdCount
call Subtract
xorlw .1 ; check for returned wreg value. A  zero occurs if a 1 was returned
btfss STATUS,Z ; branch if 1 was returned, else
goto Fixremainder ; due to last subtraction the remainder went -ve, so we fix this.
incf Hundreds,f
goto HdrdCount

Ten    movlw .10
subwf TempL,f; test for 10 if carry clr wreg is under 10
btfsc STATUS,C ;skip if carry clr (-ve result) , else
incf Tens,f; incr the tens column
btfsc STATUS,C ; skip if carry clr (-ve result), else
goto Ten ; now check for 20,30 etc
movf TempL,w
addlw .10; as last calc was -ve recreate +ve number.
DispLEDS
call Dpattern
Movwf Units ; store 7 seg display pattern for units
movf Tens,w
call Dpattern
movwf Tens; store 7 seg display pattern for tens
movf Hundreds,w
call Dpattern
movwf Hundreds; store 7 seg display pattern for tens
movf Thousands,w
call Dpattern
movwf Thousands
return
Subtract ; 2byte subtraction to determine decimal 7 seg columns
movf    FixedL,W
subwf   TempL,f ; TEMPL= TEMPL-FIXEDL
movf    FixedH,W
btfss   STATUS,C ; branch if TEMPL>FIXEDL, else
incfsz  FixedH,W ; setup to reduced TempH by 1 extra (sim. a borrow).
subwf   TempH,f  ;TempH=TempH-FixedH
btfss    STATUS,C ;branch if TempH>FixedH, else
retlw .0 ; if carry is clr return with a 0 in wreg
retlw .1 ; if carry is set return with a 1 in wreg.

Last edited:

#### Mosaic

##### Well-Known Member
That's a bit of apples vs oranges.

My code u quoted does more than just Bin to Dec. Apart from being faster, it does 3 other things.
It handles the 7 segment LED pattern display updates, the over-range error condition msg as well as provides a gen purpose 16 bit subtract (& compare routine for bubble sorting values) which is often reused for other parts of application dev.
Around 35 lines extra for just those items.

#### Mike - K8LH

##### Well-Known Member
That's a bit of apples vs oranges.

My code u quoted does more than just Bin to Dec. Apart from being faster, it does 3 other things.
It handles the 7 segment LED pattern display updates, the over-range error condition msg as well as provides a gen purpose 16 bit subtract (& compare routine for bubble sorting values) which is often reused for other parts of application dev.
Around 35 lines extra for just those items.
Yes, there are some easy-to-reconcile differences. For example, the "DispLEDS" routine at the tail end of your binary-to-decimal routine performs four separate conversions of numeric data in the "Thousands", "Hundreds", "Tens", and "Units" variables into LED segment data;
Code:
;
;  excerpt from Mosaic's binary-to-decimal subroutine
;
DispLEDS
call    Dpattern
Movwf   Units           ; store 7 seg display pattern for units
movf    Tens,w
call    Dpattern
movwf   Tens            ; store 7 seg display pattern for tens
movf    Hundreds,w
call    Dpattern
movwf   Hundreds        ; store 7 seg display pattern for tens
movf    Thousands,w
call    Dpattern
movwf   Thousands
return
Since this code doesn't have anything to do with binary-to-decimal conversion, it seems out-of-place tacked onto the end of the binary-to-decimal subroutine. As an alternative... is there a reason why you couldn't leave numeric data in the "Thousands", "Hundreds", "Tens", and "Units" variables and eliminate this section of code, replacing it with a single "call Dpattern" instruction in the "Strobe" subroutine where the segment data is actually used?
Code:
;
;  excerpt from Mosaic's "Strobe" subroutine
;
Chk_Dcount
bsf     Tmp1,4          ; initialize the bitmask to 0001XXXX
clrc                    ; clear Carry
decfsz  Offset,F        ; digit number, 1..4 on entry
goto    ShiftDigit      ;
bsf     Offset,3        ; make offset=8 for # of bits in Character pattern to send serially in Transmit routine called next.
movf    INDF,W          ; get digit value, 0..9
->      call    Dpattern        ; get LED segment data
movwf   Tmp             ; store in Tmp, clearing this GPR here can cause an empty display, useful for PWM Dimming.
call    Transmit        ; setup Shift register with character data pattern.
I certainly don't mean to imply there's anything wrong with your code. I'm simply pointing out a way to reconcile one of the differences you mentioned with a simple change in program organization that reduces code size and overhead and which may seem more intuitive to someone studying the program.

Finally, since I'm familiar with this particular multiplexing method, would you mind if I pointed out a few confusing code fragments that I perceive to be problems or errors in a subsequent post? I figure if they cause me to scratch my head while muttering "huh?", they might cause similar confusion for someone studying the program who isn't as familiar with the method. It may be that I'm simply missing some subtle details or objectives and you can straighten me out.

Cheerful regards, Mike

Last edited:

#### Mosaic

##### Well-Known Member
Yes, there are some easy-to-reconcile differences. For example, the "DispLEDS" routine at the tail end of your binary-to-decimal routine performs four separate conversions of numeric data in the "Thousands", "Hundreds", "Tens", and "Units" variables into LED segment data;
Code:
;
;  excerpt from Mosaic's binary-to-decimal subroutine
;
DispLEDS
call    Dpattern
Movwf   Units           ; store 7 seg display pattern for units
movf    Tens,w
call    Dpattern
movwf   Tens            ; store 7 seg display pattern for tens
movf    Hundreds,w
call    Dpattern
movwf   Hundreds        ; store 7 seg display pattern for tens
movf    Thousands,w
call    Dpattern
movwf   Thousands
return
Since this code doesn't have anything to do with binary-to-decimal conversion, it seems out-of-place tacked onto the end of the binary-to-decimal subroutine. As an alternative... is there a reason why you couldn't leave numeric data in the "Thousands", "Hundreds", "Tens", and "Units" variables and eliminate this section of code, replacing it with a single "call Dpattern" instruction in the "Strobe" subroutine where the segment data is actually used?
You must note that you are looking at an extract from a much larger application used as an example.
As I recall it had to do with refresh rates and memory paging. I didn't want the refresh rate of the digit values to be the same as the strobe refresh so this method provided that flexibility.
Then there was memory segmentation issues on 256 byte boundaries for lookup tables etc. Again, the strobe code may have been in page 2 and the character pattern lookup table was in the same page as the bin-dec code so....that's another issue.
There will be approaches used that may not have apparent reasons in this code fragment, however, there will be reasons if the larger application was studied. Being over 7K in ASM size now....that's not really an option.

I am certain that if each code module is studied I could find certain optimizations as there are sometimes remnants of structures from 10 versions before. I'm afraid I couldn't invest the time in that right now. Perhaps If I were to do a tut. from scratch things would be neater but more time consuming.
So, I publish what I have that I know works and others can feel free to mod. as they need. Everyone will have their own set of requirements for their apps. Some need it compact, some need it speedy, some just need quick and dirty, some need super flexibility with indirect addressing for portability. Ain't no way what I publish can satisfy all comers...its merely a starting point.

You're modding it as u go along...and that's cool. That's the intent, to spur on development. Perhaps the next time I need similar code I could use yours if it's a better match to the app. spec. requirements!
Perhaps what you publish will meet the needs of some more readers .
It's all good.

I recently updated another thread in which I published on quadrature encoder code as BobW published an approach ( in a different thread) that seemed a better solution, so i quoted his post in my own thread. Now, I'll be using that new approach the next time I need an encoder!