• 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.
Resource icon

I'm sick of counter requests.. So here's a few 2016-12-01

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
C:
unsigned char DIGITS[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
In Asm
Code:
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..

C:
   PORTC = DIGIT[number];
Code:
  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:
  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   0xff
   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..
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 = 1;           // 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:
  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   0xff
   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

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 = 1;           // 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:
  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   0xff
   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..

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 = 1;           // 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
Author
Ian Rogers
First release
Last update
Rating
5.00 star(s) 1 ratings

Latest reviews

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

Latest threads

EE World Online Articles

Loading

 
Top