Using a PIC12F683 to replace failed quartz clock electronics. - Page 1
Forum section: electronics / general

author: lmester
author_rank: Regular Contributor
timestamp: April 07, 2020, 06:36:36 am
content: Since i'm now doing "Social distancing", i'm at home most of the time. I now have plenty of time for low priority projects like this.First some background info:Clock repair is a hobby of mine. I'm a member of the National Association of Watch and Clock Collectors.https://nawcc.org/Some NAWCC members have posted that it would be useful to be able to replace failed quartz clock electronics.For most failed quartz clocks you'll just throw the clock away and buy a new clock. Some clocks are actually worth repairing. For most of those, you can use a generic replacement quartz clock movement. For a few, a generic movement won't fit. The only option is to repair the dead movement.Newer quartz clocks use a COB (Chip on board). No way to replace these. Older quartz clocks use a through hole DIP. Unfortunately, these IC's are obsolete unobtanium. The earliest battery operated quartz clocks used discrete components to make a divider chain. These are repairable if you can find the obsolete germanium transistors that were used.I used a 12F683 only because it's available in a small 8 pin package and, I already had some in my parts bin. I was able to get the 12F683 to accurately drive the clock. I can post the code if anyone's interested. It's not complex. This is basically what it does:Sleep for 1 second.Provide a drive pulse to the Lavet step motor.Toggle the motor drive state.repeat.I'm using a 32.768KHz crystal to drive TIMER1. This provides accurate hardware timing to drive the clock's Lavet step motor. Unfortunately, I can't get the the CCP Special Event Trigger to work with an external clock. It looks like CCP trigger isn't supported with an external clock source. Possibly, i'm not configuring TIMER1 and CCP correctly. Because of this, I'm stuck with a two second wake up interval for TIMER1. The PIC wakes up when the 16 bit timer rolls over. Using the CCP trigger set to 32K would make things easier. I'm using the watchdog timer to generate another wake up for the next one second interval. The WDT is using the internal 32KHz low speed oscillator to wake up. This isn't an accurate timer. Long term accuracy of the clock is still preserved since it syncs every two seconds using the crystal and TIMER1.The big problem with the 12F683 is that it drops out at about 2 volts. Quartz clocks normally use a 1.5V battery. Older quartz clocks use a C size battery. Newer ones use an AA size battery. 3.6V lithium batteries are available in C and AA sizes. The 12F683 PIC is usable if the 1.5V battery is replaced with a 3.6V lithium cell. This could cause problems for the clock owner. They must install a non-standard battery into the clock.I'd like to find a better part for this. Low voltage operation down to about one volt would be great. Hardware timing for a one second interval would also be nice. I have the most experience using Microchip PIC devices. A PIC that can do this would be great. Other Microcoltroller part suggestions are fine.I've done some parametric searches from electronics suppliers. I didn't find any part that is a good match for this application.Are there any microcontroller experts reading this? Would you be bored enough to help me with a low priority hobby project?Finally, a picture. Every post needs a picture! My HP5335A measuring the TIMER1 interval. Timing looks good.

author: Kilrah
author_rank: Unknown
timestamp: April 07, 2020, 07:55:42 am
content: You know you can set the timer value, right? Set it to 32768 on the second update so that you'll wake up in 1 second instead of 2.

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 07, 2020, 08:01:04 am
content: Quote from: lmester on April 07, 2020, 06:36:36 amJust a suggestion. Why can't you immediately change the high byte of TIMER1 from &H00 to &H80 when the PIC wakes up? This would cause it to overflow again after one second. You do not need to change the TIMER1 low byte.

author: SeanB
author_rank: Super Contributor
timestamp: April 07, 2020, 08:09:44 am
content: How about just regard that tiny COB board as a replacement IC, and use 4 wires to connect it in place of the original. They are pretty small, and as there likely is a lot of room on the case for it it will be easy enough. Only need the 2 power pins and the 2 coil connections to be made, using single strands of wire from some scrap cable strands.You will lose functionality though, if there is an output for an alarm it is generally not available, though I am sure you can grab a cheap alarm clock movement and use the COB from that, just add the fifth wire for the alarm tone output.

author: lmester
author_rank: Regular Contributor
timestamp: April 07, 2020, 09:28:25 am
content: Quote from: Kilrah on April 07, 2020, 07:55:42 amYou need hardwarwe crystal oscillator cycle accurate timing to run a quartz clock. I think that resetting TIMER1 could cause a loss of accuracy. The PIC code is running using the low freq. internal osc. It's not synched to the external crystal osc. Resetting TIMER1 using PIC code running on the internal oscillator could cause hardware timing inaccuracy.This might be incorrect. I'm a PIC hobbyist, not an expert.I should post the code that I wrote. Some readers will hate me for it. I'm an old person. This code is in assembly language. C is a "second language" for me. For tiny programs like this I still use assembly language.Code:list p=12F683 ; list directive to define processor#include <p12F683.inc> ; processor specific variable definitionserrorlevel -302 ; suppress message 302 from list file__CONFIG _FCMEN_ON & _IESO_OFF & _CP_OFF & _CPD_OFF & _BOD_OFF & _MCLRE_ON & _WDT_OFF & _PWRTE_ON & _INTOSCIO ;_FOSC_LP ; _FOSC_HS;PIC Pins used:;GP0 pin 7 output for debug/status LED.;GP1 pin 6 output for Lavet motor drive.;GP2 pin 5 output for Lavet motor drive.;CLKOUT pin 3 External 32.768KHz crystal.;CLKIN pin 2 External 32.768KHz crystal.;***** Constants *****;Number of times to trigger the watchdog timer.;Used for the 2nd clock beat time delay.WDTCycequ.6;Lavet step motor pulse duration.;Delay to allow the motor to respond to the drive impulse.;Delay is 5 miliseconds multiplied by MotDrvTm.MotDrvTmequ.8 ;.7;***** VARIABLE DEFINITIONS;Bank 1 variables.b1datudata0x20;Bank 2 variables.b2datudata0xa0;Bank 0-1 shared variables.;16 bytes shared memory.sdatudata_shr0xf0d1res1;Time delay counter.d2res1;Time delay counter.GPIOTres1;GPIO temps.MotStateres1;Lavet step motor state.Mdlyres1;Lavet step motor impulse timer.WDTCCntres1;Watchdog timer counter.LEDb1res 1;LED blinker count.w_temp res1; variable used for context savingstatus_temp res 1; variable used for context saving;**********************************************************************ORG0x000 ; processor reset vector.goto main ; go to beginning of program.ORG 0x004 ; interrupt vector location.movwfw_temp ; save off current W register contents.movfSTATUS,w ; move status register into W register.movwfstatus_temp ; save off contents of STATUS register.IDie:goto IDie;No interrupts in use, halt if we get one.movf status_temp,w ; retrieve copy of STATUS register.movwfSTATUS ; restore pre-isr STATUS register contents.swapf w_temp,fswapf w_temp,w ; restore pre-isr W register contents.retfie ; return from interrupt.;**********************************************************************main:bankselANSELclrfANSEL;Digital I/O pins.bankselWPUclrfWPU;Pull ups off.bankselTRISIOclrfTRISIO;All pins as output.bankselCMCON0movlw0xffmovwfCMCON0;Comparator off pins for I/O.clrfGPIOT;Init GPIO and buffer.clrfGPIOclrfMotState;Initial Lavet step motor state.bankselOSCCONmovlwb'00000001';Use 31KHz internal clock.movwfOSCCONoscwait:btfssOSCCON,LTS;Wait for oscillator stable.gotooscwaitmovlw.2;Blink LED W times. Yes, the PIC is running :-)callBlinkNW;Timer 1 init.;2 second hardware timing.;Timer1 is used for the clock rate timebase.bankselTMR1HclrfTMR1HclrfTMR1LbankselT1CONbsfT1CON,TMR1CS;Timer1 external crystal clock 32768Hz.bsfT1CON,T1OSCENbsfT1CON,.2;T1SYNC;External clock sync (??? Looks like T1SYNC is not defined use bit .2 instead).bsfT1CON,TMR1ON;Timer1 on.bcfPIR1,TMR1IF;reset int. flag.bankselINTCONbcfINTCON,GIE;Global Int. disabled.bsfINTCON,PEIE;Peripheral interrupts on.bankselPIE1bsfPIE1,TMR1IE;For timer 1.;Watchdog timer init.;Used to time the next clock beat after each timer1 2 second trigger.;WDT prescale rate.bankselWDTCONmovlwb'00001110' ;4K b'00010010' ;16Kb'00010100' ;32K;Watchdog timer rate.movwfWDTCON;Timer0 watchdog timer. Prescaler assigned to timer0.bankselOPTION_REGbcfOPTION_REG,PSA;################;Clock timing and Lavet motor drive.;Accurate crystal timing provided by timer1 (2 second interval).;Watchdog timer used to provide the next one second clock drive impulse.;Sleep as much as possible to save battery power.Zzz:sleep;Wait for timer1 rollover.bankselPIR1bcfPIR1,TMR1IF;reset int. flag.callLEDOn;Show motor drive status. !!! Remove when done testing to save power !!!callClockDrive;Advance the clock Lavet step motor.CallLEDOff;Set approximately 1 second delay for the next clock drive impulse.;Can't do it accurately since timer1 restarts on a 2 second interval.movlwWDTCyc;Cycle the watchdog timer W times.movwfWDTCCntbankselWDTCONWDTCount:clrwdtbsfWDTCON,SWDTEN;Watchdog timer on.sleep;Wait for watchdog timeout.bcfWDTCON,SWDTEN;Watchdog off.decfszWDTCCnt, fgotoWDTCount;Do another watchdog timer cycle.callClockDrive;Advance the clock Lavet step motor.gotoZzz;Go back to sleep and wait for timer1.;################;**************;Advance the clock Lavet step motor.ClockDrive:btfscMotState,0gotoMState1bsfGPIOT,2;State 0.bcfGPIOT,1gotoMDrvMState1:bcfGPIOT,2;State 1.bsfGPIOT,1MDrv:movfGPIOT,wmovwfGPIO;Write new state.movlwMotDrvTm;5mS delay multiplied by W.callMotDelay;Wait for motor to respond.bcfGPIOT,2;State 2 Idle, no power aplied.bcfGPIOT,1movfGPIOT,wmovwfGPIO;Write new state.incfMotState,f;Toggle the motor state.return;**************;Motor impulse timer.;! should be doing this while sleeping !;Delay = W times 5mS delay.MotDelay:movwfMdlyMotD:callDelay005;Delay 5mSdecfszMdly, fgotoMotDreturn;===========================; Delay = 0.25 seconds; Clock frequency = 0.032768 MHz; Actual delay = 0.25 seconds = 2048 cycles; Error = 0 %Delay250:;2043 cyclesmovlw0x98movwfd1movlw0x02movwfd2Delay250_0decfszd1, fgoto$+2decfszd2, fgotoDelay250_0;1 cyclenop;4 cycles (including call)return;===========================; Delay = 0.005 seconds; Clock frequency = 0.032768 MHz; Actual delay = 0.0050048828125 seconds = 41 cycles; Error = -0.09765625 %Delay005:;37 cyclesmovlw0x0Cmovwfd1Delay_0decfszd1, fgotoDelay_0;4 cycles (including call)return;**************;Drive status/debug LED.LEDOn:bankselGPIObcfGPIOT,GP0;GP0 Low LED onmovfGPIOT,wmovwfGPIOreturn;**************LEDOff:bankselGPIObsfGPIOT,GP0;GP0 High LED offmovfGPIOT,wmovwfGPIOreturn;**************;Blink LED on GP0 W times.BlinkNW:movwfLEDb1Blink_GP0:bankselGPIObcfGPIOT,GP0;GP0 hi LED onmovfGPIOT,wmovwfGPIOcallDelay250bsfGPIOT,GP0;GP0 lo LED offmovfGPIOT,wmovwfGPIOcallDelay250decfszLEDb1, fgotoBlink_GP0return;===========================END

author: lmester
author_rank: Regular Contributor
timestamp: April 07, 2020, 09:40:37 am
content: Quote from: SeanB on April 07, 2020, 08:09:44 amNAWCC users have done that. Cut out the COB from the PCB and use it as a replacement. Sometimes this fails. The Lavet motor impulse time varies. The COB might not have the correct impulse timing. Using a microcontroller lets you adjust this time. This is a constant that can be adjusted in my code.

author: Kilrah
author_rank: Unknown
timestamp: April 07, 2020, 05:34:03 pm
content: Quote from: lmester on April 07, 2020, 09:28:25 amYou'll obviously have to correct the actual value you load for the wakeup time etc but once the right value is found I wouldn't expect it to change, oscillator sync issues should average out IMO.

author: lmester
author_rank: Regular Contributor
timestamp: April 07, 2020, 06:26:28 pm
content: Quote from: eblc1388 on April 07, 2020, 08:01:04 amI'll try that. As long as I don't mess with the low byte I assume that TIMER1 won't lose any cycles?

author: ve7xen
author_rank: Super Contributor
timestamp: April 07, 2020, 08:50:12 pm
content: Silicon Labs C8051F9xx family looks like a good fit for you. They're cheap, run down to 0.9V using the integrated boost converter, and at least the one I looked at includes a 32k oscillator (and an RTC with dedicated oscillator - maybe lower power than running a timer peripheral).

author: dmendesf
author_rank: Frequent Contributor
timestamp: April 08, 2020, 02:22:20 am
content: If I'm not mistaken you cannot update just the higher byte of the counter because when you write to it you just update a shadow register. When you write the lower byte it actually updates both (this sidesteps the problem of bytes overflows between writes). There's also some MSP430 that works at 0.9V and I think they don't have an step up converter (so maybe more efficient? I don't know). Look for MSP430L092.

author: lmester
author_rank: Regular Contributor
timestamp: April 08, 2020, 02:34:15 am
content: I just tested resetting TIMER1 to 32K after each rollover.Code:Zzz:sleep;Wait for timer1 rollover.bankselTMR1HbsfTMR1H,7;Reset timer to 32KbankselPIR1bcfPIR1,TMR1IF;reset int. flag.call LEDOn;Show motor drive status. !!! Remove when done testing to save power !!!call ClockDrive;Advance the clock Lavet step motor.Call LEDOffgotoZzz;Go back to sleep and wait for timer1.It is working but does not seem to have accurate timing.With the timer free-running I get a period of 1.999994sI would expect half of that with the timer preset to 32K.I should have 0.999997sUnfortunately I'm atually measuring 1.000173sAlso,ve7xen suggested the C8051F9xx part. I've been looking at the data sheet. This looks like an ideal part for this application. It even has adjustable load capacitors so that you can trim the crystal osc. Thanks ve7xen!

author: dmendesf
author_rank: Frequent Contributor
timestamp: April 08, 2020, 03:22:54 am
content: This happens because when you actually get inside the timer interrupt the counter already incremented. I'm used to code this in CCS C as set_timer1(32768-get_timer1());. Another thing... Do you know about the Time Nuts mail list? There's a person there that maintains a code (in PIC ASM) to do very precise clock divisions with a PIC:https://github.com/TAPR/TICC/blob/master/picdiv/pd15.asm

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 08, 2020, 04:02:31 am
content: Quote from: dmendesf on April 08, 2020, 02:22:20 amWhile your comment is generally true for many MCUs, the PIC 12F683 is a small 8-pin PIC and don't have the mentioned register. In fact the datasheet warns about possible contention if about updating one of the timer registers. Contention will not happen if a shadow register is being used. After overflow, the timer low byte is still a long way off counting up towards 255 while the high byte is being updated. This will not cause any contention problem.

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 08, 2020, 04:09:59 am
content: Quote from: lmester on April 08, 2020, 02:34:15 amI'm puzzled. You are clocking the PIC using 32,768Hz. Therefore one instruction cycle would take (4*1/32768 = 122uS). I don't see why the timing error is 173us and not multiple of 122us?

author: dmendesf
author_rank: Frequent Contributor
timestamp: April 08, 2020, 04:25:50 am
content: I understand that he's not clocking the uP from 32k, just the timer.

author: lmester
author_rank: Regular Contributor
timestamp: April 08, 2020, 04:43:43 am
content: Quote from: dmendesf on April 08, 2020, 03:22:54 amIn my code i'm only using interrupts to wake up the PIC. The GIE flag is clear. I copied and pasted PIC code from an earlier project. The isr handler is in my code but it's not used in this application. I can still see that there might be some reason that modifying the TIMER1 high byte might cause it to lose a cycle. What does the timer do when it's registers are being written while it's clock source cycles?Also, I'm a member of the Time Nuts mailing list. I got some good info from there to get my test instruments connectd to a GPSDO frequency reference.

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 08, 2020, 04:58:56 am
content: Quote from: lmester on April 08, 2020, 04:43:43 amYou can find this out easily by adding two extra instructions.In your code, modify the high byte and then redo the change. If the timing shown on your frequency/period counter changes, then you can be sure one cannot update the timer without introducing timing error. If on the other hand the timing remains the same, then you'll know.Please do try it out and let us know.

author: lmester
author_rank: Regular Contributor
timestamp: April 08, 2020, 05:03:24 am
content: Quote from: eblc1388 on April 08, 2020, 04:09:59 amdmendesf is correct. The uP is running on the internal LF osc. The timer is using the 32K crystal. I'm doing this because if you use the crystal to clock the uP the external osc shuts down when you go to sleep. We need to keep the TIMER1 osc runing.I believe that the internal LF osc is listed in the data sheet as ~31KHz. This might be the difference between 122us and 173us. I need to measure the actual LF osc frequency. This has become an interesting problem to solve! My original code that uses TIMER1 and the WDT to generate one second pulsed is working accurately. I'd still like to understand why resetting the high bit of TIMER1 causes problems.

author: lmester
author_rank: Regular Contributor
timestamp: April 08, 2020, 06:02:36 am
content: Quote from: eblc1388 on April 08, 2020, 04:58:56 amYou should get an award for the best troubleshooting suggestion for this!I added one and then two extra bsf instructions to the code. bsf (bit set file) does a read modify write to the register.Code:Zzz:sleep;Wait for timer1 rollover.bankselTMR1Hbsf TMR1H,7;Reset timer to 32Kbsf TMR1H,7;Reset timer to 32Kbsf TMR1H,7;Reset timer to 32KbankselPIR1bcf PIR1,TMR1IF;reset int. flag.call LEDOn;Show motor drive status. !!! Remove when done testing to save power !!!call ClockDrive;Advance the clock Lavet step motor.Call LEDOffgoto Zzz;Go back to sleep and wait for timer1.With one write to the timer I had a 1.000 172s period.With two writes 1.000 324swith three writes 1.000 477s.152us to 153us difference for each write of the timer high byte.

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 08, 2020, 07:20:30 am
content: thanks for do the experiment.Suggestion: If you replace the bsf instructions by NOP, does the timing change? It should not. If it changes, something is not quite right.

author: Kilrah
author_rank: Unknown
timestamp: April 08, 2020, 08:06:17 am
content: Quote from: eblc1388 on April 08, 2020, 07:20:30 amThe timer1 oscillator isn't stopped during sleep since it's what counts the time, if it was stopped it wouildn't run...

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 08, 2020, 08:42:44 am
content: Quote from: Kilrah on April 08, 2020, 08:06:17 amSorry, I was misled by the datasheet section 6.4, saying timer1 osc will also work in LP mode during sleep.This is certainly the case for some other PICs but not the 12F683.Instead 12F683 Timer1 OSC will go on working during sleep, but only if user had selected internal oscillator with no clock out option. All other modes including LP mode would disable the timer1 oscillator.

author: lmester
author_rank: Regular Contributor
timestamp: April 08, 2020, 08:47:53 am
content: Quote from: eblc1388 on April 08, 2020, 07:20:30 amI just tested the LF internal osc in my PIC. It's period is 151us.This verifies that the cycle loss is related to the LF internal osc. The one that i'm using for the uC instruction clock.When I change the BSF instructions to NOP's, the extra cycles decrease the same as if the BSF was removed. NOP's don't change the timing. Only extra BSF (timer writes) change it.I couldn't get the code to work using the 32K crystal for both clock sources.From my reading of the datasheet and my previous testing, i've found that the TIMER1 external osc and the external uC osc work this way:If TIMER1 is set to use the external crystal osc, the osc will keep running when the uC sleeps. If the uC clock source is set to use the external crystal osc, the osc will stop when you execute SLEEP. I couldn't get it to use the external osc pins with a crystal for both TIMER1 and the uC instruction clock. If the clock source is set to uC instruction clock, the clock stops when you sleep even if TIMER1 also uses this clock source.

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 08, 2020, 12:52:32 pm
content: I have found the explanation why writing the Timer1 high byte causes the timer to stop count for one cycle, from an Timer 1 Module Errata by Microchip DS80329B dated 2010. It affects many PICs. See quote below:QuoteThe Errata also provided work around but the solution proposed is under the assumption that a very fast PIC system clock is being use therefore not applicable in your case with the 31KHz internal oscillator.I have some new suggestions for your code - to do away with SLEEP and Timer1.Clocking the PIC using the LP oscillator mode with 32.768KHz crystal. Using a 1:32 prescaler on Timer0, it will overflow every second and you can do your business in the interrupt routine. Doing so without sleep will increase your overall current consumption, but then you don't have to use the LFINTOSC and WDT anymore which would save some juice. The current increase might not be so big afterall, compare to the benefit of absolute accuracy on the per second timing count.It is interesting to find out the power consumption difference between your existing and the no sleep approach.

author: lmester
author_rank: Regular Contributor
timestamp: April 10, 2020, 05:12:06 am
content: Quote from: eblc1388 on April 08, 2020, 12:52:32 pmI did some more testing tonight. Sleeping as much as possible, the average PIC current is 8uA. Without sleep, this rises to 22uA. This doesn't include the step motor drive current. The motor requires 8mA for 40ms. I'll need to do some math and try to determine if the non motor drive current increase is significant. With a 1000 times current increase during motor drive, the idle current increase from 8uA to 22uA might not make much difference in the battery life.This is all new to me. Before this project, I've never tried to squeeze a microcontroler's power usage down to the absolute minimum.Finally, I'm still studying the C8051F9xx part suggested by ve7xen. It has one big plus. It can run of of a single 1.5V battery.The big minus for this part is that I've never used Silicon Labs products. Is a cheap programmer available? PICKit3 programmer clones are only about $15. Next will be to climb up the learning curve with a Silicon Labs IDE. Not a bad thing. Learning new skills helps to keep your mind sharp!I probably need to make a new thread asking what programmer and tools are needed for a newbie to get started with a C8051F9xx series microcontroler.

author: eblc1388
author_rank: Frequent Contributor
timestamp: April 11, 2020, 01:50:23 am
content: Quote from: lmester on April 10, 2020, 05:12:06 amThanks for the test. I would definitely go with your original approach as it keep perfect timing and save the most current by going to sleep in between.Best wishes to you starting a new path into the C8051F9xx family.