Multiplexing 7Segs and using timer Countdown

Discussion in 'Programmer's Corner' started by R!f@@, May 6, 2014.

  1. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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: May 6, 2014
  2. JohnInTX


    Jun 26, 2012
    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: May 6, 2014
    R!f@@ likes this.
  3. elec_mech

    Senior Member

    Nov 12, 2008
    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.
    R!f@@ likes this.
  4. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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
  5. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    I don't get it :confused:

    16F887 is far cheaper than a 16F88 :eek:

    Guess the F887 has to do.
  6. JohnInTX


    Jun 26, 2012
    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: May 7, 2014
    R!f@@ likes this.
  7. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    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.
    R!f@@ likes this.
  8. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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: May 7, 2014
  9. JohnInTX


    Jun 26, 2012
    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).

    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:
    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: May 7, 2014
  10. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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
  11. JohnInTX


    Jun 26, 2012
    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.

    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.
  12. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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: May 7, 2014
  13. R!f@@

    Thread Starter AAC Fanatic!

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

    See how I managed to utilize every pin of 16F887 :D
    Code ( (Unknown Language)):
    1.  *Pin Connections:
    2.       RE3 is the MCLR
    4.   PortA are the LED and Relay drives
    5.      RA0 - Charging/Complete LED (Blinks when completed
    6.      RA1 - CV mode LED
    7.      RA2 - CC mode LED
    8.      RA3 - Battery Inserted LED
    9.      RA4 - Battery Over temp LED
    10.      RA5 - 4.2V or 8.4V Selector Relay
    11.      RA6 - Charger Relay
    12.      RA7 - Battery Voltage Checker Relay
    15.   PortB are Analog and PB switch inputs
    16.      RB0 - Voltage ADC input
    17.      RB1 - Current ADC input
    18.      RB2 - Battery temperature ADC input
    19.      RB3 - Battery Insert Switch
    20.      RB4 - Charge Start/Halt/Stop Switch
    21.      RB5 - Selector Sw, Voltage & Charge Termination Current & Timer (works with encoder)
    22.      RB6 - RotaryEncoder input A
    23.      RB7 - RotaryEncoder input B
    25.   PortC + RE2 are the digit drivers:
    26.      RC0 - Digit 1(Timer Hr digit)
    27.      RC1 - Digit 2(Timer Min digit)
    28.      RC2 - Digit 3(Timer Min digit)
    29.      RC3 - Digit 4(Voltmeter digit)
    30.      RC4 - Digit 5(Voltmeter digit)
    31.      RC5 - Digit 6(Voltmeter digit)
    32.      RC6 - Digit 7(Ammeter digit)
    33.      RC7 - Digit 8(Ammeter digit)
    34.      RE2 - Digit 9(Ammeter digit)
    36.   PortE is used for heatsink temp sensing and Fan driving
    37.      RE0 - HeatSink Temp ADC input
    38.      RE1 - Fan Diver(heatsink)
    40.   PortD are the segment drives:
    41.      RD0 - Segment A
    42.      RD1 - Segment B
    43.      RD2 - Segment C
    44.      RD3 - Segment D
    45.      RD4 - Segment E
    46.      RD5 - Segment F
    47.      RD6 - Segment G
    48.      RD7 - Decimal point
    I went for PORTB inputs so I can use interrupt on change for the switches.
    Am I correct to assume tht ?
  14. JohnInTX


    Jun 26, 2012
    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:
    Code ( (Unknown Language)):
    1.  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.

    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.

    Code ( (Unknown Language)):
    1. /*
    2.  * File:   Guichess.c
    3.  * Author: John
    4.  *
    5.  * Created on February 10, 2014, 11:49 PM
    6.  */
    8. #pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN)
    9. #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
    10. #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
    11. #pragma config MCLRE = OFF // MCLR Pin Function Select bit (MCLR pin function is digital input, MCLR internally tied to VDD)
    12. #pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)
    13. #pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
    14. #pragma config BOREN = OFF // Brown Out Detect (BOR disabled)
    15. #pragma config IESO = ON // Internal External Switchover bit (Internal External Switchover mode is enabled)
    16. #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)
    19. #include <xc.h>
    20. #include <stdlib.h>
    23. /************************************************************/
    24. //              HARDWARE CONFIGURATION
    25. /************************************************************/
    26. #define _XTAL_FREQ 8000000
    27. #define usTcyc ((_XTAL_FREQ / 1000000) / 4) // how many uS 1 Tcyc is
    29. //*****************  CODE CONFIGURATION  ********************
    30. #define usSysTik 10000      // 10msec systik
    31. #define ms100PSset 10       // 10 * systik = 100ms
    34. /************************************************************/
    35. //              TIMERS
    36. /************************************************************/
    37. #if 0
    38.     Timer 1 is used to generate a 10msec system tik. Its the
    39.     smallest derived time available and is used to drive the
    40.     debouncer and EE update timer.
    41.     Timer 0 is reserved to free up the prescaler for the WDT.
    43.     The timer runs w/o prescaler so that we can load it with
    44.     an adjusted time without having to worry about clearing the PS
    45. #endif
    47. #define T1CONinit 0b00000000    // No gate, 1:1ps, Internal, OFF
    48. #define TMR1adj 9               // Number of Tcyc it takes to stop-load-start TMR1
    49.                                 // determined by looking at disassembly.
    50.     //Calculate reload value for timer counting up to rollover. Result is
    51.     // number of counts based on Tcyc, adjustment, SysTik value etc.
    52. #define TMR1setTiks (2^16-(usSysTik / usTcyc) + TMR1adj)
    54. unsigned char  ms100PreScaler;
    55. bit ms10_happened;      // bits get set by IRQ at the noted time interval
    56. bit ms100_happened;
    58. //Derived timers
    59. // These get loaded with count by main program. They count to 00 and stay there
    60. // When main sees they are 00, it knows to do something
    62. unsigned char Timer10ms_A;
    63. unsigned char Timer100ms_A;
    65. void interrupt isr(void)
    66. {
    67.     //---------------- MAINTAIN SYSTIK  ---------------------
    68.     if(TMR1IF){
    69.         TMR1IF = 0;     // ack the IRQ
    71.         TMR1ON = 0;     // stop the timer, reload it, restart it
    72.         TMR1 += TMR1setTiks;  // any accumulated time + the reload value
    73.         TMR1ON = 1;
    75.         //------------- PROCESS SYSTIK  ------------------------
    76.         // do 10msec stuff here
    77.         // Flag that systik happened
    78.         ms10_happened = 1;                 // flag 10 ms happened
    79.         if (Timer10ms_A) Timer10ms_A--;   // count 10ms timer to 0
    81.         //---------------- 100 ms TIMERS --------------------------
    82.         // 10ms systik is prescaled to 100ms..
    83.         ms100PreScaler--;
    84.         if(ms100PreScaler == 0){
    85.             ms100PreScaler = ms100PSset;        // reset prescaler
    86.             ms100_happened = 1;         // flag 100ms happened
    87.             //--------------- SERVICE 100ms TIMERS  ----------------
    88.             // These timers run at 100ms tiks.
    89.             if (Timer100ms_A) Timer100ms_A--; // maintain Timer100ms_A timer
    90.         }
    91.     } // TMR1IF
    94. void main(void) {
    95. TRISIO = 0b00001000;
    96. GPIO = 0b00000000;
    97. ANSEL = 0;
    98. CMCON0 = 0b00000111;
    99. OSCCON = 0b01110111;
    101.     //--------------- INIT SYSTIK -------------------------------
    102.     // Timer 1 is the master timer.
    103.     // Timer 0 is not used so far.
    105.     T1CON = T1CONinit;  // stop timer, configure it
    106.     TMR1 = TMR1setTiks; // load it
    107.                         // init timer chain
    108.     ms100PreScaler = ms100PSset;
    110.     TMR1IF = 0;         // clear IRQ
    111.     TMR1IE = 1;         // then enable TMR1 interrupt
    112.     PEIE = 1;           // and peripherial IRQ
    113.     TMR1ON = 1;         // start the timer
    115.     //--------------- FIRE UP THE SYSTEM -------------------------
    116.     GIE = 1;            //GLOBAL INTERRUPTS ENABLED
    118.     while(1){
    119.         // Toggle GP1 at 500ms uning a derived timer
    120.         if(Timer10ms_A == 0){
    121.              GP1 = !GP1;
    122.              Timer10ms_A = 50;   // reload timer for next time
    123.         }
    125.         // Toggle GP0 at 100ms using flag
    126.         if(ms100_happened){
    127.             ms100_happened = 0;  // reset flag for next time
    128.             GP0 = !GP0;
    129.         }
    130.     }// while
    133. }//main
    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: May 7, 2014
  15. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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.
  16. JohnInTX


    Jun 26, 2012
    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: May 8, 2014
  17. R!f@@

    Thread Starter AAC Fanatic!

    Apr 2, 2009
    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:
    Code ( (Unknown Language)):
    1.   Indicators are CC 7 Segments Muxed(PortD).
    2.       3 Digits for 0.00 to 8.40 Volts
    3.       3 Digits for 0.00 to 5.00 Amps
    4.       3 Digits for 2.00 Hour Timer
    6.   LED's are connected as CC and Muxed with the 7 segments and controlled by RE1
    7.   (anodes are connected as indicated below)
    8.    There are total of 5 Led indicators
    9.       1. Charging / Charge completed (blinks when Charging) - RD0
    10.       2. Constant Voltage mode - RD1
    11.       3. Constant Current mode - RD2
    12.       4. Battery inserted indicator - RD3
    13.       5. Battery Over heat indicator - RD4
    15. ==============================================================================
    16.  *Pin Connections:
    17.       RE3 is the MCLR
    19.   PortA Connections
    20.      RA0 - Voltage ADC input
    21.      RA1 - Current ADC input
    22.      RA2 - Battery temperature ADC input
    23.      RA3 - Heat Sink temperature ADC input
    24.      RA4 - Not used
    25.      RA5 - Not used
    26.      RA6 - Not used
    27.      RA7 - Buzzer - Just may be.
    29.   PortB Connections
    30.      RB0 - Charger Relay
    31.      RB1 - Battery Voltage Checker Relay
    32.      RB2 - 4.2V or 8.4V Selector Relay
    33.      RB3 - Charge Start/Halt/Stop Switch
    34.      RB4 - Battery Insert Switch
    35.      RB5 - Selector Sw, Voltage & Charge Termination Current & Timer (works with encoder)
    36.      RB6 - RotaryEncoder input A
    37.      RB7 - RotaryEncoder input B
    40.   PortC + RE1 & RE2 are the digit/LED drivers:
    41.      RC0 - Digit 1(Timer Hr digit)
    42.      RC1 - Digit 2(Timer Min digit)
    43.      RC2 - Digit 3(Timer Min digit)
    44.      RC3 - Digit 4(Voltmeter digit)
    45.      RC4 - Digit 5(Voltmeter digit)
    46.      RC5 - Digit 6(Voltmeter digit)
    47.      RC6 - Digit 7(Ammeter digit)
    48.      RC7 - Digit 8(Ammeter digit)
    49.      RE2 - Digit 9(Ammeter digit)
    50.      RE1 - LED Drive
    52.   PortE
    53.      RE0 - Fan Diver(heatsink)
    55.   PortD are the segment drives:
    56.      RD0 - Segment A
    57.      RD1 - Segment B
    58.      RD2 - Segment C
    59.      RD3 - Segment D
    60.      RD4 - Segment E
    61.      RD5 - Segment F
    62.      RD6 - Segment G
    63.      RD7 - Decimal point
    Starting to code now.........
    Last edited: May 8, 2014
  18. R!f@@

    Thread Starter AAC Fanatic!

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

    Code ( (Unknown Language)):
    1. /*Timer0
    2.   Interrupt Time is like 9.984 ms  */
    3. void Timer0(){
    4.   OPTION_REG = 0x86;          //Option Reg; Pull ups disable, Prescale to Timer0 & 1:128
    5.   TMR0 = 100;                 //Load timer with 100
    6.   INTCON = 0xA0;              //Intcon Reg; Enable Global & Timer0 overflow Int.
    7. }
    8. /*Timer1
    9.   Interrupt Time is 100 ms */
    10. void Timer1(){
    11.   T1CON    = 0x21;               //Timer1 control; 1:4 prescale & Enable timer
    12.   TMR1IF_bit = 0;             //PIR1 Reg; Clear the Timer1 overflow Interrupt flag bit
    13.   TMR1H = 0x3C;               //Load the Timer1 MSB &
    14.   TMR1L = 0xB0;               //load the Timer1 LSB with the Preload of 15536
    15.   TMR1IE_bit = 1;             //PIE1 Reg; Enable Timer1 overflow interrupt
    16.   INTCON = 0xC0;              //Intcon Reg; Enable Global Int & Peripheral Int.
    17. }
    18. /*********** Interrupt Service Routine ***************/
    19. void Interrupt(){
    20.   if (TMR0IF_bit){
    21.     TMR0IF_bit = 0;
    22.     TMR0 = 100;
    23.     //Code goes here
    26.   }
    27.   if (TMR1IF_bit){
    28.     TMR1IF_bit = 0;
    29.     TMR1H = 0x3C;
    30.     TMR1L = 0xB0;
    31.     //Code goes here
    34.   }
    35. }
    36. /*********** Main Routine ****************************/
    37. void main() {
    38.     PORTA = 0;                // Clear All Ports
    39.     PORTB = 0;                // ***** do ******
    40.     PORTC = 0;                // ***** do ******
    41.     PORTD = 0;                // ***** do ******
    42.     PORTE = 0;                // ***** do ******
    43.     ANSEL = 0x0F;             // RA0 to RA3 as Analog Inputs
    44.     ANSELH = 0x00;            // Rest are Digital I/O's
    45.     CM1CON0.C1ON = 0;         // Disable Comparator 1
    46.     CM2CON0.C2ON = 0;         // Disable Comparator 2
    47.     TRISA = 0x7F;             // All Inputs except RA7 as Output
    48.     TRISB = 0xF8;             // RB <7:3> Inputs & RB <2:0> Ouputs
    49.     TRISC = 0x00;             // PORTC are Ouputs
    50.     TRISD = 0x00;             // PORTD are Ouputs
    51.     TRISE = 0x00;             // PORTE are Ouputs (MCLR is an input)
    52. }
    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: May 8, 2014
  19. JohnInTX


    Jun 26, 2012
    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..

    Code ( (Unknown Language)):
    1. // THis is GENERIC C/pseudocode -  just to show how to set up the timer chains
    3. void Interrupt(){
    4.     //****************** DISPLAY MUX  *******************
    6.   if (TMR0IF_bit){
    7.     TMR0IF_bit = 0;        // ack IRQ
    8.     TMR0 += TMR0set;    // reset timer
    9.     //Code goes here to output segs to next digit
    10.   }
    12.     //**************** SYSTEM TIK - 10ms  ***********************
    14.   if (CCP1IF_bit){        // Interrupting on TIMER1 == CCP
    15.     CCPIF_bit = 0;        // ack IRQ
    16.     //TMR1H = 0x3C;        // Timer resets automatically - nice!
    17.     //TMR1L = 0xB0;
    18.     //Code goes here to do derived timers - see example
    19.   }
    20. }
    22.     //******************* INIT TIMER CHAIN  *********************
    23.     // Two timers are used.
    24.     // TMR0 times the display mux
    25.     // TMR1 generates the systik.
    27.     // They are separate for development, could be combined later if the
    28.     // times are compatible.
    30.     // Timer 0 set to interrupt about 5msec for now
    31.     // Setting is 256 - (5ms / .5usTcyc) / 64Prescaler = 256 - 156.25 counts = 99.75 ~= 100
    32. #define TMR0set 100        // counts 100->255 then rolls over and interrupts    
    33. #define OPTION_REGinit 0b11110101 // clock at internal Tcyc/64Prescaler
    35.     // Timer 1 setup - counts from 0000h to value in CCPR1, resets timer automatically and interrupts
    36. #define T1CONinit 0b0011000    // no gate, internal Tcyc/8, timer STOPPED
    37. #define CCP1CONinit 0b00001011    // compare CCPR1 to TIMER1, IRQ and reset TMR1 on match
    38. #define CCPR1init 1250        // .5usTcyc *8Prescale * 1250 = 5msec
    41. void initTIMERS(void)
    42. {
    43.     INTCON = 0;        // NO interrupts during this
    44.                 // set up timer 0 params
    45.     OPTION_REG = OPTION_REGinit;
    47.     T1CON = T1CONinit;    // set timer 1 params
    48.     CCP1CON = CCP1CONinit;    // set up as a period register w/auto reset
    49.     CCPR1 = CCPR1init;    // load CCP reg 1 with count
    50.     TMR1 = 0;        // clear the timer
    52.     TMR0 = TMR0set        // set timer 0, it begins counting up to FFh
    54.     CCP1IF = 0;        // clear interupt flags
    55.     TMR0IF = 0;
    56.     CCP1IE = 1;        // then enable individual interrupts
    57.     TMR0IE = 1;        
    59.     T1CON.TMR1ON = 1;    // start timer 1
    60.     PEIE = 1;        // enable global interrupts
    61.     GIE = 1;
    62. }
    64.     //******************* DUMMY MAIN  ***************************
    66. main(){
    68.     initIO();        // init IO ports
    69.     init..other tings    
    70.     initTIMERs();        // fire up the IRQ-driven timers
    72.     while(1){
    73.     // timers are running..
    75.     }    
    76. }
  20. JohnInTX


    Jun 26, 2012
    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.

    Code ( (Unknown Language)):
    1. // LEDmux.c
    2. // PIC16F887
    3. // Demo of how to look up segments from integer values.
    4. // Demo ONLY!  Robustness features (bounds checking etc) not present.
    5. // LEDs present only in digits buffer.
    7. //-------------------- DEFINE SEGMENTS ON PORT -----------------------
    8. // Where each segment is on the port. Assign each seg to one pin (in any order)
    9. #define segA (0x01)
    10. #define segB (0x02)
    11. #define segC (0x04)
    12. #define segD (0x08)
    13. #define segE (0x10)
    14. #define segF (0x20)
    15. #define segG (0x40)
    16. #define segDP (0x80)
    18. //---------------------- CONSTRUCT DIGITS FROM SEGMENTS  ---------------
    19. // What segments make up digits..
    20. #define dig0 (segA + segB + segC + segD + segE + segF)
    21. #define dig1 (segB + segC)
    22. #define dig2 (segA + segB + segD + segE + segG)
    23. #define dig3 (segA + segB + segC + segD + segG)
    24. #define dig4 (segB + segC + segF + segG)
    25. #define dig5 (segA + segC + segD + segF + segG)
    26. #define dig6 (segA + segC + segD + segE + segF + segG)
    27. #define dig7 (segA + segB + segC)
    28. #define dig8 (segA + segB + segC + segD + segE + segF + segG)
    29. #define dig9 (segA + segB + segC + segF + segG)
    31. #define digA (segA + segB + segC + segE + segF + segG)
    32. #define digB (segC + segD + segE + segF + segG)
    33. #define digC (segA + segD + segE + segF)
    34. #define digD (segB + segC + segD + segE + segG)
    35. #define digE (segA + segD + segE + segF + segG)
    36. #define digF (segA + segE + segF + segG)
    38. #define digMinus (segG) // '-' sign
    40. //--------------------- SEGMENT LOOKUP TABLE  ------------------------------
    42. const unsigned char segTable[] = {dig0,dig1,dig2,dig3,dig4,dig5,dig6,dig7,\
    43.                                   dig8,dig9,digA,digB,digC,digD,digE,digF,digMinus};
    45. //---------------------- SEGMENTS BUFFER  -----------------------------------
    46. // The LED multiplexer will fetch these segment patterns in sequence and
    47. // light successive displays
    49. #define N_DIGITS 10
    50. unsigned char digitsBuf[N_DIGITS];       // 10 digits of segment patterns incl LEDs
    52. //----------------------- DISPLAY MAP  --------------------------------------
    53. // Which groups of 3 digits show what - specified as offset into digitsBuf
    54. #define dVOLTS 0            // digits 0-2 are volts
    55. #define dAMPS  3            // 3-5 show amps
    56. #define dMINS  6            // 6-8 show minutes
    57. #define dLEDs  9            // LEDs are offset 9 (the 10th 'digit')
    59. //---------------------- TEST VARIABLES  ------------------------------------
    60. unsigned int volts;
    61. unsigned int amps;
    62. unsigned int minutes;
    64.  //--------------------- CONVERT 0-999 to 3 DIGITS AT SPECIFIED DISPLAY AREA --------------
    66. void int2segs(unsigned int i, short ofs)
    67. {
    68.      short x;
    69.      x= i/100;                          // extract 100s digit's segs to offset+0
    70.      digitsBuf[ofs]= segTable[x];
    72.      x =(i%100)/10;
    73.      digitsBuf[ofs+1]= segTable[x];     // extract 10s digit's segs to offset +1
    75.      x = (i%10);
    76.      digitsBuf[ofs+2]= segTable[x];     // extract 1's digit's segs of offset+2
    78. }
    79.  //----------------- TEST LOOP ---------------------------------------
    80.  // In debugger, break on volts and modify values, watch digitsBuf[] in watch window.
    81.  // Inspect changing segment patterns as you change values.
    82. void main() {
    83.      volts = 123;                       // test volt
    84.      amps = 456;                        // test amps
    85.      minutes = 789;                     // test mins
    86.      while(1){
    87.        int2segs(volts,dVOLTS);            // write volts at dVOLTS region of display
    88.        int2segs(amps,dAMPS);              // amps ditto
    89.        int2segs(minutes,dMINS);           // minutes ditto
    90.       }//while
    91. }