Set RTC from GPS
Set RTC from GPS
I am trying to accurately set the RTC of the PyBoard with a GPS.
My theory is to read the GPRMC sentence from the GPS until a valid fix is obtained then extract the time and date. Then wait for a pin which the PPS output of the GPS receiver is connected to to go high and then set the RTC to the time and date value plus one second. This seems to work fine.
I then want to adjust the calibration factor of the RTC according to the PPS out put of the GPS to keep the RTC accurate without any large slewing. So I set up an interupt on the PPS pin. In the ISR I want to read the RTC's current subseconds and then set the RTC calibration factor accordingly. This generates an exception because, apparently, reading the current value of the RTC causes a heap allocation. Here is some simplified minimum code to demonstrate the problem:
[code]
from pyb import ExtInt
from machine import Pin
import micropython
rtc = pyb.RTC()
micropython.alloc_emergency_exception_buf(100)
pin = Pin("X1", Pin.IN, Pin.PULL_DOWN)
def callback(line):
print(rtc.datetime())
#Set up ISR to handle PPS
extint = pyb.ExtInt(pin, pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, callback)
while True:
pyb.delay(100)
Uncaught exception in ExtInt interrupt handler line 0
Traceback (most recent call last):
File "main.py", line 15, in callback
MemoryError: memory allocation failed, heap is locked
[/code]
How can I work around this so that I can read the RTC value and set its calibration factor in response to the PPS from the GPS?
Any advice appreciated.
My theory is to read the GPRMC sentence from the GPS until a valid fix is obtained then extract the time and date. Then wait for a pin which the PPS output of the GPS receiver is connected to to go high and then set the RTC to the time and date value plus one second. This seems to work fine.
I then want to adjust the calibration factor of the RTC according to the PPS out put of the GPS to keep the RTC accurate without any large slewing. So I set up an interupt on the PPS pin. In the ISR I want to read the RTC's current subseconds and then set the RTC calibration factor accordingly. This generates an exception because, apparently, reading the current value of the RTC causes a heap allocation. Here is some simplified minimum code to demonstrate the problem:
[code]
from pyb import ExtInt
from machine import Pin
import micropython
rtc = pyb.RTC()
micropython.alloc_emergency_exception_buf(100)
pin = Pin("X1", Pin.IN, Pin.PULL_DOWN)
def callback(line):
print(rtc.datetime())
#Set up ISR to handle PPS
extint = pyb.ExtInt(pin, pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, callback)
while True:
pyb.delay(100)
Uncaught exception in ExtInt interrupt handler line 0
Traceback (most recent call last):
File "main.py", line 15, in callback
MemoryError: memory allocation failed, heap is locked
[/code]
How can I work around this so that I can read the RTC value and set its calibration factor in response to the PPS from the GPS?
Any advice appreciated.
Re: Set RTC from GPS
First of all, I've no experience of interrupt callbacks on this platform, so these are just things to have a go at as they cross my mind.
Try not creating a Datetime 8-tuple and printing it (which presumably triggers a heap allocation). The only thing you need in the interrupt is a momentary snapshot of the time.
If I were in your shoes, within the def callback I would try storing a value from ticks_us() or better, ticks_cpu() into a preallocated name
https://docs.micropython.org/en/latest/ ... utime.html
...which I'd then resolve to a datetime using ticks_diff() in my main loop in normal single-threaded operation (taking place outside the interrupt where I can freely make heap allocations).
No idea if this would actually work, but that would be the direction I would explore as a workaround.
Try not creating a Datetime 8-tuple and printing it (which presumably triggers a heap allocation). The only thing you need in the interrupt is a momentary snapshot of the time.
If I were in your shoes, within the def callback I would try storing a value from ticks_us() or better, ticks_cpu() into a preallocated name
https://docs.micropython.org/en/latest/ ... utime.html
...which I'd then resolve to a datetime using ticks_diff() in my main loop in normal single-threaded operation (taking place outside the interrupt where I can freely make heap allocations).
No idea if this would actually work, but that would be the direction I would explore as a workaround.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Set RTC from GPS
Another option is to use micropython.schedule - see the docs.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Set RTC from GPS
Thanks for your suggestion, Peter. I have tried this and it does work. However, I am concerned about the time it might take between execution of the ISR and execution of the scheduled function. How can I time this?
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Set RTC from GPS
Have a global variable set in the ISR to machine.ticks_us(). In your scheduled handler use machine.ticks_diff() to measure the elapsed time. You should see values on the order of 0-4ms depending on whether a garbage collection was running when the interrupt occurred.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Set RTC from GPS
Thanks, Peter, I will give this a try. Setting the RTC is not the main program function so I will get the rest of the code working as I expect this may well have an effect on the amount of GC going on and thus the average delay between the PPS pulse and the scheduled function. If larger delays are only occasional this should not be a problem as the clock speed (drift compensation) will only be changed briefly. Or, perhaps I could use the machine.ticks_diff() measurement to compensate.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Set RTC from GPS
Indeed, if you're expecting that level of accuracy. But the GPS sentences only offer one second precision. The GPS receiver and UART data transmission will have some latency so I'd be interested to hear how you intend to achieve millisecond level accuracy. Or are you only trying to remove variable sources of latency in the hope that other sources are constant?
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
Re: Set RTC from GPS
I am going to use the Pusle Per Second (PPS) output of the GPS receiver to sync the RTC, not the NMEA sentences received by the UART. The leading edge of the PPS signal is aligned with the whole second rollover to within 10ns accuracy according to the manufacturer's data sheet. My plan is:
1. Read the NMEA sentences as they arrive using uasyncio and store the current time.
2. When the first PPS arrives, set the RTC to the stored time + 1 second.
3. When the next and subsequent PPSs arrive, calculate the difference between (stored time + 1 second) and the RTC time (apply machine.ticks_diff() here).
4. Calculate "cal" to apply to RTC.calibration(cal) to gradually make the RTC running speed correct.
5. Rinse and repeat so that the cal factor will eventually be zero and the RTC is in-line with the GPS time.
I believe this is similar to how the Network Time Protocol (NTP) works, but without all the network latency stuff.
1. Read the NMEA sentences as they arrive using uasyncio and store the current time.
2. When the first PPS arrives, set the RTC to the stored time + 1 second.
3. When the next and subsequent PPSs arrive, calculate the difference between (stored time + 1 second) and the RTC time (apply machine.ticks_diff() here).
4. Calculate "cal" to apply to RTC.calibration(cal) to gradually make the RTC running speed correct.
5. Rinse and repeat so that the cal factor will eventually be zero and the RTC is in-line with the GPS time.
I believe this is similar to how the Network Time Protocol (NTP) works, but without all the network latency stuff.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Set RTC from GPS
After reading your previous post I checked the chip datasheet and realised that PPS was the way to go. You may need to investigate the relative timing of PPS and the arrival of NMEA sentences. I don't know if PPS can arrive in the middle of a sentence but it may need a little study.
I wrote some code here - look for DS3231 for calibrating the Pyboard RTC from an external time source, in this case a DS3231 precision RTC. I found that you can achieve excellent precision by measuring the drift over five minutes and calculating the calibration value from that. There was no practical gain in waiting longer owing to the granularity of the calibration constant. You might find this approach easier than an iterative method.
I wrote some code here - look for DS3231 for calibrating the Pyboard RTC from an external time source, in this case a DS3231 precision RTC. I found that you can achieve excellent precision by measuring the drift over five minutes and calculating the calibration value from that. There was no practical gain in waiting longer owing to the granularity of the calibration constant. You might find this approach easier than an iterative method.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Set RTC from GPS
I looked at this on a scope and the leading edge of the PPS signal always arrives before the first NMEA sentence. These arrive in a burst which completes here in a maximum of 400ms, although this could be longer depending on the number of satellites in view. With a repetition rate of 1Hz the serial data has ended before the arrival of the next PPS. So your approach can be expected to work.
Peter Hinch
Index to my micropython libraries.
Index to my micropython libraries.