What would be the best way to use a P1 cog for real time keeping?
I've looked but it doesn't seem this topic has ever been covered here. I could be wrong about that though.
Working on a project that needs time keeping capability. It would seem the most obvious solution would be to use a separate RTC chip but have no free pins, even P30 and 31. However, it doesn't really matter that much in this case, the time could be easily be reset in event of a power failure or occasionally adjusted for drift, but this would be somewhat of an inconvenience. So its worth the effort to make the most accurate timekeeping method accomplish-able using only the prop itself. I'm hoping someone here has done this before or has some insight on the best approach to this.
Presently using an FC5BQCCMC18.432 crystal but if it would make a difference, could easily swap to something different as long as it is somewhat compact.
Here's what I'm using now:
_CLKMODE = XTAL1 + PLL4X
_XINFREQ = 20_000_000
Then I have the following running in a separate cog. The seconds and hours are global variables which along with minutes are manipulated in the main program to adjust to a real time output.
repeat repeat 258_500 seconds++ if seconds > 3599 seconds := 0
I'm finding this rather terrible though. No matter how much I bugger the value in the repeat line, time is not consistent, sometimes too fast other times too slow probably depending on other processing going on. ERF!
Would anyone know of a better way of doing this?
The spin interpreter may not have consistent performance for delay loops. But even if the delay was consistent, every hour your code will take a little bit longer to run. This would need to be compensated somehow.
You'll want to use WAITCNT to trigger the rest of the code exactly once per second.
https://www.parallax.com/package/propeller-manual/ pg 220 for an example of waitcnt Synchronized Delay This also explains the trouble of using a fixed delay in time critical code.
I'm not sure that's actually legal on a P1 ?
The PLL design means the XIN is limited - Data says : XIN (4 MHz to 8 MHz with Clock PLL running)
If you disable the PLL, and just use the 18.432 crystal with _XINFREQ = 18_432_000 directly, you could see if the wobble has gone away ?
The 'most accurate timekeeping method accomplish-able using only the prop itself' will depend on the clock source. How accurate do you actually need ?
You can measure the crystal, by setting a divided pin for testing, and store that in EEPROM as a correction/trim value. That gets fixed room temp/soldering offsets accounted for, leaving temperature and aging.
Ordinary crystals can come as good as about ±10ppm initial, plus ±10ppm drift over -20°C ~ 70°C, and they age a few ppm per year.
If you need better than that, a TCXO is the next step, with 19.2MHz and 26.00MHz being popular and thus low cost values. (also made are 32MHz and 38.4MHz and 52MHz)
A TCXO might spec ± 0.5ppm max.@ -30 ~ +85℃, Aging ± 1.0ppm max 1Year, Reflow soldering ± 1.0ppm max 2times
Addit: The TCXO's are commonly 'clipped sine' output, but another thread reports CAP coupling a clipped sine 26MHz osc into P1 XIN, in XTALx1 mode, worked fine. (Pick lowest Cin setting)
You can share the EEPROM pins (28 and 29) with other I2C devices, including a compatible RTC. I've done this many times. You just have to ensure the RTC will tolerate 400kHz I2C because the boot process of the P1 runs I2C to the EE at about 200kHz, and slow devices (limited to 100kHz) may react poorly and corrupt the EE access. I have seen this, but usually not with stand-alone chips; it usually happens with an I2C display that is using a small micro and runs low-speed I2C only.
If you want to use a background cog, that's fine so long as you don't need to maintain the clock when the P1 resets. You'll want to use a synchronized loop (using waitcnt) to keep your background clock as accurate as your P1 system clock. I've have deployed code like this many times.
I know Spin is "slow" but one can get a lot of work done is a short amount of time. I tend to make may background code run every millisecond and pick-off tasks with simple scheduling code. In one project I scanned pair of 74HC165 shift registers, and did color modulation on an R/G LED so the foreground could select off, red, green, or yellow (requires a blend that was not 50/50), and can have solid or blinking states with user-controlled timing.
Using individual second/minute/hour variables and updating them asynchronously from another cog is actually a really bad idea. If your code is trying to save/use a timestamp at the same time as, for example, the clock rolls over from 1:59:59 to 2:00:00 you might end up seeing 2:59:59 or 1:00:00, which is obviously very wrong. It is probably better to just have a seconds counter and derive the larger units from that. If you make it a
longit won't overflow for 60+ years, which should be enough.
Also, you do not need to waste a cog on this trivial task as long as you have, in any of the other cogs, some sort of loop that is guaranteed to run multiple times per second where you can call a clock update function.
If you don't quite care about the moment-to-moment accuracy and more about it staying accurate over long periods of time, you can of course also do
which doesn't need to be called very often at all...
It's a fair point, and why is used the phrase "code like this." In practice, I use a gated busy flag to prevent a read while updating, and I put the RTC registers in a long so the foreground can make a copy with one read. Here's a more practical example (which uses a very conservative busy period of ~5ms).
Agreed. In my lasertag code I use an instance of my jm_time_80.spin (attached) to maintain the game clock, but must be called regularly to prevent rollover. This object is used many times, and usually as a timer by setting a negative time and allowing it to move toward zero. When the time is at or above 0 I know the timer is expired, so it's really easy to use, especially for timing that can tolerate a little wobble (e.g., muzzle flash timing).
I usually keep time in the main COG if the repeat loop is safely under a second.
Otherwise I use another COG (where I can have other auxiliary routines).
Another solution is to use a free pin and counters.
There is an application note (ANN001)
This is my object with counters (set clkfreq, and shift. in the commenti you have rollover time and error)