Multiplexing 7Segs and using timer Countdown

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
First project was not a loss. I decided to avoid the messages but I am learning how to save msg in EEPROM.

And I am getting another PIC that is pin compatible with more memory and will port the code into that.

As for my next project I decided to make a Charger.

It needs to have a 2 Hr stopwatch for top up cycle.
And it needs to see the charge current and voltage display.

I decide to make it small so I will be using tiny 7segs.

3 for Voltage and 3 for Current and 3 for timer display.

Questions are
1. Is one PIC enough to multiplex 9 pcs of 7 segs.
I need to avoid flicker. I have seen RB did something with a lotta 7segs when I was googling.
Or do I need 2 PICs.

2. I need advice on How I should go about using the timers.
That is. Will the onboard timer be accurate enough for a 2Hr count down for my application or do I need those Real time clock generator chips.
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
One PIC will handle it if you have the I/O.

Use interrupt-driven timers for the mux timing and the countdown. You can use one timer IRQ for both but if you have two, its easier to manage.

Maintain your display outputs in BCD as the values change i.e. the main (non-irq) program decodes 123 as 01h 02h 03h in 3 RAM. The digits are the indexes into the segment table and quick to fetch when the mux interrupt occurs. On interrupt, turn off all digits, fetch the next digit value, look up the segments, write the segment pattern to the output port and turn on the next digit. You also can go one step further and maintain the digit values as the actual segment patterns to give the interrupt routine less to do - I prefer having the numerical data at hand but it works either way.

A typical mux period for 9 digits might be around 3-5ms, adjust it for a balance between flicker and execution speed. Even at 4MHz, the PIC will handle fast muxing is all it has to do is look up segments from pre-loaded BCD digits.

If you use a crystal oscillator, accuracy over 2 hours is easy to do. Pick an easy timebase (1ms, 10ms etc) then just count seconds, minutes, hours from there.

Do your timing with interrupts instead of delays and you'll have plenty of CPU left over to do the job.

Have fun!
 
Last edited:

elec_mech

Joined Nov 12, 2008
1,500
If you're feeling lazy, you could also use a MAX7221 (SPI) or MAX7219 (I2C) which will control up to 8 digits through multiplexing for you. Of course, this is not a cheap IC and adds board space.

Another thought is to use a PIC with a DAC output with some of those cheap volt meters from eBay. You'd have to set the limits up correctly so the DAC value reflected the value you want to see on the displays, but it would save on wiring and coding.

Just some thoughts.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
Muxing 9 digits can be done with a single uC eh

So I checked how I could utilize with what I have.

It's a bit of a drag.
U see with all the switches and Led's and Charge on off controls I cannot suffice with one 28pin one.
So I went for 28pin one to charge controller and a 18pin one for timer.
A 14 pin one is just enough for the timer without any I/O for start, stop or reset control.

Now I need a 14pin one and 28 pin one.
Next is to go for a 40pin one such a F887.

I was think which option would be cheaper or easier for some one like me.
I would like to know the later as I am checking for price details to my doorstep about the PIC's
 

JohnInTX

Joined Jun 26, 2012
4,787
Guess the F887 has to do.
I think using a PIC with enough IO pins is the best way to do it without question. I've used multi-CPU, shift-registers, IO expanders from time to time when absolutely necessary or when I didn't have a say in the choice and can tell you that enough resident IO is the way to go if at all possible. If you get tight for pins, there are many ways to squeeze more functions onto the IO (multiplexing the switches in the LED array is one..).

As far as the pricing, it depends on many factors. I like to click the price column on the part selector page just to make sure I haven't overlooked a cheaper part. These are usually the more recent parts as well.

If you haven't done it yet, really consider an 18F part. Many are essentially the same price as their 16F counterparts with WAY BETTER architecture, peripherals and features. When you have to contend with a noisy environment, you'll really appreciate the LATched outputs above all else.

What language/environment will you be programming in?

Post a prelim schematic when you have it with a list of IO reqs. You'll need up to 17 out for the display 9+7seg+DP.

EDIT: Take a look at the 18F4xK22 and 18F45K50. These are 5V 40 pin, all outputs 25ma, DIP-available with way more analog features than the 887 including internal voltage reference and start at pretty much the same price.

FWIW: I designed quite a few industrial battery chargers using baseline and midrange (16C/16F) when those were the only game in town. I would not even consider using either again for a charger application (or any other where an 18F would fit). Just my .02

Have fun.
 
Last edited:

THE_RB

Joined Feb 11, 2008
5,438
Definitely go for a 40 pin PIC. :)

Multiplexing 9 digits of 7seg each is not so bad, just make an interrupt every mS and do your display driving in the interrupt. 9 * 1mS = 9mS = 110Hz.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
I am learning mikroC Pro with EasyPIC7
I have only one 18F. That is what came with the EasyPIC7

I have 3 16F887 lying around. I thought I will use them.

My target is quite challenging as I need a user preset-able charge current for different Li-On's I get during repair.

I cannot do PWM yet properly. And I need a good charger fast which I can monitor the battery charge.

If I make one I know how it works properly.

What I had in mind is a linear charger with CV and CC based on LM723

I ordered some current limiter chips and shunt amplifiers from TI.

The shunt amp can scale the current to ADC input.

The Schema is still on a rough sketch and the code is just started.

I am making a test jig for 9 7 segs.

Will I have any problem with current capacity if I connect the 9 7segs to the easyPIC port headers.?
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
Will I have any problem with current capacity if I connect the 9 7segs to the easyPIC port headers.?
Maybe. The ports will source 25ma. 25/9 = 2.7ma per segment average. Whether this will give a bright enough display depends on the display (high efficiency LEDs req'd) and the brightness you expect.

The digit commons will need external drivers - I use ULN2803 or similar for common cathode - to handle the sum of all lit segments (8*25ma individual current = 200ma).

I ordered some current limiter chips and shunt amplifiers from TI.
The shunt amp can scale the current to ADC input.
Since the shunt is very low resistance, I have also gotten away with a simple difference amplifier across the shunt.

One final pitch for the 18F: Even in C, the compiler enforces limitations and necessary workarounds due to the 16xxx internals. From MikroC user's manual:
PIC16 Specifics
Breaking Through Pages
In applications targeted at PIC16, no single routine should exceed one page (2,000
instructions). If routine does not fit within one page, linker will report an error. When
confront with this problem, maybe you should rethink the design of your application
– try breaking the particular routine into several chunks, etc.
Limits of Indirect Approach Through FSR
Pointers with PIC16 are “near”: they carry only the lower 8 bits of the address. Compiler
will automatically clear the 9th bit upon startup, so that pointers will refer to
banks 0 and 1. To access the objects in banks 2 or 3 via pointer, user should manually
set the IRP
, and restore it to zero after the operation. The stated rules apply to
any indirect approach: arrays, structures and unions assignments, etc.
Note: It is very important to take care of the IRP properly, if you plan to follow this
approach. If you find this method to be inappropriate with too many variables, you
might consider upgrading to PIC18.
Note: If you have many variables in the code, try rearranging them with the linker
directive absolute
. Variables that are approached only directly should be moved to
banks 3 and 4 for increased efficiency.
Related topics: mikroC PRO for PIC specifics
Emphasis added. Add that to maximum nested calls of 8 for the 16Fxxx (actually less if you use interrupts) and eventually, you'll find yourself burning time and energy getting around the limitations of the architecture rather than actually advancing your project. A final caveat is that if you run out of memory on a 16F you are toast - and most 16F code will be bigger 18F due to the compiler working around the chip limitations. 18F parts make better code and almost always have a migration path to a fully compatible chip with more memory.

Ask me how I know these things..

Good luck!
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
I guess u know what you are talking about. :p

I am learning so lemme try this first.
I finished the 9 segment proto PCB and gonna start the coding for the F887

If it works good. If not I will go for the 18F.

What do you say to about this thread

PS. How did you know all tht ? :D
 

JohnInTX

Joined Jun 26, 2012
4,787
What do you say to about this thread
Did you get it working finally? Which PIC was it? From your and the other's comments, I'd suspect that MikroC is running out of usable RAM or at least having problems managing it - see the IRP warning above.

PS. How did you know all tht ? :D
Started where you are and moved the clock ahead about 40 years.:eek: After some 160+ PIC projects, not to mention lots of work on earlier 4 and 8 bit uP/uCs, I finally kind of got a handle on it - I think.
 

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
So it was running out of RAM. I suspected as much.
I deleted 3 messages for now and it works. Old one is PIC16F88.
I am getting the new PIC's PIC16F1847 suggested by Bob here. It seems it will work.

So I well get to back to it after checking the VA meter code with the new PIC16F1847

I just wanted to know more about the issue.

But if you got any ideas I like to hear about how to fix it. I am thinking of storing the one time only 4 Messages in the EEPROM.

This is where it started.

If you got ideas you can reply in tht thread. As I am checking all of em for new posts daily.

As for this thread.
I am reading a lot trying to cope my head around how I should start.
The 16F887 has 3 timers.
I cannot decide which to use yet. As far as I understand I should use interrupt to update display and to get one second time pulses for the timer.

I get the display update part I think but I cannot wrap my head around the 1 second generator part.

Project uses 8Mhz HS Xtal by the way.
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
I started and it is going where I am gonna go crazy :eek:

See how I managed to utilize every pin of 16F887 :D
Rich (BB code):
 *Pin Connections:
      RE3 is the MCLR

  PortA are the LED and Relay drives
     RA0 - Charging/Complete LED (Blinks when completed
     RA1 - CV mode LED
     RA2 - CC mode LED
     RA3 - Battery Inserted LED
     RA4 - Battery Over temp LED
     RA5 - 4.2V or 8.4V Selector Relay
     RA6 - Charger Relay
     RA7 - Battery Voltage Checker Relay

     
  PortB are Analog and PB switch inputs
     RB0 - Voltage ADC input
     RB1 - Current ADC input
     RB2 - Battery temperature ADC input
     RB3 - Battery Insert Switch
     RB4 - Charge Start/Halt/Stop Switch
     RB5 - Selector Sw, Voltage & Charge Termination Current & Timer (works with encoder)
     RB6 - RotaryEncoder input A
     RB7 - RotaryEncoder input B
     
  PortC + RE2 are the digit drivers:
     RC0 - Digit 1(Timer Hr digit)
     RC1 - Digit 2(Timer Min digit)
     RC2 - Digit 3(Timer Min digit)
     RC3 - Digit 4(Voltmeter digit)
     RC4 - Digit 5(Voltmeter digit)
     RC5 - Digit 6(Voltmeter digit)
     RC6 - Digit 7(Ammeter digit)
     RC7 - Digit 8(Ammeter digit)
     RE2 - Digit 9(Ammeter digit)
   
  PortE is used for heatsink temp sensing and Fan driving
     RE0 - HeatSink Temp ADC input
     RE1 - Fan Diver(heatsink)

  PortD are the segment drives:
     RD0 - Segment A
     RD1 - Segment B
     RD2 - Segment C
     RD3 - Segment D
     RD4 - Segment E
     RD5 - Segment F
     RD6 - Segment G
     RD7 - Decimal point
I went for PORTB inputs so I can use interrupt on change for the switches.
Am I correct to assume tht ?
 

JohnInTX

Joined Jun 26, 2012
4,787
I am reading a lot trying to cope my head around how I should start.
The 16F887 has 3 timers.
I cannot decide which to use yet. As far as I understand I should use interrupt to update display and to get one second time pulses for the timer.
I get the display update part I think but I cannot wrap my head around the 1 second generator part.
I would use two timers.
TIMER 0 for the display mux and TIMER1/CCP1 for the timebase.

For the timebase:
Configure CCP1M<3:0> = 1011 (compare mode, CCP1 resets TMR1). Configure the timer/CCP to interrupt the processor at some convenient period, say 100msec. T1CKPS<1:0> = 0b'11' (/8 prescale). At 8Mhz, the timer increments every 4uS. Set CCPR1H/L to 100ms/4us = 25000 = 61A8h. The PIC will be interrupted every 100ms. Since the timer is reset automatically, you don't have to manage it after setting it up.

Now, on every TMR1IF, set a flag: TIKms100 -OR- increment a 1 byte variable: TIKs_ms100. The timer/CCP will generate an accurate 100ms tik or count of tiks elapsed since you last looked. That's ALL for the interrupt routine.

Your main program loop samples the tik flag or accumulated tiks variable and adds 1 or whatever the tiks variable is to the hours:minutes:seconds time in the system (or counts down etc). The reason you might use an accumulator instead of a flag is so that you don't lose counts if it takes more than 100ms on average to service the flag. Clear the flag/counter when done and wait for it to set/accumulate again.
So for a 100ms tik for each tik you would
Count to 10 - that's one second
Count 60 sec = 1 minute etc.

Note that once you have a 100ms system tik, you can derive other timers off that i.e. set up 1 byte registers that decrement every 100ms and stop at 0:
Rich (BB code):
 if(timer)timer--;
Now just poke the number of 100ms tiks that you want to delay into it and check for 0 now and then. Perfect for non-critical timing like flashing LEDs, running guard timers etc.

For the display multiplexer, use TMR0 (its the least useful timer and preserves TMR2 for PWM etc). Set it up to interrupt at some convenient interval. Keep this interrupt routine short. Have a set of display digit registers preloaded with either the segment pattern or an index into a table of segment patterns. Keep a digit counter and something that will rotate a digit-select bit through the appropriate IO lines. When the timer interrupts, turn off the digit drivers, reload the timer while things settle, bump and wrap the digit counter, look up the segments and write to the segment outputs. Look up the digit select bit, set that on its output and bye! Short and sweet.

You might wonder what happens if you have a long delay running somewhere. Well, that's what accumulating the tiks in a register is for but the BEST thing is not to use any dumb delays. Instead, use derived timers off the 100ms system tik. Each function in your code is called by the main loop. If a function needs to delay, it sets its own derived timer and exits immediately. When its called again, it looks at the timer again (the timer will be decrementing to 0 and stay there when 0). When the timer is 0, the delay is done and you can proceed.

When I find it, I'll link to a post that shows what all of this entails if you are interested.

I went for PORTB inputs so I can use interrupt on Chang for the switches.
Am I correct to assume tht ?
For the rotary encoder, yes. I would not use IRQ on change for buttons, just poll them out of the fast main loop (described here) and drive a debouncer for each switch.

Here's the code snippet. Its for XC8 but you can get an idea of what I mean about eliminating dumb delays using derived timers. The two 'tasks' are flashing LEDs on a 12F part. The port and timer setups are a bit different than the 887.

Rich (BB code):
/* 
 * File:   Guichess.c
 * Author: John
 *
 * Created on February 10, 2014, 11:49 PM
 */

#pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
#pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = OFF // Brown Out Detect (BOR disabled)
#pragma config IESO = ON // Internal External Switchover bit (Internal External Switchover mode is enabled)
#pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)


#include <xc.h>
#include <stdlib.h>


/************************************************************/
//              HARDWARE CONFIGURATION
/************************************************************/
#define _XTAL_FREQ 8000000
#define usTcyc ((_XTAL_FREQ / 1000000) / 4) // how many uS 1 Tcyc is

//*****************  CODE CONFIGURATION  ********************
#define usSysTik 10000      // 10msec systik
#define ms100PSset 10       // 10 * systik = 100ms


/************************************************************/
//              TIMERS
/************************************************************/
#if 0
    Timer 1 is used to generate a 10msec system tik. Its the
    smallest derived time available and is used to drive the
    debouncer and EE update timer.
    Timer 0 is reserved to free up the prescaler for the WDT.

    The timer runs w/o prescaler so that we can load it with
    an adjusted time without having to worry about clearing the PS
#endif

#define T1CONinit 0b00000000    // No gate, 1:1ps, Internal, OFF
#define TMR1adj 9               // Number of Tcyc it takes to stop-load-start TMR1
                                // determined by looking at disassembly.
    //Calculate reload value for timer counting up to rollover. Result is
    // number of counts based on Tcyc, adjustment, SysTik value etc.
#define TMR1setTiks (2^16-(usSysTik / usTcyc) + TMR1adj)

unsigned char  ms100PreScaler;
bit ms10_happened;      // bits get set by IRQ at the noted time interval
bit ms100_happened;

//Derived timers
// These get loaded with count by main program. They count to 00 and stay there
// When main sees they are 00, it knows to do something

unsigned char Timer10ms_A;
unsigned char Timer100ms_A;

void interrupt isr(void)
{
    //---------------- MAINTAIN SYSTIK  ---------------------
    if(TMR1IF){
        TMR1IF = 0;     // ack the IRQ

        TMR1ON = 0;     // stop the timer, reload it, restart it
        TMR1 += TMR1setTiks;  // any accumulated time + the reload value
        TMR1ON = 1;

        //------------- PROCESS SYSTIK  ------------------------
        // do 10msec stuff here
        // Flag that systik happened
        ms10_happened = 1;                 // flag 10 ms happened
        if (Timer10ms_A) Timer10ms_A--;   // count 10ms timer to 0

        //---------------- 100 ms TIMERS --------------------------
        // 10ms systik is prescaled to 100ms..
        ms100PreScaler--;
        if(ms100PreScaler == 0){
            ms100PreScaler = ms100PSset;        // reset prescaler
            ms100_happened = 1;         // flag 100ms happened
            //--------------- SERVICE 100ms TIMERS  ----------------
            // These timers run at 100ms tiks.
            if (Timer100ms_A) Timer100ms_A--; // maintain Timer100ms_A timer
        }
    } // TMR1IF
}//END OF INTERRUPT SERVICE ROUTINE

void main(void) {
TRISIO = 0b00001000;
GPIO = 0b00000000;
ANSEL = 0;
CMCON0 = 0b00000111;
OSCCON = 0b01110111;

    //--------------- INIT SYSTIK -------------------------------
    // Timer 1 is the master timer.
    // Timer 0 is not used so far.

    T1CON = T1CONinit;  // stop timer, configure it
    TMR1 = TMR1setTiks; // load it
                        // init timer chain
    ms100PreScaler = ms100PSset;

    TMR1IF = 0;         // clear IRQ
    TMR1IE = 1;         // then enable TMR1 interrupt
    PEIE = 1;           // and peripherial IRQ
    TMR1ON = 1;         // start the timer

    //--------------- FIRE UP THE SYSTEM -------------------------
    GIE = 1;            //GLOBAL INTERRUPTS ENABLED

    while(1){
        // Toggle GP1 at 500ms uning a derived timer
        if(Timer10ms_A == 0){
             GP1 = !GP1;
             Timer10ms_A = 50;   // reload timer for next time
        }

        // Toggle GP0 at 100ms using flag
        if(ms100_happened){
            ms100_happened = 0;  // reset flag for next time
            GP0 = !GP0;
        }
    }// while


}//main
EDIT:
Amazing how the IO gets used up, isn't it. You can get 4 extra IO and 3 more LEDs if you put the existing 5 LEDs as 'segments' of a 10th 'digit' and using one of the previous LED lines as the 10th digit select. Turn them on and off by flipping bits in a stored segment pattern. Easy!
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
That's a lot to grasp. But I think I get it.

Your idea's are pretty good.
Glad I asked u know.

I like the idea about using Led's as a digit is awesome.

I will make the change.
 

JohnInTX

Joined Jun 26, 2012
4,787
I am way over my head.
I think a good first step would be to get the system tik running as an interrupt. Decide what a good interval would be. 100ms is probably a bit long, 5-10 might be better. If its too long its sluggish for debouncing, LED flashing etc, too short and you wind up doing a lot of accumulating counts and burning CPU time that you don't need to do. Use the systik to flash an LED for starters, just to buzz things out.

I'd do the display mux next. Once the interrupt chain is working with the systik, add the timer0 interrupt for the mux. If you use the LEDs as segments, I'd have an array of segment patterns for the display/LEDs. Don't worry about decoding yet, just hard-code some patterns into the segment array and get the display working. Tweak the refresh rate to just eliminate flicker, set the current values etc. When you are done, you'll have an interface to the display.

Rethinking the high level (hours) timing, if you are comparing times, counting down to 0 etc, a good approach might be to keep the time as an unsigned integer of seconds (holds about 18hours worth of seconds) , easy to work with. When you determine that its time to update the displayed value (one minute elapsed, for example), convert the time in seconds to hours:min etc, convert that to digits, convert the digits to segment patterns in the (already working) mux segment array.

Note the layered approach to the display. The system time doesn't know or care about the display. The display simply offers an interface to the digits/LEDs. In between are layers of code that do the conversion when necessary. This 'hardware abstraction' model allows you to do break the large program into logical functions (charger on for 2 hours, monitor voltage, charger off, flash 'done' LED etc.) that you can manage and separate those from the misery of actually talking to the hardware.

The key concept is from the top level, defer the details to lower levels of code as long as possible. Write IO with a clean, consistent interface and don't do IO in the top level. Instead, when the top level says 'Green LED ON' it interfaces with the Green LED driver instead of bsf PORTB,3. What the Green LED driver consists of is none of the top level's business. It may wind up just a macro or #define somewhere or it may load a formatted I2C message to an external device. In your case, it flips a bit in the display array. In any case, all the top level knows is that the Green LED is now on, however way its done. The payoff is that you can manage the many functions of a complex program without drowning in minutia. A HUGE payoff is that you can maintain different versions of the code without messing with your carefully implemented top level. Need to move the LEDs? Just redefine what the Green LED driver does and the rest is automatic. You can change things without breaking others.

What do think about that approach?

EDIT: As you refine the IO map, set it up so that the display segment/digit ports have no other outputs that would be used by non-interrupt code. Use any left over lines for inputs only i.e. don't share port outputs between interrupt and non-interrupt code. This will save you some grief from midrange r-m-w issues.
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
Just wanna make sure if I am on the right track. :D

LED Muxing added and changed the port connection arrangement as it is I am hung over using AN0 and onwards ( I really dunno why I like to use RA as Analog ports ) :confused:
Rich (BB code):
  Indicators are CC 7 Segments Muxed(PortD).
      3 Digits for 0.00 to 8.40 Volts
      3 Digits for 0.00 to 5.00 Amps
      3 Digits for 2.00 Hour Timer
      
  LED's are connected as CC and Muxed with the 7 segments and controlled by RE1
  (anodes are connected as indicated below)
   There are total of 5 Led indicators
      1. Charging / Charge completed (blinks when Charging) - RD0
      2. Constant Voltage mode - RD1
      3. Constant Current mode - RD2
      4. Battery inserted indicator - RD3
      5. Battery Over heat indicator - RD4
      
==============================================================================
 *Pin Connections:
      RE3 is the MCLR

  PortA Connections
     RA0 - Voltage ADC input
     RA1 - Current ADC input
     RA2 - Battery temperature ADC input
     RA3 - Heat Sink temperature ADC input
     RA4 - Not used
     RA5 - Not used
     RA6 - Not used
     RA7 - Buzzer - Just may be.

  PortB Connections
     RB0 - Charger Relay
     RB1 - Battery Voltage Checker Relay
     RB2 - 4.2V or 8.4V Selector Relay
     RB3 - Charge Start/Halt/Stop Switch
     RB4 - Battery Insert Switch
     RB5 - Selector Sw, Voltage & Charge Termination Current & Timer (works with encoder)
     RB6 - RotaryEncoder input A
     RB7 - RotaryEncoder input B

     
  PortC + RE1 & RE2 are the digit/LED drivers:
     RC0 - Digit 1(Timer Hr digit)
     RC1 - Digit 2(Timer Min digit)
     RC2 - Digit 3(Timer Min digit)
     RC3 - Digit 4(Voltmeter digit)
     RC4 - Digit 5(Voltmeter digit)
     RC5 - Digit 6(Voltmeter digit)
     RC6 - Digit 7(Ammeter digit)
     RC7 - Digit 8(Ammeter digit)
     RE2 - Digit 9(Ammeter digit)
     RE1 - LED Drive
   
  PortE
     RE0 - Fan Diver(heatsink)

  PortD are the segment drives:
     RD0 - Segment A
     RD1 - Segment B
     RD2 - Segment C
     RD3 - Segment D
     RD4 - Segment E
     RD5 - Segment F
     RD6 - Segment G
     RD7 - Decimal point
Starting to code now.........
 
Last edited:

Thread Starter

R!f@@

Joined Apr 2, 2009
10,007
Here is the timer0 and timer1 Setup and the story so far....

Rich (BB code):
/*Timer0
  Interrupt Time is like 9.984 ms  */
void Timer0(){
  OPTION_REG = 0x86;          //Option Reg; Pull ups disable, Prescale to Timer0 & 1:128
  TMR0 = 100;                 //Load timer with 100
  INTCON = 0xA0;              //Intcon Reg; Enable Global & Timer0 overflow Int.
}
/*Timer1
  Interrupt Time is 100 ms */
void Timer1(){
  T1CON    = 0x21;               //Timer1 control; 1:4 prescale & Enable timer
  TMR1IF_bit = 0;             //PIR1 Reg; Clear the Timer1 overflow Interrupt flag bit
  TMR1H = 0x3C;               //Load the Timer1 MSB &
  TMR1L = 0xB0;               //load the Timer1 LSB with the Preload of 15536
  TMR1IE_bit = 1;             //PIE1 Reg; Enable Timer1 overflow interrupt
  INTCON = 0xC0;              //Intcon Reg; Enable Global Int & Peripheral Int.
}
/*********** Interrupt Service Routine ***************/
void Interrupt(){
  if (TMR0IF_bit){
    TMR0IF_bit = 0;
    TMR0 = 100;
    //Code goes here
    
    
  }
  if (TMR1IF_bit){
    TMR1IF_bit = 0;
    TMR1H = 0x3C;
    TMR1L = 0xB0;
    //Code goes here
    
    
  }
}
/*********** Main Routine ****************************/
void main() {
    PORTA = 0;                // Clear All Ports
    PORTB = 0;                // ***** do ******
    PORTC = 0;                // ***** do ******
    PORTD = 0;                // ***** do ******
    PORTE = 0;                // ***** do ******
    ANSEL = 0x0F;             // RA0 to RA3 as Analog Inputs
    ANSELH = 0x00;            // Rest are Digital I/O's
    CM1CON0.C1ON = 0;         // Disable Comparator 1
    CM2CON0.C2ON = 0;         // Disable Comparator 2
    TRISA = 0x7F;             // All Inputs except RA7 as Output
    TRISB = 0xF8;             // RB <7:3> Inputs & RB <2:0> Ouputs
    TRISC = 0x00;             // PORTC are Ouputs
    TRISD = 0x00;             // PORTD are Ouputs
    TRISE = 0x00;             // PORTE are Ouputs (MCLR is an input)
}
Should I call this void timer0() & void timer1() inside the main loop and before it
Is the setting correct.

Mind you that this is the first time I am setting up timers
 
Last edited:

JohnInTX

Joined Jun 26, 2012
4,787
I'm practicing for juries tomorrow (playing jazz guitar for beady-eyed professors) so this will be fast.

Here is some pseudocode that shows how to set up timer 1 for a CCP based period register (instead of having to reload it which will throw off your timing).

Look it over and clean it up for MicroC so it will compile. Try to add some derived timers in the CCP1 interrupt service routine and we'll go from there.

You're on the right track.

BTW: note that there are no literals i.e. CCPR1 = 1250 in the code. The values are #defined above. This may seem like overkill but eventually you want to move all of these parameters into their own .h file. That way, you can find all the system settings in one place, nice if you have to change oscillator rates etc. For small programs its not as important but - we ain't gonna be stickin' with small programs pard'ner.

Note the sequence for firing up the timer chain - stop everything, set things up, clear any stray interrupt flags, start timers, enable interrupts last..

Rich (BB code):
// THis is GENERIC C/pseudocode -  just to show how to set up the timer chains

void Interrupt(){
    //****************** DISPLAY MUX  *******************
    
  if (TMR0IF_bit){
    TMR0IF_bit = 0;        // ack IRQ
    TMR0 += TMR0set;    // reset timer
    //Code goes here to output segs to next digit
  }

    //**************** SYSTEM TIK - 10ms  ***********************

  if (CCP1IF_bit){        // Interrupting on TIMER1 == CCP
    CCPIF_bit = 0;        // ack IRQ
    //TMR1H = 0x3C;        // Timer resets automatically - nice!
    //TMR1L = 0xB0;
    //Code goes here to do derived timers - see example
  }
}

    //******************* INIT TIMER CHAIN  *********************
    // Two timers are used. 
    // TMR0 times the display mux
    // TMR1 generates the systik.
    
    // They are separate for development, could be combined later if the
    // times are compatible.

    // Timer 0 set to interrupt about 5msec for now
    // Setting is 256 - (5ms / .5usTcyc) / 64Prescaler = 256 - 156.25 counts = 99.75 ~= 100
#define TMR0set 100        // counts 100->255 then rolls over and interrupts    
#define OPTION_REGinit 0b11110101 // clock at internal Tcyc/64Prescaler

    // Timer 1 setup - counts from 0000h to value in CCPR1, resets timer automatically and interrupts
#define T1CONinit 0b0011000    // no gate, internal Tcyc/8, timer STOPPED
#define CCP1CONinit 0b00001011    // compare CCPR1 to TIMER1, IRQ and reset TMR1 on match
#define CCPR1init 1250        // .5usTcyc *8Prescale * 1250 = 5msec


void initTIMERS(void)
{
    INTCON = 0;        // NO interrupts during this
                // set up timer 0 params
    OPTION_REG = OPTION_REGinit;

    T1CON = T1CONinit;    // set timer 1 params
    CCP1CON = CCP1CONinit;    // set up as a period register w/auto reset
    CCPR1 = CCPR1init;    // load CCP reg 1 with count
    TMR1 = 0;        // clear the timer

    TMR0 = TMR0set        // set timer 0, it begins counting up to FFh
    
    CCP1IF = 0;        // clear interupt flags
    TMR0IF = 0;
    CCP1IE = 1;        // then enable individual interrupts
    TMR0IE = 1;        

    T1CON.TMR1ON = 1;    // start timer 1
    PEIE = 1;        // enable global interrupts
    GIE = 1;
}

    //******************* DUMMY MAIN  ***************************

main(){

    initIO();        // init IO ports
    init..other tings    
    initTIMERs();        // fire up the IRQ-driven timers

    while(1){
    // timers are running..

    }    
}
 

JohnInTX

Joined Jun 26, 2012
4,787
This code shows how to convert an integer to an array of segment patterns and get them to the display. The actual multiplexer will wait until you get the interrupts going. This will build in MicroC.

Rich (BB code):
// LEDmux.c
// PIC16F887
// Demo of how to look up segments from integer values.
// Demo ONLY!  Robustness features (bounds checking etc) not present.
// LEDs present only in digits buffer.

//-------------------- DEFINE SEGMENTS ON PORT -----------------------
// Where each segment is on the port. Assign each seg to one pin (in any order)
#define segA (0x01)
#define segB (0x02)
#define segC (0x04)
#define segD (0x08)
#define segE (0x10)
#define segF (0x20)
#define segG (0x40)
#define segDP (0x80)

//---------------------- CONSTRUCT DIGITS FROM SEGMENTS  ---------------
// What segments make up digits..
#define dig0 (segA + segB + segC + segD + segE + segF)
#define dig1 (segB + segC)
#define dig2 (segA + segB + segD + segE + segG)
#define dig3 (segA + segB + segC + segD + segG)
#define dig4 (segB + segC + segF + segG)
#define dig5 (segA + segC + segD + segF + segG)
#define dig6 (segA + segC + segD + segE + segF + segG)
#define dig7 (segA + segB + segC)
#define dig8 (segA + segB + segC + segD + segE + segF + segG)
#define dig9 (segA + segB + segC + segF + segG)

#define digA (segA + segB + segC + segE + segF + segG)
#define digB (segC + segD + segE + segF + segG)
#define digC (segA + segD + segE + segF)
#define digD (segB + segC + segD + segE + segG)
#define digE (segA + segD + segE + segF + segG)
#define digF (segA + segE + segF + segG)

#define digMinus (segG) // '-' sign

//--------------------- SEGMENT LOOKUP TABLE  ------------------------------

const unsigned char segTable[] = {dig0,dig1,dig2,dig3,dig4,dig5,dig6,dig7,\
                                  dig8,dig9,digA,digB,digC,digD,digE,digF,digMinus};
                                  
//---------------------- SEGMENTS BUFFER  -----------------------------------
// The LED multiplexer will fetch these segment patterns in sequence and
// light successive displays

#define N_DIGITS 10
unsigned char digitsBuf[N_DIGITS];       // 10 digits of segment patterns incl LEDs

//----------------------- DISPLAY MAP  --------------------------------------
// Which groups of 3 digits show what - specified as offset into digitsBuf
#define dVOLTS 0            // digits 0-2 are volts
#define dAMPS  3            // 3-5 show amps
#define dMINS  6            // 6-8 show minutes
#define dLEDs  9            // LEDs are offset 9 (the 10th 'digit')

//---------------------- TEST VARIABLES  ------------------------------------
unsigned int volts;
unsigned int amps;
unsigned int minutes;

 //--------------------- CONVERT 0-999 to 3 DIGITS AT SPECIFIED DISPLAY AREA --------------
 
void int2segs(unsigned int i, short ofs)
{
     short x;
     x= i/100;                          // extract 100s digit's segs to offset+0
     digitsBuf[ofs]= segTable[x];
     
     x =(i%100)/10;
     digitsBuf[ofs+1]= segTable[x];     // extract 10s digit's segs to offset +1
     
     x = (i%10);
     digitsBuf[ofs+2]= segTable[x];     // extract 1's digit's segs of offset+2
  
}
 //----------------- TEST LOOP ---------------------------------------
 // In debugger, break on volts and modify values, watch digitsBuf[] in watch window.
 // Inspect changing segment patterns as you change values.
void main() {
     volts = 123;                       // test volt
     amps = 456;                        // test amps
     minutes = 789;                     // test mins
     while(1){
       int2segs(volts,dVOLTS);            // write volts at dVOLTS region of display
       int2segs(amps,dAMPS);              // amps ditto
       int2segs(minutes,dMINS);           // minutes ditto
      }//while
}
 
Top