Delays are shorter than expected, due to rounding down, and coarse millisecond precision.
This code gives a 125 Hz waveform, but should be approx 100 Hz.
import board
import digitalio
import time
led = digitalio.DigitalInOut(board.D13)
led.switch_to_output()
while True:
led.value = True
time.sleep(0.005)
led.value = False
time.sleep(0.005)
Details here:
https://forum.pjrc.com/threads/59040-CircuitPython-on-Teensy-4!?p=226231&viewfull=1#post226231
Mere mortal here. I'd be pleased to help characterize this.
On Feather M0 Express using the latest CP and the above script but A0, I get this:
124.97 Hz, On 3.984-4.001 ms, Off 4.003-4.019 ms (Saleae 500MHz samples)
So, yeah, 125 Hz.
print(0.005, math.floor(1000*0.005))
print("{0:20.18f}".format(0.005))
==>
0.005 4
0.004999999046325684
A workaround of adding 0.000_01 s seems to help.
So it's a core issue, not i.MX specific then?
@arturo182 Seems that way to me.
I saw the exact same ~125 Hz behavior on Teensy 4.0 and Metro Grand Central M4.
See the forum thread for scope screen captures for each...
So I guess it's truncating down from 5 msecs to 4 msecs, since 0.005 is not represented exactly.
https://www.h-schmidt.net/FloatConverter/IEEE754.html

and with the 30-bit float representation we use (uncheck the rightmost two bits:

My more general frequency generator has a jitter problem, but good frequency:
import board
import digitalio
import time
import math
led = digitalio.DigitalInOut(board.A0)
led.switch_to_output()
print(0.005, math.floor(1000*0.005))
print("{0:20.18f}".format(0.005))
f = 100.0
duty_cycle = 0.5
period_ns = (1/f) * 1_000_000_000
on_ns = period_ns * duty_cycle
while True:
led.value = math.fmod(time.monotonic_ns(),period_ns) < on_ns
In general, it would be nice if time.sleep() and time.monotonic_ns() had the best resolution convenient per platform. There might be a benefit in keeping all 1 ms resolution, but one doesn't come to my mind right now.
The order of operations matters for floating point precision, what if instead of:
period_ns = (1/f) * 1_000_000_000
you did
period_ns = 1_000_000_000 / f
I made the change and still got the jitter. I think this is to be expected with floating point. I expect that If I change to integer math with corresponding limitations in the f value, this would not have the jitter.
@PaulStoffregen Would _rounding_ to the closest ms (or the current sleep resolution) be the fix you are imagining? Since folks can be easily surprised by 0.005 s rounding down to 4 ms, I think that is better than explaining it in documentation.
And if I might add to that fix an enhancement request: Increase sleep and monotonic_() resolution (as I mentioned above). Should that go into a separate issue?
This all made me wonder what micropython did for this, from docs: https://docs.micropython.org/en/latest/library/utime.html#utime.sleep - I like the description advising against use of negative numbers for utime.sleep_us()!
@kevinjwalters Yeah, utime is interesting. The functions returning wrap-around time can give new programmers headaches. The unspecified period can give everybody problems. If my code asks the question, "Has some length of time passed since this event of interest happened?", I want to feel comfortable that the counter has not wrapped around some number of times, but that the wrap-around time is much longer than the interest in the question. I'd rather not have to count wraps.
I understand the importance of not using negative numbers for sleep. One time, my code after a sleep started running before I even downloaded my program, perhaps even before I typed in the bug with the negative time.
We could make the underlying call use microseconds instead of milliseconds. I do want to avoid giving the illusion of false precision though since background tasks can come along and make things run longers.
Not to hijack (too much) the initial concern of rounding down to next ms instead of to nearest ms in time.sleep()...
Yes, I like microseconds. In addition to giving a false impression of precision is that what works on one board might not work as well on another. Those might be a documentation task. Even so, I favor a move to microseconds, if not for both sleep() and monotonic_ns(), then just for the latter.
Is there a guideline as to how long a background task can keep sleep() from returning? Would then a microsecond sleep be possibly up to that time longer than expected? Or are you saying the clock runs slow when background tasks are running?
My common use cases with monotonic_ns() (and somewhat with sleep) are settling time, stale timing, timed polling and so on. Here, a small occasional delay is OK. Even so, it would be nice to be able to generate a nice pulse, for example.
When the input is a float (offering plenty of precision), why would you not just delay to the best accuracy the hardware is capable of implementing?
@PaulStoffregen Yeah, I like that idea. Though, I would want to acknowledge that there might be some software concerns that might limit it a little tighter than the hardware capability. This will still need some docs work to create appropriate expectations.
Could this be it?

common_hal_time_delay_ms() takes uint32_t
Sounds like a job for ceilf()
Or round()?
Just remember these math.h functions are 64 bit double without the trailing "f" on the function name. Not a huge cost for M7 with FPU that can handle 64 bit double, but a huge unncessary cost on M4 with only single precision FPU.
Wow, thanks! I'm new to ARM. Maybe a + 0.5 would also work. Or using a different delay function.
fwiw, this looks like a classic HAL trade-off. I'm sure Adafruit's main goal is minimizing engineering time to support as much hardware as possible. But like most hardware abstraction layers, the result is often constraining the most powerful hardware to a set of assumptions based on experience with the least powerful hardware.
As someone who focuses on powerful hardware, but also has only limited hours to do far more that people want than can ever be accomplished... I have mixed feelings about these sort of abstraction layers.
I get lost in these layers. Here we have a function with no prefix calling a function with a common_hal_ prefix. This looks like the right layer for rounding to nearest (or up). If this is the time for better resolution, then a different delay function is needed at the right level, one that can be called here.
The imprecision here is an intentional decision, as I said when this first came up. The Python code will never have reliable timing. We provide other modules for timing sensitive functions. Sure, it'd be nice if this had sub-millisecond precision but it isn't a priority. We have more reliable alternatives.
So, we'll happily take a PR for this but please don't read too much into this.
OK. Can we leave this issue at rounding instead for flooring?
(Sorry about misreading your earlier comment.)
What do you recommend for timing sensitive functions?
@Dar-Scott Rounding works for me.
For timing sensitive stuff, it depends on what you are trying to do. Generally the pulseio module is what you want for pin toggling.
Rounding down is probably not what people expect. I recommend always rounding up.
Most helpful comment
When the input is a float (offering plenty of precision), why would you not just delay to the best accuracy the hardware is capable of implementing?