mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
BREAKING: In an effort to simplify NuttX initialization, NSH_ARCHINIT is removed. board_app_initialize is also removed. BOARD_LATE_INITIALIZE now performs all board initialization logic, and is by default enabled. All references to these symbols are removed. BOARDIOC_INIT remains, but will result in -ENOTTY when called. It is to be removed in a later commit. Quick fix: Boards relying on NSH_ARCHINIT should now enable CONFIG_BOARD_LATE_INITIALIZE instead. If the application needs fine-grained control over board initialization from userspace, the logic performed by BOARDIOC_INIT may be copied to the board_finalinitialize function and used instead via BOARDIOC_FINALINIT. All board_app_initialize logic provided by NuttX is now moved to board_late_initialize, and the same should be done for out-of-tree boards. Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
546 lines
18 KiB
ReStructuredText
546 lines
18 KiB
ReStructuredText
Timer Drivers
|
|
=============
|
|
|
|
Files supporting the timer driver can be found in the following
|
|
locations:
|
|
|
|
- **Interface Definition**. The header file for the NuttX timer
|
|
driver reside at ``include/nuttx/timers/timer.h``. This header
|
|
file includes both the application level interface to the timer
|
|
driver as well as the interface between the "upper half" and
|
|
"lower half" drivers. The timer driver uses a standard
|
|
character driver framework.
|
|
- **"Upper Half" Driver**. The generic, "upper half" timer driver
|
|
resides at ``drivers/timers/timer.c``.
|
|
- **"Lower Half" Drivers**. Platform-specific timer drivers
|
|
reside in ``arch/<architecture>/src/<hardware>``
|
|
directory for the specific processor ``<architecture>`` and for
|
|
the specific ``<chip>`` timer peripheral devices.
|
|
|
|
There are two ways to enable Timer Support along with the Timer Example. The
|
|
first is faster and simpler. Just run the following command to use a ready
|
|
config file with timer support and example included. You need to check if there's
|
|
a timer config file for your specific chip. You may check it at the specific
|
|
board's path: ``/boards/<arch>/<chip>/<variant>/config``.
|
|
|
|
.. code-block:: console
|
|
|
|
$ ./tools/configure.sh <variant>:timer
|
|
|
|
And the second way is creating your own config file. To do so, follow the next
|
|
instructions.
|
|
|
|
Enabling the Timer Support and Example in ``menuconfing``
|
|
----------------------------------------------------------
|
|
|
|
1. Select Timer Instances
|
|
|
|
To select these timers browse in the ``menuconfig`` using the following path:
|
|
|
|
Go into menu :menuselection:`System Type --> <Chip> Peripheral Selection` and press :kbd:`Enter`.
|
|
|
|
Then select one or more timers according to availability.
|
|
|
|
2. Enable the Timer Support
|
|
|
|
Go into menu :menuselection:`Device Drivers --> Timer Driver Support` and press :kbd:`Enter`. Then enable:
|
|
|
|
- [x] Timer Support
|
|
- [x] Timer Arch Implementation
|
|
|
|
3. Include the Timer Example
|
|
|
|
Go into menu :menuselection:`Application Configuration --> Examples` and press :kbd:`Enter`. Then select the Timer Example.
|
|
|
|
- [x] Timer example
|
|
|
|
Below the option, it is possible to manually configure some parameters as the
|
|
standard timer device path, the timeout, the sample rate in which the counter
|
|
will be read, the number of samples to be executed, and other parameters.
|
|
|
|
Timer Example
|
|
-------------
|
|
|
|
The previously selected example will basically consult the timer status, set a
|
|
timer alarm interval, set a timer signal handler function to be notified at the
|
|
alarm, which only increments a variable, and then it will start the timer. The
|
|
application will periodically consult the timer status at the sample rate
|
|
previously configured through the ``menuconfig`` to follow the time left until
|
|
the timer expires. After the samples have been read, the application stops the
|
|
timer.
|
|
|
|
The `example code <https://github.com/apache/nuttx-apps/blob/master/examples/timer/timer_main.c>`_
|
|
may be explored, its path is at ``/examples/timer/timer_main.c`` in the apps' repository.
|
|
|
|
In NuttX, the timer driver is a character driver and when a chip supports multiple
|
|
timers, each one is accessible through its respective file in ``/dev`` directory.
|
|
Each timer is registered using a unique numeric identifier (i.e. ``/dev/timer0``,
|
|
``/dev/timer1``, ...).
|
|
|
|
Use the following command to run the example:
|
|
|
|
.. code-block:: console
|
|
|
|
`nsh> timer`
|
|
|
|
This command will use the timer 0. To use the others, specify it through a
|
|
parameter (where x is the timer number):
|
|
|
|
.. code-block:: console
|
|
|
|
`nsh> timer -d /dev/timerx`
|
|
|
|
Application Level Interface
|
|
---------------------------
|
|
|
|
The first necessary thing to be done in order to use the timer driver in an
|
|
application is to include the header file for the NuttX timer driver. It contains
|
|
the Application Level Interface to the timer driver. To do so, include:
|
|
|
|
.. code-block:: c
|
|
|
|
#include <nuttx/timers/timer.h>
|
|
|
|
At an application level, the timer functionalities may be accessed through ``ioctl``
|
|
systems calls. The available ``ioctl`` commands are:
|
|
|
|
* :c:macro:`TCIOC_START`
|
|
* :c:macro:`TCIOC_STOP`
|
|
* :c:macro:`TCIOC_GETSTATUS`
|
|
* :c:macro:`TCIOC_SETTIMEOUT`
|
|
* :c:macro:`TCIOC_NOTIFICATION`
|
|
* :c:macro:`TCIOC_MAXTIMEOUT`
|
|
* :c:macro:`TCIOC_TICK_GETSTATUS`
|
|
* :c:macro:`TCIOC_TICK_SETTIMEOUT`
|
|
* :c:macro:`TCIOC_TICK_MAXTIMEOUT`
|
|
|
|
These ``ioctl`` commands internally call lower-half layer operations and the
|
|
parameters are forwarded to these ops through the ``ioctl`` system call. The return
|
|
of a system call is the return of an operation.
|
|
These ``struct timer_ops_s`` keeps pointers to the implementation of each operation.
|
|
Following is the struct.
|
|
|
|
.. c:struct:: timer_ops_s
|
|
.. code-block:: c
|
|
|
|
struct timer_ops_s
|
|
{
|
|
/* Required methods *******************************************************/
|
|
|
|
/* Start the timer, resetting the time to the current timeout */
|
|
|
|
CODE int (*start)(FAR struct timer_lowerhalf_s *lower);
|
|
|
|
/* Stop the timer */
|
|
|
|
CODE int (*stop)(FAR struct timer_lowerhalf_s *lower);
|
|
|
|
/* Get the current timer status */
|
|
|
|
CODE int (*getstatus)(FAR struct timer_lowerhalf_s *lower,
|
|
FAR struct timer_status_s *status);
|
|
|
|
/* Set a new timeout value (and reset the timer) */
|
|
|
|
CODE int (*settimeout)(FAR struct timer_lowerhalf_s *lower,
|
|
uint32_t timeout);
|
|
|
|
/* Call the NuttX INTERNAL timeout callback on timeout.
|
|
* NOTE: Providing callback==NULL disable.
|
|
* NOT to call back into applications.
|
|
*/
|
|
|
|
CODE void (*setcallback)(FAR struct timer_lowerhalf_s *lower,
|
|
CODE tccb_t callback, FAR void *arg);
|
|
|
|
/* Any ioctl commands that are not recognized by the "upper-half" driver
|
|
* are forwarded to the lower half driver through this method.
|
|
*/
|
|
|
|
CODE int (*ioctl)(FAR struct timer_lowerhalf_s *lower, int cmd,
|
|
unsigned long arg);
|
|
|
|
/* Get the maximum supported timeout value */
|
|
|
|
CODE int (*maxtimeout)(FAR struct timer_lowerhalf_s *lower,
|
|
FAR uint32_t *maxtimeout);
|
|
};
|
|
|
|
Since ``ioctl`` system calls expect a file descriptor, before using these commands,
|
|
it's necessary to open the timer device special file in order to get a file descriptor.
|
|
The following snippet demonstrates how to do so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Open the timer device */
|
|
|
|
printf("Open %s\n", devname);
|
|
|
|
fd = open(devname, O_RDONLY);
|
|
if (fd < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to open %s: %d\n",
|
|
devname, errno);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
.. c:macro:: TCIOC_START
|
|
|
|
The ``TCIOC_START`` command calls the ``start`` operation, which is described below.
|
|
|
|
.. c:function:: int start(void)
|
|
|
|
The start operation configures the timer, enables the interrupt if ``TCIOC_NOTIFICATION``
|
|
has already been called and finally starts the timer.
|
|
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Start the timer */
|
|
|
|
printf("Start the timer\n");
|
|
|
|
ret = ioctl(fd, TCIOC_START, 0);
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to start the timer: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
.. c:macro:: TCIOC_STOP
|
|
|
|
The ``TCIOC_STOP`` command calls the ``stop`` operation, which is described below.
|
|
|
|
.. c:function:: int stop(void)
|
|
|
|
The stop operation stops the timer and disables the interrupt.
|
|
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Stop the timer */
|
|
|
|
printf("\nStop the timer\n");
|
|
|
|
ret = ioctl(fd, TCIOC_STOP, 0);
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to stop the timer: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
.. c:macro:: TCIOC_GETSTATUS
|
|
|
|
The ``TCIOC_GETSTATUS`` command calls the ``getstatus`` operation, which is described below.
|
|
|
|
.. c:function:: int getstatus(FAR struct timer_status_s *status)
|
|
|
|
The getstatus operation gathers the timer's current information.
|
|
|
|
:param status: A writable pointer to a struct ``timer_status_s``. This struct
|
|
contains 3 fields: ``flags`` (``uint32_t``), ``timeout`` (``uint32_t``)
|
|
and ``timeleft`` (``uint32_t``). Bit 0 from `flags` indicates the timer's
|
|
status, 1 indicates that the timer is running, zero it is stopped. Bit 1
|
|
from `flags` indicates if there's a callback registered. The `timeout`
|
|
indicates the time interval that was configured to trigger an alarm, it
|
|
is in microseconds. The `timeleft` interval indicates how many microseconds
|
|
it's missing to trigger the alarm. The following snippet demonstrates how
|
|
to use it and how to access these fields.
|
|
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Get timer status */
|
|
|
|
ret = ioctl(fd, TCIOC_GETSTATUS, (unsigned long)((uintptr_t)&status));
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to get timer status: %d\n", errno);
|
|
close(fd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Print the timer status */
|
|
|
|
printf(" flags: %08lx timeout: %lu timeleft: %lu\n",
|
|
(unsigned long)status.flags, (unsigned long)status.timeout,
|
|
(unsigned long)status.timeleft);
|
|
|
|
.. c:macro:: TCIOC_SETTIMEOUT
|
|
|
|
The ``TCIOC_SETTIMEOUT`` command calls the ``settimeout`` operation, which is described below.
|
|
|
|
.. c:function:: int settimeout(uint32_t timeout)
|
|
|
|
The settimeout operation sets a timeout interval to trigger the alarm and then
|
|
trigger an interrupt. It defines the timer interval in which the handler will
|
|
be called.
|
|
|
|
:param timeout: An argument of type ``uint32_t`` with the timeout value in microseconds.
|
|
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Set the timer interval */
|
|
|
|
printf("Set timer interval to %lu\n",
|
|
(unsigned long)CONFIG_EXAMPLES_TIMER_INTERVAL);
|
|
|
|
ret = ioctl(fd, TCIOC_SETTIMEOUT, CONFIG_EXAMPLES_TIMER_INTERVAL);
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to set the timer interval: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
.. c:macro:: TCIOC_NOTIFICATION
|
|
|
|
The ``TCIOC_NOTIFICATION`` is used to configure the timer callback to notify the
|
|
application via a signal when the timer expires. This command calls the ``setcallback``
|
|
operation. Which will not be described here, since the application does not set a
|
|
callback directly. Instead, the user should configure a signal handler to catch
|
|
notifications, and then, configure a timer notifier to notify and to signal the
|
|
previously configured signal handler. For a better performance, a separate pthread
|
|
may be configured to wait on sigwaitinfo() for timer events.
|
|
|
|
In any case, this command expects a read-only pointer to a struct `timer_notify_s`.
|
|
This struct contains 2 fields: ``pid`` (``pid_t``), that indicates the ID of the
|
|
task/thread to receive the signal and ``event`` (``struct sigevent``), which
|
|
describes the way a task will be notified.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
printf("Configure the notification\n");
|
|
|
|
notify.pid = getpid();
|
|
notify.event.sigev_notify = SIGEV_SIGNAL;
|
|
notify.event.sigev_signo = CONFIG_EXAMPLES_TIMER_SIGNO;
|
|
notify.event.sigev_value.sival_ptr = NULL;
|
|
|
|
ret = ioctl(fd, TCIOC_NOTIFICATION, (unsigned long)((uintptr_t)¬ify));
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to set the timer handler: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
.. c:macro:: TCIOC_MAXTIMEOUT
|
|
|
|
The ``TCIOC_MAXTIMEOUT`` command calls the ``maxtimeout`` operation, which is described below.
|
|
|
|
.. c:function:: int maxtimeout(uint32_t *status)
|
|
|
|
The maxtimeout operation gets the maximum timeout value that can be configured.
|
|
|
|
:param maxtimeout: A writable pointer to a variable of ``uint32_t`` type in
|
|
which the value will be stored.
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Get the maximum timer timeout */
|
|
|
|
printf("Get the maximum timer timeout\n");
|
|
|
|
ret = ioctl(fd, TCIOC_MAXTIMEOUT, (uint32_t*)(&max_timeout));
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to read the timer's maximum timeout: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* Print the maximum supported timeout */
|
|
|
|
printf("Maximum supported timeout: %" PRIu32 "\n", max_timeout);
|
|
|
|
The ``TCIOC_TICK_GETSTATUS`` command invokes the ``getstatus`` lower-half
|
|
operation and returns the current timer status expressed in timer ticks.
|
|
The conversion from microseconds to ticks is performed by the timer
|
|
upper-half driver.
|
|
|
|
The ``getstatus`` operation gathers the timer's current information.
|
|
When invoked via ``TCIOC_TICK_GETSTATUS``, the ``timeout`` and
|
|
``timeleft`` fields are converted from microseconds to timer ticks
|
|
before being returned to the caller.
|
|
|
|
:param status: A writable pointer to a struct timer_status_s.
|
|
This structure contains the same fields as used by
|
|
`TCIOC_GETSTATUS`, but the timeout and timeleft
|
|
values are expressed in timer ticks instead of
|
|
microseconds.
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Get timer status in ticks */
|
|
|
|
ret = ioctl(fd, TCIOC_TICK_GETSTATUS, (unsigned long)((uintptr_t)&status));
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to get timer tick status: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
printf("flags: %08lx timeout(ticks): %lu timeleft(ticks): %lu\n",
|
|
(unsigned long)status.flags, (unsigned long)status.timeout,
|
|
(unsigned long)status.timeleft);
|
|
|
|
The ``TCIOC_TICK_SETTIMEOUT`` command calls the ``settimeout`` operation and
|
|
sets a new timeout value expressed in timer ticks.
|
|
|
|
The settimeout operation configures the timer to expire after the specified
|
|
number of timer ticks and resets the timer. The timeout value is converted
|
|
from ticks to microseconds by the timer upper-half driver before invoking
|
|
the lower-half settimeout operation.
|
|
|
|
:param timeout: An argument of type uint32_t that specifies the timeout
|
|
interval in timer ticks.
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Set timer timeout in ticks */
|
|
|
|
printf("Set timer timeout to %lu ticks\n",
|
|
(unsigned long)timeout_ticks);
|
|
|
|
ret = ioctl(fd, TCIOC_TICK_SETTIMEOUT, timeout_ticks);
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to set timer tick timeout: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
The ``TCIOC_TICK_MAXTIMEOUT`` command calls the ``maxtimeout`` operation and
|
|
returns the maximum supported timeout value expressed in timer ticks.
|
|
|
|
The maxtimeout operation gets the maximum timeout value that can be
|
|
configured for the timer when using tick-based time units.
|
|
|
|
:param maxtimeout: A writable pointer to a variable of uint32_t type in
|
|
which the maximum supported timeout (in ticks) will be
|
|
stored.
|
|
:return: A Linux System Error Code for failing or 0 for success.
|
|
|
|
This command may be used like so:
|
|
|
|
.. code-block:: c
|
|
|
|
/* Get the maximum timer timeout in ticks */
|
|
|
|
printf("Get the maximum timer timeout in ticks\n");
|
|
|
|
ret = ioctl(fd, TCIOC_TICK_MAXTIMEOUT, (uint32_t *)(&max_timeout));
|
|
if (ret < 0)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to read timer tick maximum timeout: %d\n", errno);
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* Print the maximum supported timeout (ticks) */
|
|
|
|
printf("Maximum supported timeout (ticks): %" PRIu32 "\n", max_timeout);
|
|
|
|
Those snippets were taken from the Example which provides a great resource to
|
|
demonstrate how to use those ``ioctl`` commands.
|
|
|
|
Software Timer Implementation (timer_wdog)
|
|
------------------------------------------
|
|
|
|
The ``CONFIG_TIMER_WDOG`` option enables a software-based implementation
|
|
of the timer driver interface. Instead of using hardware timer peripherals,
|
|
this implementation leverages the NuttX internal watchdog (wdog) timer
|
|
subsystem to provide timer functionality.
|
|
|
|
**Concept**
|
|
|
|
The wdog subsystem is a kernel-level software timer mechanism that
|
|
maintains a queue of pending timeouts. When ``timer_wdog`` is enabled,
|
|
it creates a standard timer device (e.g., ``/dev/timer0``) that internally
|
|
uses ``wd_start()`` and related wdog APIs to schedule and manage timeouts.
|
|
This allows platforms without dedicated hardware timers to still provide
|
|
the standard timer driver interface to applications.
|
|
|
|
**Enabling timer_wdog**
|
|
|
|
To enable this feature, set the following configuration options:
|
|
|
|
.. code-block:: none
|
|
|
|
CONFIG_TIMER=y
|
|
CONFIG_TIMER_WDOG=y
|
|
|
|
Then call ``timer_wdog_initialize()`` from your board initialization code.
|
|
|
|
.. code-block:: c
|
|
|
|
#include <nuttx/timers/timer_wdog.h>
|
|
|
|
#ifdef CONFIG_TIMER_WDOG
|
|
ret = timer_wdog_initialize(0); /* Creates /dev/timer0 */
|
|
if (ret < 0)
|
|
{
|
|
syslog(LOG_ERR, "ERROR: timer_wdog_initialize failed: %d\n", ret);
|
|
}
|
|
#endif
|
|
|
|
**Pros**
|
|
|
|
- **No hardware dependency**: Works on any platform that supports the
|
|
NuttX wdog subsystem, including the simulator (sim).
|
|
- **Easy to enable**: Requires minimal board-level code; just a single
|
|
initialization call.
|
|
- **Standard interface**: Applications use the same timer ioctl commands
|
|
as hardware-based timers, ensuring portability.
|
|
- **Useful for testing**: Ideal for testing timer-dependent application
|
|
code on the simulator or platforms lacking hardware timers.
|
|
- **Multiple instances**: Multiple software timers can be created by
|
|
calling ``timer_wdog_initialize()`` with different IDs.
|
|
|
|
**Cons**
|
|
|
|
- **Resolution limited by system tick**: The timer resolution is bound
|
|
to the system tick rate (``CONFIG_USEC_PER_TICK``). For example, with
|
|
a 10ms tick, the minimum timeout granularity is 10ms.
|
|
- **Less accurate than hardware timers**: Software timers depend on the
|
|
scheduler and may experience jitter, especially under heavy system load.
|
|
- **Runs in interrupt context**: The timeout callback executes in the
|
|
context of the system timer interrupt, which may introduce latency for
|
|
signal delivery to applications.
|
|
- **Not suitable for high-precision timing**: Applications requiring
|
|
microsecond-level precision should use hardware timer implementations.
|
|
|
|
**Use Cases**
|
|
|
|
- Simulator-based development and testing
|
|
- Platforms without available hardware timer peripherals
|
|
- Prototyping timer-based application logic
|
|
- Educational purposes and learning the NuttX timer interface
|