PIC16F57 Digital Clock - timing a minute

Discussion in 'Embedded Systems and Microcontrollers' started by RobinGriffiths, Oct 15, 2012.

  1. RobinGriffiths

    Thread Starter New Member

    Aug 16, 2011
    I've built a PIC16F57 4 digit digital clock with 4 x 7 segment displays, with a strobe line activating each digit one at a time. I've got everything working nicely so that I have hours in one file register and minutes in another file register. I've also got a nice subroutine to add a minute to the time, and to display the contents of the time file registers.

    In my main loop I then have

    call AddMinute ; adds minute to time file registers (carrying to hours as appropriate)
    call MoveTimeToDisplay ; writes to 7 seg display with strobing
    ; some extra stuff
    goto loop

    My problem now, is how to invoke the AddMinute subroutine once every minute. Timer clock speed after prescaling is 2400Hz.

    I had erroneously assumed I could use modification of code from Morton's 'The Pic Microcontroller - your personal introductory course' which uses 2 extra registers and code to divide 2400Hz to 1Hz, but I realise now, he's using the code to delay for 1 second, not to flag an action every second while going round the main loop. I realise in retrospect I should have used a pic with a timer that can raise an interrupt, but now I've invested time in hardware and software, I'd quite like to finish the project.

    So in summary, is there a way I can trigger my subroutine exactly once every minute ?


  2. t06afre

    AAC Fanatic!

    May 11, 2009
    Can you post your code. The best approach is using a timer and an Interrupt Service Routine(ISR). Else you may end up with a clock that is not very accurate. But the good thing with micro-controllers is that you can redesign you software and keep the hardware
  3. JohnInTX


    Jun 26, 2012
    Use TMR0 in a free running mode. Arrange to sample it at something more often than 1/2 its period. Examine the MSbit and update your counters whenever it changes. In detail:

    Suppose TMR0 has a period of 10msec exactly. The MSbit will change state every 5msec. Use some RAM to remember the previous MSbit.

    Periodically, read TMR0 to W and save its value in 'temp'
    XOR W with 'last' value and save it in 'changed'
    Save 'temp' to 'last' for next time.

    If the MSbit of 'changed' is '1', at least 50ms have elapsed. Accumulate these until you have 1 sec the do your updates.

    Don't attempt to write to TMR0 each time to get a nice period. Writing clears the prescaler and you'll get varying periods each time.

    You don't need to use an entire byte of RAM if you only use the MSbit. I did to use the other bits as multiple (faster) timers. Each bit in the RAM reg. after the XOR will indicate a changed bit in TMR0. In this example, bit 6 will change every 25ms etc.

    Your oscillator will have to be some nice multiple of a second unless you want to do some post scaling (ugly).

    Agreed but the '57 doesn't have interrupts. There likely is a pinout compatible 16F6xxx that would do it.
    Last edited: Oct 15, 2012
  4. Papabravo


    Feb 24, 2006
    It might not have TMR0 either. You might have to find a way to use the dreaded RTCC ?!:eek:!?
  5. t06afre

    AAC Fanatic!

    May 11, 2009
    It does have a TMR0. Strange controller by the way. Makes the 16f84 look modern
  6. JohnInTX


    Jun 26, 2012
    .. for all practical purposes as the RTCC. Just renamed TMR0 to agree with later PICs that have a legacy RTCC function implemented in their more capable TMR0.
  7. takao21203

    Distinguished Member

    Apr 28, 2012
    It's quite easy. You can program clock using 16F5X with correct accuracy.

    You simply test bit7, if it is set, increment your clock counter, and reset this bit. Alternatively, you branch when the bit 7 is set, but only increment once. Then in the other branch, you unlock the logic for incrementing again.
  8. RobinGriffiths

    Thread Starter New Member

    Aug 16, 2011
    Thanks all. Yes, that sounds like a good approach testing ms bit. Assuming the main programming loop takes less time than it takes tmr0 to count 1/2 cycle, then msb going from 0 to 1 would be a good proxy for a cycle. I shall give it a go tomorrow. In the meantime I was thinking of adding a real time clock chip, but I shall persevere with the software only approach.

    Thanks, Robin

    PS. You wouldn't want to see my code. Its my first PIC program, and its a bit rambling. I wanted to start off with a basic PIC in assembler before looking at more capable chips and using C.
    Last edited: Oct 15, 2012
  9. takao21203

    Distinguished Member

    Apr 28, 2012
    The 16F5X is percectly enough for LED clocks. However, using an interrupt to update the time counters can make it easier.

    For a 4-digit LED display usually 100Hz are enough. You don't need 2.4 KHz.
    What do you use as crystal? I wrote assembler code to use 4 MHz crystals some years ago, and it is particulary bad, even if it is just 20 lines.
  10. MMcLaren

    Well-Known Member

    Feb 14, 2010
    Off the top of my head, I thought I'd try an 8.388608 MHz crystal and set the TMR0 prescaler to 64. This produces TMR0 overflows 128 times per second but I test for TMR0 bit 7 transitions which occur precisely 256 times per second (~3.9 ms intervals). That's fast enough to drive the display with a 64 Hz refresh rate and sample four switches multiplexed off of the column driver lines onto a single pin at ~15.6 ms "debounce" intervals. Then use a single byte as a one second "heartbeat" counter and increment your RTC variables each time the counter decrements to zero. Here's a crude untested code excerpt of the main loop;

    Code ( (Unknown Language)):
    2. ;
    3. ;  using 8388608 Hz crystal and TMR0 prescaler 64:1 will toggle
    4. ;  TMR0 bit 7 precisely 256 times per second (~3.9 ms intervals)
    5. ;
    6. mainloop
    7.         movf    TMR0,W          ; get current TMR0 value
    8.         xorwf   tmrlatch,W      ; get changes, hi or lo
    9.         xorwf   tmrlatch,F      ; update timer state latch
    10.         andlw   b'10000000'     ; 1/256th second interval?
    11.         skpnz                   ; yes, skip, else
    12.         goto    mainloop        ; loop
    13. ;
    14. ;  refresh one digit each ~3.9 ms (1/256th second) interval for
    15. ;  a 15.625 msec display period and a 64 Hz refresh rate.
    16. ;
    17.         movf    disp+0,W        ; hh * 10 digit, 0..5
    18.         btfsc   colsel,2        ; hh * 01 digit? no, skip, else
    19.         movf    disp+1,W        ; hh * 01 digit, 0..9
    20.         btfsc   colsel,1        ; mm * 10 digit? no, skip, else
    21.         movf    disp+2,W        ; mm * 10 digit, 0..5
    22.         btfsc   colsel,0        ; mm * 01 digit? no, skip, else
    23.         movf    disp+3,W        ; mm * 01 digit, 0..9
    24.         call    segdata         ; get segment data
    25.         clrf    PORTB           ; blank the display
    26.         movwf   PORTC           ; setup new digit segment lines
    27.         movf    colsel,W        ; column select bit mask
    28.         movwf   PORTB           ; enable and display new digit
    29. ;
    30. ;  sample one of four mux'd switches each ~3.9 ms interval for
    31. ;  an effective ~15.6 ms 'debounce' interval per switch
    32. ;
    33.         movf    swold,W         ; switch state latch bits
    34.         iorwf   colsel,W        ; indicate pressed
    35.         btfss   swinput         ; is it pressed? yes, skip, else
    36.         xorwf   colsel,W        ; indicate released
    37. ;
    38. ;  K8LH Parallel Switch State Logic ("new press" filter)
    39. ;
    40. ;   wreg  ___---___---___---___   active high switch sample
    41. ;  swold  ____---___---___---__   switch state latch
    42. ;   wreg  ___-__-__-__-__-__-__   changes, press or release
    43. ;   wreg  ___-_____-_____-_____   filter out 'release' bits
    44. ;  swnew  ___-_____-_____-_____   save 'new press' flag bits
    45. ;
    46.         xorwf   swold,W         ; changes, press or release
    47.         xorwf   swold,F         ; update switch state latch
    48.         andwf   swold,W         ; filter out 'release' bits
    49.         movwf   swnew           ; save any 'new press' bits
    50. ;
    51. ;  bump column select bit mask for next ~3.9 ms refresh interval
    52. ;
    53.         rrf     colsel,W        ; preserve carry bit
    54.         rrf     colsel,W        ; shift right 1 bit
    55.         skpnc                   ; last column? no, skip, else
    56.         movlw   b'1000'         ; reset to 1st column (RB3)
    57.         movwf   colsel          ; update column select bit mask
    58. ;
    59. ;  if 1-second 'heartbeat', update real-time-clock variables
    60. ;
    61.         decfsz  sectmr,F        ; 1 second? yes, skip, else
    62.         goto    mainloop        ; branch (not 1 second interval)
    Obviously you'd have to count off 60 seconds in the RTC code section before you increment the "minutes" variables... Anyway, that's my 2 cents (it was a fun exercise).

    Cheerful regards, Mike
    Last edited: Oct 16, 2012
  11. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    There's lots of code on this web page for making any exact poeriod (like 1 second) using any PIC xtal speed, and any timer like TMR0;

    For example if you have a 4MHz xtal, and set TMR0 to 1:1 prescaler to make an interrupt every 256 ticks (every 256uS) you can use this code;
    Code ( (Unknown Language)):
    2. // uses 1 variable; unsigned long bres
    3. // gets here every TMR0 int (every 256 ticks)
    5. bres += 256;  // add 256 ticks to bresenham total
    7. if(bres >= 1000000)  // if reached 1 second!
    8. {
    9.   bres -= 1000000;   // subtract 1 second, retain error
    10.   do_1sec_event();   // update clock, etc
    11. }
  12. MMcLaren

    Well-Known Member

    Feb 14, 2010
    Roman, a method for detecting TMR0 overflow on a 12-bit core device, which doesn't have the benefit of a T0IF flag (or interrupts), would probably be a welcome addition to the examples for that timing method...

    Last edited: Oct 16, 2012
  13. MMcLaren

    Well-Known Member

    Feb 14, 2010
    Unfortunately, any write to TMR0, including clearing of bit 7, causes the prescaler to be reset, if you're using it, and also causes TMR0 to pause for 2 instruction cycles before it starts counting again.

    Caveat! This may not be a very good method for "correct accuracy".
    Last edited: Oct 16, 2012
  14. takao21203

    Distinguished Member

    Apr 28, 2012
    This is much better. C language allows it to use longint easily!
    If I ever have to make a new revision of any 4 MHz crystal based clock, I will use that code instead of my assembler solution.
  15. takao21203

    Distinguished Member

    Apr 28, 2012
    Hmm. I made one such circuit, a LED display minute counter, based on 32 KHz crystal. This is 8,192 clocks per second.

    The prescaler is set to 0x07 (256). So it is 32 clocks per second.

    In the main loop I test for 0x20:
    Code ( (Unknown Language)):
    1. if (TMR0==0x20)add_sec();
    Then I increment the seconds counter:
    Code ( (Unknown Language)):
    1. void add_sec()
    2. {
    3.     TMR0=0;
    4.     time_secs1++;
    5.     if(time_secs1==10){time_secs1=0;time_secs10++;};
    6.     if(time_secs10==6){time_secs10=0;time_mins1++;};
    7.     if(time_mins1==10){time_mins1=0;time_mins10++;};
    8.     if(time_mins10==6){time_mins10=0;};
    9. }
    I don't observe a resetting of the prescaler. Or I misunderstand something! I examined the source right now, it is just some lines C.

    Also I don't observe any inaccuracy. I am wondering about this for a while, since it is mentioned in the 16F5X datasheet.
  16. RobinGriffiths

    Thread Starter New Member

    Aug 16, 2011
    Righty. Got it working now. Am storing an old copy of timer msb in timeFlags register. Then by xoring this with current msb I know if msb has changed. I then check that msb is 1, so I know the timer has done 1 cycle. I then cascade two counters to give me my a single tick (which I signify by setting the carry bit) per second or minute as appropriate. The calling code can then check the carry bit, and increment by time registers as appropriate.

    Anyway, it's been an useful exercise both in assembly programming and using Mplab for debugging.

    I do have a hardware issue which I may raise in a separate post, but briefly my display randomly blanks out (with power led still on), and when it comes on again, the pic appears to have reset. It seems to be worse using a 3V rather than 6V supply. Touching the board may cause this behaviour, so am suspecting reset/clock circuitry. It could be a dry joint, but I think not.

    Code ( (Unknown Language)):
    2. DoTimings
    3.     ; increment counters when MSB set, and timeFlags.timerMSB not set
    4.     ; looking at bit 7, we can mask stuff out later
    6.     bcf STATUS, C            ; Clear carry. If C is clear, then no tick occured
    8.     movfw TMR0
    9.     xorwf timeFlags, w  ; if bit 7 is set, then timer msb has changed
    11.     movwf temp      ; cant do bit test directly on w, so copy out to temp
    12.     btfss temp, 7       ; If bit 7 i 0, then no change in timer msb
    13.     retlw 0
    17.     ; if going from 1 -> 0 we can return without doing
    18.         ;anything i.e. timeFlags.timerMSB = 1
    19.     btfsc timeFlags, 7
    20.     retlw 0
    25.     ; we going from 0 -> 1, decrement the counters
    26.     decfsz counter240
    27.     retlw 0             ;not zero so return
    29.     movlw D'240'             ; Reset counter240
    30.     movwf counter240
    32.     decfsz counter10
    34.     retlw 0             ;not zero so return
    36.     movlw D'10'                         ; Reset counter10
    37.     movwf counter10
    39.     bsf STATUS, C           ; Set carry to indicate 1 tick
    40.     retlw 0
    Last edited: Oct 17, 2012
  17. takao21203

    Distinguished Member

    Apr 28, 2012
    Hmm ya.

    I remember you expressed disapproval to my opinion a while ago.

    Using XC8, I can implement an expression depth that is impossible for humans to code manually.

    I researched some larger assembler programs, and BASIC scripts.
    There is a new QBasic for Windows and LINUX! But it is a mess.

    SO I am happy I found a good algorithm for 4MHz crystal here.
    I will use that.

    I welcome advice from experts. If it is good, I also increase my own professionalism.

    MMcLaren, I usually don't mix in to threads where you have a chat with someone. But I understand and hope we both can use this forum. I try to link in useful resources, from my work and research.
  18. takao21203

    Distinguished Member

    Apr 28, 2012
    I have old assembler codes around which handle that. If you wish to see an excerpt? I have done this a few times, without resetting the timer.
  19. RobinGriffiths

    Thread Starter New Member

    Aug 16, 2011
    Famous last words. Not a dry joint, but a bit of veroboard track that had raised and cracked. Right between the pull up resistor and capacitor to ground connected to MCLR, hence spurious resets. Also I realised I forgot to take the divide by 256 provided by the timer itself, so the timings now work out as:
    Clock: 2.4576MHz
    Divide by 4 for input to prescaler= 614,400Hz
    Prescale by 32 = 19,200 Hz
    TMR0 cycle - divide by 256 = 75 Hz
    File register counting to 75 = 1Hz
  20. THE_RB

    AAC Fanatic!

    Feb 11, 2008
    Thank you for the suggestion Mike. If you check the top of that page I linked, there is a section "Make a 1 second period with any PIC (Assembler)" and the second code example in there is a "no interrupts" version.

    It uses a very old system of detecting that TMR0 has rolled over, by polling TMR0 bit7 and comparing with a user "flag" bit, when bit7 is 0 and the flag bit is 1 the TMR0 roll is detected. Here is an except from that asm code;

    Code ( (Unknown Language)):
    2.     ;-------------------------------------------------
    3.     ; Poll (check) for our "fake" int here!
    4.     ;-------------------------------------------------
    5.                     ; all we do is check if timer0,bit7
    6.                     ; has changed, we compare it to our flag.
    7.                     ; if so we generate a "fake" interrupt.
    8.                     ;
    9.     btfsc TMR0,7            ; if bit7 is lo, force flag lo too
    10.     goto timer_7hi          ;
    12.                     ; TMR0,7 is lo, check if flag lo too
    13.     btfss bit7flag,7        ; skip if mismatch
    14.     goto main_loop          ; just keep looping
    16.                     ; we detected a mismatch, so do fake int!
    17.     bcf bit7flag,7          ; fix flag first
    18.     goto fake_int           ;
    20. timer_7hi               ; TMR0,7 is hi
    22.     btfsc bit7flag,7        ; test if bit7 hi AND flag lo
    23.     goto main_loop          ; flag also hi, no fake int, keep looping.
    25.                     ; we detected a mismatch, so do fake int!
    26.     bsf bit7flag,7          ; fix flag first
    28.     ;-------------------------------------------------
    29. fake_int                   
    30.     ; If it gets here we have detected a "fake" interrupt!
    31.     ;-------------------------------------------------
    To be fair to Takao21203, I think that procedure is exactly what he meant earlier in the thread when he said this;

    "... Alternatively, you branch when the bit 7 is set, but only increment once. Then in the other branch, you unlock the logic for incrementing again."

    Because TMR0 is never written to, the timing is as good as an interrupt system (provided the manual TMR0 polling is fast enough to never miss a roll). The good thing about a bresenham system is that the period of the event does not matter, so it works just as well with TMR0 prescaler of 256, giving tons of time to poll TMR0.

    (edit) Sorry when pasting that asm code I just realised that is the revised version, it generates a "fake int" for every time TMR0 bit7 changes, and runs TMR0 at half speed so each event is still 256 ticks. The revised version is better as it only needs to be polled once per 256 ticks, compared to the traditional system I described above that needs to be polled at least twice per 256 ticks.
    Last edited: Oct 18, 2012