Calculating Milliseconds using timers in PIC and synchronizing it with DS1307 timing

I have used DS1307 in my project which gives me the values of Hour, Min, Sec but I also need values of Milliseconds. I am planning to use timers with 1 millisecond timing and then synchronizing it according to seconds of DS1307.
I have made the program for 1ms delay but the problem is I am not sure if it is running with 1ms or not because I used led blinking and we cannot see led blinking at 1ms. My first question is, is there any way I can be sure that it is giving 1ms delay.?

Now lets say if its exact 1ms timer. Now how to sync it with DS1307 timing. My total application is like if I receive inputs then I have to show which inputs are turned ON on UART with exact timings (including Milliseconds). For example, if input 4 in ON then:

/*
**some code
*/
if(PORTEbits.RB5 == 1)
{
putsUART1("Input 4 ON, Time:  ");
putsUART(TIME);
}


Now this TIME should contain the values of HH:MM:SS:MS. Till now I am able to show HH:MM:SS only. I also cannot use sqw/out because its custom made PCB and that pin is left blank. Pleas help

EDIT:

I have included count++ in ISR which is incrementing at 1ms. In while(1) I am printing the values of count after reading sec in RTC_read function

 while(1)
{

sprintf(TIME,"%s%s:%s%s:%s%s",hr2,hr1,min2,min1,sec2,sec1);
putsUART1(TIME);
putsUART1(" ");
putsUART1(COUNT);
putsUART1("\n");
}

void __ISR(_TIMER_2_VECTOR, ipl3) Timer2Handler(void)
{
mT2ClearIntFlag();
count++;

}


/*
* some code
*/

Nop();
sec = I2CGetByte(RTC_I2C_BUS);
AckI2C2();
while (!I2CAcknowledgeHasCompleted(I2C2));

/*
* Milliseconds
*/
if(count >= 999)
{
count = 0;
}
else
{
sprintf(COUNT,"%d",count);

}


This is an addendum to Szidor's answer. I just wanted to sketch out some of the details in pseudo-code.

Assuming you have an accurate 1-ms timer interrupt, you can synchronize the ms counter to the external RTC by polling its seconds register from inside the Timer1 ISR. If you set the ms counter to 999 at system startup, it will automatically poll the RTC continuously to find the first time the seconds value advances, and then poll it at the end of every second thereafter to stay in sync.

int ms = 999;
int seconds;
int minutes;
int hours;

timer1_isr()
{
static int prev_seconds;

/* Advance the ms counter on every tick, but don't allow
* it to overflow.
*/
if (ms < 999) ++ms;

/* If we're close to the end of a second, poll the RTC to
* find the next boundary.
*/
if (ms > 990) {
if (seconds != prev_seconds) {
ms = 0;
}
prev_seconds = seconds;
)
}


Calibrating the interrupt (e.g., via OSCCAL) is a different matter. You have the option of doing a manual calibration (labor intensive, and subject to variation due to temperature changes, aging, etc.) or letting the system automatically calibrate itself through a feedback mechanism.

Either way, the easiest way to measure the error of the PIC's internal oscillator is to count how many Timer1 ticks occur in 10 seconds as measured by the RTC. For a manual calibration, you could use the debugger to run the following code.

int ms = 0;
int done_flag = 0;

timer1_isr()
{
static int prev_seconds;
int seconds;
if (ms == 0) {
/* wait for seconds value to advance from 0 to 1 */
if (seconds == 1 && prev_seconds == 0) ms = 1;
) else if (done_flag == 0) {
/* count ticks until seconds value advances from 10 to 11 */
++ms;
if (seconds == 11 && prev_seconds == 10) done_flag = 1;
}
prev_seconds = seconds;
}


You can turn on an LED at the same time you set done_flag. Run the code until the LED turns on, and read the value of ms using the debugger. It will be a value near 10000 — the exact value will tell you how far off your PIC's clock is, and you can use this to figure out how to adjust OSCCAL.

For extra credit, figure out how to combine these two ideas so that the OSCCAL value can be adjusted on the fly (say, once every hour or so) to keep the PIC clock calibrated to the RTC clock continuously.

• Can you please add a little more explaination in the first code you have posted in your answer. Thanks – anna carolina Feb 27 '16 at 5:20
• I've modified it a bit, but you'll have to be more specific about what isn't clear to you. – Dave Tweed Feb 27 '16 at 11:56
• Thanks. I tried your way. I have RTC_read function which contain code to read data from I2C protocol and then convert it to ASCII. In your code you are reading data from regs in ISR, I tried doing it but it gave me bus collision everytime. So I have tried doing it in other way. In RTC_read function, after reading values from sec I am running counter and included count++ in ISR so its giving me the 1ms delay. But the problem I am facing is millisecond is not starting from 0 instead it starts with 232 and ends also in 232 so its giving me the delay but not from 0-999. – anna carolina Feb 29 '16 at 9:48
• I have included code in question – anna carolina Feb 29 '16 at 9:49
• But don't you think, reading the registers in ISR will make I2C code wrong, because I can only read when I have initialized and written data on I2C and this is hardware timer interrupt. It will start as soon the program start, so resulting in improper function. Is there any way I can write this logic outside ISR. Please correct if I am wrong. – anna carolina Feb 29 '16 at 12:40

Exact timing issue is easier than you worried about. Use a hardware timer and interrupt. Wait for 600000 of it and blink the led. Did it made 600 seconds? Then you don't have to worry about 1 ms. That measurement is not to guarantee that is it accurate enough. It gives an important clue that everything is OK and so with very high probability you finally depend on the accuracy of the crystal of device, thus you have no software problems.

The sync problem:

Let's say you read the chip on every 999 mS. On average probability after 500 seconds your reading will be repeated by the same value with the previous one. That is the point where you must keep it as offset point. If you have to make measurements immediately, then make them without offset and modify it later. Basically, so. You can make this much faster by improving the alghoritm, for example:

X=500
start:
T0=the time from chip
wait_mS 1000-X
T1=the time from chip
if T0==T1, then X=X*(-1)
X=X/2
as the X gets smaller, so the offset point is more accurate.
goto start

If you want a ms clock, then a real time clock (assuming that's what a DS1307 is from context, you didn't link to a datasheet) intended for displaying seconds is inappropriate.

You've already got a PIC, although you didn't say which one. Many PICs have hardware for driving a 32768 Hz watch crystal, then counting time from that. If your PIC is a traditional PIC 16, for example, then this is part of the timer 1 hardware. Timer 1 with its crystal driver can even run while the rest of the system is sleeping.

By using a low speed crystal and timer 1, you can make your own real time clock, but also get faster clocks, like 1 ms, from the same crystal.

If power is not a issue, then you can run the PIC from a normal crystal, divide it to get 1 ms interrupt, then do all timing from there.

The below is messy, unorthodox, but I use these techniques here and there and I beleive they should give you ms-precision.

First you need a precise 1ms clock:

You can use an interrupt to blink the LED every, say, 100th tick, blinking the LED every 1/10th of a second and use that to finetune your timer.

(You can count -depending on your patience :) - a lot of blinks and measure the time, or use a metronome software to check if its in sync, etc. If you have it nearly precise, you could then set the interrupt to turn on the LED in 100 minutes, then sit back there after 95, stare at it and check on a digital watch (smartphone) when it turns on.)

The actual tuning can be done:

• With changing OSCTUNE.

• If you're not running on FRC, then what you can do is have a "microsecond per tick" constant, add that to a variable at every tick, and derive the ms from that. Changing this constant up or down from 1000 will tune your timer.

Determining the point when RTC is switching second:

If you only query the seconds at 100kHz it would take a fraction of a ms. Also, the value you get is the value at the point of the RTC detecting the START condition.

So at powerup you can query the second value like crazy, clear the TMR register at every START then just leave it alone when the second changed. You can optionally repeat that every once in a while to resync the timer.

• Michael's answer was posted while I typed mine. I like his solution better if changing the part is an option. – Szidor Feb 26 '16 at 12:54
• This is a good answer if a pure-software solution is required. – Dave Tweed Feb 26 '16 at 13:25
• I've sketched out some pseudocode in a separate answer. – Dave Tweed Feb 26 '16 at 14:17

I would suggest that your idea for millisecond resolution is somewhat unrealistic as the time it takes to read the HH:MM:SS out of the RTC chip at typical I2C data rates will have time latency variation greater than a millisecond.

Reporting millisecond resolution is also debatable when your reporting medium is a UART serial port operating at a bit rate that is slower than a millisecond per bit.

I encourage you to instead take your custom circuit board and unsolder the DS1307 chip and replace it with a pinout compatible M41T81S chip from ST Micro. (Package compatible too if you have deployed the SO-8 type device).

This latter device conveniently contains an additional time keeping register that provides you with tenths and hundredths of seconds resolution counts. Reading out HH:MM:SS.ss is compatible with the net 10msec resolution.

The M41T81S also has a very nice calibration feature that allows the internal timekeeping functions to be adjusted to trim for minor frequency variations of the RTC 32.768kHz crystal. I have a prototype unit here that has this chip used in it that once calibrated has been continuing to display the correct time (within a few seconds) for nearly a year already. I doubt that you can achieve that with your DS1307 unless you have designed its XTAL circuit with trimming capabilities.

Use the square wave output on the DS1307 (pin 7), set up to output a 1Hz output. Use the control register to enable this output as described in page 9 of the DS1307 datasheet.

When the PIC receives the pulse, reset the your PIC's millisecond timer back to zero.

Page 9 of the datasheet:

• Thanks but I have already mentioned that I am using a custom made PCB and left that pin blank i.e not connected to any pin. – anna carolina Feb 26 '16 at 10:10
• Ah sorry, I missed that. In that case, I'm all out of ideas, apologies – BenAdamson Feb 26 '16 at 10:16
• @annacarolina -- modify the board with small wires like wire wrap wire, directly to the solder pad for the pin if necessary. Rework during prototyping is a given. – Scott Seidman Feb 26 '16 at 13:23