-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Metro M7: without 32768Hz RTC crystal, RTC timekeeping is not accurate #9908
Comments
Don't know if this is a clue, but the actual runtime was closer to 83% of what was expected, which is very close to 5/6. Maybe some counter or prescaler is specified incorrectly. UPDATE: Actually a bit more than 5/6 with further testing. Hopefully I didn't just happen to get a defective M7 somehow. Waiting for further word on this. |
Not a bug (except that monotonic() returning a 32bit float should not exist and I bet most naive uses are incorrect) The test is misusing time.monotonic(), essentially comparing it across machines, and against actual elapsed time. from the test:
docs:
On my esp32-s3 qt-py a loop like that only takes about 8 microseconds, and your M7 is much faster - you're exceeding your 22 bits of precision in monotonic's short-float value. (IMHO time.monotonic should either throw an exception when this happens or should be deprecated. 32bit float is inherently broken for time.monotonic's very vague semantics) |
So time.monotonic() returns a float which is NOT measured in elapsed seconds? Does time.monotonic_ns() return integer nanoseconds, even if that clock does not have nanosecond resolution? I'm actually trying to pace a loop of code that's running at 10 or 20 Hz while it interacts with code on another Arduino or CircuitPython micro. I can expect that using too much precision will make the timing not quite periodic, but if monotonic or monotonic_ns are really returning seconds or nanoseconds, the average rate should be about right as long as the master clock oscillator isn't way off. So what WOULD be a valid way to write a timing loop that forces code to pace itself at a desired rate if I can't trust the monotonic clocks? |
Your code is correct, it sets a stop time 600 seconds in the future, and waits for that stop time to be reached. import time
# Test will run for this long, measured by this board's clock.
time_sec = 600
# Allow time for receiving code on Pi to start up.
time.sleep(30)
# Test will stop at this time.
stop_at = time.monotonic() + time_sec
# Change next line to identify board this is saved to.
print("Metro M7")
# Wait for stop time.
while time.monotonic() < stop_at:
pass
# Identify what the test period was set to.
print(time_sec) I can use a serial tool like tio with timestamps on to check that the result time does fall within milliseconds of the expected time on all boards I have tried on 9.2.1 (including a Teensy). But I don't have Metro M7 to test.
|
The docs (https://docs.circuitpython.org/en/latest/shared-bindings/time/#time.monotonic) suggest time.monotonic_ms() for measuring elapsed time safely. (your test code.py would be easy to change). docs for the monotonic* funcs are inconsistent (is value "always increasing" or "can never go backward"? both descriptions are used) and has at least one typo/error where #monotonic_ns mentions monotonic. If the M7 has a bug, a monotonic_ns version of the test will make a clean test case. (I too wish there were better high resolution time control - squashing to milliseconds on 100+mhz cpus feels restrictive) |
Comparing a change in monotonic() to an external clock would seem to violate the first paragraph of its doc, no? |
They are measuring a 100 seconds difference on a 10 minutes test, the precision of This test is not comparing to an external clock, it's comparing to itself to last 600 seconds. |
There is a calibration here: |
Sorry, I think I believed the "always increasing value" in the monotonic() doc. It is monotonic (never decreasing), not strictly increasing. I read "always" as implying it needed to increment by a ns to always increase. |
@Neradoc is correct. Metro M7 (device under test): Hello, Pi400, I'm going to use my clock to mark the start and end of what I consider to be a 10 minute interval. Please use your clock and tell the user how long you measured that time period to be. Minor variations from 10 minutes are to be expected. Pi400: Wow, M7, I clocked you at way less than 600 seconds. (Sorry, I get a bit dramatic at times. :) ) |
From @anecdata :
I figure if I'm within one or two percent of the expected rate, I'm doing well enough. It's not a precision application I have in mind, nominal rate will probably be 10 Hz. |
From @Neradoc again:
Using Arduino's Serial Monitor with timestamps: Difference: 515.303 sec, 85.9% of expected 600 sec (My) M7 is definitely running faster than it believes. |
The Metro M7 does not have a 32kHz crystal, which means the timing source is less accurate. I eventually found this information on the accuracy of the internal 32kHz clock source at https://www.digikey.com/en/htmldatasheets/production/4171371/0/0/1/mimxrt1011dae5a section 4.2.4.2:
The value you're seeing corresponds to about 38kHz, which is within the range specified by NXP. The design of the Metro M7 doesn't have a spot for a 32kHz crystal to be added, and CircuitPython's code doesn't currently allow the RTC to be based on the (more accurate) 24MHz crystal that is used to derive the CPU clock. |
Well pooh. :( Guess that M7 will be relegated to tasks that don't require a really accurate timebase. Still got an M4 sitting next to it that does have more precise timing, it actually clocked in at 600.0 seconds on my timing test. I suppose I could connect an external RTC to the M7 since I'm not looking at very high rate tasks. So far I see as options:
Both have battery backup and QWIIC connectors. The SparkFun part claims to read down to hundredths of a second while the Adafruit one reads down to seconds. And I just noticed I have one of the DS3231 parts sitting on my desk. Maybe I can code the M7 to self-calibrate its onboard RTC, say once every 10 seconds, so it can compensate for the ring oscillator on the fly. Guess I know what I'll be tinkering with tomorrow. ;) UPDATE: Don't see CircuitPython code to support the RV-8803. |
Thanks, much better title for this thread. |
@jepler Did you look in detail at this? Could we change the clock source? |
That would be cool if it could be done... but I'm still going to spend part of today poking at using my DS3231 to clock the current timing live. I've already seen my test code give fairly different measures on the M7 on different days, probably because of variations in the room temperature. (Ooohh, that cries out for a speed-to-temperature calibration.) But in the long run, changing the clock source would save some headache for future M7 users. |
I don't think there's an easy way to just make the existing RTC be clocked based on (division of) the 24MHz crystal. from what I can tell, the RTC peripheral can only be clocked from the 32kHz crystal or the internal ring oscillator. there might be some other peripheral that can be used e.g., as a cycle counter but I don't know the imxrt series in that much detail. So whatever the solution, for example with the Programmable Interrupt Timer (PIT), it would require changing the whole implementation of ticks in this port. |
In micropython, ports/mimxrt/ticks.c uses the GPT (general purpose timer) peripheral clocked from the 24MHz crystal (divided by 24, to give 1MHz) as the main timing source. This could be studied & adapted into CircuitPython, possibly. |
Another Metro M7 oddity: I'm beginning to think the Metro M7 should have been blue instead of red; I think it's actually a disguised TARDIS (wibbly-wobbly timey-wimey thing). I connected a DS3231 RTC to the M7's QWIIC port, and ran the following test code.
Even the time.sleep(1) is running faster than normal, about every seventh pair of lines printed replicates the pair of lines before. (Note: struct_time(...) lines may appear truncated.)
|
Forgot to mention: the DS3231 has a battery installed and was preset to GMT so it's five hours ahead of my local time (US Eastern Standard Time). Not that it really matters right now. |
I discovered that when a Metro M4 and Metro M7 are running the same code which acts upon a fixed period of time passing (measured by calls to time.monotonic()), my newly purchased M7 claims the time period has completed in only ~85% of the real time required by the M4. Both are sending begin and end messages via serial to a Raspberry Pi 400 which is listening to the serial via another Python code which measures the elapsed times. The Metro M4, as well as a Trinket M0, Gemma M0, and Circuit Playground Express, all come within 1% of the expected time.
Full details can be found in my forum post at https://forums.adafruit.com/viewtopic.php?p=1039811. The test procedure I used, as well as results, are posted there as well.
The text was updated successfully, but these errors were encountered: