From 81fe97b4afba65dd9d5b4abf9439c0a74249281b Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Tue, 3 Mar 2020 16:57:05 +0200 Subject: [PATCH 01/19] Add Chrono clock design document --- .../platform/clocks/clocks.md | 418 ++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 docs/design-documents/platform/clocks/clocks.md diff --git a/docs/design-documents/platform/clocks/clocks.md b/docs/design-documents/platform/clocks/clocks.md new file mode 100644 index 00000000000..bfb13243892 --- /dev/null +++ b/docs/design-documents/platform/clocks/clocks.md @@ -0,0 +1,418 @@ +# Mbed OS clocks + +# Table of contents + +1. [Mbed OS clocks](#mbed-os-clocks). +1. [Table of contents](#table-of-contents). + 1. [Revision history](#revision-history). +1. [Introduction](#introduction). + 1. [Overview and background](#overview-and-background). + 1. [Requirements and assumptions](#requirements-and-assumptions). +1. [System architecture and high-level design](#system-architecture-and-high-level-design). + 1. [Which clock to use?](#which-clock-to-use). +1. [Detailed design](#detailed-design). + 1. [Accessing Chrono APIs](#accessing-chrono-apis) + 1. [The RTOS clock](#the-rtos-clock) + 1. [High-resolution and low-power clocks](#high-resolution-and-low-power-clocks) + 1. [The real-time clock](#the-real-time-clock) + 1. [Comparison to standard C++ Chrono clocks](#comparison-to-standard-c++-chrono-clocks) +1. [Usage scenarios and examples](#usage-scenarios-and-examples). + 1. [Using the RTOS clock](#using-the-rtos-clock) + 1. [Using the high-resolution clock](#using-the-high-resolution-clock) + 1. [Using the real-time clock](#using-the-real-time-clock) + 1. [Being more generic](#being-more-generic) +1. [Other information](#other-information). + 1. [Deprecations](#deprecations). + 1. [References](#references). + +### Revision history + +1.0 - Initial revision (Mbed OS 6.0) - Kevin Bracey - 03 Mar 2020 + +# Introduction + +### Overview and background + +Mbed OS 6.0 provides a set of timing APIs that use C++'s `` library. This provides strong typing for measuring time durations (`std::chrono::duration`) and time points (`std::chrono::time_point`). Previous APIs based on integer types are deprecated. + +### Requirements and assumptions + +The new API relies on C++14 to provide the library functions, and allow duration constants like `200ms`. Mbed OS 6.0 generally requires a C++14 or later toolchain, so the Chrono API is now possible. Some core APIs are also provided in C form. + +# System architecture and high-level design + +Mbed OS has four primary central clocks: + +- The RTOS clock. This is managed by the RTOS if present, else based on a HAL clock, and has millisecond resolution. +- The high-resolution clock (`us_ticker`). This is provided by the target's HAL, and has approximately microsecond resolution. It does not run in deep sleep, so its users will usually lock deep sleep. +- The low-power clock (`lp_ticker`). This is provided by most targets' HAL, and has sub-millisecond resolution. It continues to run in deep sleep. +- The real-time clock. This is provided by some targets' HAL, often runs when the main CPU does not, and has one-second resolution. + +The way these APIs map to hardware varies by target. In some cases, they may use four distinct peripherals, but often they share hardware. + +Capabilities and uses of the clocks vary, but they all share the need to describe time points and durations, and as such use the `time_point` and `duration` template types from `` to provide strongly-typed APIs. + +### Which clock to use? + +In many higher-level APIs, you do not directly specify a clock - for example `EventQueue` just takes relative times, with no direct indication of the clock being used. But when implementing such code, you will need to make a choice. + +1. As an application, you should by default use the RTOS clock. This is the most general-purpose clock, with the best scheduling capability, and the API is available even in a bare-metal build. + +1. If higher-resolution timing is required, then you can use the high-resolution clock. But this does mean locking deep sleep. + +1. If real calendar/wall-clock time is required, then you can use the real-time clock. You may want to combine its use with another clock to get better-than-second resolution or event scheduling. + +1. The low-power clock should not normally be used to schedule events. It is only really recommended as a slightly-higher-resolution time-point clock than the RTOS millisecond clock (4kHz to 64kHz). But it cannot be easily used for event scheduling as care must be taken to compensate for deep sleep wake latency, which is usually multiple milliseconds. Such latency compensation is included in the RTOS timing system. + +`EventQueue` is a case study in API choice - previously it used the RTOS clock in RTOS builds and the low-power clock in bare-metal builds (because the RTOS clock was not available). It now uses the RTOS clock in all builds, meaning it now gets deep sleep wake latency compensation in bare metal builds too. + +# Detailed design + +### The RTOS clock + +**API description** + +The RTOS tracks time at millisecond resolution, and this clock is exposed in C++ form as `Kernel::Clock`. + +All other RTOS APIs that use time, such as `ThisThread::sleep_for`, `Semaphore::try_acquire_for` and `EventFlags::wait_any_until` use this clock. + +Unlike direct use of the low-power clock, RTOS wake events compensate for deep sleep latency. So requesting `ThisThread::sleep_for(10ms)` will wake after 10ms, even if it causes deep sleep and there is a 4ms wake delay from deep sleep. + +The RTOS clock is the best general-purpose clock for most use cases. Subsystems such as `EventQueue` use the RTOS timing APIs internally. + +The RTOS clock API and a subset of other RTOS APIs are available in bare-metal builds even when an actual RTOS is not present. + +**RTOS clock generation** + +There are three current ways that the system can produce the RTOS clock: + +* The RTOS can use a dedicated millisecond ticker such as the SysTick peripheral. This ticker always runs, and deep sleep does not occur while the RTOS is active, unless manually requested by the application. +* The RTOS can be in "tickless" mode, where ticks and wake-ups from idle periods are generated using the low-power clock. Deep sleep is entered whenever idle, if it is unlocked and there is sufficient time before the next scheduled wake. +* The system is built as "bare-metal", so there is no actual RTOS. A subset of RTOS APIs are implemented using the low-power clock. + +When there is an actual RTOS, the timing system is always present in the build, so it's a sunk cost. In a bare-metal build, the timer is not necessarily needed - it will be included in the build and started if used. The separation of APIs into distinct untimed and timed forms like `Mutex::try_lock` and `Mutex::try_lock_for` are intended to help exclude timer code from bare-metal builds. + +### High-resolution and low-power clocks + +The high-resolution and low-power clocks share a common API and implementation, so are described together. + +Both present themselves via a microsecond-based API. The high-resolution clock has at least 250kHz (4us) resolution, and the low-power clock has 4kHz-64kHz (16us-250us) resolution, depending on target. + +Aside from the resolution, key differences are: + +* The high-resolution clock does not operate in deep sleep, so deep sleep must be locked to keep it running, affecting power consumption. It should normally only be used for measuring short times while the device is active. Unless the high resolution is needed, the RTOS timing APIs are preferable. +* The low-resolution clock permits deep sleep, so clock-timed wake-ups can suffer from deep sleep wake-up latency delays. It does not compensate for these, so a `LowPowerTimeout` requested for 10ms could easily wake up after 14ms. The RTOS timing APIs are usually preferable. + +**API description** + +At the lowest level, these clocks are obtained by calling `get_us_ticker data`or `get_lp_ticker_data`, and +using the returned `ticker_data_t *` with the C functions in `ticker_api.h`. + +The underlying `ticker_data` can describe hardware clocks that can have varying bit-width and frequency. Each clock provides one single-shot interrupt. + +The C ticker API functions convert the underlying clock to an absolute 64-bit microsecond timebase, and add a queue for registering multiple single-shot events. Interrupts are automatically scheduled before the underlying clock wraps to update the long-term 64-bit timebase. + +The classes `HighResClock`, and `LowPowerClock` act as Chrono clock interfaces for the two primary clocks. + +* `HighResClock`: reads `us_ticker` and provides manual locks for deep sleep; it is steady while locked. +* `LowPowerClock`: reads `lp_ticker`; the clock is inherently steady, but provides dummy lock methods to keep the same API as `HighResClock`. + +If using the high-resolution clock, to save power it should only be run when necessary, which means using `HighResClock`'s `lock` and `unlock` methods to indicate when it is required. + +Higher-level clock functions are provided via the following classes: + +* `Timer` / `LowPowerTimer`: acts like a stopwatch for elapsed time - can be stopped, started and reset +* `Ticker` / `LowPowerTicker`: calls a callback from interrupt context periodically +* `Timeout` / `LowPowerTimeout`: calls a callback from interrupt context once + +The classes without the `LowPower` prefix are the high-resolution ones; they should arguably be named `HighResTimer`, and so on. + +Unlike direct clock access, those high-level classes always handle deep sleep locking automatically for the high-resolution clock. Deep sleep is locked whenever a high-resolution `Timer` is running, or when a `Ticker` or `Timeout` callback is pending, and it is unlocked when stopped, removed or destroyed. + +**Other ticker_data_t clocks** + +There is also a generic `TickerDataClock` which provides a Chrono Clock wrapper for a `ticker_data *`, so that `TickerDataClock::time_point` can be used to express absolute times for arbitrary clocks. + +Note that the `TickerDataClock` is a non-standard Chrono clock, in that its `now` method is non-static. The Chrono type-checking will not detect that `TickerDataClock::time_point`s for different tickers are not interchangable. It is generally preferable to use the dedicated `HighResClock` or `LowPowerClock`, but `TickerDataClock` is used by some generic code, to avoid template bloat from separate code for each Clock. + +The base classes `TimerBase`, `TickerBase` and `TimeoutBase` use `TickerDataClock` to provide the core of the high-level implementation, and these are then used to provide the separate strongly-typed high-resolution and low-power classes above. + +Systems wishing to provide more HAL clocks beyond the basic two can use the same framework to give that clock its own custom `Clock/Timer/Ticker/Timeout` set of classes, using the base classes. + +### The real-time clock + +The real-time clock, if available, provides calendar date and time information, at one-second resolution. The RTC can continue to run while the CPU does not. + +The C APIs in `rtc_api.h` read and write the time as a C `time_t`. + +The class `RealTimeClock` provides a wrapper that acts as a Chrono clock, so that `RealTimeClock::time_point`can be used to express absolute real time. + +The methods `to_time_t` and `from_time_t` convert to and from C `time_t`. + +At present there are no other APIs using `RealTimeClock`, and no real-time-based wake or alarm facility, so utility is limited, but the class has been created for framework symmetry with the other clocks. + +### Comparison to standard C++ Chrono clocks + +Mbed OS does not provide the standard C++11 Chrono Clocks, as it is not possible to provide consistent behaviour across the various toolchains and libraries supported. Here's a suggested mapping from the 3 standard C++ Clocks to Mbed OS. + +**std::chrono::steady_clock** + +`std::chrono::steady_clock` represents a clock for which values of `time_point` never decrease as physical time advances and for which values of `time_point` advance at a steady rate relative to real time. + +`Kernel::Clock` can be used in place of `std::chrono::steady_clock` for general use. + +For slightly higher timestamp resolution you can use `LowPowerClock`, but if attempting to schedule time events, using RTOS APIs with `Kernel::Clock` will often work better due to the RTOS's deep sleep wake latency compensation. + +If high resolution is required, you can use `HighResClock`, as long as the lock is held while in use to keep it steady. + +**std::chrono::high_resolution_clock** + +`std::chrono::high_resolution_clock` represents the clock with the shortest tick period. + +`HighResClock` is the best choice for a high-resolution clock, but it will not run during deep sleep. You can use `HighResClock::lock` to keep it running, or otherwise ensure that deep sleep is locked. + +**std::chrono::system_clock** + +`std::chrono::system_clock` represents wall clock time from the system-wide realtime clock. + +`RealTimeClock` can be used in place of `std::chrono::system_clock`, and provides the same `to_time_t` and `from_time_t` methods. However, unlike many implementations, its resolution is only seconds. + +# Usage scenarios and examples + +### Accessing Chrono APIs + +Using the Chronos API needs a little thought about namespaces. This section covers that; the following sections will assume the complete Chrono API has been imported. + +By default C++14 duration literals will be available after including `mbed.h` due to it including files that use `` and having a `using namespace std` directive: + +```C++ +#include "mbed.h" +auto delay = 5ms; +``` + +If `MBED_NO_GLOBAL_USING_DIRECTIVE` is defined, or you are not including `mbed.h`, you will need your own directive, and possibly own include, to use the literals: + +```C++ +#include +using namespace std::chrono_literals; +auto delay = 5ms; +``` + +All Chrono APIs are in the namespace `std::chrono`, so it can be convenient to have a directive to import this complete namespace to reduce typing: + +```C++ +#include +using namespace std::chrono; // provides both types like std::chrono::microseconds and the chrono_literals + +microseconds t = 1h; +printf("1 hour is %d us\n", int(t.count())); +``` + +Namespace directives should only be used in source files to avoid unexpected name collision for users. Header files intended for general use will need to fully qualify their use of Chrono with `std::chrono` prefixes. This also precludes use of literals: + +```C++ +// In a header file +#include +void my_function(std::chrono::microseconds timeout = std::chrono::seconds{1}); +``` + +### Using the RTOS clock + +**Basic delays** + +```C++ +for (;;) { + LED1 = !LED1; + ThisThread::sleep_for(500ms); +} +``` + +**Prefer absolute time** + +Using absolute time aids precision, guaranteeing that there is no drift due to code execution time. This applies to all clocks. + +```C++ +auto next_wake = Kernel::Clock::now(); +for (;;) { + LED1 = !LED1; + next_wake += 500ms; + ThisThread::sleep_until(next_wake); +} +``` + +Periodic callback APIs such as `Ticker` or `EventQueue::call_every` use this mechanism. If manually rescheduling events yourself, you should use the same technique, rather than using `call_in` within a callback. + +**Measuring elapsed time via the RTOS** + +```C++ +auto start = Kernel::Clock::now(); // type will be Kernel::Clock::time_point +do_operation(); +milliseconds elapsed_time = Kernel::Clock::now() - start; +printf("elapsed time = %d ms\n", int(elapsed_time.count())); +``` + +Explicitly stating that `elapsed_time` is `milliseconds` provides a cross-check that the `count()` for the print really is milliseconds. If the clock had lower resolution, it would be implicitly converted to milliseconds. If the clock had higher resolution, it would be a compilation error - see "being more generic" below to handle this. + +### Using the high-resolution clock + +**Measuring elapsed time using Timer** +```C++ +template +microseconds time_operation(const F &operation) +{ + Timer timer; + timer.start(); // deep sleep is automatically locked while timer is running + operation(); + return timer.elapsed_time(); +} // Deep sleep automatically unlocked when timer is destroyed. + +microseconds print_time = time_operation([] { printf("Hello!\n"); }); +printf("Printing that took %d us\n", int(print_time.count())); +``` + +This example relies on the destruction of the timer to unlock the clock. If using a timer that doesn't get destroyed, you must remember to stop it manually to allow deep sleep to be entered again. + +**Recording high-resolution time points** +```C++ +HighResClock::lock(); // take lock to ensure clock is steady (by preventing deep sleep) +auto t1 = HighResClock::now(); +do_operation1(); +auto t2 = HighResClock::now(); +do_operation2(); +auto t3 = HighResClock::now(); +HighResClock::unlock(); +microseconds total = t3 - t1; +microseconds stage1 = t2 - t1; +microseconds stage2 = t3 - t2; +printf("total time = %d us\n", int(total.count())); +printf("stage 1 time = %d us\n", int(stage1.count())); +printf("stage 2 time = %d us\n", int(stage2.count())); +``` + +**Blinking an LED at 1kHz** +```C++ +Ticker ticker; +ticker.attach([] { LED1 = !LED1; }, 500us); +``` + +### Using the real-time clock + +**Logging times** +```C++ +auto woken = RealTimeClock::now(); +time_t t = RealTimeClock::to_time_t(rtc_woken); +printf("Woken at %s", ctime(&t)); +do_work(); +t = RealTimeClock::to_time_t(woken + 3h); +printf("Sleeping until %s", ctime(&t)); +ThisThread::sleep_for(3h); +``` + +Note that RTOS APIs currently only accept RTOS time points, which is why the above example uses `sleep_for`; you would get a compile error for `ThisThread::sleep_until(woken + 3h)`. If the work took a long time, then the schedule would drift away from a regular 3 hour period. See next example for an absolute-time-based approach. + +**Making helpers to combine clocks** + +The above example could be adapted to use absolute wake times with a helper function, guaranteeing a regular 3-hour period, regardless of how long the work takes: + +``C++ +void sleep_until_rtc(RealTimeClock::time_point abs_time) +{ + auto rel_time = abs_time - RealTimeClock::now(); + ThisThread::sleep_until(Kernel::Clock::now() + rel_time); +} + +auto woken = RealTimeClock::now(); +time_t t = RealTimeClock::to_time_t(rtc_woken); +printf("Woken at %s", ctime(&t)); +do_work(); +auto wake = woken + 3h; +t = RealTimeClock::to_time_t(wake); +printf("Sleeping until %s", ctime(&t)); +sleep_until_rtc(wake); +``` + +Note that `sleep_until_rtc` would not respond to any adjustments made to `RealTimeClock` made while sleeping. + +### Being more generic + +**Allowing choice of clock** + +As the high-resolution and low-power clocks provide the same API, code can be written generically to work with either clock. For example: +```C++ +#ifdef USE_HIGHRES_CLOCK // or Clock could be a template parameter +using Clock = HighResClock; +#else +using Clock = LowPowerClock; +#endif +Clock::lock(); // will lock deep sleep if high-res clock, else no-op +auto t1 = Clock::now(); +do_operation1(); +auto t2 = Clock::now(); +do_operation2(); +auto t3 = Clock::now(); +Clock::unlock(); +microseconds total = t3 - t1; +microseconds stage1 = t2 - t1; +microseconds stage2 = t3 - t2; +printf("total time = %d us\n", int(total.count())); +printf("stage 1 time = %d us\n", int(stage1.count())); +printf("stage 2 time = %d us\n", int(stage2.count())); +``` + +This same code would actually also work with `Kernel::Clock` and `RealTimeClock`, due to the care taken with the types, including explicit use of `microseconds`. For more details, see next section. + +**Coping with different resolutions** + +At present, the clocks have specified periods: seconds, milliseconds or microseconds. Chrono prevents any errors being made by mixing different-resolution clocks at compile time, except when entering or leaving the Chrono domain by constructing a `duration` or using `duration::count()`. + +In principle, the periods of these clocks could be changed in future, for example to make `HighResClock` match the native hardware rate. Generic code itself may want to select between RTOS and high-resolution clocks which have different periods. To support both cases you may want to write code that always works and compiles regardless of resolution. + +The basic rule is that Chrono conversions are implicit if they don't lose precision. So `milliseconds d = t1 - t2;` will work when `t1` and `t2` have millisecond precision or lower. If they were `RealTimeClock::time_point`s measured in seconds, then there would be an automatic multiplcation by 1000 to convert rates. To permit precision-losing conversions, such as dividing by 1000, you need use `duration_cast` or `time_point_cast`. + +Guidelines: + +* use `auto` for your variables to match the clock you're using; don't convert to a specific representation unless necessary +* use `duration_cast` to round to a particular precision +* use templates to accept any resolution and round (see references for examples) + +Here is the high-res timer example from above adjusted to still compile if the high-res timer was increased to higher than microsecond resolution: + +```C++ +Timer timer; +timer.start(); +do_operation(); +auto elapsed_time = duration_cast(timer.elapsed_time()); // rounds down +printf("elapsed time = %d us\n", int(elapsed_time.count())); +``` + +If the timer actually is microseconds, then this cast does not actually need to generate any code. + +# Other information + +### Deprecations + +Many older C++ APIs using integer or float time repres are deprecated in favour of the Chrono APIs. + +These include: + +* `Kernel::get_ms_count` +* `Semaphore::try_acquire_for(uint32_t)` and similar RTOS functions +* `Semaphore::try_acquire_until(uint64_t)` and similar RTOS functions +* `Timer::read` +* `Timer::read_ms` +* `Timer::read_us` +* `Timer::read_high_resolution_us` +* `Timer::operator float` +* `Ticker::attach(F, float)` +* `Ticker::attach_us(Callback, us_timestamp_t )` +* `EventQueue::call_in(int, ...)` +* `EventQueue::call_every(int, ...)` + +### References + +General Chrono background: + +* [N2661 - original C++ proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2661.htm) +* [Video: CppCon 2016: Howard Hinnant “A \ Tutorial"](https://www.youtube.com/watch?v=P32hvk8b13M) +* [Introduction to std::chrono](http://rachelnertia.github.io/programming/2018/01/07/intro-to-std-chrono/) + From c144769553c87065b1307c5dd360e257495c59e9 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Wed, 12 Feb 2020 15:26:59 +0200 Subject: [PATCH 02/19] Add Chrono support to Kernel and SysTimer --- platform/source/SysTimer.cpp | 163 +++++++++--------- platform/source/SysTimer.h | 42 +++-- platform/source/mbed_os_timer.cpp | 38 ++-- platform/source/mbed_os_timer.h | 50 +++++- platform/source/mbed_thread.cpp | 17 +- platform/source/mbed_wait_api_rtos.cpp | 17 +- rtos/Kernel.h | 74 ++++++++ rtos/source/Kernel.cpp | 7 + rtos/source/TARGET_CORTEX/mbed_rtx_idle.cpp | 19 +- .../travis-ci/doxy-spellchecker/ignore.en.pws | 2 + 10 files changed, 287 insertions(+), 142 deletions(-) diff --git a/platform/source/SysTimer.cpp b/platform/source/SysTimer.cpp index 1513a301696..8c387d5a67a 100644 --- a/platform/source/SysTimer.cpp +++ b/platform/source/SysTimer.cpp @@ -29,6 +29,10 @@ extern "C" { #endif } +using namespace std::chrono; + +constexpr milliseconds deep_sleep_latency{MBED_CONF_TARGET_DEEP_SLEEP_LATENCY}; + #if (defined(NO_SYSTICK)) /** * Return an IRQ number that can be used in the absence of SysTick @@ -45,30 +49,21 @@ extern "C" IRQn_ID_t mbed_get_a9_tick_irqn(void); namespace mbed { namespace internal { -template -SysTimer::SysTimer() : +template +SysTimer::SysTimer() : #if DEVICE_LPTICKER - TimerEvent(get_lp_ticker_data()), + SysTimer(get_lp_ticker_data()) #else - TimerEvent(get_us_ticker_data()), + SysTimer(get_us_ticker_data()) #endif - _epoch(ticker_read_us(_ticker_data)), - _time_us(_epoch), - _tick(0), - _unacknowledged_ticks(0), - _wake_time_set(false), - _wake_time_passed(false), - _wake_early(false), - _ticking(false), - _deep_sleep_locked(false) { } -template -SysTimer::SysTimer(const ticker_data_t *data) : +template +SysTimer::SysTimer(const ticker_data_t *data) : TimerEvent(data), - _epoch(ticker_read_us(_ticker_data)), - _time_us(_epoch), + _epoch(_ticker_data.now()), + _time(_epoch), _tick(0), _unacknowledged_ticks(0), _wake_time_set(false), @@ -79,15 +74,15 @@ SysTimer::SysTimer(const ticker_data_t *data) : { } -template -SysTimer::~SysTimer() +template +SysTimer::~SysTimer() { cancel_tick(); cancel_wake(); } -template -void SysTimer::set_wake_time(uint64_t at) +template +void SysTimer::set_wake_time(time_point at) { // SysTimer must not be active - we must be in suspend state MBED_ASSERT(!_ticking); @@ -105,8 +100,8 @@ void SysTimer::set_wake_time(uint64_t at) return; } - uint64_t ticks_to_sleep = at - _tick; - uint64_t wake_time = _epoch + at * US_IN_TICK; + duration ticks_to_sleep = at - get_tick(); + highres_time_point wake_time = _epoch + at.time_since_epoch(); /* Set this first, before attaching the interrupt that can unset it */ _wake_time_set = true; @@ -117,8 +112,8 @@ void SysTimer::set_wake_time(uint64_t at) sleep_manager_lock_deep_sleep(); } /* Consider whether we will need early or precise wake-up */ - if (MBED_CONF_TARGET_DEEP_SLEEP_LATENCY > 0 && - ticks_to_sleep > MBED_CONF_TARGET_DEEP_SLEEP_LATENCY && + if (deep_sleep_latency > deep_sleep_latency.zero() && + ticks_to_sleep > deep_sleep_latency && !_deep_sleep_locked) { /* If there is deep sleep latency, but we still have enough time, * and we haven't blocked deep sleep ourselves, @@ -126,14 +121,14 @@ void SysTimer::set_wake_time(uint64_t at) * Actual sleep may or may not be deep, depending on other actors. */ _wake_early = true; - insert_absolute(wake_time - MBED_CONF_TARGET_DEEP_SLEEP_LATENCY * US_IN_TICK); + insert_absolute(wake_time - deep_sleep_latency); } else { /* Otherwise, set up to wake at the precise time. * If there is a deep sleep latency, ensure that we're holding the lock so the sleep * is shallow. (If there is no deep sleep latency, we're fine with it being deep). */ _wake_early = false; - if (MBED_CONF_TARGET_DEEP_SLEEP_LATENCY > 0 && !_deep_sleep_locked) { + if (deep_sleep_latency > deep_sleep_latency.zero() && !_deep_sleep_locked) { _deep_sleep_locked = true; sleep_manager_lock_deep_sleep(); } @@ -141,8 +136,8 @@ void SysTimer::set_wake_time(uint64_t at) } } -template -void SysTimer::cancel_wake() +template +void SysTimer::cancel_wake() { MBED_ASSERT(!_ticking); // Remove ensures serialized access to SysTimer by stopping timer interrupt @@ -157,24 +152,26 @@ void SysTimer::cancel_wake() } } -template -uint64_t SysTimer::_elapsed_ticks() const +template +auto SysTimer::_elapsed_ticks() const -> duration { - uint64_t elapsed_us = ticker_read_us(_ticker_data) - _time_us; - if (elapsed_us < US_IN_TICK) { - return 0; - } else if (elapsed_us < 2 * US_IN_TICK) { - return 1; - } else if (elapsed_us <= 0xFFFFFFFF) { + highres_duration elapsed_us = _ticker_data.now() - _time; + // Fastest common cases avoiding any division for 0 or 1 ticks + if (elapsed_us < duration(1)) { + return duration(0); + } else if (elapsed_us < duration(2)) { + return duration(1); + } else if (elapsed_us.count() <= 0xFFFFFFFF) { // Fast common case avoiding 64-bit division - return (uint32_t) elapsed_us / US_IN_TICK; + return duration_cast(highres_duration_u32(elapsed_us)); } else { - return elapsed_us / US_IN_TICK; + // Worst case will require 64-bit division to convert highres to ticks + return duration_cast(elapsed_us); } } -template -void SysTimer::start_tick() +template +void SysTimer::start_tick() { _ticking = true; if (_unacknowledged_ticks > 0) { @@ -183,14 +180,14 @@ void SysTimer::start_tick() _schedule_tick(); } -template -void SysTimer::_schedule_tick() +template +void SysTimer::_schedule_tick() { - insert_absolute(_time_us + US_IN_TICK); + insert_absolute(_time + duration(1)); } -template -void SysTimer::acknowledge_tick() +template +void SysTimer::acknowledge_tick() { // Try to avoid missed ticks if OS's IRQ level is not keeping // up with our handler. @@ -202,8 +199,8 @@ void SysTimer::acknowledge_tick() } } -template -void SysTimer::cancel_tick() +template +void SysTimer::cancel_tick() { // Underlying call is interrupt safe @@ -213,66 +210,66 @@ void SysTimer::cancel_tick() _clear_irq_pending(); } -template -uint64_t SysTimer::get_tick() const +template +auto SysTimer::get_tick() const -> time_point { // Atomic is necessary as this can be called from any foreground context, // while IRQ can update it. - return core_util_atomic_load_u64(&_tick); + return time_point(duration(core_util_atomic_load_u64(&_tick))); } -template -uint64_t SysTimer::update_and_get_tick() +template +auto SysTimer::update_and_get_tick() -> time_point { MBED_ASSERT(!_ticking && !_wake_time_set); // Can only be used when no interrupts are scheduled // Update counters to reflect elapsed time - uint64_t elapsed_ticks = _elapsed_ticks(); + duration elapsed_ticks = _elapsed_ticks(); _unacknowledged_ticks = 0; - _time_us += elapsed_ticks * US_IN_TICK; - _tick += elapsed_ticks; + _time += elapsed_ticks; + _tick += elapsed_ticks.count(); - return _tick; + return time_point(duration(_tick)); } -template -us_timestamp_t SysTimer::get_time() const +template +auto SysTimer::get_time() const -> highres_time_point { // Underlying call is interrupt safe - return ticker_read_us(_ticker_data); + return _ticker_data.now(); } -template -us_timestamp_t SysTimer::get_time_since_tick() const +template +auto SysTimer::get_time_since_tick() const -> highres_duration { - // Underlying call is interrupt safe, and _time_us is not updated by IRQ + // Underlying call is interrupt safe, and _time is not updated by IRQ - return get_time() - _time_us; + return get_time() - _time; } #if (defined(NO_SYSTICK)) -template -IRQn_Type SysTimer::get_irq_number() +template +IRQn_Type SysTimer::get_irq_number() { return mbed_get_m0_tick_irqn(); } #elif (TARGET_CORTEX_M) -template -IRQn_Type SysTimer::get_irq_number() +template +IRQn_Type SysTimer::get_irq_number() { return SysTick_IRQn; } #elif (TARGET_CORTEX_A) -template -IRQn_ID_t SysTimer::get_irq_number() +template +IRQn_ID_t SysTimer::get_irq_number() { return mbed_get_a9_tick_irqn(); } #endif -template -void SysTimer::_set_irq_pending() +template +void SysTimer::_set_irq_pending() { // Protected function synchronized externally if (!IRQ) { @@ -287,8 +284,8 @@ void SysTimer::_set_irq_pending() #endif } -template -void SysTimer::_clear_irq_pending() +template +void SysTimer::_clear_irq_pending() { // Protected function synchronized externally if (!IRQ) { @@ -303,17 +300,17 @@ void SysTimer::_clear_irq_pending() #endif } -template -void SysTimer::_increment_tick() +template +void SysTimer::_increment_tick() { // Protected function synchronized externally _tick++; - _time_us += US_IN_TICK; + _time += duration(1); } -template -void SysTimer::handler() +template +void SysTimer::handler() { /* To reduce IRQ latency problems, we do not re-arm in the interrupt handler */ if (_wake_time_set) { @@ -343,18 +340,18 @@ void SysTimer::handler() MBED_STATIC_ASSERT(1000000 % OS_TICK_FREQ == 0, "OS_TICK_FREQ must be a divisor of 1000000 for correct tick calculations"); #define OS_TICK_US (1000000 / OS_TICK_FREQ) #if OS_TICK_US != 1000 -template class SysTimer; +template class SysTimer, std::micro>>; #endif #endif /* Standard 1ms SysTimer */ -template class SysTimer<1000>; +template class SysTimer; /* Standard 1ms SysTimer that doesn't set interrupts, used for Greentea tests */ -template class SysTimer<1000, false>; +template class SysTimer; /* Slowed-down SysTimer that doesn't set interrupts, used for Greentea tests */ -template class SysTimer<42000, false>; +template class SysTimer, std::milli>, false>; } // namespace internal } // namespace mbed diff --git a/platform/source/SysTimer.h b/platform/source/SysTimer.h index e285c600ecf..76e54f41e53 100644 --- a/platform/source/SysTimer.h +++ b/platform/source/SysTimer.h @@ -20,6 +20,7 @@ #include "platform/NonCopyable.h" #include "platform/mbed_atomic.h" #include "drivers/TimerEvent.h" +#include #include "cmsis.h" extern "C" { @@ -45,10 +46,26 @@ namespace internal { * * @note SysTimer is not the part of Mbed API. */ -template -class SysTimer: private mbed::TimerEvent, private mbed::NonCopyable > { +template +class SysTimer: private mbed::TimerEvent, private mbed::NonCopyable > { public: + /* pseudo-Clock for our ticks - see TickerDataClock for more discussion */ + using rep = uint64_t; + using period = Period; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; + static const bool is_steady = false; + + /** duration type used for underlying high-res timer */ + using highres_duration = TickerDataClock::duration; + /** time_point type used for underlying high-res timer */ + using highres_time_point = TickerDataClock::time_point; + /** period of underlying high-res timer */ + using highres_period = TickerDataClock::period; + + static_assert(std::ratio_divide::den == 1, "Tick period must be an exact multiple of highres time period"); + /** * Default constructor uses LPTICKER if available (so the timer will * continue to run in deep sleep), else USTICKER. @@ -90,7 +107,7 @@ class SysTimer: private mbed::TimerEvent, private mbed::NonCopyable unacknowledged_ticks() const { - return core_util_atomic_load_u8(&_unacknowledged_ticks); + return std::chrono::duration(core_util_atomic_load_u8(&_unacknowledged_ticks)); } /** Get the current tick count @@ -186,7 +203,7 @@ class SysTimer: private mbed::TimerEvent, private mbed::NonCopyable; virtual void handler(); void _increment_tick(); void _schedule_tick(); - uint64_t _elapsed_ticks() const; + duration _elapsed_ticks() const; static void _set_irq_pending(); static void _clear_irq_pending(); - const us_timestamp_t _epoch; - us_timestamp_t _time_us; + const highres_time_point _epoch; + highres_time_point _time; uint64_t _tick; uint8_t _unacknowledged_ticks; bool _wake_time_set; diff --git a/platform/source/mbed_os_timer.cpp b/platform/source/mbed_os_timer.cpp index 9b9bec1270e..ef922ef05eb 100644 --- a/platform/source/mbed_os_timer.cpp +++ b/platform/source/mbed_os_timer.cpp @@ -68,12 +68,12 @@ OsTimer *init_os_timer() /* Optionally timed operation, with optional predicate */ struct timed_predicate_op { - timed_predicate_op(uint64_t t) : wake_time(t), orig_predicate(NULL), orig_handle(NULL) + timed_predicate_op(OsClock::time_point t) : wake_time(t), orig_predicate(NULL), orig_handle(NULL) { init_os_timer(); } - timed_predicate_op(uint64_t t, bool (*wake_predicate)(void *), void *wake_predicate_handle) : wake_time(t), orig_predicate(wake_predicate), orig_handle(wake_predicate_handle) + timed_predicate_op(OsClock::time_point t, bool (*wake_predicate)(void *), void *wake_predicate_handle) : wake_time(t), orig_predicate(wake_predicate), orig_handle(wake_predicate_handle) { init_os_timer(); } @@ -92,18 +92,18 @@ struct timed_predicate_op { void sleep_prepare() { - if (wake_time != (uint64_t) -1) { - os_timer->set_wake_time(wake_time); + if (wake_time != wake_time.max()) { + OsClock::set_wake_time(wake_time); } } bool sleep_prepared() { - return wake_time == (uint64_t) -1 || os_timer->wake_time_set(); + return wake_time == wake_time.max() || os_timer->wake_time_set(); } private: - uint64_t wake_time; + OsClock::time_point wake_time; bool (*orig_predicate)(void *); void *orig_handle; }; @@ -186,23 +186,23 @@ void do_sleep_operation(OpT &op) * The wake predicate will be called from both outside and inside a critical * section, so appropriate atomic care must be taken. */ -uint64_t do_timed_sleep_absolute(uint64_t wake_time, bool (*wake_predicate)(void *), void *wake_predicate_handle) +OsClock::time_point do_timed_sleep_absolute(OsClock::time_point wake_time, bool (*wake_predicate)(void *), void *wake_predicate_handle) { { timed_predicate_op op(wake_time, wake_predicate, wake_predicate_handle); do_sleep_operation(op); } - return os_timer->update_and_get_tick(); + return OsClock::now_with_init_done(); } #if MBED_CONF_RTOS_PRESENT /* The 32-bit limit is part of the API - we will always wake within 2^32 ticks */ /* This version is tuned for RTOS use, where the RTOS needs to know the time spent sleeping */ -uint32_t do_timed_sleep_relative(uint32_t wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle) +OsClock::duration_u32 do_timed_sleep_relative(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle) { - uint64_t sleep_start = init_os_timer()->get_tick(); + OsClock::time_point sleep_start = OsClock::now(); // When running with RTOS, the requested delay will be based on the kernel's tick count. // If it missed a tick as entering idle, we should reflect that by moving the // start time back to reflect its current idea of time. @@ -211,9 +211,9 @@ uint32_t do_timed_sleep_relative(uint32_t wake_delay, bool (*wake_predicate)(voi // clear the unacknowledged tick count. sleep_start -= os_timer->unacknowledged_ticks(); - uint64_t sleep_finish = do_timed_sleep_absolute(sleep_start + wake_delay, wake_predicate, wake_predicate_handle); + OsClock::time_point sleep_finish = do_timed_sleep_absolute(sleep_start + wake_delay, wake_predicate, wake_predicate_handle); - return static_cast(sleep_finish - sleep_start); + return sleep_finish - sleep_start; } #else @@ -225,23 +225,23 @@ void do_untimed_sleep(bool (*wake_predicate)(void *), void *wake_predicate_handl do_sleep_operation(op); } -/* (uint32_t)-1 delay is treated as "wait forever" */ +/* max() delay is treated as "wait forever" */ /* This version is tuned for non-RTOS use, where we don't need to return sleep time, and waiting forever is possible */ -void do_timed_sleep_relative_or_forever(uint32_t wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle) +void do_timed_sleep_relative_or_forever(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle) { // Special-case 0 delay, to save multiple callers having to do it. Just call the predicate once. - if (wake_delay == 0) { + if (wake_delay == wake_delay.zero()) { if (wake_predicate) { wake_predicate(wake_predicate_handle); } return; } - uint64_t wake_time; - if (wake_delay == (uint32_t) -1) { - wake_time = (uint64_t) -1; + OsClock::time_point wake_time; + if (wake_delay == OsClock::duration_u32::max()) { + wake_time = OsClock::time_point::max(); } else { - wake_time = init_os_timer()->update_and_get_tick() + wake_delay; + wake_time = OsClock::now() + wake_delay; } /* Always use timed_predicate_op here to save pulling in two templates */ timed_predicate_op op(wake_time, wake_predicate, wake_predicate_handle); diff --git a/platform/source/mbed_os_timer.h b/platform/source/mbed_os_timer.h index a765e911da5..3ad3ac12170 100644 --- a/platform/source/mbed_os_timer.h +++ b/platform/source/mbed_os_timer.h @@ -17,6 +17,7 @@ #ifndef MBED_MBED_SLEEP_TIMER_H #define MBED_MBED_SLEEP_TIMER_H +#include #include "platform/source/SysTimer.h" #if MBED_CONF_RTOS_PRESENT @@ -29,11 +30,10 @@ namespace mbed { namespace internal { #if MBED_CONF_RTOS_PRESENT -#define OS_TICK_US (1000000 / OS_TICK_FREQ) +using OsTimer = SysTimer>; #else -#define OS_TICK_US 1000 +using OsTimer = SysTimer; #endif -typedef SysTimer OsTimer; /* A SysTimer is used to provide the timed sleep - this provides access to share it for * other use, such as ticks. If accessed this way, it must not be in use when a sleep function below is called. @@ -41,20 +41,54 @@ typedef SysTimer OsTimer; extern OsTimer *os_timer; OsTimer *init_os_timer(); -/* -1 is effectively "sleep forever" */ -uint64_t do_timed_sleep_absolute(uint64_t wake_time, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL); +/** A C++11 chrono TrivialClock for os_timer + * + * @note To fit better into the chrono framework, OsClock uses + * chrono::milliseconds as its representation, which makes it signed + * and at least 45 bits (so it will be int64_t or equivalent). + */ +struct OsClock { + /* Standard TrivialClock fields */ + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static constexpr bool is_steady = true; + static time_point now() + { + // We are a real Clock with a well-defined epoch. As such we distinguish ourselves + // from the less-well-defined SysTimer pseudo-Clock. This means our time_points + // are not convertible, so need to fiddle here. + return time_point(init_os_timer()->update_and_get_tick().time_since_epoch()); + } + // Slightly-optimised variant of OsClock::now() that assumes os_timer is initialised. + static time_point now_with_init_done() + { + return time_point(os_timer->update_and_get_tick().time_since_epoch()); + } + static void set_wake_time(time_point wake_time) + { + return os_timer->set_wake_time(OsTimer::time_point(wake_time.time_since_epoch())); + } + /* Extension to + * make it easy to use 32-bit durations for some APIs, as we historically do */ + using duration_u32 = std::chrono::duration; +}; + +/* time_point::max() is effectively "sleep forever" */ +OsClock::time_point do_timed_sleep_absolute(OsClock::time_point wake_time, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL); #if MBED_CONF_RTOS_PRESENT /* Maximum sleep time is 2^32-1 ticks; timer is always set to achieve this */ /* Assumes that ticker has been in use prior to call, so restricted to RTOS use */ -uint32_t do_timed_sleep_relative(uint32_t wake_delay, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL); +OsClock::duration_u32 do_timed_sleep_relative(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL); #else void do_untimed_sleep(bool (*wake_predicate)(void *), void *wake_predicate_handle = NULL); -/* (uint32_t)-1 delay is sleep forever */ +/* duration_u32::max() delay is sleep forever */ -void do_timed_sleep_relative_or_forever(uint32_t wake_delay, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL); +void do_timed_sleep_relative_or_forever(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL); #endif diff --git a/platform/source/mbed_thread.cpp b/platform/source/mbed_thread.cpp index f24f8e2c554..08d2cb26bfe 100644 --- a/platform/source/mbed_thread.cpp +++ b/platform/source/mbed_thread.cpp @@ -26,33 +26,38 @@ #include "rtos/ThisThread.h" #endif +using namespace std::chrono; + extern "C" { uint64_t get_ms_count(void) { #if MBED_CONF_RTOS_PRESENT - return rtos::Kernel::get_ms_count(); + rtos::Kernel::Clock::time_point tp = rtos::Kernel::Clock::now(); #else - return mbed::internal::init_os_timer()->update_and_get_tick(); + mbed::internal::OsClock::time_point tp = mbed::internal::OsClock::now(); #endif + return duration(tp.time_since_epoch()).count(); } void thread_sleep_for(uint32_t millisec) { + auto d = duration(millisec); #if MBED_CONF_RTOS_PRESENT - rtos::ThisThread::sleep_for(millisec); + rtos::ThisThread::sleep_for(d); #else // Undocumented, but osDelay(UINT32_MAX) does actually sleep forever - mbed::internal::do_timed_sleep_relative_or_forever(millisec); + mbed::internal::do_timed_sleep_relative_or_forever(d); #endif } void thread_sleep_until(uint64_t millisec) { + auto d = duration(millisec); #if MBED_CONF_RTOS_PRESENT - rtos::ThisThread::sleep_until(millisec); + rtos::ThisThread::sleep_until(rtos::Kernel::Clock::time_point(d)); #else - mbed::internal::do_timed_sleep_absolute(millisec); + mbed::internal::do_timed_sleep_absolute(mbed::internal::OsClock::time_point(d)); #endif } diff --git a/platform/source/mbed_wait_api_rtos.cpp b/platform/source/mbed_wait_api_rtos.cpp index 163054753bb..dc0521e7b49 100644 --- a/platform/source/mbed_wait_api_rtos.cpp +++ b/platform/source/mbed_wait_api_rtos.cpp @@ -29,25 +29,28 @@ #include "platform/mbed_power_mgmt.h" #include "platform/mbed_error.h" +using namespace std::chrono; + void wait(float s) { - if ((s >= 0.01f) && core_util_are_interrupts_enabled()) { - rtos::ThisThread::sleep_for(s * 1000.0f); + auto rel_float = duration(s); + if (rel_float >= duration(10) && core_util_are_interrupts_enabled()) { + rtos::ThisThread::sleep_for(duration_cast(rel_float)); return; } - uint32_t us = (s * 1000000.0f); + duration us = duration_cast>(rel_float); const ticker_data_t *const ticker = get_us_ticker_data(); uint32_t start = ticker_read(ticker); - if ((us >= 1000) && core_util_are_interrupts_enabled()) { + if (us >= 1ms && core_util_are_interrupts_enabled()) { // Use the RTOS to wait for millisecond delays if possible sleep_manager_lock_deep_sleep(); - rtos::ThisThread::sleep_for((uint32_t)us / 1000); + rtos::ThisThread::sleep_for(duration_cast(us)); sleep_manager_unlock_deep_sleep(); } // Use busy waiting for sub-millisecond delays, or for the whole // interval if interrupts are not enabled - while ((ticker_read(ticker) - start) < (uint32_t)us); + while ((ticker_read(ticker) - start) < (uint32_t)us.count()); } /* The actual time delay may be up to one timer tick less - 1 msec */ @@ -61,7 +64,7 @@ void wait_ms(int ms) wait_us(ms * 1000); #endif } else { - rtos::ThisThread::sleep_for(ms); + rtos::ThisThread::sleep_for(milliseconds(ms)); } } diff --git a/rtos/Kernel.h b/rtos/Kernel.h index e6e7a61e289..13067621faf 100644 --- a/rtos/Kernel.h +++ b/rtos/Kernel.h @@ -24,15 +24,32 @@ #define KERNEL_H #include +#include #include "rtos/mbed_rtos_types.h" +#include "platform/mbed_toolchain.h" +#if !MBED_CONF_RTOS_PRESENT +#include "platform/source/mbed_os_timer.h" +#endif + namespace rtos { + /** \addtogroup rtos-public-api */ /** @{*/ /** Functions in the Kernel namespace control RTOS kernel information. */ namespace Kernel { +namespace impl { +/* Internal integer-returning function. + * + * ARM EABI means that `time_point`s do not get returned in registers, so + * it's worth having the actual exteernal definition return an integer, and only + * convert to `time_point` via the inline function `now()`. + */ +uint64_t get_tick_count(); +} + /** Read the current RTOS kernel millisecond tick count. The tick count corresponds to the tick count the RTOS uses for timing purposes. It increments monotonically from 0 at boot, so it effectively @@ -42,9 +59,66 @@ namespace Kernel { @note Mbed OS always uses millisecond RTOS ticks, and this could only wrap after half a billion years. @note You cannot call this function from ISR context. + @deprecated Use `Kernel::Clock::now()` to get a chrono time_point instead of an integer millisecond count. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Use `Kernel::Clock::now()` to get a chrono time_point instead of an integer millisecond count.") uint64_t get_ms_count(); +/** A C++11 chrono TrivialClock for the kernel millisecond tick count + * + * @note To fit better into the chrono framework, Kernel::Clock uses + * std::chrono::milliseconds as its representation, which makes it signed + * and at least 45 bits (so it will be int64_t or equivalent). + */ +struct Clock { + Clock() = delete; + /* Standard TrivialClock fields */ + using duration = std::chrono::milliseconds; + using rep = duration::rep; + using period = duration::period; +#if MBED_CONF_RTOS_PRESENT + using time_point = std::chrono::time_point; +#else + /* In non-RTOS builds, the clock maps directly to the underlying clock, and must + * indicate that here, so we can do implicit conversion internally. + */ + using time_point = std::chrono::time_point; +#endif + static constexpr bool is_steady = true; + static time_point now() + { + return time_point(duration(impl::get_tick_count())); + } + /* Extension to make it easy to use 32-bit durations for some APIs, as we historically have, + * for efficiency. + */ + using duration_u32 = std::chrono::duration; + + /** Lock the clock to ensure it stays running; dummy for API compatibility with HighResClock */ + static void lock() + { + } + + /** Unlock the clock, allowing it to stop during power saving; dummy for API compatibility with HighResClock */ + static void unlock() + { + } +}; + +/** Maximum duration for Kernel::Clock::duration_u32-based APIs + * + * @note As duration_u32-based APIs pass through straight to CMSIS-RTOS, they will + * interpret duration_u32(0xFFFFFFFF) as "wait forever". Indicate maximum + * wait time of 0xFFFFFFFE for these calls (which is ~49 days). + */ +constexpr Clock::duration_u32 wait_for_u32_max{osWaitForever - 1}; + +/** Magic "wait forever" constant for Kernel::Clock::duration_u32-based APIs + * + * Many duration_u32-based APIs treat duration_u32(0xFFFFFFFF) as "wait forever". + */ +constexpr Clock::duration_u32 wait_for_u32_forever{osWaitForever}; + /** Attach a function to be called by the RTOS idle task. @param fptr pointer to the function to be called diff --git a/rtos/source/Kernel.cpp b/rtos/source/Kernel.cpp index 3102016e693..5f99e491195 100644 --- a/rtos/source/Kernel.cpp +++ b/rtos/source/Kernel.cpp @@ -35,7 +35,14 @@ namespace rtos { +constexpr bool Kernel::Clock::is_steady; + uint64_t Kernel::get_ms_count() +{ + return impl::get_tick_count(); + +} +uint64_t Kernel::impl::get_tick_count() { #if MBED_CONF_RTOS_PRESENT // CMSIS-RTOS 2.1.0 and 2.1.1 differ in the time type. We assume diff --git a/rtos/source/TARGET_CORTEX/mbed_rtx_idle.cpp b/rtos/source/TARGET_CORTEX/mbed_rtx_idle.cpp index e89155854a0..713402a5e55 100644 --- a/rtos/source/TARGET_CORTEX/mbed_rtx_idle.cpp +++ b/rtos/source/TARGET_CORTEX/mbed_rtx_idle.cpp @@ -22,6 +22,7 @@ */ #include "rtos/source/rtos_idle.h" +#include "rtos/Kernel.h" #include "platform/mbed_power_mgmt.h" #include "platform/source/mbed_os_timer.h" #include "TimerEvent.h" @@ -95,7 +96,7 @@ extern "C" { // Get System Timer count. uint32_t OS_Tick_GetCount(void) { - return (uint32_t) os_timer->get_time_since_tick(); + return (uint32_t) os_timer->get_time_since_tick().count(); } // Get OS Tick IRQ number. @@ -115,13 +116,17 @@ extern "C" { // Get OS Tick timer clock frequency uint32_t OS_Tick_GetClock(void) { - return 1000000; + static_assert(OsTimer::highres_duration::period::num == 1, "Non-integral timer frequency"); + static_assert(OsTimer::highres_duration::period::den <= 0xFFFFFFFF, "Too fast timer frequency"); + return OsTimer::highres_duration::period::den; } // Get OS Tick interval. uint32_t OS_Tick_GetInterval(void) { - return 1000; + static_assert(OsTimer::period::num == 1, "Non-integral tick frequency"); + static_assert(OsTimer::period::den <= 0xFFFFFFFF, "Too fast tick frequency"); + return OsTimer::period::den; } static bool rtos_event_pending(void *) @@ -131,12 +136,12 @@ extern "C" { static void default_idle_hook(void) { - uint32_t ticks_to_sleep = osKernelSuspend(); + rtos::Kernel::Clock::duration_u32 ticks_to_sleep{osKernelSuspend()}; // osKernelSuspend will call OS_Tick_Disable, cancelling the tick, which frees // up the os timer for the timed sleep - uint64_t ticks_slept = mbed::internal::do_timed_sleep_relative(ticks_to_sleep, rtos_event_pending); - MBED_ASSERT(ticks_slept < osWaitForever); - osKernelResume((uint32_t) ticks_slept); + rtos::Kernel::Clock::duration_u32 ticks_slept = mbed::internal::do_timed_sleep_relative(ticks_to_sleep, rtos_event_pending); + MBED_ASSERT(ticks_slept < rtos::Kernel::wait_for_u32_max); + osKernelResume(ticks_slept.count()); } diff --git a/tools/test/travis-ci/doxy-spellchecker/ignore.en.pws b/tools/test/travis-ci/doxy-spellchecker/ignore.en.pws index 7535d003fb0..a70f5685cea 100644 --- a/tools/test/travis-ci/doxy-spellchecker/ignore.en.pws +++ b/tools/test/travis-ci/doxy-spellchecker/ignore.en.pws @@ -111,4 +111,6 @@ pppdebug ppp api uart +chrono +Hinnant _doxy_ From e456499975577abb3712626fcc5c263766c1a695 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Wed, 12 Feb 2020 15:31:17 +0200 Subject: [PATCH 03/19] Add Chrono and predicate support to ConditionVariable --- rtos/ConditionVariable.h | 209 +++++++++++++++++++++++++++++- rtos/source/ConditionVariable.cpp | 33 +++-- 2 files changed, 231 insertions(+), 11 deletions(-) diff --git a/rtos/ConditionVariable.h b/rtos/ConditionVariable.h index 9f433f5a697..57a6f5b7003 100644 --- a/rtos/ConditionVariable.h +++ b/rtos/ConditionVariable.h @@ -24,6 +24,7 @@ #define CONDITIONVARIABLE_H #include +#include #include "rtos/mbed_rtos_types.h" #include "rtos/Mutex.h" #include "rtos/Semaphore.h" @@ -36,6 +37,11 @@ namespace rtos { /** \addtogroup rtos-public-api */ /** @{*/ +enum class cv_status { + no_timeout, + timeout +}; + struct Waiter; /** * \defgroup rtos_ConditionVariable ConditionVariable class @@ -176,7 +182,7 @@ class ConditionVariable : private mbed::NonCopyable { * should check to make sure the condition the caller is waiting on has * been met. * - * @note - The current thread releases the lock while inside the wait + * @note - The current thread releases the mutex while inside the wait * function and reacquires it upon exiting the function. * * Example: @@ -196,6 +202,43 @@ class ConditionVariable : private mbed::NonCopyable { */ void wait(); + /** Wait for a predicate. + * + * Wait causes the current thread to block until the predicate is + * true. + * + * @param pred A function-like object such that `pred()` is convertible to bool + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - The current thread releases the mutex while inside the wait + * function and reacquires it upon exiting the function. + * + * Example: + * @code + * extern bool data_available(); + * + * mutex.lock(); + * + * cond.wait(data_available); + * + * function_to_handle_data(); + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + template + void wait(Predicate pred) + { + while (!pred()) { + wait(); + } + } + + /** Wait for a notification until the specified time. * * Wait until causes the current thread to block until the condition @@ -234,9 +277,94 @@ class ConditionVariable : private mbed::NonCopyable { * @endcode * * @note You cannot call this function from ISR context. + * @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + * rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") bool wait_until(uint64_t millisec); + /** Wait for a notification until the specified time. + * + * Wait until causes the current thread to block until the condition + * variable is notified, or a specific time given by millisec parameter is + * reached. + * + * @param abs_time Absolute end time referenced to `Kernel::Clock` + * @return `cv_status::timeout` if a timeout occurred, `cv_status::no_timeout` otherwise. + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - Spurious notifications can occur, so the caller of this API + * should check to make sure the condition the caller is waiting on has + * been met. + * + * @note - The current thread releases the lock while inside the wait + * function and reacquires it upon exiting the function. + * + * Example: + * @code + * mutex.lock(); + * Kernel::Clock::time_point end_time = Kernel::Clock::now() + 2s; + * + * while (!condition_met) { + * if (cond.wait_until(end_time) == cv_status::timeout) { + * break; + * } + * } + * + * if (condition_met) { + * function_to_handle_condition(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + cv_status wait_until(Kernel::Clock::time_point abs_time); + + /** Wait for a predicate until the specified time. + * + * Wait until causes the current thread to block until the predicate is true, + * or a specific time given by abs_time parameter is reached. + * + * @param abs_time Absolute end time referenced to `Kernel::Clock` + * @param pred A function-like object such that `pred()` is convertible to bool + * @return The state of the predicate + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - The current thread releases the mutex while inside the wait + * function and reacquires it upon exiting the function. + * + * Example: + * @code + * extern bool data_available(); + * + * mutex.lock(); + * + * if (cond.wait_until(Kernel::Clock::now() + 2s, data_available)) { + * function_to_handle_data(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + template + bool wait_until(Kernel::Clock::time_point abs_time, Predicate pred) + { + while (!pred()) { + if (wait_until(abs_time) == cv_status::timeout) { + return pred(); + } + } + return true; + } + /** Wait for a notification or timeout. * * `Wait for` causes the current thread to block until the condition @@ -275,9 +403,88 @@ class ConditionVariable : private mbed::NonCopyable { * @endcode * * @note You cannot call this function from ISR context. + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") bool wait_for(uint32_t millisec); + /** Wait for a notification or timeout. + * + * `Wait for` causes the current thread to block until the condition + * variable receives a notification from another thread, or the timeout + * specified by the millisec parameter is reached. + * + * @param rel_time Timeout value. + * @return `cv_status::timeout` if a timeout occurred, `cv_status::no_timeout` otherwise. + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - Spurious notifications can occur, so the caller of this API + * should check to make sure the condition the caller is waiting on has + * been met. + * + * @note - The current thread releases the lock while inside the wait + * function and reacquire it upon exiting the function. + * + * Example: + * @code + * mutex.lock(); + * + * while (!condition_met) { + * cond.wait_for(MAX_SLEEP_TIME); + * if (!condition_met) { + * do_other_work_while_condition_false(); + * } + * } + * + * if (condition_met) { + * function_to_handle_condition(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + cv_status wait_for(Kernel::Clock::duration_u32 rel_time); + + /** Wait for a predicate or timeout. + * + * `Wait for` causes the current thread to block until the predicate + * is true, or the timeout specified by the rel_time parameter is reached. + * + * @param rel_time Timeout value. + * @param pred a function-like object such that `pred()` is convertible to bool + * @return The state of the predicate + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex, and it must be locked exactly once. + * + * @note - The current thread releases the mutex while inside the wait + * function and reacquire it upon exiting the function. + * + * Example: + * @code + * extern bool data_available(); + * + * mutex.lock(); + * + * if (cond.wait_for(2s, data_available)) { + * function_to_handle_data(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + template + bool wait_for(Kernel::Clock::duration rel_time, Predicate pred) + { + return wait_until(Kernel::Clock::now() + rel_time, std::move(pred)); + } + /** Notify one waiter on this condition variable that a condition changed. * * This function unblocks one of the threads waiting for the condition diff --git a/rtos/source/ConditionVariable.cpp b/rtos/source/ConditionVariable.cpp index 36cf9b0bbcb..d157114d39f 100644 --- a/rtos/source/ConditionVariable.cpp +++ b/rtos/source/ConditionVariable.cpp @@ -29,6 +29,9 @@ #if MBED_CONF_RTOS_PRESENT +using std::chrono::duration; +using std::milli; + namespace rtos { ConditionVariable::Waiter::Waiter(): sem(0), prev(nullptr), next(nullptr), in_list(false) @@ -43,10 +46,15 @@ ConditionVariable::ConditionVariable(Mutex &mutex): _mutex(mutex), _wait_list(nu void ConditionVariable::wait() { - wait_for(osWaitForever); + wait_for(Kernel::wait_for_u32_forever); } bool ConditionVariable::wait_for(uint32_t millisec) +{ + return wait_for(duration(millisec)) == cv_status::timeout; +} + +cv_status ConditionVariable::wait_for(Kernel::Clock::duration_u32 rel_time) { Waiter current_thread; MBED_ASSERT(_mutex.get_owner() == ThisThread::get_id()); @@ -55,7 +63,7 @@ bool ConditionVariable::wait_for(uint32_t millisec) _mutex.unlock(); - bool timeout = !current_thread.sem.try_acquire_for(millisec); + cv_status status = current_thread.sem.try_acquire_for(rel_time) ? cv_status::no_timeout : cv_status::timeout; _mutex.lock(); @@ -63,24 +71,29 @@ bool ConditionVariable::wait_for(uint32_t millisec) _remove_wait_list(&_wait_list, ¤t_thread); } - return timeout; + return status; } bool ConditionVariable::wait_until(uint64_t millisec) { - uint64_t now = Kernel::get_ms_count(); + return wait_until(Kernel::Clock::time_point(duration(millisec))) == cv_status::timeout; +} + +cv_status ConditionVariable::wait_until(Kernel::Clock::time_point abs_time) +{ + Kernel::Clock::time_point now = Kernel::Clock::now(); - if (now >= millisec) { + if (now >= abs_time) { // Time has already passed - standard behaviour is to // treat as a "try". - return wait_for(0); - } else if (millisec - now >= osWaitForever) { + return wait_for(Kernel::Clock::duration_u32::zero()); + } else if (abs_time - now > Kernel::wait_for_u32_max) { // Exceeds maximum delay of underlying wait_for - // spuriously wake after 49 days, indicating no timeout. - wait_for(osWaitForever - 1); - return false; + wait_for(Kernel::wait_for_u32_max); + return cv_status::no_timeout; } else { - return wait_for(millisec - now); + return wait_for(abs_time - now); } } From ac1f0d1b66500535eaa4c8dc24efb439d216e0a4 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Wed, 12 Feb 2020 15:57:42 +0200 Subject: [PATCH 04/19] Add Chrono support to ThisThread --- rtos/ThisThread.h | 83 ++++++++++++++++++++++++++++++++ rtos/source/ThisThread.cpp | 97 +++++++++++++++++++++++++------------- 2 files changed, 148 insertions(+), 32 deletions(-) diff --git a/rtos/ThisThread.h b/rtos/ThisThread.h index bda5fe4d207..76c1387b0b4 100644 --- a/rtos/ThisThread.h +++ b/rtos/ThisThread.h @@ -24,6 +24,8 @@ #define THIS_THREAD_H #include +#include "platform/mbed_toolchain.h" +#include "rtos/Kernel.h" #include "rtos/mbed_rtos_types.h" namespace rtos { @@ -105,9 +107,21 @@ uint32_t flags_wait_any(uint32_t flags, bool clear = true); @return actual thread flags before clearing, which may not satisfy the wait @note You cannot call this function from ISR context. @see Thread::flags_set + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") uint32_t flags_wait_all_for(uint32_t flags, uint32_t millisec, bool clear = true); +/** Wait for all of the specified Thread Flags to become signaled for the current thread. + @param flags specifies the flags to wait for + @param rel_time timeout value. + @param clear whether to clear the specified flags after waiting for them. (default: true) + @return actual thread flags before clearing, which may not satisfy the wait + @note You cannot call this function from ISR context. + @see Thread::flags_set +*/ +uint32_t flags_wait_all_for(uint32_t flags, Kernel::Clock::duration_u32 rel_time, bool clear = true); + /** Wait for all of the specified Thread Flags to become signaled for the current thread. @param flags specifies the flags to wait for @param millisec absolute timeout time, referenced to Kernel::get_ms_count() @@ -119,9 +133,26 @@ uint32_t flags_wait_all_for(uint32_t flags, uint32_t millisec, bool clear = true wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, the wait will time out earlier than specified. @see Thread::flags_set + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + rather than `Kernel::get_ms_count() + 5000`. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") uint32_t flags_wait_all_until(uint32_t flags, uint64_t millisec, bool clear = true); +/** Wait for all of the specified Thread Flags to become signaled for the current thread. + @param flags specifies the flags to wait for + @param abs_time absolute timeout time, referenced to Kernel::Clock + @param clear whether to clear the specified flags after waiting for them. (default: true) + @return actual thread flags before clearing, which may not satisfy the wait + @note You cannot call this function from ISR context. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the wait will time out earlier than specified. + @see Thread::flags_set +*/ +uint32_t flags_wait_all_until(uint32_t flags, Kernel::Clock::time_point abs_time, bool clear = true); + /** Wait for any of the specified Thread Flags to become signaled for the current thread. @param flags specifies the flags to wait for @param millisec timeout value. @@ -129,9 +160,21 @@ uint32_t flags_wait_all_until(uint32_t flags, uint64_t millisec, bool clear = tr @return actual thread flags before clearing, which may not satisfy the wait @note You cannot call this function from ISR context. @see Thread::flags_set + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") uint32_t flags_wait_any_for(uint32_t flags, uint32_t millisec, bool clear = true); +/** Wait for any of the specified Thread Flags to become signaled for the current thread. + @param flags specifies the flags to wait for + @param rel_time timeout value. + @param clear whether to clear the specified flags after waiting for them. (default: true) + @return actual thread flags before clearing, which may not satisfy the wait + @note You cannot call this function from ISR context. + @see Thread::flags_set +*/ +uint32_t flags_wait_any_for(uint32_t flags, Kernel::Clock::duration_u32 rel_time, bool clear = true); + /** Wait for any of the specified Thread Flags to become signaled for the current thread. @param flags specifies the flags to wait for @param millisec absolute timeout time, referenced to Kernel::get_ms_count() @@ -143,16 +186,43 @@ uint32_t flags_wait_any_for(uint32_t flags, uint32_t millisec, bool clear = true wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, the wait will time out earlier than specified. @see Thread::flags_set + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + rather than `Kernel::get_ms_count() + 5000`. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") uint32_t flags_wait_any_until(uint32_t flags, uint64_t millisec, bool clear = true); +/** Wait for any of the specified Thread Flags to become signaled for the current thread. + @param flags specifies the flags to wait for + @param abs_time absolute timeout time, referenced to Kernel::Clock + @param clear whether to clear the specified flags after waiting for them. (default: true) + @return actual thread flags before clearing, which may not satisfy the wait + @note You cannot call this function from ISR context. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the wait will time out earlier than specified. + @see Thread::flags_set +*/ +uint32_t flags_wait_any_until(uint32_t flags, Kernel::Clock::time_point abs_time, bool clear = true); + /** Sleep for a specified time period in millisec: @param millisec time delay value @note You cannot call this function from ISR context. @note The equivalent functionality is accessible in C via thread_sleep_for. + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") void sleep_for(uint32_t millisec); +/** Sleep for a specified time period: + @param rel_time time delay value + @note You cannot call this function from ISR context. + @note The equivalent functionality is accessible in C via thread_sleep_for. +*/ +void sleep_for(Kernel::Clock::duration_u32 rel_time); + + /** Sleep until a specified time in millisec The specified time is according to Kernel::get_ms_count(). @param millisec absolute time in millisec @@ -160,9 +230,22 @@ void sleep_for(uint32_t millisec); @note if millisec is equal to or lower than the current tick count, this returns immediately. @note The equivalent functionality is accessible in C via thread_sleep_until. + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use + `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`. */ +MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") void sleep_until(uint64_t millisec); +/** Sleep until a specified time in millisec + The specified time is according to Kernel::Clock. + @param abs_time absolute time + @note You cannot call this function from ISR context. + @note if abs_time is equal to or lower than Kernel::Clock::now(), this + returns immediately. + @note The equivalent functionality is accessible in C via thread_sleep_until. +*/ +void sleep_until(Kernel::Clock::time_point abs_time); + /** Pass control to next equal-priority thread that is in state READY. (Higher-priority READY threads would prevent us from running; this will not enable lower-priority threads to run, as we remain READY). diff --git a/rtos/source/ThisThread.cpp b/rtos/source/ThisThread.cpp index d4d754cf933..921b10a8813 100644 --- a/rtos/source/ThisThread.cpp +++ b/rtos/source/ThisThread.cpp @@ -31,6 +31,12 @@ #include "platform/mbed_critical.h" #include "platform/source/mbed_os_timer.h" +using std::milli; +using std::chrono::duration; +using rtos::Kernel::Clock; +using rtos::Kernel::wait_for_u32_max; +using rtos::Kernel::wait_for_u32_forever; + #if !MBED_CONF_RTOS_PRESENT /* If the RTOS is not present, we call mbed_thread.cpp to do the work */ /* If the RTOS is present, mbed_thread.cpp calls us to do the work */ @@ -95,16 +101,16 @@ bool non_rtos_check_flags(void *handle) } #endif -static uint32_t flags_wait_for(uint32_t flags, uint32_t millisec, bool clear, uint32_t options) +static uint32_t flags_wait_for(uint32_t flags, Clock::duration_u32 rel_time, bool clear, uint32_t options) { if (!clear) { options |= osFlagsNoClear; } #if MBED_CONF_RTOS_PRESENT - flags = osThreadFlagsWait(flags, options, millisec); + flags = osThreadFlagsWait(flags, options, rel_time.count()); if (flags & osFlagsError) { - MBED_ASSERT((flags == osFlagsErrorTimeout && millisec != osWaitForever) || - (flags == osFlagsErrorResource && millisec == 0)); + MBED_ASSERT((flags == osFlagsErrorTimeout && rel_time != wait_for_u32_forever) || + (flags == osFlagsErrorResource && rel_time == rel_time.zero())); flags = ThisThread::flags_get(); } #else @@ -113,7 +119,7 @@ static uint32_t flags_wait_for(uint32_t flags, uint32_t millisec, bool clear, ui check.options = options; check.flags_wanted = flags; check.result = 0; - mbed::internal::do_timed_sleep_relative_or_forever(millisec, rtos::internal::non_rtos_check_flags, &check); + mbed::internal::do_timed_sleep_relative_or_forever(rel_time, rtos::internal::non_rtos_check_flags, &check); flags = check.result; #endif @@ -123,7 +129,7 @@ static uint32_t flags_wait_for(uint32_t flags, uint32_t millisec, bool clear, ui static uint32_t flags_wait(uint32_t flags, bool clear, uint32_t options) { #if MBED_CONF_RTOS_PRESENT - return flags_wait_for(flags, osWaitForever, clear, options); + return flags_wait_for(flags, wait_for_u32_forever, clear, options); #else /* Avoids pulling in timer if not used */ if (!clear) { @@ -141,20 +147,20 @@ static uint32_t flags_wait(uint32_t flags, bool clear, uint32_t options) #endif } -static uint32_t flags_wait_until(uint32_t flags, uint64_t millisec, bool clear, uint32_t options) +static uint32_t flags_wait_until(uint32_t flags, Clock::time_point abs_time, bool clear, uint32_t options) { - uint64_t now = Kernel::get_ms_count(); + Clock::time_point now = Clock::now(); - uint32_t delay; - if (now >= millisec) { - delay = 0; - } else if (millisec - now >= osWaitForever) { + Clock::duration_u32 rel_time; + if (now >= abs_time) { + rel_time = rel_time.zero(); + } else if (abs_time - now > wait_for_u32_max) { // Documentation permits early return for big offsets - delay = osWaitForever - 1; + rel_time = wait_for_u32_max; } else { - delay = millisec - now; + rel_time = abs_time - now; } - return flags_wait_for(flags, delay, clear, options); + return flags_wait_for(flags, rel_time, clear, options); } uint32_t ThisThread::flags_wait_all(uint32_t flags, bool clear) @@ -164,12 +170,22 @@ uint32_t ThisThread::flags_wait_all(uint32_t flags, bool clear) uint32_t ThisThread::flags_wait_all_for(uint32_t flags, uint32_t millisec, bool clear) { - return flags_wait_for(flags, millisec, clear, osFlagsWaitAll); + return flags_wait_all_for(flags, duration(millisec), clear); +} + +uint32_t ThisThread::flags_wait_all_for(uint32_t flags, Clock::duration_u32 rel_time, bool clear) +{ + return flags_wait_for(flags, rel_time, clear, osFlagsWaitAll); } uint32_t ThisThread::flags_wait_all_until(uint32_t flags, uint64_t millisec, bool clear) { - return flags_wait_until(flags, millisec, clear, osFlagsWaitAll); + return flags_wait_all_until(flags, Clock::time_point(duration(millisec)), clear); +} + +uint32_t ThisThread::flags_wait_all_until(uint32_t flags, Clock::time_point abs_time, bool clear) +{ + return flags_wait_until(flags, abs_time, clear, osFlagsWaitAll); } uint32_t ThisThread::flags_wait_any(uint32_t flags, bool clear) @@ -179,45 +195,62 @@ uint32_t ThisThread::flags_wait_any(uint32_t flags, bool clear) uint32_t ThisThread::flags_wait_any_for(uint32_t flags, uint32_t millisec, bool clear) { - return flags_wait_for(flags, millisec, clear, osFlagsWaitAny); + return flags_wait_any_for(flags, duration(millisec), clear); +} + +uint32_t ThisThread::flags_wait_any_for(uint32_t flags, Clock::duration_u32 rel_time, bool clear) +{ + return flags_wait_for(flags, rel_time, clear, osFlagsWaitAll); } uint32_t ThisThread::flags_wait_any_until(uint32_t flags, uint64_t millisec, bool clear) { - return flags_wait_until(flags, millisec, clear, osFlagsWaitAny); + return flags_wait_any_until(flags, Clock::time_point(duration(millisec)), clear); +} + +uint32_t ThisThread::flags_wait_any_until(uint32_t flags, Clock::time_point abs_time, bool clear) +{ + return flags_wait_until(flags, abs_time, clear, osFlagsWaitAny); } void ThisThread::sleep_for(uint32_t millisec) +{ + ThisThread::sleep_for(duration(millisec)); +} + +void ThisThread::sleep_for(Clock::duration_u32 rel_time) { #if MBED_CONF_RTOS_PRESENT - osStatus_t status = osDelay(millisec); + osStatus_t status = osDelay(rel_time.count()); MBED_ASSERT(status == osOK); #else - thread_sleep_for(millisec); + thread_sleep_for(rel_time.count()); #endif } void ThisThread::sleep_until(uint64_t millisec) { -#if MBED_CONF_RTOS_PRESENT - // CMSIS-RTOS 2.1.0 had 64-bit time and osDelayUntil, but that's been revoked. - // Limit ourselves to manual implementation assuming a >=32-bit osDelay. + ThisThread::sleep_until(Clock::time_point(duration(millisec))); +} - // 64-bit time doesn't wrap (for half a billion years, at last) - // make the effort to loop for unlimited sleep, as it doesn't cost much - uint64_t now; +void ThisThread::sleep_until(Clock::time_point abs_time) +{ +#if MBED_CONF_RTOS_PRESENT + Clock::time_point now; - while ((now = Kernel::get_ms_count()) < millisec) { - if (millisec - now > UINT32_MAX) { - sleep_for(UINT32_MAX); + while ((now = Clock::now()) < abs_time) { + if (abs_time - now > wait_for_u32_max) { + osStatus_t status = osDelay(wait_for_u32_max.count()); + MBED_ASSERT(status == osOK); continue; } else { - sleep_for(millisec - now); + osStatus_t status = osDelay((abs_time - now).count()); + MBED_ASSERT(status == osOK); break; } } #else - thread_sleep_until(millisec); + thread_sleep_until(abs_time.time_since_epoch().count()); #endif } From 589580eb727d187a750e0f8526aa0a9f670dc1c6 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Wed, 12 Feb 2020 16:12:56 +0200 Subject: [PATCH 05/19] Add Chrono support to Semaphore --- rtos/Semaphore.h | 27 ++++++++++++++++++++++++++ rtos/source/Semaphore.cpp | 40 ++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/rtos/Semaphore.h b/rtos/Semaphore.h index f5fbc69fcb1..ef95ae2369a 100644 --- a/rtos/Semaphore.h +++ b/rtos/Semaphore.h @@ -24,9 +24,11 @@ #define SEMAPHORE_H #include +#include #include "rtos/mbed_rtos_types.h" #include "rtos/mbed_rtos1_types.h" #include "rtos/mbed_rtos_storage.h" +#include "rtos/Kernel.h" #include "platform/mbed_toolchain.h" #include "platform/NonCopyable.h" @@ -109,9 +111,19 @@ class Semaphore : private mbed::NonCopyable { @return true if a resource was acquired, false otherwise. @note You may call this function from ISR context if the millisec parameter is set to 0. + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") bool try_acquire_for(uint32_t millisec); + /** Wait until a Semaphore resource becomes available. + @param rel_time timeout value. + @return true if a resource was acquired, false otherwise. + + @note You may call this function from ISR context if the rel_time parameter is set to 0. + */ + bool try_acquire_for(Kernel::Clock::duration_u32 rel_time); + /** Wait until a Semaphore resource becomes available. @param millisec absolute timeout time, referenced to Kernel::get_ms_count() @return true if a resource was acquired, false otherwise. @@ -121,9 +133,24 @@ class Semaphore : private mbed::NonCopyable { the acquire attempt will time out earlier than specified. @note You cannot call this function from ISR context. + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use + `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") bool try_acquire_until(uint64_t millisec); + /** Wait until a Semaphore resource becomes available. + @param millisec absolute timeout time, referenced to Kernel::get_ms_count() + @return true if a resource was acquired, false otherwise. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the acquire attempt will time out earlier than specified. + + @note You cannot call this function from ISR context. + */ + bool try_acquire_until(Kernel::Clock::time_point abs_time); + /** Release a Semaphore resource that was obtain with Semaphore::acquire. @return status code that indicates the execution status of the function: @a osOK the token has been correctly released. diff --git a/rtos/source/Semaphore.cpp b/rtos/source/Semaphore.cpp index 65016137cc8..5eaad18b3ad 100644 --- a/rtos/source/Semaphore.cpp +++ b/rtos/source/Semaphore.cpp @@ -29,6 +29,10 @@ #include +using namespace std::chrono_literals; +using std::chrono::duration; +using std::milli; + namespace rtos { Semaphore::Semaphore(int32_t count) @@ -111,7 +115,7 @@ int32_t Semaphore::wait(uint32_t millisec) return _wait(millisec); #else sem_wait_capture capture = { this, false }; - mbed::internal::do_timed_sleep_relative_or_forever(millisec, semaphore_available, &capture); + mbed::internal::do_timed_sleep_relative_or_forever(duration(millisec), semaphore_available, &capture); if (capture.acquired) { return core_util_atomic_load_s32(&_count) + 1; } else { @@ -123,7 +127,7 @@ int32_t Semaphore::wait(uint32_t millisec) int32_t Semaphore::wait_until(uint64_t millisec) { #if MBED_CONF_RTOS_PRESENT - uint64_t now = Kernel::get_ms_count(); + uint64_t now = duration(Kernel::Clock::now().time_since_epoch()).count(); if (now >= millisec) { return _wait(0); @@ -135,7 +139,7 @@ int32_t Semaphore::wait_until(uint64_t millisec) } #else sem_wait_capture capture = { this, false }; - mbed::internal::do_timed_sleep_absolute(millisec, semaphore_available, &capture); + mbed::internal::do_timed_sleep_absolute(mbed::internal::OsClock::time_point(duration(millisec)), semaphore_available, &capture); if (capture.acquired) { return core_util_atomic_load_s32(&_count) + 1; } else { @@ -161,15 +165,20 @@ void Semaphore::acquire() } bool Semaphore::try_acquire_for(uint32_t millisec) +{ + return try_acquire_for(duration(millisec)); +} + +bool Semaphore::try_acquire_for(Kernel::Clock::duration_u32 rel_time) { #if MBED_CONF_RTOS_PRESENT - osStatus_t status = osSemaphoreAcquire(_id, millisec); + osStatus_t status = osSemaphoreAcquire(_id, rel_time.count()); if (status == osOK) { return true; } - bool success = (status == osErrorResource && millisec == 0) || - (status == osErrorTimeout && millisec != osWaitForever); + bool success = (status == osErrorResource && rel_time == rel_time.zero()) || + (status == osErrorTimeout); if (!success) { MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_KERNEL, MBED_ERROR_CODE_SEMAPHORE_LOCK_FAILED), "Semaphore acquire failed", status); @@ -177,27 +186,32 @@ bool Semaphore::try_acquire_for(uint32_t millisec) return false; #else sem_wait_capture capture = { this, false }; - mbed::internal::do_timed_sleep_relative_or_forever(millisec, semaphore_available, &capture); + mbed::internal::do_timed_sleep_relative_or_forever(rel_time, semaphore_available, &capture); return capture.acquired; #endif } bool Semaphore::try_acquire_until(uint64_t millisec) +{ + return try_acquire_until(Kernel::Clock::time_point(duration(millisec))); +} + +bool Semaphore::try_acquire_until(Kernel::Clock::time_point abs_time) { #if MBED_CONF_RTOS_PRESENT - uint64_t now = Kernel::get_ms_count(); + Kernel::Clock::time_point now = Kernel::Clock::now(); - if (now >= millisec) { + if (now >= abs_time) { return try_acquire(); - } else if (millisec - now >= osWaitForever) { + } else if (abs_time - now > Kernel::wait_for_u32_max) { // API permits early return - return try_acquire_for(osWaitForever - 1); + return try_acquire_for(Kernel::wait_for_u32_max); } else { - return try_acquire_for(millisec - now); + return try_acquire_for(abs_time - now); } #else sem_wait_capture capture = { this, false }; - mbed::internal::do_timed_sleep_absolute(millisec, semaphore_available, &capture); + mbed::internal::do_timed_sleep_absolute(abs_time, semaphore_available, &capture); return capture.acquired; #endif } From c6057d328e2691781f7bd3ae7d1a1b7c8bab745b Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Wed, 12 Feb 2020 16:18:18 +0200 Subject: [PATCH 06/19] Add Chrono support to Mutex --- rtos/Mutex.h | 40 ++++++++++++++++++++++++++++++++++++++++ rtos/source/Mutex.cpp | 32 +++++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/rtos/Mutex.h b/rtos/Mutex.h index afd2fb2047a..49963302c05 100644 --- a/rtos/Mutex.h +++ b/rtos/Mutex.h @@ -27,6 +27,7 @@ #include "rtos/mbed_rtos_types.h" #include "rtos/mbed_rtos1_types.h" #include "rtos/mbed_rtos_storage.h" +#include "rtos/Kernel.h" #include "platform/NonCopyable.h" #include "platform/ScopedLock.h" @@ -105,9 +106,23 @@ class Mutex : private mbed::NonCopyable { the lock attempt will time out earlier than specified. @note You cannot call this function from ISR context. + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") bool trylock_for(uint32_t millisec); + /** Try to lock the mutex for a specified time + @param rel_time timeout value. + @return true if the mutex was acquired, false otherwise. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the lock attempt will time out earlier than specified. + + @note You cannot call this function from ISR context. + */ + bool trylock_for(Kernel::Clock::duration_u32 rel_time); + /** Try to lock the mutex until specified time @param millisec absolute timeout time, referenced to Kernel::get_ms_count() @return true if the mutex was acquired, false otherwise. @@ -117,9 +132,24 @@ class Mutex : private mbed::NonCopyable { the lock attempt will time out earlier than specified. @note You cannot call this function from ISR context. + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use + `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") bool trylock_until(uint64_t millisec); + /** Try to lock the mutex until specified time + @param abs_time absolute timeout time, referenced to Kernel::get_ms_count() + @return true if the mutex was acquired, false otherwise. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the lock attempt will time out earlier than specified. + + @note You cannot call this function from ISR context. + */ + bool trylock_until(Kernel::Clock::time_point abs_time); + /** Unlock the mutex that has previously been locked by the same thread @@ -178,11 +208,21 @@ inline bool Mutex::trylock_for(uint32_t) return true; } +inline bool Mutex::trylock_for(Kernel::Clock::duration_u32) +{ + return true; +} + inline bool Mutex::trylock_until(uint64_t) { return true; } +inline bool Mutex::trylock_until(Kernel::Clock::time_point) +{ + return true; +} + inline void Mutex::unlock() { } diff --git a/rtos/source/Mutex.cpp b/rtos/source/Mutex.cpp index 5742822abce..6860a7884da 100644 --- a/rtos/source/Mutex.cpp +++ b/rtos/source/Mutex.cpp @@ -29,6 +29,10 @@ #if MBED_CONF_RTOS_PRESENT +using namespace std::chrono_literals; +using std::milli; +using std::chrono::duration; + namespace rtos { Mutex::Mutex() @@ -71,20 +75,25 @@ void Mutex::lock(void) bool Mutex::trylock() { - return trylock_for(0); + return trylock_for(0s); } bool Mutex::trylock_for(uint32_t millisec) { - osStatus status = osMutexAcquire(_id, millisec); + return trylock_for(duration(millisec)); +} + +bool Mutex::trylock_for(Kernel::Clock::duration_u32 rel_time) +{ + osStatus status = osMutexAcquire(_id, rel_time.count()); if (status == osOK) { _count++; return true; } bool success = (status == osOK || - (status == osErrorResource && millisec == 0) || - (status == osErrorTimeout && millisec != osWaitForever)); + (status == osErrorResource && rel_time == rel_time.zero()) || + (status == osErrorTimeout && rel_time <= Kernel::wait_for_u32_max)); if (!success && !mbed_get_error_in_progress()) { MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_KERNEL, MBED_ERROR_CODE_MUTEX_LOCK_FAILED), "Mutex lock failed", status); @@ -95,15 +104,20 @@ bool Mutex::trylock_for(uint32_t millisec) bool Mutex::trylock_until(uint64_t millisec) { - uint64_t now = Kernel::get_ms_count(); + return trylock_until(Kernel::Clock::time_point(duration(millisec))); +} + +bool Mutex::trylock_until(Kernel::Clock::time_point abs_time) +{ + Kernel::Clock::time_point now = Kernel::Clock::now(); - if (now >= millisec) { + if (now >= abs_time) { return trylock(); - } else if (millisec - now >= osWaitForever) { + } else if (abs_time - now > Kernel::wait_for_u32_max) { // API permits early return - return trylock_for(osWaitForever - 1); + return trylock_for(Kernel::wait_for_u32_max); } else { - return trylock_for(millisec - now); + return trylock_for(abs_time - now); } } From f625737c80e909df482f189845fc32b261d3ea31 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Wed, 12 Feb 2020 16:22:32 +0200 Subject: [PATCH 07/19] Thread: minor space optimisation --- rtos/Thread.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtos/Thread.h b/rtos/Thread.h index 10aa6176f64..f4a4e602d0f 100644 --- a/rtos/Thread.h +++ b/rtos/Thread.h @@ -268,10 +268,10 @@ class Thread : private mbed::NonCopyable { osThreadId_t _tid; osThreadAttr_t _attr; bool _dynamic_stack; + bool _finished; Semaphore _join_sem; mutable Mutex _mutex; mbed_rtos_storage_thread_t _obj_mem; - bool _finished; }; /** @}*/ /** @}*/ From 151e7efcd272c823e20bcda78a1f711b65b7afa1 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 09:52:21 +0200 Subject: [PATCH 08/19] Add Chrono support to EventFlags --- rtos/EventFlags.h | 45 ++++++++++++++++++++++++++++++++- rtos/source/EventFlags.cpp | 52 +++++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/rtos/EventFlags.h b/rtos/EventFlags.h index 2e8a81f63b8..67691428bc4 100644 --- a/rtos/EventFlags.h +++ b/rtos/EventFlags.h @@ -25,6 +25,7 @@ #include #include +#include "rtos/Kernel.h" #include "rtos/mbed_rtos_types.h" #include "rtos/mbed_rtos1_types.h" #include "rtos/mbed_rtos_storage.h" @@ -98,6 +99,26 @@ class EventFlags : private mbed::NonCopyable { */ uint32_t wait_all(uint32_t flags = 0, uint32_t millisec = osWaitForever, bool clear = true); + /** Wait for all of the specified event flags to become signaled. + @param flags the flags to wait for. + @param rel_time timeout value. + @param clear clear specified event flags after waiting for them (default: true). + @return event flags before clearing or error code if highest bit set (see @a osFlagsError for details). + + @note You may call this function from ISR context if the rel_time parameter is set to 0. + */ + uint32_t wait_all_for(uint32_t flags, Kernel::Clock::duration_u32 rel_time, bool clear = true); + + /** Wait for all of the specified event flags to become signaled. + @param flags the flags to wait for. + @param abs_time timeout value. + @param clear clear specified event flags after waiting for them (default: true). + @return event flags before clearing or error code if highest bit set (see @a osFlagsError for details). + + @note You cannot call this function from ISR context. + */ + uint32_t wait_all_until(uint32_t flags, Kernel::Clock::time_point abs_time, bool clear = true); + /** Wait for any of the specified event flags to become signaled. @param flags the flags to wait for (default: 0 -- no flags). @param millisec timeout value (default: osWaitForever). @@ -108,6 +129,26 @@ class EventFlags : private mbed::NonCopyable { */ uint32_t wait_any(uint32_t flags = 0, uint32_t millisec = osWaitForever, bool clear = true); + /** Wait for any of the specified event flags to become signaled. + @param flags the flags to wait for. + @param rel_time timeout value. + @param clear clear specified event flags after waiting for them (default: true). + @return event flags before clearing or error code if highest bit set (see @a osFlagsError for details). + + @note This function may be called from ISR context if the millisec parameter is set to 0. + */ + uint32_t wait_any_for(uint32_t flags, Kernel::Clock::duration_u32 rel_time, bool clear = true); + + /** Wait for any of the specified event flags to become signaled. + @param flags the flags to wait for. + @param abs_time timeout value. + @param clear clear specified event flags after waiting for them (default: true). + @return event flags before clearing or error code if highest bit set (see @a osFlagsError for details). + + @note You cannot call this function from ISR context. + */ + uint32_t wait_any_until(uint32_t flags, Kernel::Clock::time_point abs_time, bool clear = true); + /** EventFlags destructor. @note You cannot call this function from ISR context. @@ -116,7 +157,9 @@ class EventFlags : private mbed::NonCopyable { private: void constructor(const char *name = nullptr); - uint32_t wait(uint32_t flags, uint32_t opt, uint32_t millisec, bool clear); + uint32_t wait_for(uint32_t flags, uint32_t opt, Kernel::Clock::duration_u32 rel_time, bool clear); + uint32_t wait_until(uint32_t flags, uint32_t opt, Kernel::Clock::time_point abs_time, bool clear); + #if MBED_CONF_RTOS_PRESENT osEventFlagsId_t _id; mbed_rtos_storage_event_flags_t _obj_mem; diff --git a/rtos/source/EventFlags.cpp b/rtos/source/EventFlags.cpp index ea3b5cc1db1..bd235cd81f0 100644 --- a/rtos/source/EventFlags.cpp +++ b/rtos/source/EventFlags.cpp @@ -27,6 +27,9 @@ #include "platform/mbed_error.h" #include "platform/mbed_assert.h" +using std::milli; +using std::chrono::duration; + namespace rtos { EventFlags::EventFlags() @@ -82,12 +85,32 @@ uint32_t EventFlags::get() const uint32_t EventFlags::wait_all(uint32_t flags, uint32_t millisec, bool clear) { - return wait(flags, osFlagsWaitAll, millisec, clear); + return wait_all_for(flags, duration(millisec), clear); +} + +uint32_t EventFlags::wait_all_for(uint32_t flags, Kernel::Clock::duration_u32 rel_time, bool clear) +{ + return wait_for(flags, osFlagsWaitAll, rel_time, clear); +} + +uint32_t EventFlags::wait_all_until(uint32_t flags, Kernel::Clock::time_point abs_time, bool clear) +{ + return wait_until(flags, osFlagsWaitAll, abs_time, clear); } uint32_t EventFlags::wait_any(uint32_t flags, uint32_t millisec, bool clear) { - return wait(flags, osFlagsWaitAny, millisec, clear); + return wait_any_for(flags, duration(millisec), clear); +} + +uint32_t EventFlags::wait_any_for(uint32_t flags, Kernel::Clock::duration_u32 rel_time, bool clear) +{ + return wait_for(flags, osFlagsWaitAny, rel_time, clear); +} + +uint32_t EventFlags::wait_any_until(uint32_t flags, Kernel::Clock::time_point abs_time, bool clear) +{ + return wait_until(flags, osFlagsWaitAny, abs_time, clear); } EventFlags::~EventFlags() @@ -97,14 +120,14 @@ EventFlags::~EventFlags() #endif } -uint32_t EventFlags::wait(uint32_t flags, uint32_t opt, uint32_t millisec, bool clear) +uint32_t EventFlags::wait_for(uint32_t flags, uint32_t opt, Kernel::Clock::duration_u32 rel_time, bool clear) { if (clear == false) { opt |= osFlagsNoClear; } #if MBED_CONF_RTOS_PRESENT - return osEventFlagsWait(_id, flags, opt, millisec); + return osEventFlagsWait(_id, flags, opt, rel_time.count()); #else rtos::internal::flags_check_capture check; check.flags = &_flags; @@ -112,10 +135,10 @@ uint32_t EventFlags::wait(uint32_t flags, uint32_t opt, uint32_t millisec, bool check.flags_wanted = flags; check.result = 0; check.match = false; - mbed::internal::do_timed_sleep_relative_or_forever(millisec, rtos::internal::non_rtos_check_flags, &check); + mbed::internal::do_timed_sleep_relative_or_forever(rel_time, rtos::internal::non_rtos_check_flags, &check); if (check.match) { return check.result; - } else if (millisec == 0) { + } else if (rel_time == rel_time.zero()) { return osErrorResource; } else { return osErrorTimeout; @@ -123,4 +146,21 @@ uint32_t EventFlags::wait(uint32_t flags, uint32_t opt, uint32_t millisec, bool #endif } +uint32_t EventFlags::wait_until(uint32_t flags, uint32_t opt, Kernel::Clock::time_point abs_time, bool clear) +{ + Kernel::Clock::time_point now = Kernel::Clock::now(); + + Kernel::Clock::duration_u32 rel_time; + if (now >= abs_time) { + rel_time = rel_time.zero(); + } else if (abs_time - now > Kernel::wait_for_u32_max) { + // Documentation permits early return for big offsets + rel_time = Kernel::wait_for_u32_max; + } else { + rel_time = abs_time - now; + } + return wait_for(flags, opt, rel_time, clear); +} + + } From dd95de8ecbd0f815bf73ec8f08d5f47f038b8975 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 11:07:12 +0200 Subject: [PATCH 09/19] Add Chrono support to Queue,MemoryPool,Mail --- rtos/Mail.h | 105 ++++++++++++++++++++++++++++++++++++++++++---- rtos/MemoryPool.h | 95 +++++++++++++++++++++++++++++++++++------ rtos/Queue.h | 102 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 273 insertions(+), 29 deletions(-) diff --git a/rtos/Mail.h b/rtos/Mail.h index a4a584abf45..382994bc711 100644 --- a/rtos/Mail.h +++ b/rtos/Mail.h @@ -67,7 +67,7 @@ class Mail : private mbed::NonCopyable > { * * @note You cannot call this function from ISR context. */ - Mail() { }; + Mail() = default; /** Check if the mail queue is empty. * @@ -109,6 +109,19 @@ class Mail : private mbed::NonCopyable > { return _pool.alloc(); } + /** Allocate a memory block of type T, optionally blocking. + * + * @param rel_time Timeout value, or Kernel::wait_for_u32_forever. + * + * @return Pointer to memory block that you can fill with mail or nullptr in case error. + * + * @note You may call this function from ISR context if the millisec parameter is set to 0. + */ + T *alloc_for(Kernel::Clock::duration_u32 rel_time) + { + return _pool.alloc_for(rel_time); + } + /** Allocate a memory block of type T, optionally blocking. * * @param millisec Timeout value, or osWaitForever. @@ -116,10 +129,29 @@ class Mail : private mbed::NonCopyable > { * @return Pointer to memory block that you can fill with mail or nullptr in case error. * * @note You may call this function from ISR context if the millisec parameter is set to 0. + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") T *alloc_for(uint32_t millisec) { - return _pool.alloc_for(millisec); + return alloc_for(std::chrono::duration(millisec)); + } + + /** Allocate a memory block of type T, blocking. + * + * @param abs_time Absolute timeout time, referenced to Kernel::Clock. + * + * @return Pointer to memory block that you can fill with mail or nullptr in case error. + * + * @note You cannot call this function from ISR context. + * @note the underlying RTOS may have a limit to the maximum wait time + * due to internal 32-bit computations, but this is guaranteed to work if the + * wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + * the wait will time out earlier than specified. + */ + T *alloc_until(Kernel::Clock::time_point abs_time) + { + return _pool.alloc_until(abs_time); } /** Allocate a memory block of type T, blocking. @@ -133,10 +165,13 @@ class Mail : private mbed::NonCopyable > { * due to internal 32-bit computations, but this is guaranteed to work if the * wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, * the wait will time out earlier than specified. + * @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + * rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") T *alloc_until(uint64_t millisec) { - return _pool.alloc_until(millisec); + return alloc_until(Kernel::Clock::time_point(std::chrono::duration(millisec))); } /** Allocate a memory block of type T, and set memory block to zero. @@ -153,6 +188,19 @@ class Mail : private mbed::NonCopyable > { return _pool.calloc(); } + /** Allocate a memory block of type T, optionally blocking, and set memory block to zero. + * + * @param rel_time Timeout value, or Kernel::wait_for_u32_forever. + * + * @return Pointer to memory block that you can fill with mail or nullptr in case error. + * + * @note You may call this function from ISR context if the rel_time parameter is set to 0. + */ + T *calloc_for(Kernel::Clock::duration_u32 rel_time) + { + return _pool.alloc_for(rel_time); + } + /** Allocate a memory block of type T, optionally blocking, and set memory block to zero. * * @param millisec Timeout value, or osWaitForever. @@ -160,10 +208,29 @@ class Mail : private mbed::NonCopyable > { * @return Pointer to memory block that you can fill with mail or nullptr in case error. * * @note You may call this function from ISR context if the millisec parameter is set to 0. + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") T *calloc_for(uint32_t millisec) { - return _pool.calloc_for(millisec); + return calloc_for(std::chrono::duration(millisec)); + } + + /** Allocate a memory block of type T, blocking, and set memory block to zero. + * + * @param abs_time Absolute timeout time, referenced to Kernel::Clock. + * + * @return Pointer to memory block that you can fill with mail or nullptr in case error. + * + * @note You cannot call this function from ISR context. + * @note the underlying RTOS may have a limit to the maximum wait time + * due to internal 32-bit computations, but this is guaranteed to work if the + * wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + * the wait will time out earlier than specified. + */ + T *calloc_until(Kernel::Clock::time_point abs_time) + { + return _pool.calloc_until(abs_time); } /** Allocate a memory block of type T, blocking, and set memory block to zero. @@ -177,10 +244,13 @@ class Mail : private mbed::NonCopyable > { * due to internal 32-bit computations, but this is guaranteed to work if the * wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, * the wait will time out earlier than specified. + * @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + * rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") T *calloc_until(uint64_t millisec) { - return _pool.calloc_until(millisec); + return calloc_until(Kernel::Clock::time_point(std::chrono::duration(millisec))); } /** Put a mail in the queue. @@ -198,7 +268,26 @@ class Mail : private mbed::NonCopyable > { /** Get a mail from the queue. * - * @param millisec Timeout value (default: osWaitForever). + * @param millisec Timeout value. + * + * @return Event that contains mail information or error code. + * @retval osEventMessage Message received. + * @retval osOK No mail is available (and no timeout was specified). + * @retval osEventTimeout No mail has arrived during the given timeout period. + * @retval osErrorParameter A parameter is invalid or outside of a permitted range. + * + * @note You may call this function from ISR context if the millisec parameter is set to 0. + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. + */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") + osEvent get(uint32_t millisec) + { + return get(std::chrono::duration(millisec)); + } + + /** Get a mail from the queue. + * + * @param rel_time Timeout value (default: Kernel::wait_for_u32_forever). * * @return Event that contains mail information or error code. * @retval osEventMessage Message received. @@ -208,9 +297,9 @@ class Mail : private mbed::NonCopyable > { * * @note You may call this function from ISR context if the millisec parameter is set to 0. */ - osEvent get(uint32_t millisec = osWaitForever) + osEvent get(Kernel::Clock::duration_u32 rel_time = Kernel::wait_for_u32_forever) { - osEvent evt = _queue.get(millisec); + osEvent evt = _queue.get(rel_time); if (evt.status == osEventMessage) { evt.status = osEventMail; } diff --git a/rtos/MemoryPool.h b/rtos/MemoryPool.h index 3c4d856d6bc..311f7b6372d 100644 --- a/rtos/MemoryPool.h +++ b/rtos/MemoryPool.h @@ -91,15 +91,38 @@ class MemoryPool : private mbed::NonCopyable > { return (T *)osMemoryPoolAlloc(_id, 0); } + /** Allocate a memory block from a memory pool, without blocking. + @return address of the allocated memory block or nullptr in case of no memory available. + + @note You may call this function from ISR context. + */ + T *try_alloc(void) + { + return (T *)osMemoryPoolAlloc(_id, 0); + } + /** Allocate a memory block from a memory pool, optionally blocking. @param millisec timeout value (osWaitForever to wait forever) @return address of the allocated memory block or nullptr in case of no memory available. @note You may call this function from ISR context if the millisec parameter is set to 0. + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") T *alloc_for(uint32_t millisec) { - return (T *)osMemoryPoolAlloc(_id, millisec); + return alloc_for(std::chrono::duration(millisec)); + } + + /** Allocate a memory block from a memory pool, optionally blocking. + @param rel_time timeout value (Kernel::wait_for_u32_forever to wait forever) + @return address of the allocated memory block or nullptr in case of no memory available. + + @note You may call this function from ISR context if the rel_time parameter is set to 0. + */ + T *alloc_for(Kernel::Clock::duration_u32 rel_time) + { + return (T *)osMemoryPoolAlloc(_id, rel_time.count()); } /** Allocate a memory block from a memory pool, blocking. @@ -111,21 +134,38 @@ class MemoryPool : private mbed::NonCopyable > { due to internal 32-bit computations, but this is guaranteed to work if the wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, the wait will time out earlier than specified. + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") T *alloc_until(uint64_t millisec) { - uint64_t now = Kernel::get_ms_count(); - uint32_t delay; - if (now >= millisec) { - delay = 0; - } else if (millisec - now >= osWaitForever) { - delay = osWaitForever - 1; + return alloc_until(Kernel::Clock::time_point(std::chrono::duration(millisec))); + } + + /** Allocate a memory block from a memory pool, blocking. + @param abs_time absolute timeout time, referenced to Kernel::Clock. + @return address of the allocated memory block or nullptr in case of no memory available. + + @note You cannot call this function from ISR context. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the wait will time out earlier than specified. + */ + T *alloc_until(Kernel::Clock::time_point abs_time) + { + Kernel::Clock::time_point now = Kernel::Clock::now(); + Kernel::Clock::duration_u32 rel_time; + if (now >= abs_time) { + rel_time = rel_time.zero(); + } else if (abs_time - now > Kernel::wait_for_u32_max) { + rel_time = Kernel::wait_for_u32_max; } else { - delay = millisec - now; + rel_time = abs_time - now; } - return alloc_for(delay); + return alloc_for(rel_time); } - /** Allocate a memory block from a memory pool, without blocking, and set memory block to zero. @return address of the allocated memory block or nullptr in case of no memory available. @@ -145,10 +185,23 @@ class MemoryPool : private mbed::NonCopyable > { @return address of the allocated memory block or nullptr in case of no memory available. @note You may call this function from ISR context if the millisec parameter is set to 0. + @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") T *calloc_for(uint32_t millisec) { - T *item = alloc_for(millisec); + return calloc_for(std::chrono::duration(millisec)); + } + + /** Allocate a memory block from a memory pool, optionally blocking, and set memory block to zero. + @param rel_time timeout value (Kernel::wait_for_u32_forever to wait forever) + @return address of the allocated memory block or nullptr in case of no memory available. + + @note You may call this function from ISR context if the rel_time parameter is set to 0. + */ + T *calloc_for(Kernel::Clock::duration_u32 rel_time) + { + T *item = alloc_for(rel_time); if (item != nullptr) { memset(item, 0, sizeof(T)); } @@ -164,10 +217,28 @@ class MemoryPool : private mbed::NonCopyable > { due to internal 32-bit computations, but this is guaranteed to work if the wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, the wait will time out earlier than specified. + @deprecated Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` + rather than `Kernel::get_ms_count() + 5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer millisecond count. For example use `Kernel::Clock::now() + 5s` rather than `Kernel::get_ms_count() + 5000`.") T *calloc_until(uint64_t millisec) { - T *item = alloc_until(millisec); + return alloc_until(Kernel::Clock::time_point(std::chrono::duration(millisec))); + } + + /** Allocate a memory block from a memory pool, blocking, and set memory block to zero. + @param abs_time absolute timeout time, referenced to Kernel::Clock. + @return address of the allocated memory block or nullptr in case of no memory available. + + @note You cannot call this function from ISR context. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the wait will time out earlier than specified. + */ + T *calloc_until(Kernel::Clock::time_point abs_time) + { + T *item = alloc_until(abs_time); if (item != nullptr) { memset(item, 0, sizeof(T)); } diff --git a/rtos/Queue.h b/rtos/Queue.h index e5f172ad269..9479285dd94 100644 --- a/rtos/Queue.h +++ b/rtos/Queue.h @@ -26,6 +26,7 @@ #include "rtos/mbed_rtos_types.h" #include "rtos/mbed_rtos1_types.h" #include "rtos/mbed_rtos_storage.h" +#include "rtos/Kernel.h" #include "platform/mbed_error.h" #include "platform/NonCopyable.h" @@ -120,6 +121,47 @@ class Queue : private mbed::NonCopyable > { return osMessageQueueGetCount(_id); } + /** Inserts the given element to the end of the queue. + * + * This function puts the message pointed to by `data` into the queue. The + * parameter `prio` is used to sort the message according to their priority + * (higher numbers indicate higher priority) on insertion. + * + * The timeout indicated by the parameter `rel_time` specifies how long the + * function blocks waiting for the message to be inserted into the + * queue. + * + * The parameter `rel_time` can have the following values: + * - When the duration is 0 (the default), the function returns instantly. + * - When the duration is Kernel::wait_for_u32_forever, the function waits for an + * infinite time. + * - For all other values, the function waits for the given duration. + * + * @param data Pointer to the element to insert into the queue. + * @param rel_time Timeout for the operation to be executed, or 0 in case + * of no timeout. (default: 0) + * @param prio Priority of the operation or 0 in case of default. + * (default: 0) + * + * @return Status code that indicates the execution status of the function: + * @a osOK The message has been successfully inserted + * into the queue. + * @a osErrorTimeout The message could not be inserted into the + * queue in the given time. + * @a osErrorResource The message could not be inserted because + * the queue is full. + * @a osErrorParameter Internal error or nonzero timeout specified + * in an ISR. + * + * @note You may call this function from ISR context if the rel_time + * parameter is set to 0. + * + */ + osStatus put(T *data, Kernel::Clock::duration_u32 rel_time = Kernel::Clock::duration_u32::zero(), uint8_t prio = 0) + { + return osMessageQueuePut(_id, &data, prio, rel_time.count()); + } + /** Inserts the given element to the end of the queue. * * This function puts the message pointed to by `data` into the queue. The @@ -155,11 +197,12 @@ class Queue : private mbed::NonCopyable > { * * @note You may call this function from ISR context if the millisec * parameter is set to 0. - * + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") osStatus put(T *data, uint32_t millisec = 0, uint8_t prio = 0) { - return osMessageQueuePut(_id, &data, prio, millisec); + return put(data, std::chrono::duration(millisec), prio); } /** Get a message or wait for a message from the queue. @@ -167,12 +210,12 @@ class Queue : private mbed::NonCopyable > { * This function retrieves a message from the queue. The message is stored * in the value field of the returned `osEvent` object. * - * The timeout specified by the parameter `millisec` specifies how long the + * The timeout specified by the parameter `rel_time` specifies how long the * function waits to retrieve the message from the queue. * * The timeout parameter can have the following values: * - When the timeout is 0, the function returns instantly. - * - When the timeout is osWaitForever (default), the function waits + * - When the timeout is Kernel::wait_for_u32_forever (default), the function waits * infinite time until the message is retrieved. * - When the timeout is any other value, the function waits for the * specified time before returning a timeout error. @@ -181,8 +224,8 @@ class Queue : private mbed::NonCopyable > { * share the same priority level, they are retrieved in first-in, first-out * (FIFO) order. * - * @param millisec Timeout value. - * (default: osWaitForever). + * @param rel_time Timeout value. + * (default: Kernel::wait_for_u32_forever). * * @return Event information that includes the message in event. Message * value and the status code in event.status: @@ -194,14 +237,14 @@ class Queue : private mbed::NonCopyable > { * @a osErrorParameter A parameter is invalid or outside of a * permitted range. * - * @note You may call this function from ISR context if the millisec + * @note You may call this function from ISR context if the rel_time * parameter is set to 0. */ - osEvent get(uint32_t millisec = osWaitForever) + osEvent get(Kernel::Clock::duration_u32 rel_time = Kernel::wait_for_u32_forever) { osEvent event; T *data = nullptr; - osStatus_t res = osMessageQueueGet(_id, &data, nullptr, millisec); + osStatus_t res = osMessageQueueGet(_id, &data, nullptr, rel_time.count()); switch (res) { case osOK: @@ -224,6 +267,47 @@ class Queue : private mbed::NonCopyable > { return event; } + /** Get a message or wait for a message from the queue. + * + * This function retrieves a message from the queue. The message is stored + * in the value field of the returned `osEvent` object. + * + * The timeout specified by the parameter `millisec` specifies how long the + * function waits to retrieve the message from the queue. + * + * The timeout parameter can have the following values: + * - When the timeout is 0, the function returns instantly. + * - When the timeout is osWaitForever (default), the function waits + * infinite time until the message is retrieved. + * - When the timeout is any other value, the function waits for the + * specified time before returning a timeout error. + * + * Messages are retrieved in descending priority order. If two messages + * share the same priority level, they are retrieved in first-in, first-out + * (FIFO) order. + * + * @param millisec Timeout value. + * (default: osWaitForever). + * + * @return Event information that includes the message in event. Message + * value and the status code in event.status: + * @a osEventMessage Message successfully received. + * @a osOK No message is available in the queue, and no + * timeout was specified. + * @a osEventTimeout No message was received before a timeout + * event occurred. + * @a osErrorParameter A parameter is invalid or outside of a + * permitted range. + * + * @note You may call this function from ISR context if the millisec + * parameter is set to 0. + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. + */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") + osEvent get(uint32_t millisec = osWaitForever) + { + return get(std::chrono::duration(millisec)); + } private: osMessageQueueId_t _id; char _queue_mem[queue_sz * (sizeof(T *) + sizeof(mbed_rtos_storage_message_t))]; From 628d67b1968a9808fbf834f46858d81c23f6dd69 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Tue, 3 Mar 2020 16:48:40 +0200 Subject: [PATCH 10/19] Add HighResClock and LowPowerClock --- drivers/HighResClock.h | 80 +++++++++++++++++++++++++++++++++++++++++ drivers/LowPowerClock.h | 66 ++++++++++++++++++++++++++++++++++ mbed.h | 2 ++ 3 files changed, 148 insertions(+) create mode 100644 drivers/HighResClock.h create mode 100644 drivers/LowPowerClock.h diff --git a/drivers/HighResClock.h b/drivers/HighResClock.h new file mode 100644 index 00000000000..079708090dd --- /dev/null +++ b/drivers/HighResClock.h @@ -0,0 +1,80 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_HIGHRESCLOCK_H +#define MBED_HIGHRESCLOCK_H + +#include +#include "hal/us_ticker_api.h" +#include "platform/mbed_power_mgmt.h" + +namespace mbed { +/** + * \defgroup drivers_HighResClock HighResClock class + * \ingroup drivers-public-api-ticker + * @{ + */ + +/** + * A C++11 Clock representing the HAL us_ticker. + * + * The high resolution clock will pause whenever deep sleep is entered. + * + * Lock and unlock methods are provided to control deep sleep to keep the + * clock running. LowPowerClock provides the same methods as dummies to support + * generic code that works with either clock. + * + * Locks and unlocks are passed to the deep sleep manager, so are reference counted. + * + * The lock and unlock methods mean this is a C++11 BasicLockable class, + * so scoped_lock or unique_lock could be used to + * manage ownership and guarantee balanced unlocking. + * + * @note To avoid the need to worry about locking, consider using the Timer, Ticker or + * Timeout classes, which lock automatically and only whenever they are active + */ +class HighResClock { +public: + /* duration is C++11 standard microseconds, so representation will be 64-bit signed integer */ + using duration = std::chrono::microseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static const bool is_steady = false; + + /** Read the current time */ + static time_point now() + { + return time_point{duration{ticker_read_us(get_us_ticker_data())}}; + } + + /** Lock the clock to ensure it stays running */ + static void lock() + { + sleep_manager_lock_deep_sleep(); + } + + /** Unlock the clock, allowing it to stop during power saving */ + static void unlock() + { + sleep_manager_unlock_deep_sleep(); + } +}; + +/** @}*/ + +} +#endif /* MBED_HIGHRESCLOCK_H */ diff --git a/drivers/LowPowerClock.h b/drivers/LowPowerClock.h new file mode 100644 index 00000000000..e9e66a6a20d --- /dev/null +++ b/drivers/LowPowerClock.h @@ -0,0 +1,66 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_LOWPOWERCLOCK_H +#define MBED_LOWPOWERCLOCK_H + +#include +#include "hal/lp_ticker_api.h" + +namespace mbed { +/** + * \defgroup drivers_LowPowerClock LowPowerClock class + * \ingroup drivers-public-api-ticker + * @{ + */ + +/** + * A C++11 Clock representing the HAL lp_ticker. + * + * Dummy lock/unlock methods are provided to have the same generic API + * as UnlockedHighPowerClock. No action is required as the lp_ticker + * runs continuously. + */ +class LowPowerClock { +public: + /* duration is C++11 standard microseconds, so representation will be 64-bit signed integer */ + using duration = std::chrono::microseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static const bool is_steady = true; + + /** Read the current time */ + static time_point now() + { + return time_point{duration{ticker_read_us(get_lp_ticker_data())}}; + } + + /** Lock the clock to ensure it stays running; dummy for API compatibility with HighResClock */ + static void lock() + { + } + + /** Unlock the clock, allowing it to stop during power saving; dummy for API compatibility with HighResClock */ + static void unlock() + { + } +}; + +/** @}*/ + +} +#endif /* MBED_LOWPOWERCLOCK_H */ diff --git a/mbed.h b/mbed.h index 350826c37ae..84b5fa9d865 100644 --- a/mbed.h +++ b/mbed.h @@ -79,9 +79,11 @@ // mbed Internal components #include "drivers/ResetReason.h" +#include "drivers/HighResClock.h" #include "drivers/Timer.h" #include "drivers/Ticker.h" #include "drivers/Timeout.h" +#include "drivers/LowPowerClock.h" #include "drivers/LowPowerTimeout.h" #include "drivers/LowPowerTicker.h" #include "drivers/LowPowerTimer.h" From 69cb06785de1bdb2e7a8a9438845378587cec3a6 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 11:21:20 +0200 Subject: [PATCH 11/19] Add Chrono support to Ticker et al --- drivers/LowPowerTicker.h | 8 +- drivers/LowPowerTimeout.h | 40 ++++++-- drivers/LowPowerTimer.h | 4 +- drivers/Ticker.h | 74 +++++++++++---- drivers/TickerDataClock.h | 170 ++++++++++++++++++++++++++++++++++ drivers/Timeout.h | 64 ++++++++++++- drivers/Timer.h | 45 +++++---- drivers/TimerEvent.h | 41 ++++++-- drivers/source/Ticker.cpp | 63 +++++++++---- drivers/source/Timeout.cpp | 38 +++++++- drivers/source/Timer.cpp | 83 +++++++++-------- drivers/source/TimerEvent.cpp | 23 +++-- hal/mbed_ticker_api.c | 15 +++ hal/ticker_api.h | 8 ++ 14 files changed, 545 insertions(+), 131 deletions(-) create mode 100644 drivers/TickerDataClock.h diff --git a/drivers/LowPowerTicker.h b/drivers/LowPowerTicker.h index 9a2a93cdf48..00fef8d1a22 100644 --- a/drivers/LowPowerTicker.h +++ b/drivers/LowPowerTicker.h @@ -40,14 +40,10 @@ namespace mbed { * * @note Synchronization level: Interrupt safe */ -class LowPowerTicker : public Ticker, private NonCopyable { +class LowPowerTicker : public TickerBase { public: - LowPowerTicker() : Ticker(get_lp_ticker_data()) - { - } - - virtual ~LowPowerTicker() + LowPowerTicker() : TickerBase(get_lp_ticker_data()) { } }; diff --git a/drivers/LowPowerTimeout.h b/drivers/LowPowerTimeout.h index 91c60b7c101..c627c0fe1db 100644 --- a/drivers/LowPowerTimeout.h +++ b/drivers/LowPowerTimeout.h @@ -21,9 +21,8 @@ #if DEVICE_LPTICKER || defined(DOXYGEN_ONLY) -#include "hal/lp_ticker_api.h" -#include "drivers/LowPowerTicker.h" -#include "platform/NonCopyable.h" +#include "drivers/LowPowerClock.h" +#include "drivers/Timeout.h" namespace mbed { /** @@ -36,14 +35,37 @@ namespace mbed { * * @note Synchronization level: Interrupt safe */ -class LowPowerTimeout : public LowPowerTicker, private NonCopyable { -#if !defined(DOXYGEN_ONLY) -private: - virtual void handler(void) +class LowPowerTimeout : public TimeoutBase { +public: + LowPowerTimeout(); + + /** Clock to use with attach_absolute, guaranteeing running only while attached or manually locked */ + using clock = LowPowerClock; + + /** Clock to use with attach_absolute, running always */ + using steady_clock = LowPowerClock; + + /** @copydoc TimeoutBase::scheduled_time() */ + LowPowerClock::time_point scheduled_time() const { - _function.call(); + /* Massage from virtual TickerDataClock::time_point used internally to true HighResClock::time_point */ + return LowPowerClock::time_point{TimeoutBase::scheduled_time().time_since_epoch()}; + } + + /** Attach a function to be called by the Timeout, specifying the absolute time + * + * @param func pointer to the function to be called + * @param abs_time the absolute time for the call, referenced to LowPowerClock + * + * @note setting @a abs_time to a time in the past means the event will be scheduled immediately + * resulting in an instant call to the function. + */ + template + void attach_absolute(F &&func, LowPowerClock::time_point abs_time) + { + /* Massage from true LowPowerClock::time_point to virtual TickerDataClock::time_point used internally */ + TimeoutBase::attach_absolute(std::forward(func), TickerDataClock::time_point{abs_time.time_since_epoch()}); } -#endif }; /** @}*/ diff --git a/drivers/LowPowerTimer.h b/drivers/LowPowerTimer.h index 43560362376..2000ef9ca8f 100644 --- a/drivers/LowPowerTimer.h +++ b/drivers/LowPowerTimer.h @@ -36,10 +36,10 @@ namespace mbed { * * @note Synchronization level: Interrupt safe */ -class LowPowerTimer : public Timer, private NonCopyable { +class LowPowerTimer : public TimerBase { public: - LowPowerTimer() : Timer(get_lp_ticker_data()) + LowPowerTimer() : TimerBase(get_lp_ticker_data()) { } diff --git a/drivers/Ticker.h b/drivers/Ticker.h index dac0f99dede..1de8838eaae 100644 --- a/drivers/Ticker.h +++ b/drivers/Ticker.h @@ -17,7 +17,9 @@ #ifndef MBED_TICKER_H #define MBED_TICKER_H +#include #include +#include "drivers/TickerDataClock.h" #include "drivers/TimerEvent.h" #include "platform/Callback.h" #include "platform/mbed_toolchain.h" @@ -25,6 +27,7 @@ #include "hal/lp_ticker_api.h" namespace mbed { + /** * \defgroup drivers_Ticker Ticker class * \ingroup drivers-public-api-ticker @@ -42,6 +45,7 @@ namespace mbed { * // Toggle the blinking LED after 5 seconds * * #include "mbed.h" + * using namespace std::chrono; * * Ticker timer; * DigitalOut led1(LED1); @@ -54,26 +58,20 @@ namespace mbed { * } * * int main() { - * timer.attach(&attime, 5); + * timer.attach(&attime, 5us); * while(1) { * if(flip == 0) { * led1 = !led1; * } else { * led2 = !led2; * } - * wait(0.2); + * ThisThread::sleep_for(200ms); * } * } * @endcode */ -class Ticker : public TimerEvent, private NonCopyable { - +class TickerBase : public TimerEvent, private NonCopyable { public: - Ticker(); - - // When low power ticker is in use, then do not disable deep sleep. - Ticker(const ticker_data_t *data); - /** Attach a function to be called by the Ticker, specifying the interval in seconds * * The method forwards its arguments to attach_us() rather than copying them which @@ -82,18 +80,21 @@ class Ticker : public TimerEvent, private NonCopyable { * possible given attach_us() expects an integer value for the callback interval. * @param func pointer to the function to be called * @param t the time between calls in seconds + * @deprecated Pass a chrono duration, not a float second count. For example use `10ms` rather than `0.01f`. */ #if defined(__ICCARM__) + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not a float second count. For example use `10ms` rather than `0.01f`.") MBED_FORCEINLINE template #else template MBED_FORCEINLINE + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not a float second count. For example use `10ms` rather than `0.01f`.") #endif void attach(F &&func, float t) { - attach_us(std::forward(func), t * 1000000.0f); + auto float_interval = std::chrono::duration(t); + attach(std::forward(func), std::chrono::duration_cast(float_interval)); } - /** Attach a function to be called by the Ticker, specifying the interval in microseconds * * @param func pointer to the function to be called @@ -102,15 +103,23 @@ class Ticker : public TimerEvent, private NonCopyable { * @note setting @a t to a value shorter than it takes to process the ticker callback * causes the system to hang. Ticker callback is called constantly with no time * for threads scheduling. + * @deprecated Pass a chrono duration, not an integer microsecond count. For example use `10ms` rather than `10000`. * */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer microsecond count. For example use `10ms` rather than `10000`.") void attach_us(Callback func, us_timestamp_t t); - - virtual ~Ticker() - { - detach(); - } + /** Attach a function to be called by the Ticker, specifying the interval in microseconds + * + * @param func pointer to the function to be called + * @param t the time between calls in micro-seconds + * + * @note setting @a t to a value shorter than it takes to process the ticker callback + * causes the system to hang. Ticker callback is called constantly with no time + * for threads scheduling. + * + */ + void attach(Callback func, std::chrono::microseconds t); /** Detach the function */ @@ -118,16 +127,41 @@ class Ticker : public TimerEvent, private NonCopyable { #if !defined(DOXYGEN_ONLY) protected: - void setup(us_timestamp_t t); - virtual void handler(); + TickerBase(const ticker_data_t *data); + TickerBase(const ticker_data_t *data, bool lock_deepsleep); -protected: - us_timestamp_t _delay; /**< Time delay (in microseconds) for resetting the multishot callback. */ + ~TickerBase() + { + detach(); + } + + /** Attach a function to be called by the Ticker, specifying the absolute call time + * + * If used, handler must be overridden, as TickerBase::handler would attempt + * to reschedule. This is done by `TimeoutBase` (used by `Timeout` and `LowPowerTimeout`). + * + * @param func pointer to the function to be called + * @param abs_time the time for the call + * + * @note setting @a abs_time to a time in the past means the event will be scheduled immediately + * resulting in an instant call to the function. + */ + void attach_absolute(Callback func, TickerDataClock::time_point abs_time); + + void handler() override; + std::chrono::microseconds _delay; /**< Time delay (in microseconds) for resetting the multishot callback. */ Callback _function; /**< Callback. */ bool _lock_deepsleep; /**< Flag which indicates if deep sleep should be disabled. */ #endif +private: + void setup(std::chrono::microseconds t); + void setup_absolute(TickerDataClock::time_point t); }; +class Ticker : public TickerBase { +public: + Ticker(); +}; /** @}*/ } // namespace mbed diff --git a/drivers/TickerDataClock.h b/drivers/TickerDataClock.h new file mode 100644 index 00000000000..5c894442f49 --- /dev/null +++ b/drivers/TickerDataClock.h @@ -0,0 +1,170 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_TICKERDATACLOCK_H +#define MBED_TICKERDATACLOCK_H + +#include +#include "hal/ticker_api.h" + +namespace mbed { +/** + * \defgroup drivers_TickerDataClock TickerDataClock class + * \ingroup drivers-public-api-ticker + * @{ + */ + +/** + * A partial implementation of a C++11 Clock representing a HAL ticker. + * + * This class allows us to create chrono time_points for objects like Timer, + * with the limitation that the tickers are not singletons. This means: + * + * * the now() function is not static - this will limit + * use with some algorithms, + * * there is no distinction between time_points for different + * tickers + * + * This "pseudo-Clock" approach has been endorsed by Howard Hinnant + * (designer of Chrono) here: + * https://stackoverflow.com/questions/56400313/why-does-the-c-standard-require-the-clocknow-function-to-be-static + * + * TickerDataClock::time_point values should only be used with mbed APIs specifically taking + * them, not passed to generic templated chrono algorithms, and it is up to the user to use them + * in conjunction with the correct TickerDataClock. + * + * operators for `->` and conversion to `ticker_data_t *` are provided allowing + * TickerDataClock to be easily substituted in place of a `ticker_data_t *`. + */ +class TickerDataClock { +public: + /** Construct a TickerDataClock referring to a ticker_data_t */ + constexpr TickerDataClock(const ticker_data_t *ticker) : _ticker(ticker) + { + } + + /* duration is C++11 standard microseconds, so representation will be 64-bit signed integer */ + using duration = std::chrono::microseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static const bool is_steady = false; + + /** Initialize a ticker and set the event handler + * + * @param handler A handler to be set + */ + void set_handler(ticker_event_handler handler) + { + return ticker_set_handler(_ticker, handler); + } + + /** Remove an event from the queue + * + * @param obj The event object to be removed from the queue + */ + void remove_event(ticker_event_t *obj) + { + ticker_remove_event(_ticker, obj); + } + + /** Insert an event to the queue + * + * The event will be executed in timestamp - ticker_read_us() us. + * + * @note If an event is inserted with a timestamp less than the current + * timestamp then the event will be scheduled immediately resulting in + * an instant call to event handler. + * + * @param obj The event object to be inserted to the queue + * @param timestamp The event's timestamp + * @param id The event object + */ + void insert_event(ticker_event_t *obj, time_point timestamp, uint32_t id) + { + ticker_insert_event_us(_ticker, obj, timestamp.time_since_epoch().count(), id); + } + + /** Read the current (absolute) ticker's timestamp + * + * @warning Return an absolute timestamp counting from the initialization of the + * ticker. + * + * @return The current timestamp + */ + time_point now() const + { + return time_point(duration(ticker_read_us(_ticker))); + } + + /** Read the next event's timestamp + * + * @param timestamp The timestamp object. + * @return 1 if timestamp is pending event, 0 if there's no event pending + */ + int get_next_timestamp(time_point *timestamp) const + { + us_timestamp_t t; + int ret = ticker_get_next_timestamp_us(_ticker, &t); + *timestamp = time_point(duration(t)); + return ret; + } + + /** Suspend this ticker + * + * When suspended reads will always return the same time and no + * events will be dispatched. When suspended the common layer + * will only ever call the interface function clear_interrupt() + * and that is only if ticker_irq_handler is called. + * + */ + void suspend() + { + ticker_suspend(_ticker); + } + + /** Resume this ticker + * + * When resumed the ticker will ignore any time that has passed + * and continue counting up where it left off. + */ + void resume() + { + ticker_resume(_ticker); + } + + constexpr const ticker_data_t *operator->() const + { + return _ticker; + } + constexpr operator const ticker_data_t *() const + { + return _ticker; + } + +private: + const ticker_data_t *const _ticker; +}; + +inline TickerDataClock::time_point get_time_point(const ticker_event_t &event) +{ + return TickerDataClock::time_point{TickerDataClock::duration{event.timestamp}}; +} + +/** @}*/ + +} +#endif /* MBED_TICKERDATACLOCK_H */ diff --git a/drivers/Timeout.h b/drivers/Timeout.h index bbf71c4c85e..932690e60d1 100644 --- a/drivers/Timeout.h +++ b/drivers/Timeout.h @@ -17,8 +17,8 @@ #ifndef MBED_TIMEOUT_H #define MBED_TIMEOUT_H +#include "drivers/HighResClock.h" #include "drivers/Ticker.h" -#include "platform/NonCopyable.h" namespace mbed { /** @@ -57,14 +57,72 @@ namespace mbed { * } * @endcode */ -class Timeout : public Ticker, private NonCopyable { +class TimeoutBase : public TickerBase { +public: + /** Return time remaining until callback + * + * @return time remaining until callback is due + * + * @note if the callback is overdue, or has already run, the returned value will be negative + */ + std::chrono::microseconds remaining_time() const + { + return scheduled_time() - _ticker_data.now(); + } #if !defined(DOXYGEN_ONLY) protected: - virtual void handler(); + using TickerBase::TickerBase; + ~TimeoutBase() = default; + + void handler() final; + + /** Return scheduled callback time + * + * @return scheduled callback time + * + * @note if the callback is overdue, or has already run, the returned value will be in the past + */ + TickerDataClock::time_point scheduled_time() const + { + return get_time_point(event); + } #endif }; +class Timeout : public TimeoutBase { +public: + Timeout(); + + /** Clock to use with attach_absolute, guaranteeing running only while attached or manually locked */ + using clock = HighResClock; + + /** Clock to use with attach_absolute, running always */ + using steady_clock = SteadyHighResClock; + + /** @copydoc TimeoutBase::scheduled_time() */ + HighResClock::time_point scheduled_time() const + { + /* Massage from virtual TickerDataClock::time_point used internally to true HighResClock::time_point */ + return HighResClock::time_point{TimeoutBase::scheduled_time().time_since_epoch()}; + } + + /** Attach a function to be called by the Timeout, specifying the absolute time + * + * @param func pointer to the function to be called + * @param abs_time the absolute time for the call, referenced to HighResClock + * + * @note setting @a abs_time to a time in the past means the event will be scheduled immediately + * resulting in an instant call to the function. + */ + template + void attach_absolute(F &&func, HighResClock::time_point abs_time) + { + /* Massage from true HighResClock::time_point to virtual TickerDataClock::time_point used internally */ + TimeoutBase::attach_absolute(std::forward(func), TickerDataClock::time_point{abs_time.time_since_epoch()}); + } +}; + /** @}*/ } // namespace mbed diff --git a/drivers/Timer.h b/drivers/Timer.h index 03474cc6f60..cb8768f0cfd 100644 --- a/drivers/Timer.h +++ b/drivers/Timer.h @@ -18,7 +18,7 @@ #define MBED_TIMER_H #include "platform/platform.h" -#include "hal/ticker_api.h" +#include "drivers/TickerDataClock.h" #include "platform/NonCopyable.h" namespace mbed { @@ -51,13 +51,9 @@ namespace mbed { * } * @endcode */ -class Timer : private NonCopyable { +class TimerBase : private NonCopyable { public: - Timer(); - Timer(const ticker_data_t *data); - ~Timer(); - /** Start the timer */ void start(); @@ -76,40 +72,57 @@ class Timer : private NonCopyable { * * @returns Time passed in seconds */ - float read(); + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Floating point operators should normally be avoided for code size. If really needed, you can use `duration{elapsed_time()}.count()`") + float read() const; /** Get the time passed in milliseconds * * @returns Time passed in milliseconds */ - int read_ms(); + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Use the Chrono-based elapsed_time method. If integer milliseconds are needed, you can use `duration_cast(elapsed_time()).count()`") + int read_ms() const; /** Get the time passed in microseconds * * @returns Time passed in microseconds */ - int read_us(); + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Use the Chrono-based elapsed_time method. If integer microseconds are needed, you can use `elapsed_time().count()`") + int read_us() const; /** An operator shorthand for read() */ - operator float(); + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Floating point operators should normally be avoided for code size. If really needed, you can use `duration{elapsed_time()}.count()`") + operator float() const; /** Get in a high resolution type the time passed in microseconds. * Returns a 64 bit integer. */ - us_timestamp_t read_high_resolution_us(); + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Use the Chrono-based elapsed_time method. If integer microseconds are needed, you can use `elapsed_time().count()`") + us_timestamp_t read_high_resolution_us() const; + + /** Get in a high resolution type the time passed in microseconds. + * Returns a 64 bit integer chrono duration. + */ + std::chrono::microseconds elapsed_time() const; #if !defined(DOXYGEN_ONLY) protected: - us_timestamp_t slicetime(); - int _running; // whether the timer is running - us_timestamp_t _start; // the start time of the latest slice - us_timestamp_t _time; // any accumulated time from previous slices - const ticker_data_t *_ticker_data; + TimerBase(const ticker_data_t *data); + TimerBase(const ticker_data_t *data, bool lock_deepsleep); + ~TimerBase(); + std::chrono::microseconds slicetime() const; + TickerDataClock::time_point _start; // the start time of the latest slice + std::chrono::microseconds _time; // any accumulated time from previous slices + TickerDataClock _ticker_data; bool _lock_deepsleep; // flag that indicates if deep sleep should be disabled + bool _running = false; // whether the timer is running }; #endif +class Timer : public TimerBase { +public: + Timer(); +}; /** @}*/ } // namespace mbed diff --git a/drivers/TimerEvent.h b/drivers/TimerEvent.h index ff00ef3d3fe..47b486f92a0 100644 --- a/drivers/TimerEvent.h +++ b/drivers/TimerEvent.h @@ -19,8 +19,10 @@ #include "hal/ticker_api.h" #include "platform/NonCopyable.h" +#include "drivers/TickerDataClock.h" namespace mbed { + /** * \defgroup drivers_TimerEvent TimerEvent class * \ingroup drivers-public-api-ticker @@ -32,8 +34,8 @@ namespace mbed { * @note Synchronization level: Interrupt safe */ class TimerEvent : private NonCopyable { -public: - TimerEvent(); +#if !defined(DOXYGEN_ONLY) +protected: TimerEvent(const ticker_data_t *data); /** The handler registered with the underlying timer interrupt @@ -44,10 +46,8 @@ class TimerEvent : private NonCopyable { /** Destruction removes it... */ - virtual ~TimerEvent(); + ~TimerEvent(); -#if !defined(DOXYGEN_ONLY) -protected: // The handler called to service the timer event of the derived class virtual void handler() = 0; @@ -62,25 +62,54 @@ class TimerEvent : private NonCopyable { * Ticker's present timestamp is used for reference. For timestamps * from the past the event is scheduled after ticker's overflow. * For reference @see convert_timestamp + * + * @deprecated use `insert(std::chrono::microseconds timestamp)` */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer microsecond count. For example use `5ms` rather than `5000`.") void insert(timestamp_t timestamp); + /** Set relative timestamp of the internal event. + * @param timestamp event's us timestamp + * + * @warning + * Do not insert more than one timestamp. + * The same @a event object is used for every @a insert/insert_absolute call. + * + * @warning + * Ticker's present timestamp is used for reference. For timestamps + * from the past the event is scheduled after ticker's overflow. + * For reference @see convert_timestamp + */ + void insert(std::chrono::microseconds timestamp); + /** Set absolute timestamp of the internal event. * @param timestamp event's us timestamp * * @warning * Do not insert more than one timestamp. * The same @a event object is used for every @a insert/insert_absolute call. + * + * @deprecated use `insert_absolute(TickerDataClock::time_point timestamp)` */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono time_point, not an integer microsecond count. For example use `_ticker_data.now() + 5ms` rather than `ticker_read_us(_ticker_data) + 5000`.") void insert_absolute(us_timestamp_t timestamp); + /** Set absolute timestamp of the internal event. + * @param timestamp event's us timestamp + * + * @warning + * Do not insert more than one timestamp. + * The same @a event object is used for every @a insert/insert_absolute call. + */ + void insert_absolute(TickerDataClock::time_point timestamp); + /** Remove timestamp. */ void remove(); ticker_event_t event; - const ticker_data_t *_ticker_data; + TickerDataClock _ticker_data; #endif }; diff --git a/drivers/source/Ticker.cpp b/drivers/source/Ticker.cpp index 47b49602950..68f128625da 100644 --- a/drivers/source/Ticker.cpp +++ b/drivers/source/Ticker.cpp @@ -16,25 +16,26 @@ */ #include "drivers/Ticker.h" -#include "drivers/TimerEvent.h" -#include "hal/ticker_api.h" -#include "platform/mbed_critical.h" +#include "hal/us_ticker_api.h" +#include "platform/CriticalSectionLock.h" #include "platform/mbed_power_mgmt.h" +using namespace std::chrono; + namespace mbed { -Ticker::Ticker() : TimerEvent(), _delay(0), _lock_deepsleep(true) +TickerBase::TickerBase(const ticker_data_t *data) : TickerBase(data, !data->interface->runs_in_deep_sleep) { } // When low power ticker is in use, then do not disable deep sleep. -Ticker::Ticker(const ticker_data_t *data) : TimerEvent(data), _delay(0), _lock_deepsleep(!data->interface->runs_in_deep_sleep) +TickerBase::TickerBase(const ticker_data_t *data, bool lock_deepsleep) : TimerEvent(data), _lock_deepsleep(lock_deepsleep) { } -void Ticker::detach() +void TickerBase::detach() { - core_util_critical_section_enter(); + CriticalSectionLock lock; remove(); // unlocked only if we were attached (we locked it) and this is not low power ticker if (_function && _lock_deepsleep) { @@ -42,36 +43,64 @@ void Ticker::detach() } _function = 0; - core_util_critical_section_exit(); } -void Ticker::setup(us_timestamp_t t) +void TickerBase::setup(microseconds t) { - core_util_critical_section_enter(); remove(); _delay = t; - insert_absolute(_delay + ticker_read_us(_ticker_data)); - core_util_critical_section_exit(); + insert_absolute(_ticker_data.now() + t); +} + +void TickerBase::setup_absolute(TickerDataClock::time_point t) +{ + remove(); + insert_absolute(t); } -void Ticker::handler() +void TickerBase::handler() { - insert_absolute(event.timestamp + _delay); + insert_absolute(get_time_point(event) + _delay); if (_function) { _function(); } } -void Ticker::attach_us(Callback func, us_timestamp_t t) +void TickerBase::attach(Callback func, microseconds t) { - core_util_critical_section_enter(); + CriticalSectionLock lock; // lock only for the initial callback setup and this is not low power ticker if (!_function && _lock_deepsleep) { sleep_manager_lock_deep_sleep(); } _function = func; setup(t); - core_util_critical_section_exit(); +} + +void TickerBase::attach_us(Callback func, us_timestamp_t t) +{ + CriticalSectionLock lock; + // lock only for the initial callback setup and this is not low power ticker + if (!_function && _lock_deepsleep) { + sleep_manager_lock_deep_sleep(); + } + _function = func; + setup(microseconds{t}); +} + +void TickerBase::attach_absolute(Callback func, TickerDataClock::time_point abs_time) +{ + CriticalSectionLock lock; + // lock only for the initial callback setup and this is not low power ticker + if (!_function && _lock_deepsleep) { + sleep_manager_lock_deep_sleep(); + } + _function = func; + setup_absolute(abs_time); +} + +Ticker::Ticker() : TickerBase(get_us_ticker_data(), true) +{ } } // namespace mbed diff --git a/drivers/source/Timeout.cpp b/drivers/source/Timeout.cpp index 42141c4c48c..d890e0ef67d 100644 --- a/drivers/source/Timeout.cpp +++ b/drivers/source/Timeout.cpp @@ -15,14 +15,44 @@ * limitations under the License. */ #include "drivers/Timeout.h" +#include "drivers/LowPowerTimeout.h" +#include "drivers/HighResClock.h" +#include "drivers/LowPowerClock.h" +#include "drivers/RealTimeClock.h" + +using namespace std::chrono; namespace mbed { -void Timeout::handler() +void TimeoutBase::handler() +{ + if (_function) { + if (_lock_deepsleep) { + sleep_manager_unlock_deep_sleep(); + } + _function(); + _function = nullptr; + } +} + +Timeout::Timeout() : TimeoutBase(get_us_ticker_data(), true) +{ +} + +/* A few miscellaneous out-of-line static members from various related classes, + * just to save them getting needing their own cpp file for one line. + * (In C++17 could avoid the need for this by making the members inline). + */ +const bool HighResClock::is_steady; +const bool SteadyHighResClock::is_steady; +const bool LowPowerClock::is_steady; +mstd::once_flag SteadyHighResClock::init; +const bool RealTimeClock::is_steady; + +#if DEVICE_LPTICKER +LowPowerTimeout::LowPowerTimeout() : TimeoutBase(get_lp_ticker_data(), true) { - Callback local = _function; - detach(); - local.call(); } +#endif } // namespace mbed diff --git a/drivers/source/Timer.cpp b/drivers/source/Timer.cpp index d4f2276a416..233731ca486 100644 --- a/drivers/source/Timer.cpp +++ b/drivers/source/Timer.cpp @@ -17,105 +17,108 @@ #include "drivers/Timer.h" #include "hal/ticker_api.h" #include "hal/us_ticker_api.h" +#include "platform/CriticalSectionLock.h" #include "platform/mbed_critical.h" #include "platform/mbed_power_mgmt.h" +using std::milli; +using std::micro; +using namespace std::chrono; + namespace mbed { -Timer::Timer() : _running(), _start(), _time(), _ticker_data(get_us_ticker_data()), _lock_deepsleep(true) +TimerBase::TimerBase(const ticker_data_t *data) : TimerBase(data, !data->interface->runs_in_deep_sleep) { - reset(); } -Timer::Timer(const ticker_data_t *data) : _running(), _start(), _time(), _ticker_data(data), - _lock_deepsleep(!data->interface->runs_in_deep_sleep) +TimerBase::TimerBase(const ticker_data_t *data, bool lock_deepsleep) : _ticker_data(data), _lock_deepsleep(lock_deepsleep) { reset(); } -Timer::~Timer() +TimerBase::~TimerBase() { - core_util_critical_section_enter(); if (_running) { if (_lock_deepsleep) { sleep_manager_unlock_deep_sleep(); } } - _running = 0; - core_util_critical_section_exit(); } -void Timer::start() +void TimerBase::start() { - core_util_critical_section_enter(); + CriticalSectionLock lock; if (!_running) { if (_lock_deepsleep) { sleep_manager_lock_deep_sleep(); } - _start = ticker_read_us(_ticker_data); - _running = 1; + _start = _ticker_data.now(); + _running = true; } - core_util_critical_section_exit(); } -void Timer::stop() +void TimerBase::stop() { - core_util_critical_section_enter(); + CriticalSectionLock lock; _time += slicetime(); if (_running) { if (_lock_deepsleep) { sleep_manager_unlock_deep_sleep(); } } - _running = 0; - core_util_critical_section_exit(); + _running = false; +} + +int TimerBase::read_us() const +{ + return duration(elapsed_time()).count(); } -int Timer::read_us() +float TimerBase::read() const { - return read_high_resolution_us(); + return duration(elapsed_time()).count(); } -float Timer::read() +int TimerBase::read_ms() const { - return (float)read_high_resolution_us() / 1000000.0f; + return duration_cast>(elapsed_time()).count(); } -int Timer::read_ms() +us_timestamp_t TimerBase::read_high_resolution_us() const { - return read_high_resolution_us() / 1000; + return duration_cast>(elapsed_time()).count(); } -us_timestamp_t Timer::read_high_resolution_us() +microseconds TimerBase::elapsed_time() const { - core_util_critical_section_enter(); - us_timestamp_t time = _time + slicetime(); - core_util_critical_section_exit(); - return time; + CriticalSectionLock lock; + return _time + slicetime(); } -us_timestamp_t Timer::slicetime() +microseconds TimerBase::slicetime() const { - us_timestamp_t ret = 0; - core_util_critical_section_enter(); + CriticalSectionLock lock; + microseconds ret; if (_running) { - ret = ticker_read_us(_ticker_data) - _start; + ret = _ticker_data.now() - _start; } - core_util_critical_section_exit(); return ret; } -void Timer::reset() +void TimerBase::reset() +{ + CriticalSectionLock lock; + _start = _ticker_data.now(); + _time = 0s; +} + +TimerBase::operator float() const { - core_util_critical_section_enter(); - _start = ticker_read_us(_ticker_data); - _time = 0; - core_util_critical_section_exit(); + return duration(elapsed_time()).count(); } -Timer::operator float() +Timer::Timer() : TimerBase(get_us_ticker_data(), true) { - return read(); } } // namespace mbed diff --git a/drivers/source/TimerEvent.cpp b/drivers/source/TimerEvent.cpp index be3037e62f2..7861ee06f0a 100644 --- a/drivers/source/TimerEvent.cpp +++ b/drivers/source/TimerEvent.cpp @@ -19,21 +19,18 @@ #include #include "hal/us_ticker_api.h" -namespace mbed { +using namespace std::chrono; -TimerEvent::TimerEvent() : event(), _ticker_data(get_us_ticker_data()) -{ - ticker_set_handler(_ticker_data, (&TimerEvent::irq)); -} +namespace mbed { TimerEvent::TimerEvent(const ticker_data_t *data) : event(), _ticker_data(data) { - ticker_set_handler(_ticker_data, (&TimerEvent::irq)); + _ticker_data.set_handler(irq); } void TimerEvent::irq(uint32_t id) { - TimerEvent *timer_event = (TimerEvent *)id; + TimerEvent *timer_event = reinterpret_cast(id); timer_event->handler(); } @@ -48,14 +45,24 @@ void TimerEvent::insert(timestamp_t timestamp) ticker_insert_event(_ticker_data, &event, timestamp, (uint32_t)this); } +void TimerEvent::insert(microseconds timestamp) +{ + insert_absolute(_ticker_data.now() + timestamp); +} + void TimerEvent::insert_absolute(us_timestamp_t timestamp) { ticker_insert_event_us(_ticker_data, &event, timestamp, (uint32_t)this); } +void TimerEvent::insert_absolute(TickerDataClock::time_point timestamp) +{ + _ticker_data.insert_event(&event, timestamp, (uint32_t)this); +} + void TimerEvent::remove() { - ticker_remove_event(_ticker_data, &event); + _ticker_data.remove_event(&event); } } // namespace mbed diff --git a/hal/mbed_ticker_api.c b/hal/mbed_ticker_api.c index 55aeb7685c0..5f363651bb9 100644 --- a/hal/mbed_ticker_api.c +++ b/hal/mbed_ticker_api.c @@ -465,6 +465,21 @@ int ticker_get_next_timestamp(const ticker_data_t *const data, timestamp_t *time return ret; } +int ticker_get_next_timestamp_us(const ticker_data_t *const data, us_timestamp_t *timestamp) +{ + int ret = 0; + + /* if head is NULL, there are no pending events */ + core_util_critical_section_enter(); + if (data->queue->head != NULL) { + *timestamp = data->queue->head->timestamp; + ret = 1; + } + core_util_critical_section_exit(); + + return ret; +} + void ticker_suspend(const ticker_data_t *const ticker) { core_util_critical_section_enter(); diff --git a/hal/ticker_api.h b/hal/ticker_api.h index 9d3d7ddc91d..28017feea1b 100644 --- a/hal/ticker_api.h +++ b/hal/ticker_api.h @@ -187,6 +187,14 @@ us_timestamp_t ticker_read_us(const ticker_data_t *const ticker); */ int ticker_get_next_timestamp(const ticker_data_t *const ticker, timestamp_t *timestamp); +/** Read the next event's timestamp + * + * @param ticker The ticker object. + * @param timestamp The timestamp object. + * @return 1 if timestamp is pending event, 0 if there's no event pending + */ +int ticker_get_next_timestamp_us(const ticker_data_t *const ticker, us_timestamp_t *timestamp); + /** Suspend this ticker * * When suspended reads will always return the same time and no From e63e74c019cb758885fbf759d1702bc92a6e8502 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 12:55:58 +0200 Subject: [PATCH 12/19] Add mbed_chrono.h for handy extensions --- platform/mbed_chrono.h | 127 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 platform/mbed_chrono.h diff --git a/platform/mbed_chrono.h b/platform/mbed_chrono.h new file mode 100644 index 00000000000..1ee2f49390b --- /dev/null +++ b/platform/mbed_chrono.h @@ -0,0 +1,127 @@ + +/* + * Copyright (c) 2015-2019, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __MBED_CHRONO_H__ +#define __MBED_CHRONO_H__ + +#include +#include +#include +#include + +/** \addtogroup platform-public-api */ +/** @{*/ + +/** + * \defgroup platform_chrono chrono utilities + * + * Additions and variations of std::chrono + * + * - unsigned 32-bit variants of standard signed 64-bit duration types + * - centiseconds and deciseconds + * @{ + */ +namespace mbed { + +/* Extensions declared in mbed::chrono, following pattern of std::chrono */ +namespace chrono { + +/* Add deciseconds and centiseconds - may be + * useful to use lower precision when not messing with templating. + */ +using deciseconds = std::chrono::duration; +using centiseconds = std::chrono::duration; + +/** 32-bit microsecond duration type + * + * Standard std::chrono::microseconds is signed 64-bit. For some purposes + * it's more efficient to process small times as 32-bit. And when doing so, + * as we likely need to worry about wrapping, use of an unsigned + * value to process modulo 2**32 is appropriate. + */ +using microseconds_u32 = std::chrono::duration; + +/** 32-bit millisecond duration type + * + * Standard std::chrono::milliseconds is signed 64-bit. For some purposes + * it's more efficient to process times as 32-bit. And when doing so, + * as we likely need to worry about wrapping, use of an unsigned + * value to process modulo 2**32 is appropriate. + */ +using milliseconds_u32 = std::chrono::duration; + +} // namespace chrono + +inline namespace literals { + +inline namespace chrono_literals { + +/** User-defined literal for deciseconds (1/10 of a second) + * + * Useful in case we might change kernel tick frequency to be slower - with tick frequency 1000Hz, it is + * possible to assign 500ms to a KernelClock::duration, but that would fail at slower rates. + * + * Example use: + * + * using namespace mbed::chrono_literals; + * + * ThisThread::sleep_for(5_ds); + */ +constexpr chrono::deciseconds operator "" _ds(unsigned long long x) +{ + chrono::deciseconds::rep val = static_cast(x); + assert(val >= 0 && static_cast(val) == x); + return chrono::deciseconds(val); +} + +/** User-defined literal for centiseconds (1/100 of a second) + * + * Useful in case we might change kernel tick frequency to be slower - with tick frequency 1000Hz, it is + * possible to assign 500ms to a KernelClock::duration, but that would fail at slower rates. + * + * Example use: + * + * using namespace mbed::chrono_literals; + * + * ThisThread::sleep_for(1_cs); + */ +constexpr chrono::centiseconds operator "" _cs(unsigned long long x) +{ + chrono::centiseconds::rep val = static_cast(x); + assert(val >= 0 && static_cast(val) == x); + return chrono::centiseconds(val); +} + +} // inline namespace chrono_literals + +} // inline namespace literals + +namespace chrono { + +using namespace chrono_literals; + +} // namespace chrono + +} // namespace mbed + +/**@}*/ + +/**@}*/ + +#endif // __MBED_CHRONO_H__ + From 78d47b67ca71a29cd62d3dd0396a423e4e907224 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 12:58:10 +0200 Subject: [PATCH 13/19] Use Chrono in LowPowerTickerWrapper --- hal/LowPowerTickerWrapper.cpp | 10 ++++++---- hal/LowPowerTickerWrapper.h | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/hal/LowPowerTickerWrapper.cpp b/hal/LowPowerTickerWrapper.cpp index a61a7a67fdf..0f03a41f470 100644 --- a/hal/LowPowerTickerWrapper.cpp +++ b/hal/LowPowerTickerWrapper.cpp @@ -17,6 +17,8 @@ #include "hal/LowPowerTickerWrapper.h" #include "platform/Callback.h" +using namespace mbed::chrono; + LowPowerTickerWrapper::LowPowerTickerWrapper(const ticker_data_t *data, const ticker_interface_t *interface, uint32_t min_cycles_between_writes, uint32_t min_cycles_until_match) : _intf(data->interface), _min_count_between_writes(min_cycles_between_writes + 1), _min_count_until_match(min_cycles_until_match + 1), _suspended(false) { @@ -247,12 +249,12 @@ bool LowPowerTickerWrapper::_match_check(timestamp_t current) _ticker_match_interval_passed(_last_set_interrupt, current, _cur_match_time); } -uint32_t LowPowerTickerWrapper::_lp_ticks_to_us(uint32_t ticks) +microseconds_u32 LowPowerTickerWrapper::_lp_ticks_to_us(uint32_t ticks) { MBED_ASSERT(core_util_in_critical_section()); // Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period) - return _us_per_tick * ticks + 4; + return microseconds_u32(_us_per_tick * ticks + 4); } void LowPowerTickerWrapper::_schedule_match(timestamp_t current) @@ -277,7 +279,7 @@ void LowPowerTickerWrapper::_schedule_match(timestamp_t current) // then don't schedule it again. if (!_pending_timeout) { uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match; - _timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); + _timeout.attach(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); _pending_timeout = true; } return; @@ -309,7 +311,7 @@ void LowPowerTickerWrapper::_schedule_match(timestamp_t current) // Low power ticker incremented to less than _min_count_until_match // so low power ticker may not fire. Use Timeout to ensure it does fire. uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match; - _timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); + _timeout.attach(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); _pending_timeout = true; return; } diff --git a/hal/LowPowerTickerWrapper.h b/hal/LowPowerTickerWrapper.h index 7a59261726d..a709d140cd2 100644 --- a/hal/LowPowerTickerWrapper.h +++ b/hal/LowPowerTickerWrapper.h @@ -26,6 +26,7 @@ #include "hal/us_ticker_api.h" #include "drivers/Timeout.h" +#include "platform/mbed_chrono.h" #include "platform/mbed_critical.h" @@ -227,7 +228,7 @@ class LowPowerTickerWrapper { * * This value is always larger or equal to exact value. */ - uint32_t _lp_ticks_to_us(uint32_t); + mbed::chrono::microseconds_u32 _lp_ticks_to_us(uint32_t); /* * Schedule a match interrupt to fire at the correct time From 9c9a5ec7bdd604927b18884f17fcdf807b51ad3d Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 13:06:11 +0200 Subject: [PATCH 14/19] Add Chrono support to Event/EventQueue --- events/Event.h | 51 ++++++-- events/EventQueue.h | 234 ++++++++++++++++++++++++++++------ events/source/equeue_mbed.cpp | 10 +- 3 files changed, 242 insertions(+), 53 deletions(-) diff --git a/events/Event.h b/events/Event.h index ef15882489d..8e13c48632a 100644 --- a/events/Event.h +++ b/events/Event.h @@ -17,6 +17,7 @@ #ifndef EVENT_H #define EVENT_H +#include #include "events/EventQueue.h" #include "platform/mbed_assert.h" @@ -45,6 +46,8 @@ class Event; template class Event { public: + using duration = std::chrono::duration; + /** Create an event * * Constructs an event bound to the specified event queue. The specified @@ -63,13 +66,13 @@ class Event { if (_event) { _event->equeue = &q->_equeue; _event->id = 0; - _event->delay = 0; - _event->period = -1; + _event->delay = duration(0); + _event->period = duration(-1); _event->post = &Event::event_post; _event->dtor = &Event::event_dtor; - new (_event + 1) F(f); + new (_event + 1) F(std::move(f)); _event->ref = 1; } @@ -113,26 +116,48 @@ class Event { /** Configure the delay of an event * - * @param delay Millisecond delay before dispatching the event + * @param d Millisecond delay before dispatching the event */ - void delay(int delay) + void delay(duration d) { if (_event) { - _event->delay = delay; + _event->delay = d; } } + /** Configure the delay of an event + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. + * + * @param d Millisecond delay before dispatching the event + */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") + void delay(int d) + { + delay(duration(d)); + } + /** Configure the period of an event * - * @param period Millisecond period for repeatedly dispatching an event + * @param p Millisecond period for repeatedly dispatching an event */ - void period(int period) + void period(duration p) { if (_event) { - _event->period = period; + _event->period = p; } } + /** Configure the period of an event + * @deprecated Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`. + * + * @param p Millisecond period for repeatedly dispatching an event + */ + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") + void period(int p) + { + period(duration(p)); + } + /** Posts an event onto the underlying event queue * * The event is posted to the underlying queue and is executed in the @@ -209,8 +234,8 @@ class Event { equeue_t *equeue; int id; - int delay; - int period; + duration delay; + duration period; int (*post)(struct event *, ArgTs... args); void (*dtor)(struct event *); @@ -229,8 +254,8 @@ class Event { } new (p) C(*(F *)(e + 1), args...); - equeue_event_delay(p, e->delay); - equeue_event_period(p, e->period); + equeue_event_delay(p, e->delay.count()); + equeue_event_period(p, e->period.count()); equeue_event_dtor(p, &EventQueue::function_dtor); return equeue_post(e->equeue, &EventQueue::function_call, p); } diff --git a/events/EventQueue.h b/events/EventQueue.h index 6f26d6dc3ce..1bcc0186af8 100644 --- a/events/EventQueue.h +++ b/events/EventQueue.h @@ -22,6 +22,8 @@ #include "platform/Callback.h" #include "platform/NonCopyable.h" #include +#include +#include #include namespace events { @@ -59,6 +61,8 @@ class UserAllocatedEvent; */ class EventQueue : private mbed::NonCopyable { public: + using duration = std::chrono::duration; + /** Create an EventQueue * * Create an event queue. The event queue either allocates a buffer of @@ -358,6 +362,7 @@ class EventQueue : private mbed::NonCopyable { * events out of IRQ contexts. * * @param ms Time to delay in milliseconds + * @param f Function to execute in the context of the dispatch loop * @param args Arguments to pass to the callback * @return A unique ID that represents the posted event and can * be passed to cancel, or an ID of 0 if there is not @@ -365,21 +370,22 @@ class EventQueue : private mbed::NonCopyable { * * @code * #include "mbed.h" + * using namespace std::chrono_literals; * * int main() { * // creates a queue with the default size * EventQueue queue; * * // events are simple callbacks - * queue.call_in(2000, printf, "called in 2 seconds\n"); + * queue.call_in(2s, printf, "called in 2 seconds\n"); * * // the dispatch methods executes events * queue.dispatch(); * } * @endcode */ - template - int call_in(int ms, Args ...args); + template + int call_in(duration ms, F f, ArgTs ...args); /** Calls an event on the queue after a specified delay * @@ -399,6 +405,7 @@ class EventQueue : private mbed::NonCopyable { * * @code * #include "mbed.h" + * using namespace std::chrono_literals; * * class EventHandler { * int _id; @@ -419,7 +426,7 @@ class EventQueue : private mbed::NonCopyable { * * // events are simple callbacks, call object method in 2 seconds * // with provided parameter - * queue.call_in(2000, &handler_cb, &EventHandler::handler, 4); + * queue.call_in(2s, &handler_cb, &EventHandler::handler, 4); * * // the dispatch method executes events * queue.dispatch(); @@ -428,8 +435,8 @@ class EventQueue : private mbed::NonCopyable { */ // AStyle ignore, not handling correctly below // *INDENT-OFF* - template - int call_in(int ms, T *obj, R (T::*method)(Args ...args), Args ...args); + template + int call_in(duration ms, T *obj, R (T::*method)(ArgTs ...args), ArgTs ...args); // *INDENT-ON* /** Calls an event on the queue periodically @@ -452,6 +459,7 @@ class EventQueue : private mbed::NonCopyable { * * @code * #include "mbed.h" + * using namespace std::chrono_literals; * * class EventHandler { * int _id; @@ -468,15 +476,15 @@ class EventQueue : private mbed::NonCopyable { * EventQueue queue; * * // events are simple callbacks, call every 2 seconds - * queue.call_every(2000, printf, "Calling every 2 seconds\n"); + * queue.call_every(2s, printf, "Calling every 2 seconds\n"); * * // the dispatch method executes events * queue.dispatch(); * } * @endcode */ - template - int call_every(int ms, F f, Args ...args); + template + int call_every(duration ms, F f, ArgTs ...args); /** Calls an event on the queue periodically * @@ -496,6 +504,7 @@ class EventQueue : private mbed::NonCopyable { * * @code * #include "mbed.h" + * using namespace std::chrono_literals; * * class EventHandler { * int _id; @@ -516,7 +525,7 @@ class EventQueue : private mbed::NonCopyable { * * // events are simple callbacks, call object method every 2 seconds * // with provided parameter - * queue.call_every(2000, &handler_cb, &EventHandler::handler, 6); + * queue.call_every(2s, &handler_cb, &EventHandler::handler, 6); * * // the dispatch method executes events * queue.dispatch(); @@ -525,8 +534,8 @@ class EventQueue : private mbed::NonCopyable { */ // AStyle ignore, not handling correctly below // *INDENT-OFF* - template - int call_every(int ms, T *obj, R (T::*method)(Args ...args), Args ...args); + template + int call_every(duration ms, T *obj, R (T::*method)(ArgTs ...args), ArgTs ...args); // *INDENT-ON* /** Creates an event bound to the event queue @@ -571,8 +580,8 @@ class EventQueue : private mbed::NonCopyable { */ // AStyle ignore, not handling correctly below // *INDENT-OFF* - template - Event event(R (*func)(BoundArgs..., Args...), ContextArgs ...context_args); + template + Event event(R (*func)(BoundArgTs..., ArgTs...), ContextArgTs ...context_args); // *INDENT-ON* /** Creates an event bound to the event queue @@ -619,8 +628,8 @@ class EventQueue : private mbed::NonCopyable { */ // AStyle ignore, not handling correctly below // *INDENT-OFF* - template - Event event(T *obj, R (T::*method)(BoundArgs..., Args...), ContextArgs ...context_args); + template + Event event(T *obj, R (T::*method)(BoundArgTs..., ArgTs...), ContextArgTs ...context_args); // *INDENT-ON* /** Creates an event bound to the event queue @@ -658,8 +667,8 @@ class EventQueue : private mbed::NonCopyable { * } * @endcode */ - template - Event event(mbed::Callback cb, ContextArgs ...context_args); + template + Event event(mbed::Callback cb, ContextArgTs ...context_args); /** Creates an user allocated event bound to the event queue * @@ -749,7 +758,7 @@ class EventQueue : private mbed::NonCopyable { return 0; } - F *e = new (p) F(f); + F *e = new (p) F(std::move(f)); equeue_event_dtor(e, &EventQueue::function_dtor); return equeue_post(&_equeue, &EventQueue::function_call, e); } @@ -763,7 +772,7 @@ class EventQueue : private mbed::NonCopyable { template int call(F f, ArgTs... args) { - return call(context(f, args...)); + return call(context(std::move(f), args...)); } /** Calls an event on the queue @@ -817,15 +826,15 @@ class EventQueue : private mbed::NonCopyable { * enough memory to allocate the event. */ template - int call_in(int ms, F f) + int call_in(duration ms, F f) { void *p = equeue_alloc(&_equeue, sizeof(F)); if (!p) { return 0; } - F *e = new (p) F(f); - equeue_event_delay(e, ms); + F *e = new (p) F(std::move(f)); + equeue_event_delay(e, ms.count()); equeue_event_dtor(e, &EventQueue::function_dtor); return equeue_post(&_equeue, &EventQueue::function_call, e); } @@ -837,45 +846,119 @@ class EventQueue : private mbed::NonCopyable { * @param args Arguments to pass to the callback */ template + int call_in(duration ms, F f, ArgTs... args) + { + return call_in(ms, context(std::move(f), args...)); + } + + /** Calls an event on the queue after a specified delay + * @see EventQueue::call_in + */ + template + int call_in(duration ms, T *obj, R(T::*method)(ArgTs...), ArgTs... args) + { + return call_in(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue after a specified delay + * @see EventQueue::call_in + */ + template + int call_in(duration ms, const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) + { + return call_in(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue after a specified delay + * @see EventQueue::call_in + */ + template + int call_in(duration ms, volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) + { + return call_in(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue after a specified delay + * @see EventQueue::call_in + */ + template + int call_in(duration ms, const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) + { + return call_in(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue after a specified delay + * + * The specified callback will be executed in the context of the event + * queue's dispatch loop. + * + * The call_in function is IRQ safe and can act as a mechanism for moving + * events out of IRQ contexts. + * + * @param ms Time to delay in milliseconds + * @param f Function to execute in the context of the dispatch loop + * @return A unique id that represents the posted event and can + * be passed to cancel, or an id of 0 if there is not + * enough memory to allocate the event. + */ + template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") + int call_in(int ms, F f) + { + return call_in(duration(ms), std::move(f)); + } + + /** Calls an event on the queue after a specified delay + * @see EventQueue::call_in + * @param ms Time to delay in milliseconds + * @param f Function to execute in the context of the dispatch loop + * @param args Arguments to pass to the callback + */ + template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_in(int ms, F f, ArgTs... args) { - return call_in(ms, context(f, args...)); + return call_in(duration(ms), std::move(f), args...); } /** Calls an event on the queue after a specified delay * @see EventQueue::call_in */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_in(int ms, T *obj, R(T::*method)(ArgTs...), ArgTs... args) { - return call_in(ms, mbed::callback(obj, method), args...); + return call_in(duration(ms), obj, method, args...); } /** Calls an event on the queue after a specified delay * @see EventQueue::call_in */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_in(int ms, const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) { - return call_in(ms, mbed::callback(obj, method), args...); + return call_in(duration(ms), obj, method, args...); } /** Calls an event on the queue after a specified delay * @see EventQueue::call_in */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_in(int ms, volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) { - return call_in(ms, mbed::callback(obj, method), args...); + return call_in(duration(ms), obj, method, args...); } /** Calls an event on the queue after a specified delay * @see EventQueue::call_in */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_in(int ms, const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) { - return call_in(ms, mbed::callback(obj, method), args...); + return call_in(duration(ms), obj, method, args...); } /** Calls an event on the queue periodically @@ -896,16 +979,16 @@ class EventQueue : private mbed::NonCopyable { * enough memory to allocate the event. */ template - int call_every(int ms, F f) + int call_every(duration ms, F f) { void *p = equeue_alloc(&_equeue, sizeof(F)); if (!p) { return 0; } - F *e = new (p) F(f); - equeue_event_delay(e, ms); - equeue_event_period(e, ms); + F *e = new (p) F(std::move(f)); + equeue_event_delay(e, ms.count()); + equeue_event_period(e, ms.count()); equeue_event_dtor(e, &EventQueue::function_dtor); return equeue_post(&_equeue, &EventQueue::function_call, e); } @@ -917,45 +1000,122 @@ class EventQueue : private mbed::NonCopyable { * @param ms Period of the event in milliseconds */ template + int call_every(duration ms, F f, ArgTs... args) + { + return call_every(ms, context(std::move(f), args...)); + } + + /** Calls an event on the queue periodically + * @see EventQueue::call_every + */ + template + int call_every(duration ms, T *obj, R(T::*method)(ArgTs...), ArgTs... args) + { + return call_every(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue periodically + * @see EventQueue::call_every + */ + template + int call_every(duration ms, const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) + { + return call_every(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue periodically + * @see EventQueue::call_every + */ + template + int call_every(duration ms, volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) + { + return call_every(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue periodically + * @see EventQueue::call_every + */ + template + int call_every(duration ms, const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) + { + return call_every(ms, mbed::callback(obj, method), args...); + } + + /** Calls an event on the queue periodically + * + * @note The first call_every event occurs after the specified delay. + * To create a periodic event that fires immediately, @see Event. + * + * The specified callback will be executed in the context of the event + * queue's dispatch loop. + * + * The call_every function is IRQ safe and can act as a mechanism for + * moving events out of IRQ contexts. + * + * @param f Function to execute in the context of the dispatch loop + * @param ms Period of the event in milliseconds + * @return A unique id that represents the posted event and can + * be passed to cancel, or an id of 0 if there is not + * enough memory to allocate the event. + */ + template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") + int call_every(int ms, F f) + { + return call_every(duration(ms), std::move(f)); + } + + /** Calls an event on the queue periodically + * @see EventQueue::call_every + * @param f Function to execute in the context of the dispatch loop + * @param args Arguments to pass to the callback + * @param ms Period of the event in milliseconds + */ + template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_every(int ms, F f, ArgTs... args) { - return call_every(ms, context(f, args...)); + return call_every(duration(ms), std::move(f), args...); } /** Calls an event on the queue periodically * @see EventQueue::call_every */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_every(int ms, T *obj, R(T::*method)(ArgTs...), ArgTs... args) { - return call_every(ms, mbed::callback(obj, method), args...); + return call_every(duration(ms), obj, method, args...); } /** Calls an event on the queue periodically * @see EventQueue::call_every */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_every(int ms, const T *obj, R(T::*method)(ArgTs...) const, ArgTs... args) { - return call_every(ms, mbed::callback(obj, method), args...); + return call_every(duration(ms), obj, method, args...); } /** Calls an event on the queue periodically * @see EventQueue::call_every */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_every(int ms, volatile T *obj, R(T::*method)(ArgTs...) volatile, ArgTs... args) { - return call_every(ms, mbed::callback(obj, method), args...); + return call_every(duration(ms), obj, method, args...); } /** Calls an event on the queue periodically * @see EventQueue::call_every */ template + MBED_DEPRECATED_SINCE("mbed-os-6.0.0", "Pass a chrono duration, not an integer millisecond count. For example use `5s` rather than `5000`.") int call_every(int ms, const volatile T *obj, R(T::*method)(ArgTs...) const volatile, ArgTs... args) { - return call_every(ms, mbed::callback(obj, method), args...); + return call_every(duration(ms), obj, method, args...); } /** Creates an event bound to the event queue diff --git a/events/source/equeue_mbed.cpp b/events/source/equeue_mbed.cpp index f875f56f2a3..2380df45233 100644 --- a/events/source/equeue_mbed.cpp +++ b/events/source/equeue_mbed.cpp @@ -49,6 +49,9 @@ void equeue_tick_init() unsigned equeue_tick() { + using unsigned_ms_t = std::chrono::duration; + + unsigned_ms_t d; #if defined MBED_TICKLESS || !MBED_CONF_RTOS_PRESENT // It is not safe to call get_ms_count from ISRs, both // because documentation says so, and because it will give @@ -60,9 +63,9 @@ unsigned equeue_tick() // should not be called from critical sections, for // performance reasons, but I don't have a good // current alternative! - return mbed::internal::os_timer->get_time() / 1000; + d = std::chrono::duration_cast(mbed::internal::os_timer->get_time().time_since_epoch()); } else { - return rtos::Kernel::get_ms_count(); + d = rtos::Kernel::Clock::now().time_since_epoch(); } #else // And this is the legacy behaviour - if running in @@ -70,8 +73,9 @@ unsigned equeue_tick() // documentation saying no. (Most recent CMSIS-RTOS // permits `ososKernelGetTickCount` from IRQ, and our // `rtos::Kernel` wrapper copes too). - return rtos::Kernel::get_ms_count(); + d = rtos::Kernel::Clock::now().time_since_epoch(); #endif + return d.count(); } #else From 981efbc3ef697218f6d7ec5a50e49be06c74134d Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Tue, 25 Feb 2020 14:34:14 +0200 Subject: [PATCH 15/19] Add RealTimeClock --- TESTS/mbed_hal/rtc/main.cpp | 132 ++++++++++++++++----------------- drivers/RealTimeClock.h | 144 ++++++++++++++++++++++++++++++++++++ mbed.h | 1 + 3 files changed, 207 insertions(+), 70 deletions(-) create mode 100644 drivers/RealTimeClock.h diff --git a/TESTS/mbed_hal/rtc/main.cpp b/TESTS/mbed_hal/rtc/main.cpp index 0fb0911dc97..4fc81f97fbd 100644 --- a/TESTS/mbed_hal/rtc/main.cpp +++ b/TESTS/mbed_hal/rtc/main.cpp @@ -25,23 +25,20 @@ #include "rtc_test.h" #include "mbed.h" +#include "drivers/RealTimeClock.h" #include "rtc_api.h" using namespace utest::v1; +using namespace std::chrono; -static const uint32_t WAIT_TIME = 4; -static const uint32_t WAIT_TOLERANCE = 1; - -#define US_PER_SEC 1000000 -#define ACCURACY_FACTOR 10 - -static const uint32_t DELAY_4S = 4; -static const uint32_t DELAY_10S = 10; -static const uint32_t RTC_TOLERANCE = 1; -static const uint32_t TOLERANCE_ACCURACY_US = (DELAY_10S *US_PER_SEC / ACCURACY_FACTOR); +static constexpr auto WAIT_TIME = 4s; +static constexpr auto WAIT_TOLERANCE = 1s; #if DEVICE_LPTICKER -volatile bool expired; +mstd::atomic_bool expired; + +#define TEST_ASSERT_DURATION_WITHIN(delta, expected, actual) \ + TEST_ASSERT_INT64_WITHIN(microseconds(delta).count(), microseconds(expected).count(), microseconds(actual).count()) void set_flag_true(void) { @@ -53,7 +50,7 @@ void set_flag_true(void) void rtc_sleep_test_support(bool deepsleep_mode) { LowPowerTimeout timeout; - const uint32_t start = 100; + const auto start = RealTimeClock::time_point(100s); expired = false; /* @@ -63,17 +60,17 @@ void rtc_sleep_test_support(bool deepsleep_mode) * hardware buffers are empty. However, such an API does not exist now, * so we'll use the wait_ms() function for now. */ - wait_ms(10); + ThisThread::sleep_for(10ms); - rtc_init(); + RealTimeClock::init(); if (deepsleep_mode == false) { sleep_manager_lock_deep_sleep(); } - rtc_write(start); + RealTimeClock::write(start); - timeout.attach(set_flag_true, DELAY_4S); + timeout.attach(set_flag_true, 4s); TEST_ASSERT(sleep_manager_can_deep_sleep_test_check() == deepsleep_mode); @@ -81,9 +78,9 @@ void rtc_sleep_test_support(bool deepsleep_mode) sleep(); } - const uint32_t stop = rtc_read(); + const auto stop = RealTimeClock::now(); - TEST_ASSERT_UINT32_WITHIN(RTC_TOLERANCE, DELAY_4S, stop - start); + TEST_ASSERT_DURATION_WITHIN(1s, 4s, stop - start); timeout.detach(); @@ -91,7 +88,7 @@ void rtc_sleep_test_support(bool deepsleep_mode) sleep_manager_unlock_deep_sleep(); } - rtc_free(); + RealTimeClock::free(); } #endif @@ -99,10 +96,10 @@ void rtc_sleep_test_support(bool deepsleep_mode) void rtc_init_test() { for (int i = 0; i < 10; i++) { - rtc_init(); + RealTimeClock::init(); } - rtc_free(); + RealTimeClock::free(); } #if DEVICE_LPTICKER @@ -121,58 +118,57 @@ void rtc_sleep_test() /* Test that the RTC keeps counting even after ::rtc_free has been called. */ void rtc_persist_test() { - const uint32_t start = 100; - rtc_init(); - rtc_write(start); - rtc_free(); + const auto start = RealTimeClock::time_point(100s); + RealTimeClock::init(); + RealTimeClock::write(start); + RealTimeClock::free(); - ThisThread::sleep_for(WAIT_TIME * 1000); + ThisThread::sleep_for(WAIT_TIME); - rtc_init(); - const uint32_t stop = rtc_read(); - const int enabled = rtc_isenabled(); - rtc_free(); + RealTimeClock::init(); + const auto stop = RealTimeClock::now(); + const bool enabled = RealTimeClock::isenabled(); + RealTimeClock::free(); TEST_ASSERT_TRUE(enabled); - TEST_ASSERT_UINT32_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); + TEST_ASSERT_DURATION_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); } /* Test time does not glitch backwards due to an incorrectly implemented ripple counter driver. */ void rtc_glitch_test() { - const uint32_t start = 0xffffe; - rtc_init(); + const auto start = RealTimeClock::time_point(0xffffes); + RealTimeClock::init(); - rtc_write(start); - uint32_t last = start; - while (last < start + 4) { - const uint32_t cur = rtc_read(); + RealTimeClock::write(start); + auto last = start; + while (last < start + 4s) { + const auto cur = RealTimeClock::now(); TEST_ASSERT(cur >= last); last = cur; } - rtc_free(); + RealTimeClock::free(); } /* Test that the RTC correctly handles different time values. */ void rtc_range_test() { - static const uint32_t starts[] = { - 0x00000000, - 0xEFFFFFFF, - 0x00001000, - 0x00010000, + static const RealTimeClock::time_point starts[] { + 0x00000000s, + 0xEFFFFFFFs, + 0x00001000s, + 0x00010000s, }; - rtc_init(); - for (uint32_t i = 0; i < sizeof(starts) / sizeof(starts[0]); i++) { - const uint32_t start = starts[i]; - rtc_write(start); - ThisThread::sleep_for(WAIT_TIME * 1000); - const uint32_t stop = rtc_read(); - TEST_ASSERT_UINT32_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); + RealTimeClock::init(); + for (const auto &start : starts) { + RealTimeClock::write(start); + ThisThread::sleep_for(WAIT_TIME); + const auto stop = RealTimeClock::now(); + TEST_ASSERT_DURATION_WITHIN(WAIT_TOLERANCE, WAIT_TIME, stop - start); } - rtc_free(); + RealTimeClock::free(); } /* Test that the RTC accuracy is at least 10%. */ @@ -180,44 +176,40 @@ void rtc_accuracy_test() { Timer timer1; - const uint32_t start = 100; - rtc_init(); - rtc_write(start); + const auto start = RealTimeClock::time_point(100s); + RealTimeClock::init(); + RealTimeClock::write(start); timer1.start(); - while (rtc_read() < (start + DELAY_10S)) { + while (RealTimeClock::now() < (start + 10s)) { /* Just wait. */ } timer1.stop(); /* RTC accuracy is at least 10%. */ - TEST_ASSERT_INT32_WITHIN(TOLERANCE_ACCURACY_US, DELAY_10S * US_PER_SEC, timer1.read_us()); + TEST_ASSERT_DURATION_WITHIN(1s, 10s, timer1.read_duration()); } /* Test that ::rtc_write/::rtc_read functions provides availability to set/get RTC time. */ void rtc_write_read_test() { - static const uint32_t rtc_init_val = 100; - - rtc_init(); - - for (int i = 0; i < 3; i++) { - const uint32_t init_val = (rtc_init_val + i * rtc_init_val); + RealTimeClock::init(); + for (auto init_val = RealTimeClock::time_point(100s); init_val < RealTimeClock::time_point(400s); init_val += 100s) { core_util_critical_section_enter(); - rtc_write(init_val); - const uint32_t read_val = rtc_read(); + RealTimeClock::write(init_val); + const auto read_val = RealTimeClock::now(); core_util_critical_section_exit(); /* No tolerance is provided since we should have 1 second to * execute this case after the RTC time is set. */ - TEST_ASSERT_EQUAL_UINT32(init_val, read_val); + TEST_ASSERT(init_val == read_val); } - rtc_free(); + RealTimeClock::free(); } /* Test that ::is_enabled function returns 1 if the RTC is counting and the time has been set. */ @@ -228,10 +220,10 @@ void rtc_enabled_test() * and RTC time is set. */ - rtc_init(); - rtc_write(0); - TEST_ASSERT_EQUAL_INT(1, rtc_isenabled()); - rtc_free(); + RealTimeClock::init(); + RealTimeClock::write(RealTimeClock::time_point(0)); + TEST_ASSERT_TRUE(RealTimeClock::isenabled()); + RealTimeClock::free(); } Case cases[] = { diff --git a/drivers/RealTimeClock.h b/drivers/RealTimeClock.h new file mode 100644 index 00000000000..3c9915e01de --- /dev/null +++ b/drivers/RealTimeClock.h @@ -0,0 +1,144 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2019 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_REALTIMECLOCK_H +#define MBED_REALTIMECLOCK_H + +#include +#include "hal/rtc_api.h" + +namespace mbed { +/** + * \defgroup drivers_RealTimeClock RealTimeClock class + * \ingroup drivers-public-api-ticker + * @{ + */ + +/** + * An implementation of a C++11 Clock representing the HAL real-time clock. + * + * This is intended to be usable as a substitute for @c std::chrono::system_clock, + * but is provided as a separate class, due to inconsistencies in retargetting + * support between toolchains. + * + * This is only a thin wrapper around the HAL RTC API, and as such it requires the + * same use of initialization. + * + */ +class RealTimeClock { +public: + using duration = std::chrono::seconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static const bool is_steady = false; + + /** Initialize the RTC peripheral + * + * Power up the RTC in preparation for access. This function must be called + * before any other RealTimeClock methods ares called. This does not change the state + * of the RTC. It just enables access to it. + * + * @see ::rtc_init + */ + static void init() + { + return rtc_init(); + } + + /** Deinitialize RTC + * + * Powerdown the RTC in preparation for sleep, powerdown or reset. That should only + * affect the CPU domain and not the time keeping logic. + * After this function is called no other RealTimeClock methods should be called + * except for RealTimeClock::init. + * + * @see ::rtc_free + */ + static void free() + { + return rtc_free(); + } + + /** Check if the RTC has the time set and is counting + * + * @retval false The time reported by the RTC is not valid + * @retval true The time has been set the RTC is counting + * + * @see ::rtc_isenabled + */ + static bool isenabled() noexcept + { + return bool(rtc_isenabled()); + } + + /** Get the current time from the RTC peripheral + * + * @return The current time in seconds + * + * @see ::rtc_read + */ + static time_point now() noexcept + { + return from_time_t(rtc_read()); + } + + /** Write the current time in seconds to the RTC peripheral + * + * @param t The current time to be set in seconds. + * + * @see ::rtc_write + */ + static void write(time_point t) noexcept + { + rtc_write(to_time_t(t)); + } + + /** Convert a C time_t to C++ Chrono time_point + * + * @param t a @c time_t object + * @return a RealTimeClock::time_point object that represents the same point in time as @p t. + */ + static time_point from_time_t(time_t t) noexcept + { + return time_point{std::chrono::duration_cast(std::chrono::duration{t})}; + } + + /** Convert a C++ Chrono time_point to a C time_t + * + * @param t a RealTimeClock::time_point object + * @return a @c time_t object that represents the same point in time as @p t. + */ + static time_t to_time_t(const time_point &t) noexcept + { + return std::chrono::duration_cast>(t.time_since_epoch()).count(); + } + + /** Lock the clock to ensure it stays running; dummy for API compatibility with HighResClock */ + static void lock() + { + } + + /** Unlock the clock, allowing it to stop during power saving; dummy for API compatibility with HighResClock */ + static void unlock() + { + } +}; + +/** @}*/ + +} +#endif /* MBED_TICKERCLOCK_H */ diff --git a/mbed.h b/mbed.h index 84b5fa9d865..597a7cc7bfc 100644 --- a/mbed.h +++ b/mbed.h @@ -87,6 +87,7 @@ #include "drivers/LowPowerTimeout.h" #include "drivers/LowPowerTicker.h" #include "drivers/LowPowerTimer.h" +#include "drivers/RealTimeClock.h" #include "platform/LocalFileSystem.h" #include "drivers/InterruptIn.h" #include "platform/mbed_wait_api.h" From afc88681c0cde96184c0a2e20ec9e3e20cd038ca Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Tue, 3 Mar 2020 16:51:48 +0200 Subject: [PATCH 16/19] mstd_mutex: Add missing Chrono bits --- platform/cxxsupport/mstd_mutex | 24 +++++++++--------------- platform/cxxsupport/mstd_tuple | 9 ++++----- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/platform/cxxsupport/mstd_mutex b/platform/cxxsupport/mstd_mutex index c95d21f5b9e..e34a357cc15 100644 --- a/platform/cxxsupport/mstd_mutex +++ b/platform/cxxsupport/mstd_mutex @@ -46,6 +46,8 @@ #include #include +#include +#include #include "mbed_atomic.h" #include "mbed_assert.h" @@ -105,12 +107,10 @@ public: unique_lock(mutex_type &m, defer_lock_t) noexcept : pm(&m), owns(false) { } unique_lock(mutex_type &m, try_to_lock_t) : pm(&m), owns(m.try_lock()) { } unique_lock(mutex_type &m, adopt_lock_t) : pm(&m), owns(true) { } -#if 0 // disabled until we have functional mstd::chrono for all toolchains template - unique_lock(mutex_type &m, const chrono::time_point &abs_time) : pm(&m), owns(m.try_lock_until(abs_time)) { } + unique_lock(mutex_type &m, const std::chrono::time_point &abs_time) : pm(&m), owns(m.try_lock_until(abs_time)) { } template - unique_lock(mutex_type &m, const chrono::duration &rel_time) : pm(&m), owns(m.try_lock_for(rel_time)) { } -#endif + unique_lock(mutex_type &m, const std::chrono::duration &rel_time) : pm(&m), owns(m.try_lock_for(rel_time)) { } ~unique_lock() { if (owns) pm->unlock(); } unique_lock(const unique_lock &) = delete; @@ -141,19 +141,17 @@ public: return owns = pm->try_lock(); } -#if 0 // disabled until we have functional mstd::chrono for all toolchains template - bool try_lock_until(const chrono::time_point &abs_time) { + bool try_lock_until(const std::chrono::time_point &abs_time) { MBED_ASSERT(!owns); return owns = pm->try_lock_until(abs_time); } template - bool try_lock_for(const chrono::duration &rel_time) { + bool try_lock_for(const std::chrono::duration &rel_time) { MBED_ASSERT(!owns); return owns = pm->try_lock_for(rel_time); } -#endif void unlock() { MBED_ASSERT(owns); @@ -313,9 +311,8 @@ using std::scoped_lock; // [thread.lock.scoped] // 2+ locks - use std::lock template -class scoped_lock -#if 0 // no definition yet - needs tuple - tuple pm; +class scoped_lock { + mstd::tuple pm; static void ignore(...) { } public: explicit scoped_lock(MutexTypes &... m) : pm(tie(m...)) { mstd::lock(m...); } @@ -324,10 +321,7 @@ public: scoped_lock(const scoped_lock &) = delete; scoped_lock &operator=(const scoped_lock &) = delete; -} -#else -; -#endif +}; // 0 locks - no-op template <> diff --git a/platform/cxxsupport/mstd_tuple b/platform/cxxsupport/mstd_tuple index e2deafbb5fd..d9e926aa57d 100644 --- a/platform/cxxsupport/mstd_tuple +++ b/platform/cxxsupport/mstd_tuple @@ -41,6 +41,10 @@ using std::make_tuple; using std::forward_as_tuple; using std::tie; using std::tuple_cat; +using std::tuple_size; +using std::tuple_element; +using std::tuple_element_t; +using std::get; // [tuple.apply] #if __cpp_lib_apply >= 201603 @@ -84,11 +88,6 @@ T make_from_tuple(Tuple&& t) } #endif -using std::tuple_size; -using std::tuple_element; -using std::tuple_element_t; -using std::get; - } // namespace mstd #endif // MSTD_TUPLE_ From 8acbf5893d9955a532bd77a5cf9c5088c3be4268 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 13 Feb 2020 13:18:49 +0200 Subject: [PATCH 17/19] Fix and update tests to use Chronos APIs --- TESTS/mbedmicro-rtos-mbed/basic/main.cpp | 14 ++--- .../condition_variable/main.cpp | 11 ++-- .../mbedmicro-rtos-mbed/event_flags/main.cpp | 12 ++--- .../kernel_tick_count/main.cpp | 39 +++++++------- TESTS/mbedmicro-rtos-mbed/mutex/main.cpp | 17 +++--- TESTS/mbedmicro-rtos-mbed/semaphore/main.cpp | 17 +++--- TESTS/mbedmicro-rtos-mbed/threads/LockGuard.h | 52 ------------------- .../threads/SynchronizedIntegral.h | 14 ++--- TESTS/mbedmicro-rtos-mbed/threads/main.cpp | 15 +++--- .../platform/cxxsupport/mstd_type_traits | 14 ----- 10 files changed, 74 insertions(+), 131 deletions(-) delete mode 100644 TESTS/mbedmicro-rtos-mbed/threads/LockGuard.h diff --git a/TESTS/mbedmicro-rtos-mbed/basic/main.cpp b/TESTS/mbedmicro-rtos-mbed/basic/main.cpp index f5b38da65a1..a405ca70112 100644 --- a/TESTS/mbedmicro-rtos-mbed/basic/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/basic/main.cpp @@ -28,22 +28,24 @@ #else using utest::v1::Case; +using std::milli; +using std::micro; +using namespace std::chrono; #if defined(__CORTEX_M23) || defined(__CORTEX_M33) #define TEST_STACK_SIZE 512 #else #define TEST_STACK_SIZE 256 #endif -#define ONE_MILLI_SEC 1000 -volatile uint32_t elapsed_time_ms = 0; +static duration elapsed_time_ms; static const int test_timeout = 40; void update_tick_thread(Mutex *mutex) { while (true) { - ThisThread::sleep_for(1); + ThisThread::sleep_for(1ms); mutex->lock(); ++elapsed_time_ms; mutex->unlock(); @@ -69,7 +71,7 @@ void test(void) char _value[128] = { }; int expected_key = 1; Mutex mutex; - uint32_t elapsed_time; + duration elapsed_time; Thread tick_thread(osPriorityHigh, TEST_STACK_SIZE); tick_thread.start(callback(update_tick_thread, &mutex)); @@ -86,7 +88,7 @@ void test(void) elapsed_time = elapsed_time_ms; mutex.unlock(); // send base_time - greentea_send_kv(_key, elapsed_time * ONE_MILLI_SEC); + greentea_send_kv(_key, elapsed_time.count()); // wait for 2nd signal from host greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); @@ -95,7 +97,7 @@ void test(void) elapsed_time = elapsed_time_ms; mutex.unlock(); // send final_time - greentea_send_kv(_key, elapsed_time * ONE_MILLI_SEC); + greentea_send_kv(_key, elapsed_time.count()); //get the results from host greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); diff --git a/TESTS/mbedmicro-rtos-mbed/condition_variable/main.cpp b/TESTS/mbedmicro-rtos-mbed/condition_variable/main.cpp index 4372f462402..5424ffccc74 100644 --- a/TESTS/mbedmicro-rtos-mbed/condition_variable/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/condition_variable/main.cpp @@ -29,9 +29,10 @@ #include "rtos.h" using namespace utest::v1; +using namespace std::chrono_literals; #define TEST_STACK_SIZE 512 -#define TEST_DELAY 10 +#define TEST_DELAY 10ms static int change_counter = 0; static Mutex mutex; @@ -56,14 +57,14 @@ void test_notify_one() t1.start(increment_on_signal); t2.start(increment_on_signal); - wait_ms(TEST_DELAY); + ThisThread::sleep_for(TEST_DELAY); TEST_ASSERT_EQUAL(0, change_counter); mutex.lock(); cond.notify_one(); mutex.unlock(); - wait_ms(TEST_DELAY); + ThisThread::sleep_for(TEST_DELAY); TEST_ASSERT_EQUAL(1, change_counter); mutex.lock(); @@ -83,14 +84,14 @@ void test_notify_all() t1.start(increment_on_signal); t2.start(increment_on_signal); - wait_ms(TEST_DELAY); + ThisThread::sleep_for(TEST_DELAY); TEST_ASSERT_EQUAL(0, change_counter); mutex.lock(); cond.notify_all(); mutex.unlock(); - wait_ms(TEST_DELAY); + ThisThread::sleep_for(TEST_DELAY); TEST_ASSERT_EQUAL(2, change_counter); t1.join(); diff --git a/TESTS/mbedmicro-rtos-mbed/event_flags/main.cpp b/TESTS/mbedmicro-rtos-mbed/event_flags/main.cpp index 14052bea860..cae6e4db2b0 100644 --- a/TESTS/mbedmicro-rtos-mbed/event_flags/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/event_flags/main.cpp @@ -54,7 +54,7 @@ void send_thread(EventFlags *ef) const uint32_t flag = flags & (1 << i); if (flag) { ef->set(flag); - ThisThread::sleep_for(wait_ms); + ThisThread::sleep_for(std::chrono::milliseconds(wait_ms)); } } } @@ -67,7 +67,7 @@ void send_thread_sync(EventFlags *ef) if (flag) { sync_sem.acquire(); ef->set(flag); - ThisThread::sleep_for(wait_ms); + ThisThread::sleep_for(std::chrono::milliseconds(wait_ms)); } } } @@ -267,11 +267,11 @@ void test_multi_thread_any_timeout(void) for (int i = 0; i <= MAX_FLAG_POS; i++) { uint32_t flag = 1 << i; - ret = ef.wait_any(flag, 10); + ret = ef.wait_any_for(flag, 10ms); TEST_ASSERT_EQUAL(osFlagsErrorTimeout, ret); sync_sem.release(); - ret = ef.wait_any(flag, 10); + ret = ef.wait_any_for(flag, 10ms); TEST_ASSERT_EQUAL(flag, ret); } ret = ef.get(); @@ -297,7 +297,7 @@ void test_multi_thread_any_no_clear(void) for (int i = 0; i <= MAX_FLAG_POS; i++) { uint32_t flag = 1 << i; - ret = ef.wait_any(flag, osWaitForever, false); + ret = ef.wait_any(flag, Kernel::wait_for_u32_forever, false); TEST_ASSERT(flag | ret); ret = ef.clear(flag); TEST_ASSERT(ret < osFlagsError); @@ -375,4 +375,4 @@ int main() } #endif // !DEVICE_USTICKER -#endif // defined(MBED_RTOS_SINGLE_THREAD) || !defined(MBED_CONF_RTOS_PRESENT) \ No newline at end of file +#endif // defined(MBED_RTOS_SINGLE_THREAD) || !defined(MBED_CONF_RTOS_PRESENT) diff --git a/TESTS/mbedmicro-rtos-mbed/kernel_tick_count/main.cpp b/TESTS/mbedmicro-rtos-mbed/kernel_tick_count/main.cpp index f5951593385..1e47f25eea6 100644 --- a/TESTS/mbedmicro-rtos-mbed/kernel_tick_count/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/kernel_tick_count/main.cpp @@ -25,16 +25,16 @@ #include "rtos/Kernel.h" #include "mbed.h" +using namespace std::chrono_literals; using utest::v1::Case; #define TEST_REPEAT_COUNT 1000 -#define NUM_WAIT_TICKS 1000 +#define NUM_WAIT_TICKS rtos::Kernel::Clock::duration(1s) -// all in [us] -#define ONE_SECOND 1000000 -#define SMALL_DELTA 1500 // 0.15% -#define BIG_DELTA 15000 // 1.5% +#define ONE_SECOND Timer::duration(1s) +#define SMALL_DELTA Timer::duration(1500us) // 0.15% +#define BIG_DELTA Timer::duration(15000us) // 1.5% /** Test if kernel ticker frequency is 1kHz @@ -46,22 +46,23 @@ void test_frequency() { uint32_t freq = osKernelGetTickFreq(); TEST_ASSERT_EQUAL_UINT32_MESSAGE(1000, freq, "Expected SysTick frequency is 1kHz"); + TEST_ASSERT_TRUE_MESSAGE((std::ratio_equal::value), "Expected Kernel::Clock frequency is 1kHz"); } /** Test if kernel ticker increments by one Given a RTOS kernel ticker - When perform subsequent calls of @a rtos::Kernel::get_ms_count + When perform subsequent calls of @a rtos::Kernel::Clock::now Then subsequent reads should not differ by more than one */ void test_increment(void) { for (uint32_t i = 0; i < TEST_REPEAT_COUNT; i++) { - const uint64_t start = rtos::Kernel::get_ms_count(); + auto start = rtos::Kernel::Clock::now(); while (true) { - uint64_t diff = rtos::Kernel::get_ms_count() - start; - if (diff != 0) { - TEST_ASSERT_EQUAL_UINT64(1, diff); + rtos::Kernel::Clock::duration diff = rtos::Kernel::Clock::now() - start; + if (diff.count() != 0) { + TEST_ASSERT_EQUAL_INT64(1, diff.count()); break; } } @@ -71,35 +72,35 @@ void test_increment(void) /** Test if kernel ticker interval is 1ms Given a RTOS kernel ticker - When perform subsequent calls of @a rtos::Kernel::get_ms_count + When perform subsequent calls of @a rtos::Kernel::Clock::now Then the ticker interval should be 1ms */ void test_interval() { - uint64_t start, stop; + Kernel::Clock::time_point start, stop; Timer timer; - start = rtos::Kernel::get_ms_count(); + start = rtos::Kernel::Clock::now(); // wait for tick do { - stop = rtos::Kernel::get_ms_count(); - } while ((stop - start) == 0); + stop = rtos::Kernel::Clock::now(); + } while (stop == start); timer.start(); start = stop; // wait for NUM_WAIT_TICKS ticks do { - stop = rtos::Kernel::get_ms_count(); + stop = rtos::Kernel::Clock::now(); } while ((stop - start) != NUM_WAIT_TICKS); timer.stop(); - TEST_ASSERT_EQUAL_UINT64(NUM_WAIT_TICKS, (stop - start)); + TEST_ASSERT_EQUAL_INT64(NUM_WAIT_TICKS.count(), (stop - start).count()); #if defined(NO_SYSTICK) || defined(MBED_TICKLESS) // On targets with NO_SYSTICK/MBED_TICKLESS enabled, systick is emulated by lp_ticker what makes it less accurate // for more details https://os.mbed.com/docs/latest/reference/tickless.html - TEST_ASSERT_UINT64_WITHIN(BIG_DELTA, ONE_SECOND, timer.read_high_resolution_us()); + TEST_ASSERT_INT64_WITHIN(BIG_DELTA.count(), ONE_SECOND.count(), timer.read_duration().count()); #else - TEST_ASSERT_UINT64_WITHIN(SMALL_DELTA, ONE_SECOND, timer.read_high_resolution_us()); + TEST_ASSERT_INT64_WITHIN(SMALL_DELTA.count(), ONE_SECOND.count(), timer.read_duration().count()); #endif } diff --git a/TESTS/mbedmicro-rtos-mbed/mutex/main.cpp b/TESTS/mbedmicro-rtos-mbed/mutex/main.cpp index f90dd0bdd60..5b91f83c4ef 100644 --- a/TESTS/mbedmicro-rtos-mbed/mutex/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/mutex/main.cpp @@ -29,6 +29,7 @@ #include "rtos.h" using namespace utest::v1; +using namespace std::chrono; #if defined(__CORTEX_M23) || defined(__CORTEX_M33) #define TEST_STACK_SIZE 768 @@ -36,8 +37,8 @@ using namespace utest::v1; #define TEST_STACK_SIZE 512 #endif -#define TEST_LONG_DELAY 20 -#define TEST_DELAY 10 +#define TEST_LONG_DELAY 20ms +#define TEST_DELAY 10ms #define SIGNALS_TO_EMIT 100 Mutex stdio_mutex; @@ -89,9 +90,9 @@ void test_thread(int const *thread_delay) */ void test_multiple_threads(void) { - const int t1_delay = TEST_DELAY * 1; - const int t2_delay = TEST_DELAY * 2; - const int t3_delay = TEST_DELAY * 3; + const Kernel::Clock::duration t1_delay = TEST_DELAY * 1; + const Kernel::Clock::duration t2_delay = TEST_DELAY * 2; + const Kernel::Clock::duration t3_delay = TEST_DELAY * 3; Thread t2(osPriorityNormal, TEST_STACK_SIZE); Thread t3(osPriorityNormal, TEST_STACK_SIZE); @@ -155,7 +156,7 @@ void test_dual_thread_nolock(void) thread.start(callback(F, &mutex)); - wait_ms(TEST_DELAY); + ThisThread::sleep_for(TEST_DELAY); } void test_dual_thread_lock_unlock_thread(Mutex *mutex) @@ -184,7 +185,7 @@ void test_dual_thread_lock_unlock(void) mutex.unlock(); - wait_ms(TEST_DELAY); + ThisThread::sleep_for(TEST_DELAY); } void test_dual_thread_lock_trylock_thread(Mutex *mutex) @@ -228,7 +229,7 @@ void test_dual_thread_lock(void) thread.start(callback(F, &mutex)); - wait_ms(TEST_LONG_DELAY); + ThisThread::sleep_for(TEST_LONG_DELAY); mutex.unlock(); } diff --git a/TESTS/mbedmicro-rtos-mbed/semaphore/main.cpp b/TESTS/mbedmicro-rtos-mbed/semaphore/main.cpp index ff06ca179cc..6cdab0725e3 100644 --- a/TESTS/mbedmicro-rtos-mbed/semaphore/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/semaphore/main.cpp @@ -29,11 +29,12 @@ #include "rtos.h" using namespace utest::v1; +using namespace std::chrono; -#define THREAD_DELAY 30 +#define THREAD_DELAY 30ms #define SEMAPHORE_SLOTS 2 #define SEM_CHANGES 100 -#define SHORT_WAIT 5 +#define SHORT_WAIT 5ms #define THREAD_STACK_SIZE 320 /* larger stack cause out of heap memory on some 16kB RAM boards in multi thread test*/ @@ -43,9 +44,9 @@ volatile int change_counter = 0; volatile int sem_counter = 0; volatile bool sem_defect = false; -void test_thread(int const *delay) +void test_thread(rtos::Kernel::Clock::duration const *delay) { - const int thread_delay = *delay; + const auto thread_delay = *delay; while (true) { two_slots.acquire(); sem_counter++; @@ -68,9 +69,9 @@ void test_thread(int const *delay) */ void test_multi() { - const int t1_delay = THREAD_DELAY * 1; - const int t2_delay = THREAD_DELAY * 2; - const int t3_delay = THREAD_DELAY * 3; + const rtos::Kernel::Clock::duration t1_delay = THREAD_DELAY * 1; + const rtos::Kernel::Clock::duration t2_delay = THREAD_DELAY * 2; + const rtos::Kernel::Clock::duration t3_delay = THREAD_DELAY * 3; Thread t1(osPriorityNormal, THREAD_STACK_SIZE); Thread t2(osPriorityNormal, THREAD_STACK_SIZE); @@ -140,7 +141,7 @@ void test_single_thread() void timeout_thread(Semaphore *sem) { - bool acquired = sem->try_acquire_for(30); + bool acquired = sem->try_acquire_for(30ms); TEST_ASSERT_FALSE(acquired); } diff --git a/TESTS/mbedmicro-rtos-mbed/threads/LockGuard.h b/TESTS/mbedmicro-rtos-mbed/threads/LockGuard.h deleted file mode 100644 index 919d2e105a2..00000000000 --- a/TESTS/mbedmicro-rtos-mbed/threads/LockGuard.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2013-2016, ARM Limited, All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MBEDMICRO_RTOS_MBED_THREADS_LOCK_GUARD -#define MBEDMICRO_RTOS_MBED_THREADS_LOCK_GUARD - -#include - -/** - * RAII mutex locker. - * The mutex pass in the constructor will be locked for the lifetime of - * the LockGuard instance. - */ -class LockGuard { -public: - /** - * Construct a LockGuard instance and ackire ownership of mutex in input. - * @param mutex The mutex to ackire ownership of. - */ - LockGuard(rtos::Mutex &mutex) : _mutex(mutex) - { - _mutex.lock(); - } - - /** - * Destruct the lock and release the inner mutex. - */ - ~LockGuard() - { - _mutex.unlock(); - } - -private: - LockGuard(const LockGuard &); - LockGuard &operator=(const LockGuard &); - rtos::Mutex &_mutex; -}; -#endif /* MBEDMICRO_RTOS_MBED_THREADS_LOCK_GUARD */ diff --git a/TESTS/mbedmicro-rtos-mbed/threads/SynchronizedIntegral.h b/TESTS/mbedmicro-rtos-mbed/threads/SynchronizedIntegral.h index 988b4d44f6a..8618fa0bec7 100644 --- a/TESTS/mbedmicro-rtos-mbed/threads/SynchronizedIntegral.h +++ b/TESTS/mbedmicro-rtos-mbed/threads/SynchronizedIntegral.h @@ -19,7 +19,9 @@ #define MBEDMICRO_RTOS_MBED_THREADS_SYNCHRONIZED_INTEGRAL #include -#include "LockGuard.h" +#include + +using mstd::lock_guard; /** * Thread safe wrapper for integral types. @@ -33,35 +35,35 @@ class SynchronizedIntegral { // preincrement operator T operator++() { - LockGuard lock(_mutex); + lock_guard lock(_mutex); return ++_value; } // predecrement operator T operator--() { - LockGuard lock(_mutex); + lock_guard lock(_mutex); return --_value; } // post increment operator T operator++(int) { - LockGuard lock(_mutex); + lock_guard lock(_mutex); return _value++; } // post decrement operator T operator--(int) { - LockGuard lock(_mutex); + lock_guard lock(_mutex); return _value--; } // conversion operator, used also for <,>,<=,>=,== and != operator T() const { - LockGuard lock(_mutex); + lock_guard lock(_mutex); return _value; } diff --git a/TESTS/mbedmicro-rtos-mbed/threads/main.cpp b/TESTS/mbedmicro-rtos-mbed/threads/main.cpp index 6ab959913c9..8c410416fe5 100644 --- a/TESTS/mbedmicro-rtos-mbed/threads/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/threads/main.cpp @@ -28,7 +28,7 @@ #include "utest.h" #include "rtos.h" #include "SynchronizedIntegral.h" -#include "LockGuard.h" +#include #define THREAD_STACK_SIZE 512 #if defined(__CORTEX_A9) || defined(__CORTEX_M23) || defined(__CORTEX_M33) || defined(TARGET_ARM_FM) || defined(TARGET_CY8CKIT_062_WIFI_BT_PSA) @@ -41,6 +41,7 @@ using namespace utest::v1; +using mstd::lock_guard; // The counter type used accross all the tests // It is internall ysynchronized so read @@ -92,7 +93,7 @@ void increment_with_murder(counter_t *counter) { // take ownership of the counter mutex so it prevent the child to // modify counter. - LockGuard lock(counter->internal_mutex()); + lock_guard lock(counter->internal_mutex()); Thread *child = new (std::nothrow) Thread(osPriorityNormal, CHILD_THREAD_STACK_SIZE); char *dummy = new (std::nothrow) char[CHILD_THREAD_STACK_SIZE]; delete[] dummy; @@ -293,7 +294,7 @@ void flags_wait() void flags_wait_tout() { - uint32_t flags = ThisThread::flags_wait_all_for(0x2, 50); + uint32_t flags = ThisThread::flags_wait_all_for(0x2, 50ms); TEST_ASSERT_EQUAL(0x1, flags); } @@ -311,7 +312,7 @@ void flags_wait_multibit_any() void flags_wait_multibit_tout() { - uint32_t flags = ThisThread::flags_wait_all_for(0x1 | 0x2, 50); + uint32_t flags = ThisThread::flags_wait_all_for(0x1 | 0x2, 50ms); TEST_ASSERT_NOT_EQUAL(0x3, flags); } @@ -375,7 +376,7 @@ void flags_clear() TEST_ASSERT_EQUAL(0x1, sig); /* Flags cleared we should get timeout */ - uint32_t flags = ThisThread::flags_wait_all_for(0x1, 0); + uint32_t flags = ThisThread::flags_wait_all_for(0x1, 0s); TEST_ASSERT_EQUAL(0, flags); } @@ -473,7 +474,7 @@ void test_thread_wait() Timer timer; timer.start(); - ThisThread::sleep_for(150); + ThisThread::sleep_for(150ms); TEST_ASSERT_UINT32_WITHIN(50000, 150000, timer.read_us()); } @@ -527,7 +528,7 @@ void test_deleted() void test_delay_thread() { - ThisThread::sleep_for(50); + ThisThread::sleep_for(50ms); } /** Testing thread states: wait delay diff --git a/UNITTESTS/target_h/platform/cxxsupport/mstd_type_traits b/UNITTESTS/target_h/platform/cxxsupport/mstd_type_traits index f6974ea0e80..b1c069b8f69 100644 --- a/UNITTESTS/target_h/platform/cxxsupport/mstd_type_traits +++ b/UNITTESTS/target_h/platform/cxxsupport/mstd_type_traits @@ -300,20 +300,6 @@ struct remove_cvref : type_identity> template using remove_cvref_t = typename remove_cvref::type; -/* C++20 unwrap_reference */ -template -struct unwrap_reference : type_identity { }; -template -struct unwrap_reference> : type_identity { }; -template -using unwrap_reference_t = typename unwrap_reference::type; - -/* C++20 unwrap_ref_decay */ -template -struct unwrap_ref_decay : unwrap_reference> { }; -template -using unwrap_ref_decay_t = typename unwrap_ref_decay::type; - } #if __cpp_lib_invoke < 201411 From e15928c5bbfa82e1ce7ecdfcc16ad2d95ca96029 Mon Sep 17 00:00:00 2001 From: Rajkumar Kanagaraj Date: Tue, 24 Mar 2020 08:14:34 -0700 Subject: [PATCH 18/19] Fix the CI build and unit test issue --- .../device/athandler/athandlertest.cpp | 3 - .../framework/device/athandler/unittest.cmake | 1 - .../platform/ATCmdParser/test_ATCmdParser.cpp | 3 - UNITTESTS/stubs/Timer_stub.cpp | 74 ------------------- UNITTESTS/stubs/Timer_stub.h | 25 ------- drivers/Timeout.h | 3 - drivers/source/Timeout.cpp | 2 - rtos/ConditionVariable.h | 1 + 8 files changed, 1 insertion(+), 111 deletions(-) delete mode 100644 UNITTESTS/stubs/Timer_stub.cpp delete mode 100644 UNITTESTS/stubs/Timer_stub.h diff --git a/UNITTESTS/features/cellular/framework/device/athandler/athandlertest.cpp b/UNITTESTS/features/cellular/framework/device/athandler/athandlertest.cpp index e6f6025c478..cbbac8bcf7b 100644 --- a/UNITTESTS/features/cellular/framework/device/athandler/athandlertest.cpp +++ b/UNITTESTS/features/cellular/framework/device/athandler/athandlertest.cpp @@ -25,7 +25,6 @@ #include "mbed_poll_stub.h" #include "CellularUtil_stub.h" -#include "Timer_stub.h" using namespace mbed; using namespace events; @@ -228,7 +227,6 @@ TEST_F(TestATHandler, test_ATHandler_process_oob) at.process_oob(); at.clear_error(); - timer_stub_value = 0; filehandle_stub_table_pos = 0; at.read_bytes(buf, 5); @@ -244,7 +242,6 @@ TEST_F(TestATHandler, test_ATHandler_process_oob) filehandle_stub_table = table2; at.clear_error(); - timer_stub_value = 0; filehandle_stub_table_pos = 0; mbed_poll_stub::revents_value = POLLIN; mbed_poll_stub::int_value = 1; diff --git a/UNITTESTS/features/cellular/framework/device/athandler/unittest.cmake b/UNITTESTS/features/cellular/framework/device/athandler/unittest.cmake index 060b14a1db6..26c8058259b 100644 --- a/UNITTESTS/features/cellular/framework/device/athandler/unittest.cmake +++ b/UNITTESTS/features/cellular/framework/device/athandler/unittest.cmake @@ -28,7 +28,6 @@ set(unittest-test-sources stubs/SerialBase_stub.cpp stubs/mbed_assert_stub.cpp stubs/mbed_poll_stub.cpp - stubs/Timer_stub.cpp stubs/equeue_stub.c stubs/Kernel_stub.cpp stubs/ThisThread_stub.cpp diff --git a/UNITTESTS/platform/ATCmdParser/test_ATCmdParser.cpp b/UNITTESTS/platform/ATCmdParser/test_ATCmdParser.cpp index 535aa191df7..dbe81c549ff 100644 --- a/UNITTESTS/platform/ATCmdParser/test_ATCmdParser.cpp +++ b/UNITTESTS/platform/ATCmdParser/test_ATCmdParser.cpp @@ -23,7 +23,6 @@ #include #include "FileHandle_stub.h" #include "mbed_poll_stub.h" -#include "Timer_stub.h" using namespace mbed; @@ -110,7 +109,6 @@ TEST_F(test_ATCmdParser, test_ATCmdParser_process_oob) at.process_oob(); expected_oob_callback = false; - timer_stub_value = 0; filehandle_stub_table_pos = 0; at.read(buf, 5); @@ -127,7 +125,6 @@ TEST_F(test_ATCmdParser, test_ATCmdParser_process_oob) table2[4] = 0; filehandle_stub_table = table2; - timer_stub_value = 0; filehandle_stub_table_pos = 0; mbed_poll_stub::revents_value = POLLIN; mbed_poll_stub::int_value = 1; diff --git a/UNITTESTS/stubs/Timer_stub.cpp b/UNITTESTS/stubs/Timer_stub.cpp deleted file mode 100644 index 2e20c161aee..00000000000 --- a/UNITTESTS/stubs/Timer_stub.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) , Arm Limited and affiliates. - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Timer.h" -#include "Timer_stub.h" - -namespace mbed { - -Timer::Timer() -{ -} - -Timer::Timer(const ticker_data_t *data) -{ -} - -Timer::~Timer() -{ -} - -void Timer::start() -{ -} - -void Timer::stop() -{ - ; -} - -int Timer::read_us() -{ - return 0; -} - -float Timer::read() -{ - return 0; -} - -int Timer::read_ms() -{ - timer_stub_value += timer_stub_step; - return timer_stub_value; -} - -us_timestamp_t Timer::read_high_resolution_us() -{ - return 0; -} - -void Timer::reset() -{ -} - -Timer::operator float() -{ - return 0; -} - -} // namespace mbed diff --git a/UNITTESTS/stubs/Timer_stub.h b/UNITTESTS/stubs/Timer_stub.h deleted file mode 100644 index a97333fe63e..00000000000 --- a/UNITTESTS/stubs/Timer_stub.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) , Arm Limited and affiliates. - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TIMER_STUB_H -#define TIMER_STUB_H - - -static uint16_t timer_stub_value = 0; -static uint16_t timer_stub_step = 20; - -#endif diff --git a/drivers/Timeout.h b/drivers/Timeout.h index 932690e60d1..07d37da4d0f 100644 --- a/drivers/Timeout.h +++ b/drivers/Timeout.h @@ -97,9 +97,6 @@ class Timeout : public TimeoutBase { /** Clock to use with attach_absolute, guaranteeing running only while attached or manually locked */ using clock = HighResClock; - /** Clock to use with attach_absolute, running always */ - using steady_clock = SteadyHighResClock; - /** @copydoc TimeoutBase::scheduled_time() */ HighResClock::time_point scheduled_time() const { diff --git a/drivers/source/Timeout.cpp b/drivers/source/Timeout.cpp index d890e0ef67d..daed37e6eae 100644 --- a/drivers/source/Timeout.cpp +++ b/drivers/source/Timeout.cpp @@ -44,9 +44,7 @@ Timeout::Timeout() : TimeoutBase(get_us_ticker_data(), true) * (In C++17 could avoid the need for this by making the members inline). */ const bool HighResClock::is_steady; -const bool SteadyHighResClock::is_steady; const bool LowPowerClock::is_steady; -mstd::once_flag SteadyHighResClock::init; const bool RealTimeClock::is_steady; #if DEVICE_LPTICKER diff --git a/rtos/ConditionVariable.h b/rtos/ConditionVariable.h index 57a6f5b7003..0d842f16b7e 100644 --- a/rtos/ConditionVariable.h +++ b/rtos/ConditionVariable.h @@ -28,6 +28,7 @@ #include "rtos/mbed_rtos_types.h" #include "rtos/Mutex.h" #include "rtos/Semaphore.h" +#include "rtos/Kernel.h" #include "platform/NonCopyable.h" From bd5f9634dc822fabe7dfff7fc994ddc9d83b1fa3 Mon Sep 17 00:00:00 2001 From: Rajkumar Kanagaraj Date: Wed, 25 Mar 2020 15:44:54 +0000 Subject: [PATCH 19/19] Fix the CI build issue --- drivers/LowPowerClock.h | 2 ++ drivers/source/Timeout.cpp | 2 ++ platform/mbed_chrono.h | 10 ++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/LowPowerClock.h b/drivers/LowPowerClock.h index e9e66a6a20d..4160e89f982 100644 --- a/drivers/LowPowerClock.h +++ b/drivers/LowPowerClock.h @@ -18,6 +18,7 @@ #define MBED_LOWPOWERCLOCK_H #include +#if DEVICE_LPTICKER #include "hal/lp_ticker_api.h" namespace mbed { @@ -63,4 +64,5 @@ class LowPowerClock { /** @}*/ } +#endif /* DEVICE_LPTICKER */ #endif /* MBED_LOWPOWERCLOCK_H */ diff --git a/drivers/source/Timeout.cpp b/drivers/source/Timeout.cpp index daed37e6eae..4b7746809b4 100644 --- a/drivers/source/Timeout.cpp +++ b/drivers/source/Timeout.cpp @@ -44,7 +44,9 @@ Timeout::Timeout() : TimeoutBase(get_us_ticker_data(), true) * (In C++17 could avoid the need for this by making the members inline). */ const bool HighResClock::is_steady; +#if DEVICE_LPTICKER const bool LowPowerClock::is_steady; +#endif const bool RealTimeClock::is_steady; #if DEVICE_LPTICKER diff --git a/platform/mbed_chrono.h b/platform/mbed_chrono.h index 1ee2f49390b..a051588c71e 100644 --- a/platform/mbed_chrono.h +++ b/platform/mbed_chrono.h @@ -85,7 +85,10 @@ inline namespace chrono_literals { constexpr chrono::deciseconds operator "" _ds(unsigned long long x) { chrono::deciseconds::rep val = static_cast(x); - assert(val >= 0 && static_cast(val) == x); + if (val < 0) { + assert(false); + } + assert(static_cast(val) == x); return chrono::deciseconds(val); } @@ -103,7 +106,10 @@ constexpr chrono::deciseconds operator "" _ds(unsigned long long x) constexpr chrono::centiseconds operator "" _cs(unsigned long long x) { chrono::centiseconds::rep val = static_cast(x); - assert(val >= 0 && static_cast(val) == x); + if (val < 0) { + assert(false); + } + assert(static_cast(val) == x); return chrono::centiseconds(val); }