mirror of
https://github.com/apache/nuttx.git
synced 2026-05-19 03:03:37 +08:00
fdf3eb0d56
This commit updated the documentation for hrtimer. Signed-off-by: ouyangxiangzhen <ouyangxiangzhen@xiaomi.com>
855 lines
35 KiB
ReStructuredText
855 lines
35 KiB
ReStructuredText
=====================
|
||
System Time and Clock
|
||
=====================
|
||
|
||
Basic System Timer
|
||
==================
|
||
|
||
**System Timer** In most implementations, system time is provided
|
||
by a timer interrupt. That timer interrupt runs at rate determined
|
||
by ``CONFIG_USEC_PER_TICK`` (default 10000 microseconds or 100Hz.
|
||
If ``CONFIG_SCHED_TICKLESS`` is selected, the default is 100
|
||
microseconds). The timer generates an interrupt each
|
||
``CONFIG_USEC_PER_TICK`` microseconds and increments a counter
|
||
called ``g_system_ticks``. ``g_system_ticks`` then provides a
|
||
time-base for calculating *up-time* and elapsed time intervals in
|
||
units of ``CONFIG_USEC_PER_TICK``. The range of ``g_system_ticks``
|
||
is, by default, 32-bits. However, if the MCU supports type
|
||
``long long`` and ``CONFIG_SYSTEM_TIME16`` is selected, a 64-bit
|
||
system timer will be supported instead.
|
||
|
||
**System Timer Accuracy** On many system, the exact timer interval
|
||
specified by ``CONFIG_USEC_PER_TICK`` cannot be achieved due to
|
||
limitations in frequencies or in dividers. As a result, the time
|
||
interval specified by ``CONFIG_USEC_PER_TICK`` may only be
|
||
approximate and there may be small errors in the apparent
|
||
*up-time* time. These small errors, however, will accumulate over
|
||
time and after a long period of time may have an unacceptably
|
||
large error in the apparent *up-time* of the MCU.
|
||
|
||
If the timer tick period generated by the hardware is not exactly
|
||
``CONFIG_USEC_PER_TICK`` *and* if there you require accurate
|
||
up-time for the MCU, then there are measures that you can take:
|
||
|
||
- Perhaps you can adjust ``CONFIG_USEC_PER_TICK`` to a different
|
||
value so that an exactly ``CONFIG_USEC_PER_TICK`` can be
|
||
realized.
|
||
- Or you can use a technique known as *Delta-Sigma Modulation*.
|
||
(Suggested by Uros Platise). Consider the example below.
|
||
|
||
**Delta-Sigma Modulation Example**. Consider this case: The system
|
||
timer is a count-up timer driven at 32.768KHz. There are dividers
|
||
that can be used, but a divider of one yields the highest
|
||
accuracy. This counter counts up until the count equals a match
|
||
value, then a timer interrupt is generated. The desire frequency
|
||
is 100Hz (``CONFIG_USEC_PER_TICK`` is 10000).
|
||
|
||
This exact frequency of 100Hz cannot be obtained in this case. In
|
||
order to obtain that exact frequency a match value of 327.68 would
|
||
have to be provided. The closest integer value is 328 but the
|
||
ideal match value is between 327 and 328. The closest value, 328,
|
||
would yield an actual timer frequency of 99.9Hz! That will may
|
||
cause significant timing errors in certain usages.
|
||
|
||
Use of Delta-Sigma Modulation can eliminate this error in the long
|
||
run. Consider this example implementation:
|
||
|
||
#. Initially an accumulator is zero an the match value is
|
||
programmed to 328:
|
||
|
||
.. code-block:: c
|
||
|
||
accumulator = 0;
|
||
match = 328;
|
||
|
||
#. On each timer interrupt, accumulator is updated with difference
|
||
that, in this reflects, 100\* the error in interval that just
|
||
passed. So on the first timer interrupt, the accumulator would
|
||
be updated like:
|
||
|
||
.. code-block:: c
|
||
|
||
if (match == 328)
|
||
{
|
||
accumulator += 32; // 100*(328 - 327.68)
|
||
}
|
||
else
|
||
{
|
||
accumulator -= 68; // (100*(327 - 327.68)
|
||
}
|
||
|
||
#. And on that same timer interrupt a new match value would be
|
||
programmed:
|
||
|
||
.. code-block:: c
|
||
|
||
if (accumulator < 0)
|
||
{
|
||
match = 328;
|
||
}
|
||
else
|
||
{
|
||
match = 327;
|
||
}
|
||
|
||
In this way, the timer interval is controlled from
|
||
interrupt-to-interrupt to produce an average frequency of exactly
|
||
100Hz.
|
||
|
||
Hardware
|
||
========
|
||
|
||
To enable hardware module use the following configuration options:
|
||
|
||
``CONFIG_RTC``
|
||
Enables general support for a hardware RTC. Specific
|
||
architectures may require other specific settings.
|
||
``CONFIG_RTC_EXTERNAL``
|
||
Most MCUs include RTC hardware built into the chip. Other RTCs,
|
||
*external* MCUs, may be provided as separate chips typically
|
||
interfacing with the MCU via a serial interface such as SPI or
|
||
I2C. These external RTCs differ from the built-in RTCs in that
|
||
they cannot be initialized until the operating system is fully
|
||
booted and can support the required serial communications.
|
||
``CONFIG_RTC_EXTERNAL`` will configure the operating system so
|
||
that it defers initialization of its time facilities.
|
||
``CONFIG_RTC_DATETIME``
|
||
There are two general types of RTC: (1) A simple battery backed
|
||
counter that keeps the time when power is down, and (2) A full
|
||
date / time RTC the provides the date and time information,
|
||
often in BCD format. If ``CONFIG_RTC_DATETIME`` is selected, it
|
||
specifies this second kind of RTC. In this case, the RTC is
|
||
used to "seed"" the normal NuttX timer and the NuttX system
|
||
timer provides for higher resolution time.
|
||
``CONFIG_RTC_HIRES``
|
||
If ``CONFIG_RTC_DATETIME`` not selected, then the simple,
|
||
battery backed counter is used. There are two different
|
||
implementations of such simple counters based on the time
|
||
resolution of the counter: The typical RTC keeps time to
|
||
resolution of 1 second, usually supporting a 32-bit ``time_t``
|
||
value. In this case, the RTC is used to "seed" the normal NuttX
|
||
timer and the NuttX timer provides for higher resolution time.
|
||
If ``CONFIG_RTC_HIRES`` is enabled in the NuttX configuration,
|
||
then the RTC provides higher resolution time and completely
|
||
replaces the system timer for purpose of date and time.
|
||
``CONFIG_RTC_FREQUENCY``
|
||
If ``CONFIG_RTC_HIRES`` is defined, then the frequency of the
|
||
high resolution RTC must be provided. If ``CONFIG_RTC_HIRES``
|
||
is not defined, ``CONFIG_RTC_FREQUENCY`` is assumed to be one.
|
||
``CONFIG_RTC_ALARM``
|
||
Enable if the RTC hardware supports setting of an alarm. A
|
||
callback function will be executed when the alarm goes off
|
||
|
||
which requires the following base functions to read and set time:
|
||
|
||
- ``up_rtc_initialize()``. Initialize the built-in, MCU hardware
|
||
RTC per the selected configuration. This function is called
|
||
once very early in the OS initialization sequence. NOTE that
|
||
initialization of external RTC hardware that depends on the
|
||
availability of OS resources (such as SPI or I2C) must be
|
||
deferred until the system has fully booted. Other, RTC-specific
|
||
initialization functions are used in that case.
|
||
- ``up_rtc_time()``. Get the current time in seconds. This is
|
||
similar to the standard ``time()`` function. This interface is
|
||
only required if the low-resolution RTC/counter hardware
|
||
implementation selected. It is only used by the RTOS during
|
||
initialization to set up the system time when ``CONFIG_RTC`` is
|
||
set but neither ``CONFIG_RTC_HIRES`` nor
|
||
``CONFIG_RTC_DATETIME`` are set.
|
||
- ``up_rtc_gettime()``. Get the current time from the high
|
||
resolution RTC clock/counter. This interface is only supported
|
||
by the high-resolution RTC/counter hardware implementation. It
|
||
is used to replace the system timer (``g_system_ticks``).
|
||
- ``up_rtc_settime()``. Set the RTC to the provided time. All RTC
|
||
implementations must be able to set their time based on a
|
||
standard timespec.
|
||
|
||
System Tick and Time
|
||
====================
|
||
|
||
The system tick is represented by ``g_system_ticks``.
|
||
|
||
Running at rate of system base timer, used for time-slicing, and
|
||
so forth.
|
||
|
||
If hardware RTC is present (``CONFIG_RTC``) and and
|
||
high-resolution timing is enabled (``CONFIG_RTC_HIRES``), then
|
||
after successful initialization variables are overridden by calls
|
||
to ``up_rtc_gettime()`` which is running continuously even in
|
||
power-down modes.
|
||
|
||
In the case of ``CONFIG_RTC_HIRES`` is set the ``g_system_ticks``
|
||
keeps counting at rate of a system timer, which however, is
|
||
disabled in power-down mode. By comparing this time and RTC
|
||
(actual time) one may determine the actual system active time. To
|
||
retrieve that variable use:
|
||
|
||
Tickless OS
|
||
===========
|
||
|
||
**Default System Timer**. By default, a NuttX configuration uses a
|
||
periodic timer interrupt that drives all system timing. The timer
|
||
is provided by architecture-specific code that calls into NuttX at
|
||
a rate controlled by ``CONFIG_USEC_PER_TICK``. The default value
|
||
of ``CONFIG_USEC_PER_TICK`` is 10000 microseconds which
|
||
corresponds to a timer interrupt rate of 100 Hz.
|
||
|
||
On each timer interrupt, NuttX does these things:
|
||
|
||
- Increments a counter. This counter is the system time and has a
|
||
resolution of ``CONFIG_USEC_PER_TICK`` microseconds.
|
||
- Checks if it is time to perform time-slice operations on tasks
|
||
that have select round-robin scheduling.
|
||
- Checks for expiration of timed events.
|
||
|
||
What is wrong with this default system timer? Nothing really. It
|
||
is reliable and uses only a small fraction of the CPU band width.
|
||
But we can do better. Some limitations of default system timer
|
||
are, in increasing order of importance:
|
||
|
||
- **Overhead**: Although the CPU usage of the system timer
|
||
interrupt at 100Hz is really very low, it is still mostly
|
||
wasted processing time. On most timer interrupts, there is
|
||
really nothing that needs to be done other than incrementing the
|
||
counter.
|
||
- **Resolution**: Resolution of all system timing is also
|
||
determined by ``CONFIG_USEC_PER_TICK``. So nothing can be timed
|
||
with resolution finer than 10 milliseconds by default. To
|
||
increase this resolution, ``CONFIG_USEC_PER_TICK`` can be
|
||
reduced. However, then the system timer interrupts use more of
|
||
the CPU bandwidth processing useless interrupts.
|
||
- **Power Usage**: But the biggest issue is power usage. When the
|
||
system is IDLE, it enters a light, low-power mode (for ARMs,
|
||
this mode is entered with the ``wfi`` or ``wfe`` instructions
|
||
for example). But each interrupt awakens the system from this
|
||
low power mode. Therefore, higher rates of interrupts cause
|
||
greater power consumption.
|
||
|
||
**Tickless OS**. The so-called *Tickless OS* provides one solution
|
||
to this issue. The basic concept here is that the periodic, timer
|
||
interrupt is eliminated and replaced with a one-shot, interval
|
||
timer. It becomes event driven instead of polled: The default
|
||
system timer is a polled design. On each interrupt, the NuttX
|
||
logic checks if it needs to do anything and, if so, it does it.
|
||
|
||
Using an interval timer, one can anticipate when the next
|
||
interesting OS event will occur, program the interval time and
|
||
wait for it to fire. When the interval time fires, then the
|
||
scheduled activity is performed.
|
||
|
||
Tickless Platform Support
|
||
-------------------------
|
||
|
||
In order to use the Tickless OS, one must provide special support
|
||
from the platform-specific code. Just as with the default system
|
||
timer, the platform-specific code must provide the timer resources
|
||
to support the OS behavior. Currently these timer resources are
|
||
only provided on a few platforms. An example implementation is for
|
||
the simulation is at ``nuttx/arch/sim/src/up_tickless.c``. There
|
||
is another example for the Atmel SAMA5 at
|
||
``nuttx/arch/arm/src/sama5/sam_tickless.c``. These paragraphs will
|
||
explain how to provide the Tickless OS support to any platform.
|
||
|
||
Tickless Configuration Options
|
||
------------------------------
|
||
|
||
- ``CONFIG_ARCH_HAVE_TICKLESS``: If the platform provides
|
||
support for the *Tickless OS*, then this setting should be
|
||
selected in the ``Kconfig`` file for the architecture. Here is
|
||
what the selection looks in the ``arch/Kconfig`` file for the
|
||
simulated platform:
|
||
|
||
.. code-block:: console
|
||
|
||
config ARCH_SIM
|
||
bool "Simulation"
|
||
select ARCH_HAVE_TICKLESS
|
||
---help---
|
||
Linux/Cygwin user-mode simulation.
|
||
|
||
When the simulation platform is selected,
|
||
``ARCH_HAVE_TICKLESS`` is automatically selected, informing the
|
||
configuration system that *Tickless OS* options can be
|
||
selected.
|
||
|
||
- ``CONFIG_SCHED_TICKLESS``: If ``CONFIG_ARCH_HAVE_TICKLESS`` is
|
||
selected, then you will be able to use this option to enable the
|
||
*Tickless OS* features in NuttX.
|
||
|
||
- ``CONFIG_SCHED_TICKLESS_ALARM``: The tickless option can be
|
||
supported either via a simple interval timer (plus elapsed
|
||
time) or via an alarm. The interval timer allows programming
|
||
events to occur after an interval. With the alarm, you can set
|
||
a time in the future and get an event when that alarm goes off.
|
||
This option selects the use of an alarm.
|
||
|
||
The advantage of an alarm is that it avoids some small timing
|
||
errors; the advantage of the use of the interval timer is that
|
||
the hardware requirement may be simpler.
|
||
|
||
- ``CONFIG_USEC_PER_TICK``: This option is not unique to
|
||
*Tickless OS* operation, but changes its relevance when the
|
||
*Tickless OS* is selected. In the default configuration, where
|
||
system time is provided by a periodic timer interrupt, the
|
||
default system timer is configured for 100Hz, that is,
|
||
``CONFIG_USEC_PER_TICK=10000``. If ``CONFIG_SCHED_TICKLESS`` is
|
||
selected, then there are no system timer interrupts. In this
|
||
case, ``CONFIG_USEC_PER_TICK`` does not control any timer
|
||
rates. Rather, it only determines the resolution of time
|
||
reported by ``clock_systime_ticks()`` and the resolution of
|
||
times that can be set for certain delays including watchdog
|
||
timers and delayed work.
|
||
|
||
In this case there is still a trade-off: It is better to have
|
||
the ``CONFIG_USEC_PER_TICK`` as low as possible for higher
|
||
timing resolution. However, the time is currently held in
|
||
``unsigned int``. On some systems, this may be 16-bits in width
|
||
but on most contemporary systems it will be 32-bits. In either
|
||
case, smaller values of ``CONFIG_USEC_PER_TICK`` will reduce
|
||
the range of values that delays that can be represented. So the
|
||
trade-off is between range and resolution (you could also
|
||
modify the code to use a 64-bit value if you really want both).
|
||
|
||
The default, 100 microseconds, will provide for a range of
|
||
delays up to 120 hours.
|
||
|
||
This value should never be less than the underlying resolution
|
||
of the timer. Errors may ensue.
|
||
|
||
Tickless Imported Interfaces
|
||
----------------------------
|
||
|
||
The interfaces that must be provided by the platform specified
|
||
code are defined in ``include/nuttx/arch.h``, listed below, and
|
||
summarized in the following paragraphs:
|
||
|
||
- ``<arch>_timer_initialize()`` Initializes
|
||
the timer facilities. Called early in the initialization
|
||
sequence (by ``up_initialize()``).
|
||
- ``up_timer_gettime()``: Returns the
|
||
current time from the platform specific time source.
|
||
|
||
The tickless option can be supported either via a simple interval
|
||
timer (plus elapsed time) or via an alarm. The interval timer
|
||
allows programming events to occur after an interval. With the
|
||
alarm, you can set a time in\* the future and get an event when
|
||
that alarm goes off.
|
||
|
||
If ``CONFIG_SCHED_TICKLESS_ALARM`` is defined, then the platform
|
||
code must provide the following:
|
||
|
||
- ``up_alarm_cancel()``: Cancels the alarm.
|
||
- ``up_alarm_start()``: Enables (or
|
||
re-enables) the alarm.
|
||
|
||
If ``CONFIG_SCHED_TICKLESS_ALARM`` is *not*\ defined, then the
|
||
platform code must provide the following verify similar functions:
|
||
|
||
- ``up_timer_cancel()``: Cancels the
|
||
interval timer.
|
||
- ``up_timer_start()``: Starts (or re-starts)
|
||
the interval timer.
|
||
|
||
Note that a platform-specific implementation would probably require two
|
||
hardware timers: (1) A interval timer to satisfy the requirements of
|
||
``up_timer_start()`` and ``up_timer_cancel()``, and (2) a counter to
|
||
handle the requirement of ``up_timer_gettime()``. Ideally, both timers
|
||
would run at the rate determined by ``CONFIG_USEC_PER_TICK`` (and
|
||
certainly never slower than that rate).
|
||
|
||
Since timers are a limited resource, the use of two timers could be an
|
||
issue on some systems. The job could be done with a single timer if, for
|
||
example, the single timer were kept in a free-running mode at all times.
|
||
Some timer/counters have the capability to generate a compare interrupt
|
||
when the timer matches a comparison value but also to continue counting
|
||
without stopping. If your hardware supports such counters, one might use
|
||
the ``CONFIG_SCHED_TICKLESS_ALARM`` option and be able to simply set the
|
||
comparison count at the value of the free running timer *PLUS* the
|
||
desired delay. Then you could have both with a single timer: An alarm
|
||
and a free-running counter with the same timer!
|
||
|
||
In addition to these imported interfaces, the RTOS will export the
|
||
following interfaces for use by the platform-specific interval
|
||
timer implementation:
|
||
|
||
- ``nxsched_process_timer()``: called by the platform-specific logic when the interval time expires.
|
||
|
||
.. c:function:: void archname_timer_initialize(void)
|
||
|
||
Initializes all platform-specific timer facilities. This function is
|
||
called early in the initialization sequence by up_initialize().
|
||
On return, the current up-time should be available from up_timer_gettime()
|
||
and the interval timer is ready for use (but not actively timing).
|
||
The naming will depend on the architecture so for STM32 ``archname`` will
|
||
be ``stm32``.
|
||
|
||
:return: Zero (OK) on success; a negated errno value on failure.
|
||
|
||
**Assumptions**: Called early in the initialization sequence before
|
||
any special concurrency protections are required.
|
||
|
||
.. c:function:: int up_timer_gettime(FAR struct timespec *ts)
|
||
|
||
Return the elapsed time since power-up (or, more correctly, since
|
||
*<arch>*\ ``_timer_initialize()`` was called). This function is
|
||
functionally equivalent to ``clock_gettime()`` for the clock ID
|
||
``CLOCK_MONOTONIC``. This function provides the basis for
|
||
reporting the current time and also is used to eliminate error
|
||
build-up from small errors in interval time calculations.
|
||
|
||
:param ts: Provides the location in which to return the up-time..
|
||
|
||
:return: Zero (OK) on success; a negated errno value on failure.
|
||
|
||
**Assumptions**: Called from the normal tasking context. The
|
||
implementation must provide whatever mutual exclusion is necessary
|
||
for correct operation. This can include disabling interrupts in
|
||
order to assure atomic register operations.
|
||
|
||
.. c:function:: int up_alarm_cancel(FAR struct timespec *ts)
|
||
|
||
Cancel the alarm and return the time of cancellation of the alarm.
|
||
These two steps need to be as nearly atomic as possible.
|
||
``nxsched_process_timer()`` will not be called unless the alarm
|
||
is restarted with ``up_alarm_start()``. If, as a race condition,
|
||
the alarm has already expired when this function is called, then
|
||
time returned is the current time.
|
||
|
||
:param ts: Location to return the expiration time. The current
|
||
time should be returned if the timer is not active. ``ts`` may
|
||
be ``NULL`` in which case the time is not returned
|
||
|
||
:return: Zero (OK) on success; a negated errno value on failure.
|
||
|
||
**Assumptions**: May be called from interrupt level handling or
|
||
from the normal tasking level. interrupts may need to be disabled
|
||
internally to assure non-reentrancy.
|
||
|
||
.. c:function:: int up_alarm_start(FAR const struct timespec *ts)
|
||
|
||
Start the alarm. ``nxsched_process_timer()`` will be called
|
||
when the alarm occurs (unless ``up_alarm_cancel`` is called to
|
||
stop it).
|
||
|
||
:param ts: The time in the future at the alarm is expected to
|
||
occur. When the alarm occurs the timer logic will call
|
||
``nxsched_process_timer()``.
|
||
|
||
:return: Zero (OK) on success; a negated errno value on failure.
|
||
|
||
**Assumptions**: May be called from interrupt level handling or
|
||
from the normal tasking level. Interrupts may need to be
|
||
disabled internally to assure non-reentrancy.
|
||
|
||
.. c:function:: int up_timer_cancel(FAR struct timespec *ts)
|
||
|
||
Cancel the interval timer and return the time remaining on the
|
||
timer. These two steps need to be as nearly atomic as possible.
|
||
``nxsched_process_timer()`` will not be called unless the timer
|
||
is restarted with ``up_timer_start()``. If, as a race condition,
|
||
the timer has already expired when this function is called, then
|
||
that pending interrupt must be cleared so that
|
||
``nxsched_process_timer()`` is not called spuriously and the
|
||
remaining time of zero should be returned.
|
||
|
||
:param ts: Location to return the remaining time. Zero should be
|
||
returned if the timer is not active.
|
||
|
||
:return: Zero (OK) on success; a negated errno value on failure.
|
||
|
||
**Assumptions**: May be called from interrupt level handling or
|
||
from the normal tasking level. interrupts may need to be
|
||
disabled internally to assure non-reentrancy.
|
||
|
||
.. c:function:: int up_timer_start(FAR const struct timespec *ts)
|
||
|
||
Start the interval timer. ``nxsched_process_timer()`` will be
|
||
called at the completion of the timeout (unless
|
||
``up_timer_cancel()`` is called to stop the timing).
|
||
|
||
:param ts: Provides the time interval until
|
||
``nxsched_process_timer()`` is called.
|
||
|
||
:return: Zero (OK) on success; a negated errno value on failure.
|
||
|
||
**Assumptions**: May be called from interrupt level handling
|
||
or from the normal tasking level. Interrupts may need to be
|
||
disabled internally to assure non-reentrancy.
|
||
|
||
Watchdog Timer Interfaces
|
||
=========================
|
||
|
||
NuttX provides a general watchdog timer facility. This facility
|
||
allows the NuttX user to specify a watchdog timer function that
|
||
will run after a specified delay in tick level resolution. The
|
||
watchdog timer function will run in the context of the timer
|
||
interrupt handler. Because of this, a limited number of NuttX
|
||
interfaces are available to he watchdog timer function. However,
|
||
the watchdog timer function may use ``mq_send()``, ``sigqueue()``,
|
||
or ``kill()`` to communicate with NuttX tasks.
|
||
|
||
- :c:func:`wd_start`
|
||
- :c:func:`wd_start_next`
|
||
- :c:func:`wd_restart`
|
||
- :c:func:`wd_restart_next`
|
||
- :c:func:`wd_cancel`
|
||
- :c:func:`wd_gettime`
|
||
- Watchdog Timer Callback
|
||
|
||
.. c:function:: int wd_start(FAR struct wdog_s *wdog, clock_t delay, \
|
||
wdentry_t wdentry, wdparm_t arg)
|
||
|
||
This function adds a watchdog to the timer queue.
|
||
The specified watchdog function will be called from the interrupt
|
||
level after the specified number of ticks has elapsed. Watchdog
|
||
timers may be started from the interrupt level.
|
||
|
||
Watchdog times execute in the context of the timer interrupt
|
||
handler.
|
||
|
||
Watchdog timers execute only once.
|
||
|
||
To replace either the timeout delay or the function to be
|
||
executed, call wd_start again with the same wdog; only the most
|
||
recent wd_start() on a given watchdog ID has any effect.
|
||
|
||
:param wdog: Watchdog ID
|
||
:param delay: Delay count in clock ticks
|
||
:param wdentry: Function to call on timeout
|
||
:param arg: The parameter to pass to wdentry.
|
||
|
||
**NOTE**: The parameter must be of type ``wdparm_t``.
|
||
|
||
:return: Zero (``OK``) is returned on success; a negated ``errno`` value
|
||
is return to indicate the nature of any failure.
|
||
|
||
**Assumptions/Limitations:** The watchdog routine runs in the
|
||
context of the timer interrupt handler and is subject to all ISR
|
||
restrictions.
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface. VxWorks
|
||
provides the following comparable interface:
|
||
|
||
.. code-block:: c
|
||
|
||
STATUS wdStart (WDOG_ID wdog, int delay, FUNCPTR wdentry, int parameter);
|
||
|
||
Differences from the VxWorks interface include:
|
||
|
||
- The present implementation supports multiple parameters passed
|
||
to wdentry; VxWorks supports only a single parameter. The
|
||
maximum number of parameters is determined by
|
||
|
||
.. c:function:: int wd_start_next(FAR struct wdog_s *wdog, clock_t delay, \
|
||
wdentry_t wdentry, wdparm_t arg)
|
||
|
||
This function restart watchdog timer based on the last expiration time.
|
||
It can be used to implement a periodic watchdog timer. E.g, Call this
|
||
function instead of wd_start in the watchdog callback to restart the
|
||
next timer for better timing accuracy.
|
||
Note that calling this function outside the watchdog callback requires
|
||
the wdog->expired being set.
|
||
|
||
:param wdog: Watchdog ID
|
||
:param delay: Delay count in clock ticks
|
||
:param wdentry: Function to call on timeout
|
||
:param arg: The parameter to pass to wdentry.
|
||
|
||
**NOTE**: The parameter must be of type ``wdparm_t``.
|
||
|
||
:return: Zero (``OK``) is returned on success; a negated ``errno`` value
|
||
is return to indicate the nature of any failure.
|
||
|
||
**Assumptions/Limitations:** The watchdog routine runs in the
|
||
context of the timer interrupt handler and is subject to all ISR
|
||
restrictions.
|
||
|
||
.. c:function:: int wd_cancel(FAR struct wdog_s *wdog)
|
||
|
||
This function cancels a currently running
|
||
watchdog timer. Watchdog timers may be canceled from the interrupt
|
||
level.
|
||
|
||
:param wdog: ID of the watchdog to cancel.
|
||
|
||
:return: ``OK`` or ``ERROR``
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface. VxWorks
|
||
provides the following comparable interface:
|
||
|
||
.. code-block:: c
|
||
|
||
STATUS wdCancel (WDOG_ID wdog);
|
||
|
||
.. c:function:: int wd_gettime(FAR struct wdog_s *wdog)
|
||
|
||
Returns the time remaining before
|
||
the specified watchdog expires.
|
||
|
||
:param wdog: Identifies the watchdog that the request is for.
|
||
|
||
:return: The time in system ticks remaining until the
|
||
watchdog time expires. Zero means either that wdog is not valid or
|
||
that the wdog has already expired.
|
||
|
||
.. c:type:: void (*wdentry_t)(wdparm_t arg)
|
||
|
||
**Watchdog Timer Callback**: when a watchdog expires,
|
||
the callback function with this type is
|
||
called.
|
||
|
||
The argument is passed as scalar ``wdparm_t`` values. For
|
||
systems where the ``sizeof(pointer) < sizeof(uint32_t)``, the
|
||
following union defines the alignment of the pointer within the
|
||
``uint32_t``. For example, the SDCC MCS51 general pointer is
|
||
24-bits, but ``uint32_t`` is 32-bits (of course).
|
||
|
||
We always have ``sizeof(pointer) <= sizeof(uintptr_t)`` by
|
||
definition.
|
||
|
||
.. code-block:: c
|
||
|
||
union wdparm_u
|
||
{
|
||
FAR void *pvarg; /* The size one generic point */
|
||
uint32_t dwarg; /* Big enough for a 32-bit value in any case */
|
||
uintptr_t uiarg; /* sizeof(uintptr_t) >= sizeof(pointer) */
|
||
};
|
||
|
||
#if UINTPTR_MAX >= UINT32_MAX
|
||
typedef uintptr_t wdparm_t;
|
||
#else
|
||
typedef uint32_t wdparm_t;
|
||
#endif
|
||
|
||
High-resolution Timer Interfaces
|
||
================================
|
||
|
||
Hard real-time applications, such as motor control, often
|
||
require nanosecond-level task timing, which tick-based timers
|
||
like wdog cannot provide. Reducing the tick interval to micro-
|
||
or nanoseconds is impractical, as it would overload the CPU with interrupts.
|
||
|
||
High-resolution Timer (HRTimer) is a timer abstraction capable of achieving
|
||
nanosecond-level timing resolution, primarily used in scenarios requiring
|
||
high-resolution clock events. With the advancement of integrated circuit
|
||
technology, modern high-resolution timer hardware, such as the typical x86
|
||
HPET, can already meet sub-nanosecond timing requirements and offer
|
||
femtosecond-level jitter control.
|
||
|
||
Although the current hardware timer abstraction (`up_alarm/up_timer`)
|
||
in the NuttX kernel already supports nanosecond-level timing, its software
|
||
timer abstraction, wdog, and the timer timeout interrupt handling process
|
||
remain at microsecond-level (tick) resolution, which falls short of
|
||
high-resolution timing demands.
|
||
|
||
To address this, NuttX provides a high-resolution timer (hrtimer),
|
||
which delivers true nanosecond-level precision. HRTimer primarily provides
|
||
the following functional interfaces:
|
||
|
||
**Set a timer in nanoseconds**: Configure a software timer to trigger at
|
||
a specified nanosecond time.
|
||
|
||
**Cancel a timer**: Cancel the software timer.
|
||
|
||
**Handle timer timeout**: Execute timeout processing after the timer event
|
||
is triggered.
|
||
|
||
A user can register an hrtimer callback to execute after a specified delay.
|
||
The callback runs in the timer interrupt context, so only limited NuttX interfaces
|
||
are available, such as ``mq_send()``, ``sigqueue()``, ``nxevent_post()``, or ``kill()``,
|
||
to communicate with tasks.
|
||
|
||
The hrtimer implementation mainly includes the following interfaces:
|
||
|
||
**hrtimer_start(timer, func, arg, delay)**: Asynchronously starts a
|
||
timer that has completed or has been asynchronously canceled (its
|
||
callback function might still be executing).
|
||
|
||
**hrtimer_cancel(timer)**: Asynchronously cancels a timer. Note that
|
||
the semantics of this interface are completely different from Linux's
|
||
`try_to_cancel`. It ensures that the timer can definitely be canceled
|
||
successfully, but may need to wait for its callback function to finish
|
||
execution.
|
||
|
||
**hrtimer_cancel_sync(timer)**: Synchronously cancels a timer. If the timer's
|
||
callback function is still executing, this function will spin-wait until
|
||
the callback completes. It ensures that the user can always obtain
|
||
ownership of the timer.
|
||
|
||
The state-machine diagram of the HRTimer is as follows:
|
||
|
||
.. code-block:: text
|
||
|
||
+------------------------------------------------------+
|
||
| HRTIMER State Diagram |
|
||
+------------------------------------------------------+
|
||
| |
|
||
| +----------------------+ |
|
||
| | HRTIMER_COMPLETED | |
|
||
| | (private) | |
|
||
| +----------------------+ |
|
||
| | |
|
||
| | hrtimer_start |
|
||
| | |
|
||
| | |
|
||
| v |
|
||
| +----------------------+ |
|
||
| | HRTIMER_PENDING |---------------------+ |
|
||
| +-->| (shared) |<---+ | |
|
||
| | +----------------------+ | | |
|
||
| | | |timer callback | |
|
||
| | |hrtimer_expiry |return non-zero | |
|
||
| | | | | |
|
||
| | v | | |
|
||
| | +----------------------+ | | |
|
||
| | | HRTIMER_RUNNING |----+ | |
|
||
| | | (shared) | | |
|
||
| | +----------------------+ | |
|
||
| | | | |
|
||
| | | | |
|
||
| | |timer return zero | |
|
||
| | |or | |
|
||
| | |hrtimer_cancel | |
|
||
| | | | |
|
||
| | v | |
|
||
| | +----------------------+ | |
|
||
| | | HRTIMER_CANCELED |<--------+ |
|
||
| +---------------| (half_shared) | |
|
||
| hrtimer_start +----------------------+ |
|
||
| | |
|
||
| hrtimer_cancel_sync| |
|
||
| wait all cores | |
|
||
| v |
|
||
| +----------------------+ |
|
||
| | HRTIMER_COMPLETED | |
|
||
| | (private) | |
|
||
| +----------------------+ |
|
||
| ^ | |
|
||
| | | |
|
||
| +--+ |
|
||
| hrtimer_cancel |
|
||
+------------------------------------------------------+
|
||
|
||
The specific definitions of the states are as follows:
|
||
|
||
**HRTIMER_PENDING|shared**: `hrtimer->func != NULL`. That is, the hrtimer has
|
||
been inserted into the hrtimer queue and is waiting to be executed.
|
||
|
||
**HRTIMER_COMPLETED|private**: `hrtimer->func == NULL` ∧
|
||
`∀c ∈ [0, CONFIG_SMP_NCPUS), (g_hrtimer_running[c] & ~(1u)) != hrtimer`
|
||
That is, the hrtimer is not in a pending state, and no core is currently
|
||
executing the hrtimer's callback function.
|
||
|
||
**HRTIMER_RUNNING|shared**: `hrtimer->func == NULL` ∧
|
||
`∃c ∈ [0, CONFIG_SMP_NCPUS), g_hrtimer_running[c] == hrtimer`.
|
||
That is, the hrtimer is not in a pending state, and there exists at least one
|
||
core that is currently executing the hrtimer’s callback function.
|
||
|
||
**HRTIMER_CANCELED|half_shared**: `hrtimer->func == NULL` ∧
|
||
`∀c ∈ [0, CONFIG_SMP_NCPUS), g_hrtimer_running[c] != hrtimer`.
|
||
That is, the hrtimer is not in a pending state, and all cores have lost
|
||
ownership of the hrtimer—meaning they can no longer read from or write to the
|
||
hrtimer—though its callback function may still be in the process of being
|
||
executed.
|
||
|
||
- :c:func:`hrtimer_init`
|
||
- :c:func:`hrtimer_cancel`
|
||
- :c:func:`hrtimer_cancel_sync`
|
||
- :c:func:`hrtimer_start`
|
||
- :c:func:`hrtimer_gettime`
|
||
- High-resolution Timer Callback
|
||
|
||
.. c:function:: void hrtimer_init(FAR hrtimer_t *hrtimer, hrtentry_t func)
|
||
|
||
This function initializes a high-resolution timer instance.
|
||
Sets the expiration callback and its argument. The timer is
|
||
not started by this function.
|
||
|
||
:param hrtimer: Pointer to hrtimer instance
|
||
:param func: Expiration callback function
|
||
|
||
:return: None.
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface.
|
||
|
||
.. c:function:: int hrtimer_cancel(FAR hrtimer_t *hrtimer)
|
||
|
||
Cancel a high-resolution timer asynchronously.
|
||
|
||
If the timer is armed but has not yet expired, it will be removed from
|
||
the timer queue and the callback will not be invoked.
|
||
|
||
If the timer callback is currently executing, this function will mark
|
||
the timer as canceled and return immediately. The running callback is
|
||
allowed to complete, but it will not be invoked again.
|
||
|
||
After the function completes, the caller acquires limited ownership,
|
||
allowing timer restart but not freeing. Callback may still be executing
|
||
on another CPU. Use with caution to avoid concurrency issues.
|
||
|
||
:param hrtimer: Timer instance to cancel
|
||
|
||
:return: ``OK`` on success; negated errno on failure.
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface.
|
||
|
||
.. c:function:: int hrtimer_cancel_sync(FAR hrtimer_t *hrtimer)
|
||
|
||
Cancel a high-resolution timer and wait synchronously until the timer
|
||
becomes inactive.
|
||
|
||
This function first calls hrtimer_cancel() to request cancellation of
|
||
the timer. It sets timer to canceled state and waits for all
|
||
references to be released. Caller acquires full ownership and can
|
||
safely deallocate the timer after this function returns.
|
||
|
||
This function may sleep and must not be called from interrupt context.
|
||
|
||
:param hrtimer: Timer instance to cancel
|
||
|
||
:return: ``OK`` on success; negated errno on failure.
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface.
|
||
|
||
.. c:function:: int hrtimer_start(FAR hrtimer_t *hrtimer, \
|
||
hrtimer_entry_t func, \
|
||
uint64_t expired, \
|
||
enum hrtimer_mode_e mode)
|
||
|
||
This function starts a high-resolution timer in absolute or relative mode.
|
||
|
||
:param hrtimer: Timer instance to cancel
|
||
:param func: Expiration callback function
|
||
:param ns: Timer expiration in nanoseconds (absolute or relative)
|
||
:param mode: HRTIMER_MODE_ABS or HRTIMER_MODE_REL
|
||
|
||
:return: ``OK`` on success; negated errno on failure.
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface.
|
||
|
||
.. c:function:: uint64_t hrtimer_gettime(FAR hrtimer_t *timer)
|
||
|
||
Get the remaining time until timer expiration.
|
||
|
||
:param timer: Timer instance to query
|
||
|
||
:return: Remaining time in nanoseconds until next expiration.
|
||
|
||
**Assumptions:**
|
||
- Timer is not NULL.
|
||
|
||
**POSIX Compatibility:** This is a NON-POSIX interface.
|
||
|
||
.. c:type:: uint64_t (*hrtimer_entry_t)(FAR hrtimer_t *hrtimer, \
|
||
uint64_t expired)
|
||
|
||
**High-resolution Timer Callback**: when a hrtimer expires,
|
||
the callback function with this type is called.
|
||
|
||
:param timer: The hrtimer pointer passed to callback function,
|
||
do not modify the hrtimer when executing callback function.
|
||
:param expired: Time in nanoseconds when timer expired
|
||
|
||
:return: Next delay in nanoseconds until next expiration.
|