AccurateWaiter allows threaded applications to have threads wait for a precise amount of time, without sending the CPU into a permanent spinlock.

Dependents:   AccurateWaiter-Test

AccurateWaiter

...your order, sir?

AccurateWaiter allows threaded applications to have threads wait for a precise amount of time, without sending the CPU into a permanent spinlock.

In standard Mbed OS, certain types of threaded applications are limited by the precision of ThisThread::sleep_for(), which only allows sleeping for a whole number of milliseconds. This limitation comes from the underlying RTX RTOS, which uses a 1 ms scheduling interrupt.

What a lot of people don't know is that there is actually a way around this limitation, using the RTOS's EventFlags objects. Unlike most other RTOS objects, EventFlags are usable from interrupts. By configuring the Mbed us ticker interrupt to set EventFlags, a waiting thread can be woken immediately after an exact number of ticks on the us ticker.

The result is something that combines the best features of wait_us() and ThisThread::sleep_for(). Other threads may run during the wait period (like ThisThread::sleep_for()), but the wait time can be specified to the microsecond (like wait_us()). The only downside is a bit more overhead when starting the wait operation (to trigger the timer interrupt).

I tested this code on my NUCLEO_F429ZI board, and found that it was able to handle any time duration from 1us-1s accurately with exactly 14us of delay between the set time and the time the wait function returns. Overhead of calling the wait function also is around the 15-25us range.

Note 1: Since it uses the Mbed us ticker, this class allows waiting for >250,000 years before rollover.

Note 2: Each AccurateWaiter object may only be safely used by one thread at a time.

Committer:
Jamie Smith
Date:
Thu Nov 12 22:18:14 2020 -0800
Revision:
0:85e56bb98f6e
Initial commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Jamie Smith 0:85e56bb98f6e 1 //
Jamie Smith 0:85e56bb98f6e 2 // Created by jamie on 11/12/2020.
Jamie Smith 0:85e56bb98f6e 3 //
Jamie Smith 0:85e56bb98f6e 4
Jamie Smith 0:85e56bb98f6e 5 #ifndef LIGHTSPEEDRANGEFINDER_ACCURATEWAITER_H
Jamie Smith 0:85e56bb98f6e 6 #define LIGHTSPEEDRANGEFINDER_ACCURATEWAITER_H
Jamie Smith 0:85e56bb98f6e 7
Jamie Smith 0:85e56bb98f6e 8 #include <mbed.h>
Jamie Smith 0:85e56bb98f6e 9
Jamie Smith 0:85e56bb98f6e 10 /**
Jamie Smith 0:85e56bb98f6e 11 * --------------- AccurateWaiter ---------------
Jamie Smith 0:85e56bb98f6e 12 * ...your order, sir?
Jamie Smith 0:85e56bb98f6e 13 *
Jamie Smith 0:85e56bb98f6e 14 * AccurateWaiter allows threaded applications to have threads wait
Jamie Smith 0:85e56bb98f6e 15 * for a precise amount of time, without sending the CPU into
Jamie Smith 0:85e56bb98f6e 16 * a permanent spinlock.
Jamie Smith 0:85e56bb98f6e 17 *
Jamie Smith 0:85e56bb98f6e 18 * In standard Mbed OS, certain types of threaded applications are limited
Jamie Smith 0:85e56bb98f6e 19 * by the precision of ThisThread::sleep_for(), which only allows sleeping
Jamie Smith 0:85e56bb98f6e 20 * for a whole number of milliseconds. This limitation comes from
Jamie Smith 0:85e56bb98f6e 21 * the underlying RTX RTOS, which uses a 1 ms scheduling interrupt.
Jamie Smith 0:85e56bb98f6e 22 *
Jamie Smith 0:85e56bb98f6e 23 * What a lot of people don't know is that there is actually a way around
Jamie Smith 0:85e56bb98f6e 24 * this limitation, using the RTOS's EventFlags objects. Unlike
Jamie Smith 0:85e56bb98f6e 25 * most other RTOS objects, EventFlags are usable from interrupts. By
Jamie Smith 0:85e56bb98f6e 26 * configuring the Mbed us ticker interrupt to set EventFlags,
Jamie Smith 0:85e56bb98f6e 27 * a waiting thread can be woken immediately after an exact number
Jamie Smith 0:85e56bb98f6e 28 * of ticks on the us ticker.
Jamie Smith 0:85e56bb98f6e 29 *
Jamie Smith 0:85e56bb98f6e 30 * The result is something that combines the best features of wait_us()
Jamie Smith 0:85e56bb98f6e 31 * and ThisThread::sleep_for(). Other threads may run during the wait period
Jamie Smith 0:85e56bb98f6e 32 * (like ThisThread::sleep_for()), but the wait time can be specified to the
Jamie Smith 0:85e56bb98f6e 33 * microsecond (like wait_us()). The only downside is a bit more overhead
Jamie Smith 0:85e56bb98f6e 34 * when starting the wait operation (to trigger the timer interrupt).
Jamie Smith 0:85e56bb98f6e 35 *
Jamie Smith 0:85e56bb98f6e 36 * I tested this code on my NUCLEO_F429ZI board, and found that it was able
Jamie Smith 0:85e56bb98f6e 37 * to handle any time duration from 1us-1s accurately, with exactly 14us of
Jamie Smith 0:85e56bb98f6e 38 * delay between the set time and the time the wait function returns. Overhead
Jamie Smith 0:85e56bb98f6e 39 * of calling the function also is around the 15-25us range.
Jamie Smith 0:85e56bb98f6e 40 *
Jamie Smith 0:85e56bb98f6e 41 * Note 1: Since it uses the Mbed us ticker, this class allows waiting for
Jamie Smith 0:85e56bb98f6e 42 * >250,000 years before rollover.
Jamie Smith 0:85e56bb98f6e 43 *
Jamie Smith 0:85e56bb98f6e 44 * Note 2: Each AccurateWaiter object may only be safely used by one thread
Jamie Smith 0:85e56bb98f6e 45 * at a time.
Jamie Smith 0:85e56bb98f6e 46 */
Jamie Smith 0:85e56bb98f6e 47 class AccurateWaiter : private TimerEvent
Jamie Smith 0:85e56bb98f6e 48 {
Jamie Smith 0:85e56bb98f6e 49 // event flags, used to signal thread to wake up from interrupt
Jamie Smith 0:85e56bb98f6e 50 rtos::EventFlags flags;
Jamie Smith 0:85e56bb98f6e 51
Jamie Smith 0:85e56bb98f6e 52 // called from timer interrupt
Jamie Smith 0:85e56bb98f6e 53 void handler() override;
Jamie Smith 0:85e56bb98f6e 54
Jamie Smith 0:85e56bb98f6e 55 public:
Jamie Smith 0:85e56bb98f6e 56
Jamie Smith 0:85e56bb98f6e 57 AccurateWaiter();
Jamie Smith 0:85e56bb98f6e 58
Jamie Smith 0:85e56bb98f6e 59 /**
Jamie Smith 0:85e56bb98f6e 60 * Get Mbed's C++ Clock object representing the us ticker.
Jamie Smith 0:85e56bb98f6e 61 * Use this object to get time_points for wait_until()
Jamie Smith 0:85e56bb98f6e 62 * @return
Jamie Smith 0:85e56bb98f6e 63 */
Jamie Smith 0:85e56bb98f6e 64 TickerDataClock & clock()
Jamie Smith 0:85e56bb98f6e 65 {
Jamie Smith 0:85e56bb98f6e 66 return _ticker_data;
Jamie Smith 0:85e56bb98f6e 67 }
Jamie Smith 0:85e56bb98f6e 68
Jamie Smith 0:85e56bb98f6e 69 /**
Jamie Smith 0:85e56bb98f6e 70 * Wait for an exact amount of time.
Jamie Smith 0:85e56bb98f6e 71 * Other threads will be allowed to run during the wait period.
Jamie Smith 0:85e56bb98f6e 72 * When the timer expires, the waiting thread will be run immediately, preempting the
Jamie Smith 0:85e56bb98f6e 73 * current running thread, as long as a few things are true:
Jamie Smith 0:85e56bb98f6e 74 * - The waiting thread has higher thread priority than the currently running thread
Jamie Smith 0:85e56bb98f6e 75 * - Interrupts are not disabled
Jamie Smith 0:85e56bb98f6e 76 *
Jamie Smith 0:85e56bb98f6e 77 */
Jamie Smith 0:85e56bb98f6e 78 void wait_for(std::chrono::microseconds duration);
Jamie Smith 0:85e56bb98f6e 79
Jamie Smith 0:85e56bb98f6e 80 /**
Jamie Smith 0:85e56bb98f6e 81 * Wait for an exact amount of time.
Jamie Smith 0:85e56bb98f6e 82 * Overload that casts to us.
Jamie Smith 0:85e56bb98f6e 83 *
Jamie Smith 0:85e56bb98f6e 84 * Other threads will be allowed to run during the wait period.
Jamie Smith 0:85e56bb98f6e 85 * When the timer expires, the waiting thread will be run immediately, preempting the
Jamie Smith 0:85e56bb98f6e 86 * current running thread, as long as a few things are true:
Jamie Smith 0:85e56bb98f6e 87 * - The waiting thread has higher thread priority than the currently running thread
Jamie Smith 0:85e56bb98f6e 88 * - Interrupts are not disabled
Jamie Smith 0:85e56bb98f6e 89 */
Jamie Smith 0:85e56bb98f6e 90 template<typename _Rep, typename _Period>
Jamie Smith 0:85e56bb98f6e 91 void wait_for(std::chrono::duration<_Rep, _Period> duration)
Jamie Smith 0:85e56bb98f6e 92 {
Jamie Smith 0:85e56bb98f6e 93 wait_for(std::chrono::duration_cast<std::chrono::microseconds>(duration));
Jamie Smith 0:85e56bb98f6e 94 }
Jamie Smith 0:85e56bb98f6e 95
Jamie Smith 0:85e56bb98f6e 96 /**
Jamie Smith 0:85e56bb98f6e 97 * Wait until a specific us timestamp.
Jamie Smith 0:85e56bb98f6e 98 *
Jamie Smith 0:85e56bb98f6e 99 * Other threads will be allowed to run during the wait period.
Jamie Smith 0:85e56bb98f6e 100 * When the timer expires, the waiting thread will be run immediately, preempting the
Jamie Smith 0:85e56bb98f6e 101 * current running thread, as long as a few things are true:
Jamie Smith 0:85e56bb98f6e 102 * - The waiting thread has higher thread priority than the currently running thread
Jamie Smith 0:85e56bb98f6e 103 * - Interrupts are not disabled
Jamie Smith 0:85e56bb98f6e 104 */
Jamie Smith 0:85e56bb98f6e 105 void wait_until(TickerDataClock::time_point timePoint);
Jamie Smith 0:85e56bb98f6e 106 };
Jamie Smith 0:85e56bb98f6e 107
Jamie Smith 0:85e56bb98f6e 108 #endif //LIGHTSPEEDRANGEFINDER_ACCURATEWAITER_H