1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice
Ian Rogers

I'm sick of counter requests.. So here's a few

counter

  1. Ian Rogers
    A long time coming...

    "I need a counter!", "0~99 counter needed", "How to make seven segment counter"..

    Why can no one find a tutorial...

    Ah well! Here goes....

    First questions you need to ask yourself.... Micro?, language? LCD or LED?..

    If you want to use LED seven segment displays, then more questions... Multiplexed or non multiplexed?... Quantity of multiplexed displays?..

    LCD display units are relatively easy to hook up to a micro.. Many tutorials on this on the web... So.... Here we are plumping for the old 7-seg types...

    If we use the small 7-seg you can easily just hook it up to a micro port with current limiting resistors.... Most LED's are very visible around 8~10mA so if you base the design on 10mA you will need no current drivers... Using a 40 pin Pic, you can drive two/three LED modules directly..

    Circuit 1
    upload_2016-11-30_23-34-24.jpeg
    If you need to use a smaller pic, then you can consider multiplexing... Be warned that multiplexing is time sharing... Each module will get a percentage of display time... Two or three will be okay, but when you are multiplexing 8 modules the time to power the LED will make the LED appear a lot dimmer... You can in these cases "up the current" but the limiting factor will be the capability of the micro pin.. Just because a micro pin can sink 25mA, doesn't mean it will like doing so for ages... Also a micro port will only allow a maximum of 100mA per port... This means 14mA will be the maximum current allowed.. Again, I wouldn't recommend running near to maximum current..

    The alternative is by using shift registers with a latched output I have used 74hc595 with good success... Each register can source / sink 25mA per pin... BUT!! Alass the whole device is limited to 70mA... We are back to 10mA which, in fairness, is enough! I put transistor drivers on my LED's, this way I can have the brightness I want..

    Anyway, we'll do the multiplexed version afterwards..

    Writing the code to dive a seven segment is straight forward. All you really need is a "lookup" table to light the correct LED 's to make sense..

    If you want to display the number 1 it looks like this..
    upload_2016-12-1_19-28-42.png

    Note that to display a 1 then two segments need to be lit.. B and C..
    Assuming this display is connected via current limiting resistors to port C of out Pic the output would be binary 00000110 or 6 decimal..

    This has been done to death on the web so all the values are here..

    In C
    Code (c):

    unsigned char DIGITS[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
     
    In Asm
    Code (asm):

    DIGITS
       addwf  PCL  , f
       retlw  0x3F
       retlw   0x06
       retlw   0x5B
       retlw   0x4F
       retlw   0x66
       retlw   0x6D
       retlw   0x7D
       retlw   0x07
       retlw   0x7F
       retlw   0x6F
     
    *Note* when using Common Anode displays just flip the bits with COMF in asm or digit = ~digit; in C..

    Moving on.. To place the digit to display just use the decimal representation of the number..

    Code (c):

       PORTC = DIGIT[number];
     
    Code (asm):

      movf   number,w
      call    DIGIT
      movwf  PORTC
     
    Now each number 0 through 9 has a corresponding LED bitmap and the correct digit will be displayed..

    The counter portion is dead easy if you use timer 0.. You can set the port pin 4 on port a to increment the timer with each pulse.. I will not be using any conditioning on the input as each counter input will have differing criteria and that is the fun of design, I'll leave that up to the individual!

    Now... Timer 0 is on permanently so you don't have to start it... All that needs to be done is set the input pin and configure the micro to route the input through to the timer!!

    The main register we need to worry about is the option register..

    upload_2016-12-1_19-12-29.png

    The prescaler assignment ( PSA bit 3) selects WDT or Timer 0.. BUT!! using the WDT assignment with the WDT disabled, gives us a 1:1 on timer count and ergo on the pulse input.. Proper counting..

    Here goes..
    Code (asm):

      LIST   p=16F877a     ; tell assembler what chip we are using
       include "P16F877a.inc"     ; include the defaults for that particular chip

       __config 0x3F72       ; sets the configuration bits Watch dog off, HS crystal etc..


    digit_unit   equ   0x20
    digit_ten   equ   0x21     ; a couple of variables
    digit_hun   equ   0x23

       org   0x00
       goto   Init       ; restart vector.. A chance to jump over other vectors.

    DIGITS
       addwf  PCL  , f       ; LED bitmap
       retlw  0x3F
       retlw  0x06
       retlw  0x5B
       retlw  0x4F
       retlw  0x66
       retlw  0x6D
       retlw  0x7D
       retlw  0x07
       retlw  0x7F
       retlw  0x6F

    Init
       banksel   TRISA       ; Bank 1
       movlw   0xE
       movwf   ADCON1       ; turn off ADC
       movlw   0x255
       movwf   TRISA       ; Port A are all inputs
       movlw   0x1       ; Just port E 0 active
       movwf   TRISE
       movlw   0x00
       movwf   TRISC       ; Tens out
       movwf   TRISD       ; Units out
       movlw   0x28       ; set clock select external, Low to high Prescale 1:1
       movwf   OPTION_REG     ;
       banksel   TMR0
       movlw   0x00
       movwf   TMR0       ; clear timer

       banksel   PORTA       ; Bank 0
       movlw   0x00
       movwf   PORTD       ; dont display
       movwf   PORTC       ; rubbish


    loop   btfss   PORTE,0       ; reset count
       call   reset
       call   BCD       ; convert to BCD
       movf   digit_hun,w     ; Hundreds ( if needed )
       btfss   STATUS,Z     ; easy to convert to three digits
       goto   loop       ; this just stops the count at 100
       movf   digit_ten,w     ; Tens
       call   DIGITS
       movwf   PORTC
       movf   digit_unit,w     ; Units
       call   DIGITS
       movwf   PORTD
       goto  loop

    reset
       clrf   TMR0       ; reset timer
       return


    BCD   movlw   0x00
       movwf   digit_ten     ;
       movwf   digit_hun     ; start count at zero

       movf   TMR0,w       ; get Timer
       movwf   digit_unit     ; put in unit

    BCD1
       movlw    0x64       ; bigger than 100
       subwf   digit_unit,w
       btfss   STATUS,C     ; abort if carry set
       goto   BCD2
       movwf   digit_unit
       incf   digit_hun      ; set hundreds
       goto   BCD1       ; loop again
    BCD2:
       movlw   0xA       ; bigger than 10
       subwf   digit_unit,w
       btfss   STATUS,C     ; abort if carry set
       return
       movwf   digit_unit     ;
       incf   digit_ten      ; set tens
       goto   BCD1

    end
     
    And the C version..
    Code (c):

    #include<xc.h>         // SFR  definitions
    #pragma config FOSC = HS   // Config bits
    #pragma config LVP = OFF
    #pragma config WDTE = OFF

    unsigned char DIGITS[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};   // Led bitmap

    void main(void)
       {
       unsigned char dummycnt;     // some variable to use
       TMR0 = 0;           // clear timer
       OPTION_REG = 0x28;       // count on T0CK input 1:1 prescale
       TRISD = TRISC = 0;
       TRISE0 = 0;           // reset button
       ADCON1 = 0xE;         // No ADC

       while(1)
         {
         dummycnt = TMR0;
         if(dummycnt > 99)dummycnt = 99;         // get timer and ensure 0~99
         PORTC = DIGITS[dummycnt/10];   // Tens
         PORTD = DIGITS[dummycnt % 10];   // units
         if(!RE0) TMR0 = 0;         // reset
         }
       }
     

    Pressing the button resets the count!!

    Now multiplexing.... Virtually the same circuit but only nine pins are used to drive the LED modules... Now!! I don't like multiplexing LED's... They flicker and appear dim.. They don't simulate well... But, with the correct timing, you can get them to display well..

    Here's the new circuit..
    upload_2016-12-1_19-2-20.jpeg

    And here is the updated code
    Code (asm):

      LIST   p=16F877a     ; tell assembler what chip we are using
       include "P16F877a.inc"     ; include the defaults for that particular chip

       __config 0x3F72       ; sets the configuration bits Watch dog off, HS crystal etc..


    digit_unit   equ   0x20
    digit_ten   equ   0x21     ; a couple of variables
    digit_hun   equ   0x23

    dly1     equ   0x24
    dly2     equ   0x25


       org   0x00
       goto   Init       ; restart vector.. A chance to jump over other vectors.

    DIGITS
       addwf  PCL  , f       ; LED bitmap
       retlw  0x3F
       retlw  0x06
       retlw  0x5B
       retlw  0x4F
       retlw  0x66
       retlw  0x6D
       retlw  0x7D
       retlw  0x07
       retlw  0x7F
       retlw  0x6F

    Init
       banksel   TRISA       ; Bank 1
       movlw   0xE
       movwf   ADCON1       ; turn off ADC
       movlw   0x255
       movwf   TRISA       ; Port A are all inputs
       movlw   0x1       ; Just port E 0 active
       movwf   TRISE
       movlw   0x00
       movwf   TRISC       ; Tens out
       movwf   TRISD       ; Units out
       movlw   0x28       ; set clock select external, Low to high Prescale 1:1
       movwf   OPTION_REG     ;
       banksel   TMR0
       movlw   0x00
       movwf   TMR0       ; clear timer

       banksel   PORTA       ; Bank 0
       movlw   0x00
       movwf   PORTD       ; dont display
       movwf   PORTC       ; rubbish


    loop   btfss   PORTE,0       ; reset count
       call   reset
       call   BCD       ; convert to BCD
       movf   digit_hun,w     ; Hundreds ( if needed )
       btfss   STATUS,Z     ; easy to convert to three digits
       goto   loop       ; this just stops the count at 100
       movf   digit_ten,w     ; Tens
       call   DIGITS
       clrf   PORTC       ; blank display
       movwf   PORTD
       movlw   0x1       ; set for units
       movwf   PORTC
       call    delay       ; view time
       movf   digit_unit,w     ; Units
       call   DIGITS
       clrf   PORTC       ; blank dispay
       movwf   PORTD
       movlw   0x2       ; Tens
       movwf   PORTC
       call    delay       ; time to view..
       goto  loop

    reset
       clrf   TMR0       ; reset timer
       return


    BCD   movlw   0x00
       movwf   digit_ten     ;
       movwf   digit_hun     ; start count at zero

       movf   TMR0,w       ; get Timer
       movwf   digit_unit     ; put in unit

    BCD1
       movlw    0x64       ; bigger than 100
       subwf   digit_unit,w
       btfss   STATUS,C     ; abort if carry set
       goto   BCD2
       movwf   digit_unit
       incf   digit_hun      ; set hundreds
       goto   BCD1       ; loop again
    BCD2:
       movlw   0xA       ; bigger than 10
       subwf   digit_unit,w
       btfss   STATUS,C     ; abort if carry set
       return
       movwf   digit_unit     ;
       incf   digit_ten      ; set tens
       goto   BCD1


    delay   movlw   0       ; 15ms delay (ish)
       movwf   dly1
       movlw   14
       movwf   dly2

    d1   decfsz   dly1
       goto   d1
       decfsz   dly2
       goto   d1
       return

    end
     
    And lastly in C

    Code (C):

    #include<xc.h>         // SFR  definitions
    #define _XTAL_FREQ 4000000
    #pragma config FOSC = HS   // Config bits
    #pragma config LVP = OFF
    #pragma config WDTE = OFF

    unsigned char DIGITS[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};   // Led bitmap

    void main(void)
       {
       unsigned char dummycnt;     // some variable to use
       TMR0 = 0;           // clear timer
       OPTION_REG = 0x28;       // count on T0CK input 1:1 prescale
       TRISD = TRISC = 0;
       TRISE0 = 0;           // reset button
       ADCON1 = 0xE;         // No ADC

       while(1)
         {
         dummycnt = TMR0;
         if(dummycnt > 99)dummycnt = 99;
         RC0 = 0;RC1=0;           // get timer and ensure 0~99
         PORTD = DIGITS[dummycnt/10];   // Tens
         RC0=1;               // set tens
         __delay_ms(15);           // time to view
         RC0 = 0;RC1=0;
         PORTD = DIGITS[dummycnt % 10];   // units
         RC1 = 1;             // set units
         __delay_ms(15);           // time to view
         if(!RE0) TMR0 = 0;         // reset
         }
       }
     
    As you can see there is very little difference to code one from the other..
    As I said earlier.. I don't use multiplexing.. I almost always use a shift register.. This way you can keep the segments on as you did in the first example...

    I hope these little files find themselves useful to whom ever!!

    Now onto serially driven LED modules.. You can daisy chain LED modules forever!!!

    New circuit to cover shift registers..
    upload_2016-12-1_23-11-56.jpeg


    First note I have used "bussed" wires to connect the LED's to the current limiting resistors.. I have two modules, but many can be linked in with very little change to the code... The display routine is a software "bit banged" routine that used extensively in software serial drivers..

    If ULN2003A's are placed between the shift registers and the current limiting resistors.. The large LED modules can be used..

    Here is the asm code to drive the two serial LED modules

    Code (asm):

      LIST   p=16F877a     ; tell assembler what chip we are using
       include "P16F877a.inc"     ; include the defaults for that particular chip

       __config 0x3F72       ; sets the configuration bits Watch dog off, HS crystal etc..


    digit_unit   equ   0x20
    digit_ten   equ   0x21     ; a couple of variables
    digit_hun   equ   0x23

    dly1     equ   0x24
    dly2     equ   0x25
    MSK     equ   0x26
    temp     equ   0x27


       org   0x00
       goto   Init       ; restart vector.. A chance to jump over other vectors.


    DIGITS
       addwf  PCL  , f     ; LED bitmap
       retlw  0x3F
       retlw  0x06
       retlw  0x5B
       retlw  0x4F
       retlw  0x66
       retlw  0x6D
       retlw  0x7D
       retlw  0x07
       retlw  0x7F
       retlw  0x6F

    Init  
       banksel   TRISA       ; Bank 1
       movlw   0xE
       movwf   ADCON1       ; turn off ADC
       movlw   0x255
       movwf   TRISA       ; Port A are all inputs
       movlw   0x1       ; Just port E 0 active
       movwf   TRISE
       movlw   0x00
       movwf   TRISC       ; Tens out
       movwf   TRISD       ; Units out
       movlw   0x28       ; set clock select external, Low to high Prescale 1:1
       movwf   OPTION_REG     ;
       banksel   TMR0
       movlw   0x00
       movwf   TMR0       ; clear timer

       banksel   PORTA       ; Bank 0
       movlw   0x00
       movwf   PORTD       ; dont display
       movwf   PORTC       ; rubbish

     
    loop   btfss   PORTE,0       ; reset count
       call   reset
       call   BCD       ; convert to BCD
       call   display       ; serial load LED modules
       goto  loop

    reset  
       clrf   TMR0       ; reset timer
       return


    BCD   movlw   0x00
       movwf   digit_ten     ;
       movwf   digit_hun     ; start count at zero

       movf   TMR0,w       ; get Timer
       movwf   digit_unit     ; put in unit

    BCD1
       movlw    0x64       ; bigger than 100
       subwf   digit_unit,w
       btfss   STATUS,C     ; abort if carry set
       goto   BCD2
       movwf   digit_unit
       incf   digit_hun      ; set hundreds
       goto   BCD1       ; loop again
    BCD2:
       movlw   0xA       ; bigger than 10
       subwf   digit_unit,w
       btfss   STATUS,C     ; abort if carry set
       return
       movwf   digit_unit     ;
       incf   digit_ten      ; set tens
       goto   BCD1


    display
       movlw   0x2       ; two digits
       movwf   dly1  
       movlw   0x20       ; We need a pointer
       movwf   FSR       ; Sorry about that!! Read the text!!
    outloop
       movlw   0x80
       movwf   MSK       ; start mask at bit 7
       movlw   0x8
       movwf   dly2       ; 8 bits in shift register
       movf   INDF,w       ; point to units
       call   DIGITS       ; gab bitmap
       movwf   temp       ; save it as w is used to death..
    inloop
       movf   temp,w       ; get bitmap
       andwf   MSK,w       ; led bit on or off?
       btfss   STATUS,Z
       goto   iset
       bcf   PORTC,4       ; clear data
       goto    unset
    iset   bsf   PORTC,4       ; set data
    unset   bsf   PORTC,3       ; clock it in MSbit first
       nop
       bcf   PORTC,3
       rrf   MSK,f       ; next bit
       decfsz   dly2       ; loop if needed
       goto   inloop
       incf   FSR,f       ; next digit
       decfsz   dly1       ; all digits??
       goto    outloop
       bsf   PORTC,5       ; clock the store / latch
       nop  
       bcf   PORTC,5  
       return         ; all done..

    delay   movlw   0       ; 15ms delay (ish)
       movwf   dly1
       movlw   14
       movwf   dly2

    d1   decfsz   dly1
       goto   d1
       decfsz   dly2
       goto   d1
       return

    end
     
    For those who are fresh to asm coding... Using a pointer is a bit weird... This is the simplest form of indirect addressing... If data is placed in several consecutive memory locations, you can transverse through the data one by one just by increasing a single variable..

    The FSR is register that can point to a memory location... The data at that location can be manipulated / accessed though the INDF ( indirection ) register..

    Fortunately! C has most of this hidden... Here is the C version..

    Code (c):

    #include<xc.h>         // SFR  definitions
    #define _XTAL_FREQ 4000000
    #pragma config FOSC = HS   // Config bits
    #pragma config LVP = OFF
    #pragma config WDTE = OFF

    unsigned char DIGITS[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};   // Led bitmap
    unsigned char dummycnt;     // some variable to use

    void display(void)
       {
       unsigned char x, y;
       unsigned char MSK ;
       unsigned char dig[2];

       dig[1] = dummycnt / 10;
       dig[0] = dummycnt % 10;
       for(x=0;x<2;x++)
         {
         MSK = 0x80;
         for(y=0;y<8;y++)
           {
           RC4 = 0;
           if(MSK & DIGITS[dig[x]]) RC4 = 1;
           RC3 = 1;
           NOP();
           RC3 = 0;
           MSK>>=1;
           }
         }
       RC5 = 1;
       RC5 = 0;  
       }

    void main(void)
       {

       TMR0 = 0;           // clear timer
       OPTION_REG = 0x28;       // count on T0CK input 1:1 prescale
       TRISD = TRISC = 0;
       TRISE0 = 0;           // reset button
       ADCON1 = 0xE;         // No ADC
     
       while(1)
         {
         dummycnt = TMR0;
         if(dummycnt > 99)dummycnt = 99;  
         display();
         if(!RE0) TMR0 = 0;         // reset
         }  
       }
     
    Again... This is the model that I would use to drive multiple LED modules... The same technique can be used to drive LED matrices as well..

    Once again... I hope this helps..

    Cheers
    King Jin and Mikebits like this.

Recent Reviews

  1. Road Warrior
    Road Warrior
    5/5,
    First of all I truly love PIC programming because of the fun and enjoyment it provides. I have been considering "trying my hand" using the PIC16F877A for some time.

    I just stumbled upon Ian Rogers' "I'm sick of counter requests" using a PIC16F877A. Wow, how lucky can I be! His examples of programming this chip in assembly helps me with two projects.

    1. A great tutorial using the 16F877A... and
    2. My church is looking for a counter which would be used to count the number of members when arriving for services.

    Thank you Ian Rogers,

    Road Warrior