-
Notifications
You must be signed in to change notification settings - Fork 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
sys/wait: add module for backend agnostic waits #19719
base: master
Are you sure you want to change the base?
Conversation
Again, I don't think that |
I dropped |
I think that would be useful. Can't it just use ZTIMER_USEC? "Those are trivial wrappers over ztimer_sleep() that just use the most appropriate clock that is used anyway. It gives up control over the most suitable trade-off between resolution, accuracy (clock drift) and power consumption for some flexibility / ROM size." Repeating some arguments from #17330 ... Didn't If they are "trivial" wrappers, make them use the corresponding clock. Making this depending on which module is already used is awful, as adding modules to an already tested part of an application might add more timer modules, making the behavior change. That is especially bad for Like, "delay_s(2)" is +0..2seconds when using ZTIMER_SEC, +0..2 milliseconds when using ZTIMER_MSEC. So for the timer-based delay functions, why not just:
Alternatively, document clearly that the delays are +0..2, and where that matters, the higher resolution delay should be used. But please, don't make potentially hand-tuned timings change just because adding |
sys/include/delay.h
Outdated
*/ | ||
static inline void delay_ms(uint32_t msecs) | ||
{ | ||
if (IS_USED(MODULE_ZTIMER_MSEC)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (IS_USED(MODULE_ZTIMER_MSEC)) { | |
if (IS_USED(MODULE_ZTIMER_MSEC) && msec > 10) { |
jitter < 10%
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would trade ROM for increased accuracy. But the whole point of this API is to trade in accuracy to safe ROM / dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- ztimer_msec is not able to sleep for 1 ms ( a value of 1 is 0 usec to 2000 usec).
- there will not be any ROM usage for compiletime constant delays i would such a functions be used for (static inline)
sys/include/delay.h
Outdated
*/ | ||
static inline void delay_s(uint32_t secs) | ||
{ | ||
if (IS_USED(MODULE_ZTIMER_SEC)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (IS_USED(MODULE_ZTIMER_SEC)) { | |
if (IS_USED(MODULE_ZTIMER_SEC) && secs > 10) { |
sys/include/delay.h
Outdated
* | ||
* # Motivation | ||
* | ||
* @ref sys_ztimer already provides @ref ztimer_sleep to allow delaying the | ||
* execution of the running thread. While @ref ztimer_sleep allows via the | ||
* selection of the clock to select the best fitting trade-off between | ||
* resolution, accuracy (clock drift), power consumption and so on, it is | ||
* difficult to write code with flexible requirements that just picks whatever | ||
* clock is available. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding a paragraph on top here about what this module is supposed to do instead of having to derive it of what ztimer is not able to do. Then maybe include also a bit on when to pick this over ztimer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
What's wrong with just using ZTIMER_MSEC for that? |
To be honest, I would be in favor of falling back to |
sys/include/wait.h
Outdated
if (IS_USED(MODULE_ZTIMER_MSEC)) { | ||
ztimer_sleep(ZTIMER_MSEC, msecs); | ||
} | ||
else { | ||
uint32_t iterations = msecs * (uint64_t)coreclk() / MS_PER_SEC + 1; | ||
wait_busy_loop(iterations); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that's better than a fallback to ZTIMER_USEC
?
And why no wait_at_least_us()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that's better than a fallback to ZTIMER_USEC?
No, quite the opposite: I think falling back to ZTIMER_USEC
is indeed better before falling back to the busy loop.
But I'd rather not have this PR being deadlocked. Especially since the DHT driver could profit from this getting upstream soonish.
Let's discuss this in the next sprint meeting. Maybe some agreement can be reached.
And why no wait_at_least_us()?
That would not be allowed to fall back to ZTIMER_MSEC
. But now that there is a CPU loop to fall back to, it would be indeed more than an alias for ztimer_sleep(ZTIMER_USEC, ...)
. I'll add this again.
I still don't see any argument against using |
sys/include/ztimer.h
Outdated
* @param[in] scale Each tick in @p time corresponds to @p scale | ||
* ticks of @p clock | ||
*/ | ||
void ztimer_sleep_scale_up(ztimer_clock_t *clock, uint32_t time, uint32_t scale); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is duplicated
@maribu Maybe splilt out the more accurate arch specific spinning? |
The function is useful for other modules as well. It is however marked as internal, so that application developers do not expect it to be a stable, public API.
No need anymore to put that on a fast track. The DHT driver rewrite uses a different approach now that no longer requires And since there is no need to rush anyway, I'll re-add falling back to a different ztimer backend. |
This adds to sets of delay functions. The first kind consists of - `wait_us()`: delay execution in units of microseconds - `wait_ms()`: delay execution in units of milliseconds - `wait_s()`: delay execution in units of seconds These will use `ztimer` as backend, if used anyways. Otherwise a trivial CPU counting loop is used. The second kind consists of - `wait_us_spinning()`: busy-wait for a given number of microseconds The benefit of this compared to `ztimer_spin()` is that platform specific implementation can be used to increase accuracy. This can allow timing critical drivers (e.g. bit-banging with accuracy requirements with in the order of a few microseconds) to scale down to slow MCUs.
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
#ifndef DOXYGEN | ||
|
||
/* ARM7 uses ztimer_spin for busy wait */ | ||
|
||
#endif | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't have to provide this dummy file if you use
#if __has_include("wait_arch.h")
#include "wait_arch.h"
#endif
* a two instruction delay loop indeed takes only one cycle per | ||
* iteration once the dynamic branch prediction converges. | ||
*/ | ||
#define CPU_CYCLES_PER_WAIT_LOOP_ITERATION 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't used anywhere
if (IS_USED(MODULE_ZTIMER_USEC)) { | ||
ztimer_sleep(ZTIMER_USEC, usecs); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
if (IS_USED(MODULE_ZTIMER_USEC)) { | |
ztimer_sleep(ZTIMER_USEC, usecs); | |
} | |
if (IS_USED(MODULE_ZTIMER_USEC)) { | |
ztimer_sleep(ZTIMER_USEC, usecs); | |
} else if (IS_USED(MODULE_ZTIMER_MSEC)) { | |
ztimer_sleep(ZTIMER_MSEC, DIV_ROUND_UP(usecs, US_PER_MS); | |
} |
if (IS_USED(MODULE_ZTIMER_USEC)) { | ||
ztimer_sleep(ZTIMER_USEC, usecs); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
if (IS_USED(MODULE_ZTIMER_USEC)) { | |
ztimer_sleep(ZTIMER_USEC, usecs); | |
} | |
if (IS_USED(MODULE_ZTIMER_USEC)) { | |
ztimer_sleep(ZTIMER_USEC, usecs); | |
} else if (IS_USED(MODULE_ZTIMER_MSEC)) { | |
ztimer_sleep(ZTIMER_MSEC, DIV_ROUND_UP(usecs, US_PER_MS); | |
} |
So my main gripe with this PR is the vagueness of the API. Our "delay the current process for a while" API is ztimer. It allows arbitrary backends to be chosen. There are a couple of backends that the build system tries hard to find an implementation for (usec, msec, sec). There are multiple options for each backend (msec -> rtt, msec->usec, ...). ztimer is quantized on the clock ticks, and provides at least semantics with +0-2 guarantees. It's unsuitable for very short sleeps (<5us - <100us, depending on hardware) due to being interrupt based (meaning a context switch is always involved). So ztimer provides the functionality that this API wraps. "but now there's a ztimer dependency". Well. Let's make sure to get one ztimer clock that suits most needs (like, ztimer_msec), and use that as much as possible. Chances are, any sufficiently large application will use ztimer_msec somewhere. Any sufficiently minimal application won't feel the ram/rom cost. So maybe typing Why not chose any available backend? Because the call site suddenly doesn't clearly state what the function call is doing. We're basically introducing What way of "waiting" is actually used is already quite muddy when using ZTIMER_M/USEC, as there's already complex heuristics and configuration at play. This layer of abstraction adds even more muddyness. The tree of used waiting mechanisms (and watering of guarantees) grows to a forest. For the use case most stated ("driver needs to wait a bit for the device to be ready, a bit longer doesn't matter"), if spinning is acceptable, that must be assumed to actually be used in any case, because if spinning would not be acceptable, use of Is ztimer too fat still (implementation wise)? Is that a sentiment, or an actual issue? Because RAM/ROM use would be the only reason for not just using ztimer sleeps. My suggestion here is:
Otherwise, prove that using ztimer direct wrapping (with no fallbacks) is causing too many RAM/ROM issues in places where the |
We had some productive discussion at today's Friday meeting. Karl brought up two more aspects:
(or sth smarter) |
A spinning implementation in ztimer_sleep isn't helpfull for the read ablity of ztimer_sleep, which should just wrap a ztimer_set and required data (a ztimer_t and a mutex) to make it work (we should a void having any alternative there) that is the reason to put it somewhere else, keep ztimer_sleep simple not having an implicit spin is one of the big advancements of ztimer over xtimer (the other one is not having a absolute time interface both these features keep ztimer simple and able to perform ist power saving) |
static inline void wait_at_least_ms(uint16_t msecs) | ||
{ | ||
if (IS_USED(MODULE_ZTIMER_MSEC)) { | ||
ztimer_sleep(ZTIMER_MSEC, msecs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to add 1 for at least semantic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? There is no division here.
static inline void wait_at_least_us(uint16_t usecs) | ||
{ | ||
if (IS_USED(MODULE_ZTIMER_USEC)) { | ||
ztimer_sleep(ZTIMER_USEC, usecs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add 1 for at least semantic
} | ||
else { | ||
uint32_t iterations = msecs * (uint64_t)coreclk() / MS_PER_SEC; | ||
wait_busy_loop(iterations); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait_busy_loop(iterations); | |
busy_wait_us((uint32_t)msecs * US_PER_MS); |
Contribution description
This adds to sets of delay functions. The first kind consists of
wait_at_least_us()
: delay execution in units of microsecondswait_at_least_ms()
: delay execution in units of millisecondswait_at_least_s()
: delay execution in units of secondsThese will use ztimer as backend, if used anyways. Otherwise a trivial CPU counting loop is used.
The second kind consists of
wait_us_spinning()
: busy-wait for a given number of microsecondsThe benefit of this compared to
ztimer_spin()
is that for AVR a platform specific implementation is used that is more accurate. It allows writing drivers like the DHT11/DHT2x driver without platform specific code. Other low end platforms can follow suit and also add more accurate waiting code.Testing procedure
A test application has been added with a python script doing automatic testing. In addition, that test app performs short pulses on the LED that should be visible via a scope / logic analyzer. Finally, it will estimate the CPU cycles required per loop iteration that can be used to fine-tune the loop.
Issues/PRs references
Deprecates: #17330
Useful for: #19718