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.

Revision:
0:85e56bb98f6e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AccurateWaiter.h	Thu Nov 12 22:18:14 2020 -0800
@@ -0,0 +1,108 @@
+//
+// Created by jamie on 11/12/2020.
+//
+
+#ifndef LIGHTSPEEDRANGEFINDER_ACCURATEWAITER_H
+#define LIGHTSPEEDRANGEFINDER_ACCURATEWAITER_H
+
+#include <mbed.h>
+
+/**
+ * --------------- 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 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.
+ */
+class AccurateWaiter : private TimerEvent
+{
+	// event flags, used to signal thread to wake up from interrupt
+	rtos::EventFlags flags;
+
+	// called from timer interrupt
+	void handler() override;
+
+public:
+
+	AccurateWaiter();
+
+	/**
+	 * Get Mbed's C++ Clock object representing the us ticker.
+	 * Use this object to get time_points for wait_until()
+	 * @return
+	 */
+	TickerDataClock & clock()
+	{
+		return _ticker_data;
+	}
+
+	/**
+	 * Wait for an exact amount of time.
+	 * Other threads will be allowed to run during the wait period.
+	 * When the timer expires, the waiting thread will be run immediately, preempting the
+	 * current running thread, as long as a few things are true:
+	 * - The waiting thread has higher thread priority than the currently running thread
+	 * - Interrupts are not disabled
+	 *
+	 */
+	void wait_for(std::chrono::microseconds duration);
+
+	/**
+	 * Wait for an exact amount of time.
+	 * Overload that casts to us.
+	 *
+	 * Other threads will be allowed to run during the wait period.
+	 * When the timer expires, the waiting thread will be run immediately, preempting the
+	 * current running thread, as long as a few things are true:
+	 * - The waiting thread has higher thread priority than the currently running thread
+	 * - Interrupts are not disabled
+	 */
+	template<typename _Rep, typename _Period>
+	void wait_for(std::chrono::duration<_Rep, _Period> duration)
+	{
+		wait_for(std::chrono::duration_cast<std::chrono::microseconds>(duration));
+	}
+
+	/**
+	 * Wait until a specific us timestamp.
+	 *
+	 * Other threads will be allowed to run during the wait period.
+	 * When the timer expires, the waiting thread will be run immediately, preempting the
+	 * current running thread, as long as a few things are true:
+	 * - The waiting thread has higher thread priority than the currently running thread
+	 * - Interrupts are not disabled
+	 */
+	void wait_until(TickerDataClock::time_point timePoint);
+};
+
+#endif //LIGHTSPEEDRANGEFINDER_ACCURATEWAITER_H