Introduction
Hi! I’m Jeff Epler, AKA @jepler. Python has long had a special place in my heart, because it is the computer language that makes me feel like I can accomplish anything I dream up.
When it comes to CircuitPython I’m new and have a lot to learn, but one thing I have seen is that it’s important to go in depth on an idea and make sure that the code in the core is ready to support a wide range of uses. When we do this, we can find and fix problems before a new user having her first experience with CircuitPython has trouble.
One direction I’d like to dive deep is timekeeping. If that sounds interesting to you, read on…
Timekeeping and Time Measurement
I’m a bit of a time nut, so I’d like to enable CircuitPython to keep better time. It won’t necessarily be my focus in 2020, but it’s a set of thoughts to fall back on if I’m ever idle. What would it take to make projects like these possible in CircuitPython?
- GPS Disciplined Oscillator
- Frequency Counter accurate to 1 ppm (6 digits)
- WWVB Receiver
- World Clock
Enhance the time module
In CircuitPython today, timekeeping depends on the “SysTick”, and requires attention from the CPU every millisecond or a tick can be lost. This happens regularly during certain tasks, like updating large numbers of neopixels. We should modify most of the routines in the time module to use a built in RTC counter instead of the SysTick counter, on all ports where this is possible. This would probably still count time at around 1ms granularity, but would no longer risk losing time even while doing tasks that fully occupy the CPU for more than 1ms at a time. This may also enable calibration of a board’s timing crystal, to reduce the number of seconds per day gained or lost while the device is running. Except optionally adding a calibration value in boot.py, this would be transparent to users of CircuitPython.
Enhance the frequencyio module
A GPS Disciplined Oscillator (GPSDO) is a clock signal (often 1MHz or 10MHz) that is regulated very precisely using the GPS time as a reference. A Frequency Counter is a device to measure the frequency of a signal.
A key sub-problem of these tasks is to continuously count the relative rates of two input signals in the background, while continuing to do Python tasks in the foreground. Right now, frequencyio only supports counting frequency in the foreground.
Enhance the pulseio module
WWVB is a radio signal that can be received almost everywhere in North America. Its signal can tell you the current time, once per minute. If you have a self-setting wall clock in your home, it’s probably using this kind of signal.
For a WWVB Receiver, you need to continuously receive a low bandwidth (1 symbol per second) signal in the background while the foreground Python code decodes the signal received in a previous minute and does other tasks such as updating a clock display. Right now, pulseio only supports recording pulses in the foreground.
Enhance timezone handling
For a World Clock, you need the ability to work with standard “tzinfo” files, to convert among local time, UTC time, and other world time zones. This might be as simple as porting the python package dateutil to circuitpython, or it might end up requiring new code designed just for CircuitPython. In the end, you would be able to convert times between UTC and various local times.
[Image Credit: Mike Steele via Flickr, cropped for presentation]
To do all of these timing things:
1) Set up a timer running at the full clock speed, which interrupts every time it overflows. The ISR (Interrupt Service Routine) increments an overflow counter.
2) Set up external and/or internal inputs to trigger the timer capture and execute an interrupt. The ISR in this case reads and stores the capture register, the overflow counter, and the input source; and then releases the counter register for the next input.
3) An interface presents the (capture, overflow, input) tuples to user code. Current (counter, overflow) tuples can also be obtained so you know what ‘now’ is.
With this general facility, all of your desires (except for timezone support) can be met:
If you record the Pulses times from the WWVB (or better yet the PPS from a GPS), then you know what time it is relative to the second (and also have an increasingly refined knowledge of how fast the system clock is running);
If you record the times of each input pulse you can compare the times of the zeroth and nth pulse to get a frequency. (It helps if your ISR in #2 can be set to record only the every nth pulse of a specific input, or you can use a prescalar counter on that input.). If the number of clock cycles between the pulses is >1e6 (and the jitter is low enough) you have PPM frequency measurement, even at low frequencies over short accumulation times.
I started implementing this for the SAMDx1 chips in the ‘pulse time’ branch at
https://github.com/dmopalmer/circuitpython/tree/pulsetime
but so far I haven’t managed to get the counter to actually count.
At 48 MHz counter speed, a lot of moderately-high-precision applications are possible.
See my issue//feature request at
https://github.com/adafruit/circuitpython/issues/2210
Thanks David! This looks like a great start. I haven’t understood the samd timers in depth yet, that’s for sure. How can we help you move this PR along? (Discussing it on GitHub or discord is probably better than here)