Good day, gentlemen!
My project relies on very high time-keeping accuracy (<1µs). To do that I am using the PPS (Pulse Per Second) signal of a GPS (jitter ~10ns) that interrupts a GPIO pin. Furthermore I am using a general purpose timer to keep track of the unix time (I need very accurate timing (<1µs) WITHIN every full second). This timer runs at CPU speed and since the CPU speed can vary quite a bit and I absolutely cannot risk the drift that happens over time I need to use the PPS (as far as its available = GPS has a fix) signal to synchronize the timer to the real beginning of every second and set the load to the current CPU speed, so in case the GPS fails for a longer period of time my time extrapolation is still good.
So in a nutshell:
- The RTC timer (PGMU_RTC_TIMER) runs with a load of the CPU speed
- The CPU speed (= RTC timer load) is measured using the PPS signal, which is an actual 1 second
- On each PPS the RTC timer needs to be corrected so it starts counting the moment the PPS interrupt
In theory the RTC timer interrupt and the PPS trigger at the exact same time - so I never know which interrupt triggered first. They both got the same priority (so they do not preempt each other), however higher priority than all the other interrupts.
The things that can now happen every second:
Case A. the PPS comes before the RTC interrupt it needs to reset the RTC timer value to 0, since the RTC timer was too slow. The problem is now that this skips the RTC overflow/interrupt, thus skips incrementing the unix time. As a solution to this I could just manually trigger the RTC timer interrupt, but...
Case B. the RTC interrupt comes before the PPS the PPS ISR has to know if the RTC has already been incremented for the current second, so it does not trigger it again resulting in +2s in 1 second.
Case C. the PPS does not trigger at all (GPS has no fix) - the RTC timer just runs without "interference".
To let the both ISRs know which one executed first I brought in a second timer (PGMU_RTC_HYSTERESIS_TIMER), which is a one-shot-up timer that runs for about 990ms and resets to 0 after overflow. So the PPS ISR can check if TimerValue > 0 - which is true while the timer is running and therefore knows that the RTC has already been incremented for the current second.
The problem with my code is that after a "random" time (could be 10 minutes, could be 15 hours) the RTC is 1 or more seconds behind, which means that the RTC timer interrupt did not trigger at least one time. I have absolutely no idea how this can happen, since I think that in all the three cases above, the RTC should be triggered exactly once every second, no matter what.
Here is my code:
#define PGMU_PPS_PIN_PORT_BASE GPIO_PORTA_BASE #define PGMU_PPS_PIN_NUM GPIO_PIN_6 #define PGMU_RTC_TIMER_BASE TIMER0_BASE #define PGMU_RTC_TIMER_INT INT_TIMER0A #define PGMU_RTC_HYSTERESIS_TIMER_BASE TIMER5_BASE volatile time_t p_time_RTC = 0; // Stores unix timestamp volatile bool p_bool_RTCSynced = false; void PPSIntHandler(void) { // Get elapsed SysTicks during the past second. const uint32_t ui32_SysTicksSecond = MAP_TimerValueGet(PGMU_RTC_TIMER_BASE, TIMER_A); // Reset RTC Timer value. HWREG(PGMU_RTC_TIMER_BASE + TIMER_O_TAV) = 0; // Clear the GPIO interrupt flag. MAP_GPIOIntClear(PGMU_PPS_PIN_PORT_BASE, PGMU_PPS_PIN_NUM); // Check if RTC timer ISR already triggered. if (MAP_TimerValueGet(PGMU_RTC_HYSTERESIS_TIMER_BASE, TIMER_A) == 0) { // Trigger the RTC timer interrupt. MAP_IntTrigger(PGMU_RTC_TIMER_INT); } else { // This means the RTC ISR already executed. // Reset the RTC hysteresis timer. HWREG(PGMU_RTC_HYSTERESIS_TIMER_BASE + TIMER_O_TAV) = 0; } // Update CPU frequency. // CPU speed is either (ui32_SysTicksSecond) OR (CPUFrequencyGet() + ui32_SysTicksSecond) CPUFrequencyUpdate(&ui32_SysTicksSecond); // Set RTC timer to expected PPS tick load. MAP_TimerLoadSet(PGMU_RTC_TIMER_BASE, TIMER_A, CPUFrequencyGet()); } void RTCTimerIntHandler(void) { // Clear the timer interrupt flag. MAP_TimerIntClear(PGMU_RTC_TIMER_BASE, TIMER_TIMA_TIMEOUT); // Enable RTC hysteresis timer. MAP_TimerEnable(PGMU_RTC_HYSTERESIS_TIMER_BASE, TIMER_A); // Advance the RTC by 1 second. p_time_RTC++; }
This is probably not the most elegant solution for what I am trying to accomplish but its the best one I can currently think of.
I would be so happy if someone had a solution to this problem or better yet a more elegant (bullet proof) way of synchronizing a timer to an external pulse.