nuttx/Documentation/implementation/short_time_delays.rst
Tomasz 'CeDeROM' CEDRO 0cfa6f1b96 doc: Migrating the rest of documentation from cwiki.
* This completes task list in https://github.com/apache/nuttx/issues/11127.
* This preserves selected content from cwiki and moves it to new docs.
* Most pages are simple copy-paste with a simple RST formatting updates,
  with minor updates.
* Content update / reorganization will follow later on when needed.
* Files added (or updated title from cwiki -> current docs):
  * Documentation/implementation:
    * index.
    * cancellation_points.
    * Asynchronous vs. Synchronous Context Switches -> context_switches.rst.
    * ARMv7-M Hardfaults, SVCALL, and Debuggers -> hardfatuls.rst.
    * chip.h FAQ -> chip_h.rst.
    * Debug Output (SYSLOG) Issues -> syslog.rst.
    * Detaching File Descriptors -> file_descriptors.rst.
    * device_nodes.rst.
    * Dynamic Clocking -> power_management.rst.
    * ENOTTY ioctl() Return Value -> ioctl.rst.
    * memory_configurations.rst.
    * kernel_modules_vs_shared_libraries.rst.
    * NAKing USB OUT/IN Tokens -> usb.rst.
    * naming_arch_mcu_board_interfaces.rst.
    * naming_os_internals.rst.
    * nuttx_tasking.rst.
    * oneshot_timers_and_cpu_load.rst.
    * nuttx_initialization_sequence.rst.
    * short_time_delays.rst.
    * Signal Handler Tour -> signal_handlers.rst.
    * smp.rst.
    * syslog.rst.
    * Task Exit Sequence -> nuttx_tasking.rst.
    * tasks_vs_threads.rst.
    * tls.rst.
    * tickless_os.rst.
    * Why Can't Kernel Threads Have pthreads -> kernel_threads_vs_pthreads.rst.
  * Documentation/components/filesystem:
    * smartfs.rst.

Signed-off-by: Tomasz 'CeDeROM' CEDRO <tomek@cedro.info>
2026-05-28 09:34:04 +08:00

300 lines
9.7 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

.. _short-time-delays:
=================
Short Time Delays
=================
System Timer Interrupt
======================
This section addresses some counter-intuitive properties of using very short
time delays.
This section assumes that timing is generated by a system timer
"tick" interrupt. In Tickless Mode there is no system timer interrupt.
Much of the discussion here would also apply in the Tickless mode, however,
the terminology used here assumes system timer interrupts.
Timer Resolution
================
When we talk about short delays, we are talking about delays that are
on the same order of magnitude as the system timer interrupt interval.
That interval is controlled the configuration setting
``CONFIG_USEC_PER_TICK``. The default value of ``CONFIG_USEC_PER_TICK``
is 10,000 microseconds.
That equivalent to a timer interrupt frequency of 100Hz:
.. code-block:: c
Ftimer ticks/sec = 1,000,000 usec/sec / CONFIG_USEC_PER_TICK;
There are many different OS interfaces that implement timed delays
(or function that have timeout values such as ``sem_timedwait()``).
These all behave in basically the same way.
.. _usleep:
``usleep()``
============
For simplicity of discussion let's focus on on ``usleep()``.
``usleep()`` is a standard but deprecated interface that simply delays
for a specified number of microseconds.
.. note:: ``usleep()`` is deprecated in favor of ``clock_nanosleep()``.
Requirements/Assumptions of ``usleep()``
----------------------------------------
The prototype for ``usleep()`` is:
.. code-block:: c
int usleep(useconds_t usec);
Where ``usec`` is the number microseconds to delay.
``usleep()`` will return zero unless an error occurs in which case
it will set the errno variable and return ``-1``.
Now the interesting questions:
Suppose that ``CONFIG_USEC_PER_TICK`` is set to 10,000 microseconds,
What will happen when you try any of the following?
.. code-block:: c
usleep(0);
usleep(1);
usleep(10000);
In order to predict such behavior, we will have to enumerate some of the
assumption and requirements of ``usleep()``:
1. The contract that ``usleep()`` makes is that it will suspend the calling
thread for at least usec microseconds. It will never, under any condition,
return with a delay smaller than the requestion usec delay.
2. ``usleep()`` may wait longer than the requested delay due to small system
processing overhead, task priorities, and quantization errors.
Task Priorities
---------------
When the requested delay expires, the calling task is ready to run but still
may not actually be able to run for some time due to higher priority tasks
that block execution of the ready to run task.
Quantization Errors
-------------------
``usleep()`` is only capable of waiting for multiples of the system timer
interrupt interval (``CONFIG_USEC_PER_TICK``) If the requested ``usec`` delay
is not an even multiple of the system timer interrupt interval,
then it will be rounded up as necessary to satisfy the first requirement above.
This means that the following are really equivalent delays:
.. code-block:: c
usleep(1);
usleep(10000);
And finally, the behavior that confuses most people..
.. note::
``usleep()`` has no knowledge of the the phase of the system timer
when it is started: The next timer interrupt may occur immediately
or may be delayed for almost a full cycle.
In order to meet the contract of the first requirement, the requested
time is also always incremented by one.
This means, for example, that the the delay:
.. code-block:: c
usleep(10000);
will not delay for one clock tick! That would be impossible!
There is no event exactly one clock tick after ``usleep()`` is called.
The next timer tick will always occur at some time strictly less than
the system timer interval.
Rather, ``usleep()`` will delay for two clock ticks resulting some
actual delay between 10 and 20 microseconds, exclusive.
See the following figure:
.. figure:: short_delay.png
:alt: Short Time Delays in NuttX.
Short Time Delays in NuttX.
For the most part, when delays are large hundreds or thousands
of microseconds this error is not significant.
It becomes noticeable only if you are using ``usleep()``
for delays very close to the the timer resolution when the error
can be relatively significant.
Finally, the easy one:
.. code-block:: c
usleep(0);
This will return immediately with no delay.
That satisfies all requirements and assumptions of the interface.
Using ``usleep()`` to implement periodic delays
-----------------------------------------------
The third assumption is necessary because ``usleep()`` has
no knowledge of the current system timer phase.
But it also makes ``usleep()`` a very bad choice for implementing
periodic behavior if the ``usleep()`` delay is close to the system
timer resolution.
For example, consider a loop such as the following
(again assuming that ``CONFIG_USEC_PER_TICK`` is set
to 10,000 microseconds):
.. code-block:: c
for (; ; )
{
usleep(10000);
/* Do some periodic stuff */
}
From the preceding discussion, we know that this will not work:
It will not (and cannot) create periodic processing at the rate
of the system timer interrupt rate but, rather, at half the system
time interrupt rate in this case.
In this case, that the third assumption of ``usleep()`` is not valid.
``usleep()`` does not start timing some random, unknown phase with respect
to the system timer interrupt.
In this case, ``usleep()`` will be started at a nearly constant phase
with respect to the system timer (at some some short delay after the
system timer interrupt) and will consistently show
near-worst case timing errors.
Of course, ``usleep()`` cannot know that and, hence, is a bad choice
for implementing such periodic behavior.
The user in this case is assuming that ``usleep()`` simply waits for the
next timer tick to occur. That is not the behavior or ``usleep()`` that
describes some non-existent interface that waits for the next timer interrupt
event, not for a fixed time delay.
.. note::
``usleep()`` behavior is to wait to assure that at least usec
microseconds has elapsed. And it does that job quite well.
A final note: The above periodic delay loop can be made to work well,
on the other hand, if the delay provided to ``usleep()`` is significantly
larger that the system timer resolution.
What can you do?
----------------
What can you do to improve the resolution for such high frequency processing
loop?
First, you might consider increasing the system timer interrupt rate.
You would do this by reducing the value of ``CONFIG_USEC_PER_TICK``.
For example a value of ``1000`` for ``CONFIG_USEC_PER_TICK`` would create
a timer interrupt rate of 1KHz and a minimum loop delay of
perhaps 2 milliseconds.
The trade-off here is that when you increase the timer interrupt rate,
the timer interrupt processing will then take a proportionately larger amount
of your CPU bandwidth.
The recommended way to get very high timer resolution without increasing
the timer interrupt rate (in most use cases) is to use a Tickless Mode OS.
For example, in the Tickless configuration, you could have
``CONFIG_USEC_PER_TICK`` set to ``1`` for a 1MHz timer resolution
(with no interrupts).
If your target periodic processing time is still 1Khz,
then this periodic processing could be met with good precision.
Another option is to abandon the system timer altogether and use
a dedicated timer peripheral to perform your timed operations with
high precision.
But if you really want to use the system timer than another thing
you should consider would be to implement a Timer Hook.
.. timer_hook:
Timer Hook
==========
A Timer Hook is a user provided function that is called from the OS
on each timer interrupt.
If you enable ``CONFIG_SYSTEMTICK_HOOK=y`` in your configuration,
then the OS timer interrupt handler will call out to a user-provided function,
``board_timerhook()``, on each timer interrupt.
The full prototype of this function is provided in ``included/nuttx/board.h``
as:
.. code-block:: c
void board_timerhook(void);
.. note::
The timer hook is only available when system timer interrupts
are used; it is not available in Tickless mode.
This timer hook could be used in a scenario where you would like to have
your task run at the each timer interrupt without the strange rounding
performed by the standard delay functions.
You might do something like the following as an example:
In your servicing task, implement a loop. At the top of the loop,
you would wait on a semaphore.
In the body of the loop you perform the periodic operation
then return to the wait at the top of the loop. Like:
.. code-block:: c
int ret;
while ((ret = sem_wait(&g_waitsem) >= 0)
{
/* Do periodic operations */
}
.. note::
If the return value from ``sem_wait()`` is negative then
some unusual event occurred. In normal cases the errno might either:
``EINTR`` meaning simply that the wait was awakened with a signal.
You can continue to wait in this case. Or ``ECANCELED`` meaning
that the thread has been cancelled and you should abort the periodic
operations.
In your ``boards/<arch>/<chip>/<board>/src/`` directory you would implement
``void board_timerhook(void)``.
The implementation of this function could consist of only a single line
of code: It could just post the semaphore on each timer interrupt,
waking of the loop in the servicing task. Like:
.. code-block:: c
void board_timerhook(void)
{
(void)sem_post(&g_waitsem);
}
This is very efficient. There is no context switch overhead at all
getting from the the timing interrupt to to the servicing task.
None at all other that the normal interrupt return logic.
The servicing task should be the highest priority task in the system
to assure that it is the one that runs immediately
when the timer interrupt returns.