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

Committer:
Jamie Smith
Date:
2020-11-12
Revision:
0:85e56bb98f6e

File content as of revision 0:85e56bb98f6e:

//
// 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