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.
AccurateWaiter.h@0:85e56bb98f6e, 2020-11-12 (annotated)
- Committer:
- Jamie Smith
- Date:
- Thu Nov 12 22:18:14 2020 -0800
- Revision:
- 0:85e56bb98f6e
Initial commit
Who changed what in which revision?
User | Revision | Line number | New 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 |