Skip to content
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

Refactor modm:processing to be std::chrono compatible #217

Merged
merged 4 commits into from
Apr 9, 2020

Conversation

salkinium
Copy link
Member

@salkinium salkinium commented May 15, 2019

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 and modm::chrono::micro_clock (and aliased them to modm::Clock and modm::PreciseClock) to distinguish them from the std::chrono::steady_clock and std::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 the modm: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 accept std::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 and restart(interval) functions, since the signatures are the same unlike modm::delay(std::chrono::{nano, micro, milli}seconds vs modm::delay_{ns, us, ms}(uint32_t). Here the constructor only has a type overload modm::Timeout(std::chrono::{micro, milli}seconds) vs modm::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:

  • Milli- and Microsecond clocks
  • Implemented for Cortex-M SysTick
  • Implemented for AVR
  • Implemented for Hosted
  • Using (unsigned) 32-bit for everything
  • Adapt Timeout, PeriodicTimer and Duration/Timestamp
  • Any way to get rid of the division? ARMv6-M doesn't have udiv instruction.
  • Unit tests (particularly for overflow of µs)
  • Check microsecond clock uses for AMNB and Radio
  • Check The Logger thingy
  • Better documentation

cc @rleh @asmfreak @chris-durand @strongly-typed @se-bi

@salkinium salkinium force-pushed the feature/system_clock branch 3 times, most recently from ce67472 to 00b8f79 Compare April 9, 2020 08:34
@salkinium salkinium merged commit c63a536 into modm-io:develop Apr 9, 2020
@salkinium salkinium deleted the feature/system_clock branch April 9, 2020 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

std::chrono literals with periodic timers
1 participant