Refactor modm:processing to be std::chrono compatible #217
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This extracts the std::chrono effort from #153 and optimizes it a bit.
Closes #188.
Unfortunately std::chrono uses signed 64-bit for all durations and clocks, which makes wrapping on overflow a messy operation. So I've kept the unsigned 16-bit and 32-bit timestamps, clocks and timers. However since both clocks are not steady, I've named them
modm::chrono::milli_clock
andmodm::chrono::micro_clock
(and aliased them tomodm::Clock
andmodm::PreciseClock
) to distinguish them from thestd::chrono::steady_clock
andstd::chrono::high_resolution_clock
and their use-cases.Microsecond clocks are implemented via timer overflow counters, and division is avoided by shifting a fixed constant multiplication, resulting in a very accurate fractional division that keeps error under 1us on both Cortex-M (via single cycle 32x32=32-bit (ARMv6-M) or 32x32=64-bit (ARMv7-M) multiplication) and AVR (via single cycle 16x16=16bit multiplication).
On Cortex-M the 24-bit SysTick counter is used to implement a 250ms interrupt, instead of a 1ms interrupt, and instead both milli- and microseconds are computed via the above mechanism. This reduces the number of interrupts to only 4Hz, without reducing accuracy. This 250ms period works for a clock up to 536MHz.
Since ARMv6-M only has 32x32=32bit multiplication, the interrupt is kept at 1kHz and the microsecond computation is adapted to fit into just 32bit for max 1000us, which works just fine.
On AVR a default clock implementation using the 8-bit TimerCounter0 provides a 1kHz milliseconds clock and via the overflow a microsecond clock, which however does not have 1us step size, due to only being a 8-bit counter, thus having less than 256 steps per 1000us.
On a 16MHz clock, there is a 4us resolution with a counter overflow of 250 ticks and prescaler of 64.
All clock implementation have been tested especially for the property of only ever jumping forward (except during overflow of course), which had to be done without atomic lock and opportunistic polling, since the timers can still roll over even during an atomic lock, leading to interlacing.
The new clock implementations of
now()
are linked weak, so that their implementation can be overwritten. This is used in the unittests, so that themodm:platform:core
modules do not need to be aware of the unittests and that the user can overwrite the implementation to their needs (for example using a Low-Power Timer instead of Systick, so the clock continues running during sleep).The
modm:processing:timer
classes are refactored to use the new clocks as well as acceptstd::chrono::duration
values. Passing Integers is deprecated (should it be?) and all uses inside modm have been refactored.Here it is more difficult to deprecate the interger constructors andrestart(interval)
functions, since the signatures are the same unlikemodm::delay(std::chrono::{nano, micro, milli}seconds
vsmodm::delay_{ns, us, ms}(uint32_t)
. Here the constructor only has a type overloadmodm::Timeout(std::chrono::{micro, milli}seconds)
vsmodm::Timeout(uint32_t)
.So this requires a cast in user code, like so:
modm::Timeout(std::chrono::milliseconds(uint32_t))
, which is quite verbose and I found it annoying to use when not having chrono literals at hand (like in all modm header-only drivers).Update: I added the
using namespace std::chrono_literals
to the modm namespace, so any drivers inside modm can simply use chrono literals (and modm literals) without hassle.TODO:
udiv
instruction.cc @rleh @asmfreak @chris-durand @strongly-typed @se-bi