PIC16F57 Digital Clock - timing a minute

Thread Starter

RobinGriffiths

Joined Aug 16, 2011
20
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

loop
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 ?

Thanks,

Robin
 

t06afre

Joined May 11, 2009
5,934
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
 

JohnInTX

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

Notes:
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).

The best approach is using a timer and an Interrupt Service Routine(ISR)
Agreed but the '57 doesn't have interrupts. There likely is a pinout compatible 16F6xxx that would do it.
 
Last edited:

JohnInTX

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

takao21203

Joined Apr 28, 2012
3,702
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.
 

Thread Starter

RobinGriffiths

Joined Aug 16, 2011
20
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:

takao21203

Joined Apr 28, 2012
3,702
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.
 

MMcLaren

Joined Feb 14, 2010
861
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;

Rich (BB code):
;
;  using 8388608 Hz crystal and TMR0 prescaler 64:1 will toggle
;  TMR0 bit 7 precisely 256 times per second (~3.9 ms intervals)
;
mainloop
        movf    TMR0,W          ; get current TMR0 value
        xorwf   tmrlatch,W      ; get changes, hi or lo
        xorwf   tmrlatch,F      ; update timer state latch
        andlw   b'10000000'     ; 1/256th second interval?
        skpnz                   ; yes, skip, else
        goto    mainloop        ; loop
;
;  refresh one digit each ~3.9 ms (1/256th second) interval for
;  a 15.625 msec display period and a 64 Hz refresh rate.
;
        movf    disp+0,W        ; hh * 10 digit, 0..5
        btfsc   colsel,2        ; hh * 01 digit? no, skip, else
        movf    disp+1,W        ; hh * 01 digit, 0..9
        btfsc   colsel,1        ; mm * 10 digit? no, skip, else
        movf    disp+2,W        ; mm * 10 digit, 0..5
        btfsc   colsel,0        ; mm * 01 digit? no, skip, else
        movf    disp+3,W        ; mm * 01 digit, 0..9
        call    segdata         ; get segment data
        clrf    PORTB           ; blank the display
        movwf   PORTC           ; setup new digit segment lines
        movf    colsel,W        ; column select bit mask
        movwf   PORTB           ; enable and display new digit
;
;  sample one of four mux'd switches each ~3.9 ms interval for 
;  an effective ~15.6 ms 'debounce' interval per switch
;
        movf    swold,W         ; switch state latch bits
        iorwf   colsel,W        ; indicate pressed
        btfss   swinput         ; is it pressed? yes, skip, else
        xorwf   colsel,W        ; indicate released
;
;  K8LH Parallel Switch State Logic ("new press" filter)
;
;   wreg  ___---___---___---___   active high switch sample
;  swold  ____---___---___---__   switch state latch
;   wreg  ___-__-__-__-__-__-__   changes, press or release
;   wreg  ___-_____-_____-_____   filter out 'release' bits
;  swnew  ___-_____-_____-_____   save 'new press' flag bits
;
        xorwf   swold,W         ; changes, press or release
        xorwf   swold,F         ; update switch state latch
        andwf   swold,W         ; filter out 'release' bits
        movwf   swnew           ; save any 'new press' bits
;
;  bump column select bit mask for next ~3.9 ms refresh interval
;
        rrf     colsel,W        ; preserve carry bit
        rrf     colsel,W        ; shift right 1 bit
        skpnc                   ; last column? no, skip, else
        movlw   b'1000'         ; reset to 1st column (RB3)
        movwf   colsel          ; update column select bit mask
;
;  if 1-second 'heartbeat', update real-time-clock variables
;
        decfsz  sectmr,F        ; 1 second? yes, skip, else
        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:

THE_RB

Joined Feb 11, 2008
5,438
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;
http://www.romanblack.com/one_sec.htm

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;
Rich (BB code):
// uses 1 variable; unsigned long bres
// gets here every TMR0 int (every 256 ticks)

bres += 256;  // add 256 ticks to bresenham total

if(bres >= 1000000)  // if reached 1 second!
{
  bres -= 1000000;   // subtract 1 second, retain error
  do_1sec_event();   // update clock, etc
}
 

MMcLaren

Joined Feb 14, 2010
861
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...

Regards...
 
Last edited:

MMcLaren

Joined Feb 14, 2010
861
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.
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:

takao21203

Joined Apr 28, 2012
3,702
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;
http://www.romanblack.com/one_sec.htm

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;
Rich (BB code):
// uses 1 variable; unsigned long bres
// gets here every TMR0 int (every 256 ticks)

bres += 256;  // add 256 ticks to bresenham total

if(bres >= 1000000)  // if reached 1 second!
{
  bres -= 1000000;   // subtract 1 second, retain error
  do_1sec_event();   // update clock, etc
}
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.
 

takao21203

Joined Apr 28, 2012
3,702
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".
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:
Rich (BB code):
if (TMR0==0x20)add_sec();
Then I increment the seconds counter:
Rich (BB code):
void add_sec()
{
    TMR0=0;
    time_secs1++;
    if(time_secs1==10){time_secs1=0;time_secs10++;};
    if(time_secs10==6){time_secs10=0;time_mins1++;};
    if(time_mins1==10){time_mins1=0;time_mins10++;};
    if(time_mins10==6){time_mins10=0;};
}
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.
 

Thread Starter

RobinGriffiths

Joined Aug 16, 2011
20
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.

Rich (BB code):
DoTimings
	; increment counters when MSB set, and timeFlags.timerMSB not set
	; looking at bit 7, we can mask stuff out later

	bcf STATUS, C            ; Clear carry. If C is clear, then no tick occured

	movfw TMR0
	xorwf timeFlags, w	; if bit 7 is set, then timer msb has changed

	movwf temp		; cant do bit test directly on w, so copy out to temp
	btfss temp, 7		; If bit 7 i 0, then no change in timer msb
	retlw 0	



	; if going from 1 -> 0 we can return without doing 
        ;anything i.e. timeFlags.timerMSB = 1
	btfsc timeFlags, 7
	retlw 0	




	; we going from 0 -> 1, decrement the counters
	decfsz counter240
	retlw 0				;not zero so return
	
	movlw D'240'			 ; Reset counter240
	movwf counter240

	decfsz counter10
	
	retlw 0				;not zero so return	

	movlw D'10'	                        ; Reset counter10
	movwf counter10	

	bsf STATUS, C			; Set carry to indicate 1 tick
	retlw 0
 
Last edited:

takao21203

Joined Apr 28, 2012
3,702
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...

Regards...
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.
 

takao21203

Joined Apr 28, 2012
3,702
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...

Regards...
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.
 

Thread Starter

RobinGriffiths

Joined Aug 16, 2011
20
It could be a dry joint, but I think not
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
 

THE_RB

Joined Feb 11, 2008
5,438
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...

Regards...
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;

Rich (BB code):
	;-------------------------------------------------
	; Poll (check) for our "fake" int here!
	;-------------------------------------------------
					; all we do is check if timer0,bit7 
					; has changed, we compare it to our flag.
					; if so we generate a "fake" interrupt.
					;
	btfsc TMR0,7			; if bit7 is lo, force flag lo too
	goto timer_7hi			;

					; TMR0,7 is lo, check if flag lo too
	btfss bit7flag,7		; skip if mismatch
	goto main_loop			; just keep looping

					; we detected a mismatch, so do fake int!
	bcf bit7flag,7			; fix flag first
	goto fake_int			;

timer_7hi				; TMR0,7 is hi

	btfsc bit7flag,7		; test if bit7 hi AND flag lo
	goto main_loop			; flag also hi, no fake int, keep looping.

					; we detected a mismatch, so do fake int!
	bsf bit7flag,7			; fix flag first

	;-------------------------------------------------
fake_int					
	; If it gets here we have detected a "fake" interrupt!
	;-------------------------------------------------
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:
Top