docs: provide translation for gptimer

This commit is contained in:
Wang Zi Yan 2022-07-06 17:39:23 +08:00 committed by morris
parent 495d35949d
commit 5e768f7f2d
2 changed files with 417 additions and 64 deletions

View File

@ -4,11 +4,11 @@ General Purpose Timer (GPTimer)
Introduction
------------
The GPTimer (General Purpose Timer) is the driver of {IDF_TARGET_NAME} Timer Group peripheral. It features a hardware timer with high resolution and flexible alarm action. The behavior when the internal counter of a timer reaches a specific target value is called timer alarm. When a timer alarms, a user registered per-timer callback would be called.
GPTimer (General Purpose Timer) is the driver of {IDF_TARGET_NAME} Timer Group peripheral. The hardware timer features high resolution and flexible alarm action. The behavior when the internal counter of a timer reaches a specific target value is called a timer alarm. When a timer alarms, a user registered per-timer callback would be called.
Typically, a general purpose timer can be used in scenarios like:
- Free running as a wall clock, fetching a high-resolution time stamp at any time and any places
- Free running as a wall clock, fetching a high-resolution timestamp at any time and any places
- Generate period alarms, trigger events periodically
- Generate one-shot alarm, respond in target time
@ -17,27 +17,29 @@ Functional Overview
The following sections of this document cover the typical steps to install and operate a timer:
- `Resource Allocation <#resource-allocation>`__ - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working.
- `Set and Get count value <#set-and-get-count-value>`__ - covers how to force the timer counting from a start point and how to get the count value at anytime.
- `Set Up Alarm Action <#set-up-alarm-action>`__ - covers the parameters that should be set up to enable the alarm event.
- `Register Event Callbacks <#register-event-callbacks>`__ - covers how to hook user specific code to the alarm event callback function.
- `Enable and Disable timer <#enable-and-disable-timer>`__ - covers how to enable and disable the timer.
- `Start and Stop timer <#start-and-stop-timer>`__ - shows some typical use cases that start the timer with different alarm behavior.
- `Power Management <#power-management>`__ - describes how different source clock selections can affect power consumption.
- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache.
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
- `Kconfig options <#kconfig-options>`__ - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
- :ref:`resource-allocation` - covers which parameters should be set up to get a timer handle and how to recycle the resources when GPTimer finishes working.
- :ref:`set-and-get-count-value` - covers how to force the timer counting from a start point and how to get the count value at anytime.
- :ref:`set-up-alarm-action` - covers the parameters that should be set up to enable the alarm event.
- :ref:`register-event-callbacks` - covers how to hook user specific code to the alarm event callback function.
- :ref:`enable-and-disable-timer` - covers how to enable and disable the timer.
- :ref:`start-and-stop-timer` - shows some typical use cases that start the timer with different alarm behavior.
- :ref:`power-management` - describes how different source clock selections can affect power consumption.
- :ref:`iram-safe` - describes tips on how to make the timer interrupt and IO control functions work better along with a disabled cache.
- :ref:`thread-safety` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. _resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
Different ESP chips might have different numbers of independent timer groups, and within each group, there could also be several independent timers. [1]_
From driver's point of view, a GPTimer instance is represented by :cpp:type:`gptimer_handle_t`. The driver behind will manage all available hardware resources in a pool, so that users don't need to care about which timer and which group it belongs to.
A GPTimer instance is represented by :cpp:type:`gptimer_handle_t`. The driver behind will manage all available hardware resources in a pool, so that you do not need to care about which timer and which group it belongs to.
To install a timer instance, there's a configuration structure that needs to be given in advance: :cpp:type:`gptimer_config_t`:
To install a timer instance, there is a configuration structure that needs to be given in advance: :cpp:type:`gptimer_config_t`:
- :cpp:member:`gptimer_config_t::clk_src` selects the source clock for the timer. The available clocks are listed in :cpp:type:`gptimer_clock_source_t`, you can only pick one of them. For the effect on power consumption of different clock source, please refer to `Power management <#power-management>`__ section.
- :cpp:member:`gptimer_config_t::clk_src` selects the source clock for the timer. The available clocks are listed in :cpp:type:`gptimer_clock_source_t`, you can only pick one of them. For the effect on power consumption of different clock source, please refer to Section :ref:`power-management`.
- :cpp:member:`gptimer_config_t::direction` sets the counting direction of the timer, supported directions are listed in :cpp:type:`gptimer_count_direction_t`, you can only pick one of them.
@ -49,10 +51,10 @@ With all the above configurations set in the structure, the structure can be pas
The function can fail due to various errors such as insufficient memory, invalid arguments, etc. Specifically, when there are no more free timers (i.e. all hardware resources have been used up), then :c:macro:`ESP_ERR_NOT_FOUND` will be returned. The total number of available timers is represented by the :c:macro:`SOC_TIMER_GROUP_TOTAL_TIMERS` and its value will depend on the ESP chip.
If a previously created GPTimer instance is no longer required, you should recycle the timer by calling :cpp:func:`gptimer_del_timer`. This will allow the underlying HW timer to be used for other purposes. Before deleting a GPTimer handle, you should disable it by :cpp:func:`gptimer_disable` in advance or make sure it has not enabled yet by :cpp:func:`gptimer_enable`.
If a previously created GPTimer instance is no longer required, you should recycle the timer by calling :cpp:func:`gptimer_del_timer`. This will allow the underlying HW timer to be used for other purposes. Before deleting a GPTimer handle, please disable it by :cpp:func:`gptimer_disable` in advance or make sure it has not enabled yet by :cpp:func:`gptimer_enable`.
Creating a GPTimer Handle with Resolution of 1MHz
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Creating a GPTimer Handle with Resolution of 1 MHz
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
@ -64,66 +66,75 @@ Creating a GPTimer Handle with Resolution of 1MHz
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
.. _set-and-get-count-value:
Set and Get Count Value
^^^^^^^^^^^^^^^^^^^^^^^
When the GPTimer is created, the internal counter will be reset to zero by default. The counter value can be updated asynchronously by :cpp:func:`gptimer_set_raw_count`. The maximum count value is dependent on the hardware timer's bit-width, which is also reflected by the SOC macro :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH`. When updating the raw count of an active timer, the timer will immediately start counting from the new value.
When the GPTimer is created, the internal counter will be reset to zero by default. The counter value can be updated asynchronously by :cpp:func:`gptimer_set_raw_count`. The maximum count value is dependent on the bit width of the hardware timer, which is also reflected by the SOC macro :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH`. When updating the raw count of an active timer, the timer will immediately start counting from the new value.
Count value can be retrieved by :cpp:func:`gptimer_get_raw_count`, at anytime.
Count value can be retrieved by :cpp:func:`gptimer_get_raw_count`, at any time.
Set Up Alarm Action
.. _set-up-alarm-action:
Set up Alarm Action
^^^^^^^^^^^^^^^^^^^
Most of the use cases of GPTimer should set up the alarm action before starting the timer, except for the simple wall-clock scenario, where a free running timer is enough. To set up the alarm action, one should configure several members of :cpp:type:`gptimer_alarm_config_t` based on how he takes use of the alarm event:
For most of the use cases of GPTimer, you should set up the alarm action before starting the timer, except for the simple wall-clock scenario, where a free running timer is enough. To set up the alarm action, you should configure several members of :cpp:type:`gptimer_alarm_config_t` based on how you make use of the alarm event:
- :cpp:member:`gptimer_alarm_config_t::alarm_count` sets the target count value that will trigger the alarm event. You should also take the counting direction into consideration when setting the alarm value.
Specially, :cpp:member:`gptimer_alarm_config_t::alarm_count` and :cpp:member:`gptimer_alarm_config_t::reload_count` can't be set to the same value when :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` is true, as keeping reload with a target alarm count is meaningless.
- :cpp:member:`gptimer_alarm_config_t::alarm_count` sets the target count value that will trigger the alarm event. You should also take the counting direction into consideration when setting the alarm value. Specially, :cpp:member:`gptimer_alarm_config_t::alarm_count` and :cpp:member:`gptimer_alarm_config_t::reload_count` cannot be set to the same value when :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` is true, as keeping reload with a target alarm count is meaningless.
- :cpp:member:`gptimer_alarm_config_t::reload_count` sets the count value to be reloaded when the alarm event happens. This configuration only takes effect when :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` is set to true.
- :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` flag sets whether to enable the auto-reload feature. If enabled, the hardware timer will reload the value of :cpp:member:`gptimer_alarm_config_t::reload_count` into counter immediately when alarm event happens.
- :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` flag sets whether to enable the auto-reload feature. If enabled, the hardware timer will reload the value of :cpp:member:`gptimer_alarm_config_t::reload_count` into counter immediately when an alarm event happens.
To make the alarm configurations take effect, one should call :cpp:func:`gptimer_set_alarm_action`. Especially, if :cpp:type:`gptimer_alarm_config_t` is set to ``NULL``, the alarm function will be disabled.
To make the alarm configurations take effect, you should call :cpp:func:`gptimer_set_alarm_action`. Especially, if :cpp:type:`gptimer_alarm_config_t` is set to ``NULL``, the alarm function will be disabled.
.. note::
If an alarm value is set and the timer has already crossed this value, the alarm will be triggered immediately.
If an alarm value is set and the timer has already exceeded this value, the alarm will be triggered immediately.
.. _register-event-callbacks:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
After the timer starts up, it can generate specific event (e.g. the "Alarm Event") dynamically. If you have some function that should be called when event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`gptimer_register_event_callbacks`. All supported event callbacks are listed in the :cpp:type:`gptimer_event_callbacks_t`:
After the timer starts up, it can generate a specific event (e.g. the "Alarm Event") dynamically. If you have some functions that should be called when the event happens, please hook your function to the interrupt service routine by calling :cpp:func:`gptimer_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`gptimer_event_callbacks_t`:
- :cpp:member:`gptimer_event_callbacks_t::on_alarm` sets callback function for alarm event. As this function is called within the ISR context, user must ensure that the function doesn't attempt to block (e.g., by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The function prototype is declared in :cpp:type:`gptimer_alarm_cb_t`.
- :cpp:member:`gptimer_event_callbacks_t::on_alarm` sets a callback function for alarm events. As this function is called within the ISR context, you must ensure that the function does not attempt to block (e.g., by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The function prototype is declared in :cpp:type:`gptimer_alarm_cb_t`.
One can save his own context to :cpp:func:`gptimer_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to the callback functions.
You can save your own context to :cpp:func:`gptimer_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to the callback function.
This function will lazy install interrupt service for the timer but not enable it. So user should call this function before :cpp:func:`gptimer_enable`, otherwise the :c:macro:`ESP_ERR_INVALID_STATE` error will be returned. See also `Enable and Disable timer <#enable-and-disable-timer>`__ for more information.
This function will lazy install the interrupt service for the timer but not enable it. So please call this function before :cpp:func:`gptimer_enable`, otherwise the :c:macro:`ESP_ERR_INVALID_STATE` error will be returned. See Section :ref:`enable-and-disable-timer` for more information.
.. _enable-and-disable-timer:
Enable and Disable Timer
^^^^^^^^^^^^^^^^^^^^^^^^
Before doing IO control to the timer, user needs to enable the timer first, by calling :cpp:func:`gptimer_enable`. Internally, this function will:
Before doing IO control to the timer, you needs to enable the timer first, by calling :cpp:func:`gptimer_enable`. This function will:
* switch the timer driver state from **init** to **enable**.
* enable the interrupt service if it has been lazy installed by :cpp:func:`gptimer_register_event_callbacks`.
* acquire a proper power management lock if a specific clock source (e.g. APB clock) is selected. See also `Power management <#power-management>`__ for more information.
* Switch the timer driver state from **init** to **enable**.
* Enable the interrupt service if it has been lazy installed by :cpp:func:`gptimer_register_event_callbacks`.
* Acquire a proper power management lock if a specific clock source (e.g. APB clock) is selected. See Section :ref:`power-management` for more information.
On the contrary, calling :cpp:func:`gptimer_disable` will do the opposite, that is, put the timer driver back to the **init** state, disable the interrupts service and release the power management lock.
Calling :cpp:func:`gptimer_disable` will do the opposite, that is, put the timer driver back to the **init** state, disable the interrupts service and release the power management lock.
.. _start-and-stop-timer:
Start and Stop Timer
^^^^^^^^^^^^^^^^^^^^
The basic IO operation of a timer is to start and stop. Calling :cpp:func:`gptimer_start` can make the internal counter work, while calling :cpp:func:`gptimer_stop` can make the counter stop working. The following will illustrate on how to start a timer with or without alarm event.
The basic IO operation of a timer is to start and stop. Calling :cpp:func:`gptimer_start` can make the internal counter work, while calling :cpp:func:`gptimer_stop` can make the counter stop working. The following illustrates how to start a timer with or without an alarm event.
Start Timer As a Wall Clock
Start Timer as a Wall Clock
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
// Retrieve timestamp at anytime
// Retrieve the timestamp at any time
uint64_t count;
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
@ -140,13 +151,13 @@ Trigger Period Events
{
BaseType_t high_task_awoken = pdFALSE;
QueueHandle_t queue = (QueueHandle_t)user_ctx;
// Retrieve count value from event data
// Retrieve the count value from event data
example_queue_element_t ele = {
.event_count = edata->count_value
};
// Optional: send the event data to other task by OS queue
// Don't introduce complex logics in callbacks.
// Suggest dealing with event data in the main loop, instead of in this callback.
// Do not introduce complex logics in callbacks
// Suggest dealing with event data in the main loop, instead of in this callback
xQueueSendFromISR(queue, &ele, &high_task_awoken);
// return whether we need to yield at the end of ISR
return high_task_awoken == pdTRUE;
@ -181,7 +192,7 @@ Trigger One-Shot Event
QueueHandle_t queue = (QueueHandle_t)user_ctx;
// Stop timer the sooner the better
gptimer_stop(timer);
// Retrieve count value from event data
// Retrieve the count value from event data
example_queue_element_t ele = {
.event_count = edata->count_value
};
@ -206,7 +217,7 @@ Trigger One-Shot Event
Dynamic Alarm Update
~~~~~~~~~~~~~~~~~~~~
Alarm value can be updated dynamically inside the ISR handler callback, by changing the :cpp:member:`gptimer_alarm_event_data_t::alarm_value`. Then the alarm value will be updated after the callback function returns.
Alarm value can be updated dynamically inside the ISR handler callback, by changing :cpp:member:`gptimer_alarm_event_data_t::alarm_value`. Then the alarm value will be updated after the callback function returns.
.. code:: c
@ -218,7 +229,7 @@ Alarm value can be updated dynamically inside the ISR handler callback, by chang
{
BaseType_t high_task_awoken = pdFALSE;
QueueHandle_t queue = (QueueHandle_t)user_data;
// Retrieve count value from event data
// Retrieve the count value from event data
example_queue_element_t ele = {
.event_count = edata->count_value
};
@ -245,29 +256,33 @@ Alarm value can be updated dynamically inside the ISR handler callback, by chang
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));
.. _power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the period of a GPTimer's counting step and leading to inaccurate time keeping.
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into Light-sleep mode, thus potentially changing the period of a GPTimer's counting step and leading to inaccurate time keeping.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever the driver creates a GPTimer instance that has selected :cpp:enumerator:`GPTIMER_CLK_SRC_APB` as its clock source, the driver will guarantee that the power management lock is acquired when enable the timer by :cpp:func:`gptimer_enable`. Likewise, the driver releases the lock when :cpp:func:`gptimer_disable` is called for that timer.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever the driver creates a GPTimer instance that has selected :cpp:enumerator:`GPTIMER_CLK_SRC_APB` as its clock source, the driver will guarantee that the power management lock is acquired when enabling the timer by :cpp:func:`gptimer_enable`. Likewise, the driver releases the lock when :cpp:func:`gptimer_disable` is called for that timer.
If the gptimer clock source is selected to others like :cpp:enumerator:`GPTIMER_CLK_SRC_XTAL`, then the driver won't install power management lock for it, which is more suitable for a low power application as long as the source clock can still provide sufficient resolution.
If other gptimer clock sources are selected such as :cpp:enumerator:`GPTIMER_CLK_SRC_XTAL`, then the driver will not install power management lock. The XTAL clock source is more suitable for a low power application as long as the source clock can still provide sufficient resolution.
.. _iram-safe:
IRAM Safe
^^^^^^^^^
By default, the GPTimer interrupt will be deferred when the Cache is disabled for reasons like writing/erasing Flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.
By default, the GPTimer interrupt will be deferred when the cache is disabled because of writing or erasing the flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.
There's a Kconfig option :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` that will:
There is a Kconfig option :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` that will:
1. Enable the interrupt being serviced even when cache is disabled
2. Place all functions that used by the ISR into IRAM [2]_
3. Place driver object into DRAM (in case it's mapped to PSRAM by accident)
- Enable the interrupt being serviced even when the cache is disabled
- Place all functions that used by the ISR into IRAM [2]_
- Place driver object into DRAM (in case it is mapped to PSRAM by accident)
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
This will allow the interrupt to run while the cache is disabled, but will come at the cost of increased IRAM consumption.
There's another Kconfig option :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So that these functions can also be executable when the cache is disabled. These IO control functions are as follows:
There is another Kconfig option :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So, these functions can also be executable when the cache is disabled. These IO control functions are as follows:
- :cpp:func:`gptimer_start`
- :cpp:func:`gptimer_stop`
@ -275,11 +290,14 @@ There's another Kconfig option :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` that can
- :cpp:func:`gptimer_set_raw_count`
- :cpp:func:`gptimer_set_alarm_action`
.. _thread-safety:
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`gptimer_new_timer` is guaranteed to be thread safe by the driver, which means, user can call it from different RTOS tasks without protection by extra locks.
The following functions are allowed to run under ISR context, the driver uses a critical section to prevent them being called concurrently in both task and ISR.
The factory function :cpp:func:`gptimer_new_timer` is guaranteed to be thread safe by the driver, which means, you can call it from different RTOS tasks without protection by extra locks.
The following functions are allowed to run under ISR context, as the driver uses a critical section to prevent them being called concurrently in the task and ISR.
- :cpp:func:`gptimer_start`
- :cpp:func:`gptimer_stop`
@ -287,19 +305,21 @@ The following functions are allowed to run under ISR context, the driver uses a
- :cpp:func:`gptimer_set_raw_count`
- :cpp:func:`gptimer_set_alarm_action`
Other functions that take the :cpp:type:`gptimer_handle_t` as the first positional parameter, are not treated as thread safe. Which means the user should avoid calling them from multiple tasks.
Other functions that take :cpp:type:`gptimer_handle_t` as the first positional parameter, are not treated as thread safe, which means you should avoid calling them from multiple tasks.
.. _kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` controls where to place the GPTimer control functions (IRAM or Flash), see `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` controls where to place the GPTimer control functions (IRAM or flash), see Section :ref:`iram-safe` for more information.
- :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` controls whether the default ISR handler can work when the cache is disabled, see Section :ref:`iram-safe` for more information.
- :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size.
Application Examples
--------------------
* Typical use cases of GPTimer are listed in the example: :example:`peripherals/timer_group/gptimer`.
* Typical use cases of GPTimer are listed in the example :example:`peripherals/timer_group/gptimer`.
API Reference
-------------
@ -308,7 +328,7 @@ API Reference
.. include-build-file:: inc/timer_types.inc
.. [1]
Different ESP chip series might have different numbers of GPTimer instances. Please refer to Chapter `Timer Group (TIMG) <{IDF_TARGET_TRM_EN_URL}#timg>`__ in {IDF_TARGET_NAME} Technical Reference Manual for more details. The driver won't forbid you from applying for more timers, but it will return error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g. :cpp:func:`gptimer_new_timer`).
Different ESP chip series might have different numbers of GPTimer instances. For more details, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > Chapter *Timer Group (TIMG)* [`PDF <{IDF_TARGET_TRM_EN_URL}#timg>`__]. The driver will not forbid you from applying for more timers, but it will return error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g. :cpp:func:`gptimer_new_timer`).
.. [2]
:cpp:member:`gptimer_event_callbacks_t::on_alarm` callback and the functions invoked by itself should also be placed in IRAM, users need to take care of them by themselves.
:cpp:member:`gptimer_event_callbacks_t::on_alarm` callback and the functions invoked by the callback should also be placed in IRAM, please take care of them by yourself.

View File

@ -1 +1,334 @@
.. include:: ../../../en/api-reference/peripherals/gptimer.rst
通用定时器
=====================
简介
-----------------
通用定时器是 {IDF_TARGET_NAME} 定时器组外设的驱动程序。{IDF_TARGET_NAME} 硬件定时器分辨率高,具有灵活的报警功能。定时器内部计数器达到特定目标数值的行为被称为定时器报警。定时器报警时将调用用户注册的不同定时器回调函数。
通用定时器通常在以下场景中使用:
- 如同挂钟一般自由运行,随时随地获取高分辨率时间戳;
- 生成周期性警报,定期触发事件;
- 生成一次性警报,在目标时间内响应。
功能概述
-----------------
下文介绍了配置和操作定时器的常规步骤:
- :ref:`resource-allocation` - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。
- :ref:`set-and-get-count-value` - 如何强制定时器从起点开始计数,以及如何随时获取计数值。
- :ref:`set-up-alarm-action` - 启动警报事件应设置的参数。
- :ref:`register-event-callbacks` - 如何将用户的特定代码挂载到警报事件回调函数。
- :ref:`enable-and-disable-timer` - 如何使能和禁用定时器。
- :ref:`start-and-stop-timer` - 通过不同报警行为启动定时器的典型使用场景。
- :ref:`power-management` - 选择不同的时钟源将会如何影响功耗。
- :ref:`iram-safe` - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。
- :ref:`thread-safety` - 驱动程序保证哪些 API 线程安全。
- :ref:`kconfig-options` - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。
.. _resource-allocation:
资源分配
^^^^^^^^^^^^^^^^^^
不同的 ESP 芯片可能有不同数量的独立定时器组,每组内也可能有若干个独立定时器。[1]_
通用定时器实例由 :cpp:type:`gptimer_handle_t` 表示。后台驱动会在资源池中管理所有可用的硬件资源,这样您便无需考虑硬件所属的定时器以及定时器组。
要安装一个定时器实例,需要提前提供配置结构体 :cpp:type:`gptimer_config_t`
- :cpp:member:`gptimer_config_t::clk_src` 选择定时器的时钟源。:cpp:type:`gptimer_clock_source_t` 中列出多个可用时钟,仅可选择其中一个时钟。了解不同时钟源对功耗的影响,请查看章节 :ref:`power-management`
- :cpp:member:`gptimer_config_t::direction` 设置定时器的计数方向,:cpp:type:`gptimer_count_direction_t` 中列出多个支持的方向,仅可选择其中一个方向。
- :cpp:member:`gptimer_config_t::resolution_hz` 设置内部计数器的分辨率。计数器每滴答一次相当于 **1 / resolution_hz** 秒。
- 选用 :cpp:member:`gptimer_config_t::intr_shared` 设置是否将定时器中断源标记为共享源。了解共享中断的优缺点,请参考 :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`
完成上述结构配置之后,可以将结构传递给 :cpp:func:`gptimer_new_timer`,用以实例化定时器实例并返回定时器句柄。
该函数可能由于内存不足、参数无效等错误而失败。具体来说,当没有更多的空闲定时器(即所有硬件资源已用完)时,将返回 :c:macro:`ESP_ERR_NOT_FOUND`。可用定时器总数由 :c:macro:`SOC_TIMER_GROUP_TOTAL_TIMERS` 表示,不同的 ESP 芯片该数值不同。
如已不再需要之前创建的通用定时器实例,应通过调用 :cpp:func:`gptimer_del_timer` 回收定时器,以便底层硬件定时器用于其他目的。在删除通用定时器句柄之前,请通过 :cpp:func:`gptimer_disable` 禁用定时器,或者通过 :cpp:func:`gptimer_enable` 确认定时器尚未使能。
创建分辨率为 1 MHz 的通用定时器句柄
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
.. _set-and-get-count-value:
设置和获取计数值
^^^^^^^^^^^^^^^^^^^^^^^^^
创建通用定时器时,内部计数器将默认重置为零。计数值可以通过 :cpp:func:`gptimer_set_raw_count` 异步更新。最大计数值取决于硬件定时器的位宽,这也会在 SOC 宏 :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH` 中有所反映。当更新活动定时器的原始计数值时,定时器将立即从新值开始计数。
计数值可以随时通过 :cpp:func:`gptimer_get_raw_count` 获取。
.. _set-up-alarm-action:
设置警报动作
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置 :cpp:type:`gptimer_alarm_config_t` 的不同参数:
- :cpp:member:`gptimer_alarm_config_t::alarm_count` 设置触发警报事件的目标计数值。设置警报值时还需考虑计数方向。尤其是当 :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 为 true 时,:cpp:member:`gptimer_alarm_config_t::alarm_count`:cpp:member:`gptimer_alarm_config_t::reload_count` 不能设置为相同的值,因为警报值和重载值相同时没有意义。
- :cpp:member:`gptimer_alarm_config_t::reload_count` 代表警报事件发生时要重载的计数值。此配置仅在 :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 设置为 true 时生效。
- :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 标志设置是否使能自动重载功能。如果使能,硬件定时器将在警报事件发生时立即将 :cpp:member:`gptimer_alarm_config_t::reload_count` 的值重载到计数器中。
要使警报配置生效,需要调用 :cpp:func:`gptimer_set_alarm_action`。特别是当 :cpp:type:`gptimer_alarm_config_t` 设置为 ``NULL`` 时,报警功能将被禁用。
.. 注解::
如果警报值已设置且定时器超过该值,则会立即触发警报。
.. _register-event-callbacks:
注册事件回调函数
^^^^^^^^^^^^^^^^^^^^^^^^
定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过 :cpp:func:`gptimer_register_event_callbacks` 将函数挂载到中断服务例程 (ISR)。:cpp:type:`gptimer_event_callbacks_t` 中列出了所有支持的事件回调函数:
- :cpp:member:`gptimer_event_callbacks_t::on_alarm` 设置警报事件的回调函数。由于此函数在 ISR 上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有 ``ISR`` 后缀的 FreeRTOS API。函数原型在 :cpp:type:`gptimer_alarm_cb_t` 中有所声明。
您也可以通过参数 ``user_data`` 将自己的上下文保存到 :cpp:func:`gptimer_register_event_callbacks` 中。用户数据将直接传递给回调函数。
此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在 :cpp:func:`gptimer_enable` 之前调用这一函数,否则将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。了解详细信息,请查看章节 :ref:`enable-and-disable-timer`
.. _enable-and-disable-timer:
使能和禁用定时器
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
在对定时器进行 IO 控制之前,需要先调用 :cpp:func:`gptimer_enable` 使能定时器。此函数功能如下:
* 此函数将把定时器驱动程序的状态从 **init** 切换为 **enable**。
* 如果 :cpp:func:`gptimer_register_event_callbacks` 已经延迟安装中断服务,此函数将使能中断服务。
* 如果选择了特定的时钟源(例如 APB 时钟),此函数将获取适当的电源管理锁。了解更多信息,请查看章节 :ref:`power-management`
调用 :cpp:func:`gptimer_disable` 会进行相反的操作,即将定时器驱动程序恢复到 **init** 状态,禁用中断服务并释放电源管理锁。
.. _start-and-stop-timer:
启动和停止定时器
^^^^^^^^^^^^^^^^
启动和停止是定时器的基本 IO 操作。调用 :cpp:func:`gptimer_start` 可以使内部计数器开始工作,而 :cpp:func:`gptimer_stop` 可以使计数器停止工作。下文说明了如何在存在或不存在警报事件的情况下启动定时器。
将定时器作为挂钟启动
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
// Retrieve the timestamp at anytime
uint64_t count;
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
触发周期性事件
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
typedef struct {
uint64_t event_count;
} example_queue_element_t;
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
BaseType_t high_task_awoken = pdFALSE;
QueueHandle_t queue = (QueueHandle_t)user_ctx;
// Retrieve the count value from event data
example_queue_element_t ele = {
.event_count = edata->count_value
};
// Optional: send the event data to other task by OS queue
// Don't introduce complex logics in callbacks
// Suggest dealing with event data in the main loop, instead of in this callback
xQueueSendFromISR(queue, &ele, &high_task_awoken);
// return whether we need to yield at the end of ISR
return high_task_awoken == pdTRUE;
}
gptimer_alarm_config_t alarm_config = {
.reload_count = 0, // counter will reload with 0 on alarm event
.alarm_count = 1000000, // period = 1s @resolution 1MHz
.flags.auto_reload_on_alarm = true, // enable auto-reload
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_on_alarm_cb, // register user callback
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
触发一次性事件
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
typedef struct {
uint64_t event_count;
} example_queue_element_t;
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
BaseType_t high_task_awoken = pdFALSE;
QueueHandle_t queue = (QueueHandle_t)user_ctx;
// Stop timer the sooner the better
gptimer_stop(timer);
// Retrieve the count value from event data
example_queue_element_t ele = {
.event_count = edata->count_value
};
// Optional: send the event data to other task by OS queue
xQueueSendFromISR(queue, &ele, &high_task_awoken);
// return whether we need to yield at the end of ISR
return high_task_awoken == pdTRUE;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_on_alarm_cb, // register user callback
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
警报值动态更新
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
通过更改 :cpp:member:`gptimer_alarm_event_data_t::alarm_value`,可以在 ISR 程序回调中动态更新警报值。警报值将在回调函数返回后更新。
.. code:: c
typedef struct {
uint64_t event_count;
} example_queue_element_t;
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
BaseType_t high_task_awoken = pdFALSE;
QueueHandle_t queue = (QueueHandle_t)user_data;
// Retrieve the count value from event data
example_queue_element_t ele = {
.event_count = edata->count_value
};
// Optional: send the event data to other task by OS queue
xQueueSendFromISR(queue, &ele, &high_task_awoken);
// reconfigure alarm value
gptimer_alarm_config_t alarm_config = {
.alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
};
gptimer_set_alarm_action(timer, &alarm_config);
// return whether we need to yield at the end of ISR
return high_task_awoken == pdTRUE;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_on_alarm_cb, // register user callback
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));
.. _power-management:
电源管理
^^^^^^^^^^^^^^^^^
当使能电源管理时(即 :ref:`CONFIG_PM_ENABLE` 已打开),系统将在进入 Light-sleep 模式之前调整 APB 频率,从而可能会改变通用定时器的计数步骤周期,导致计时不准确。
然而,驱动程序可以通过获取类型为 :cpp:enumerator:`ESP_PM_APB_FREQ_MAX` 的电源管理锁来阻止系统更改 APB 频率。每当驱动程序创建一个通用定时器实例,且该实例选择 :cpp:enumerator:`GPTIMER_CLK_SRC_APB` 作为其时钟源的时,驱动程序会确保在通过 :cpp:func:`gptimer_enable` 使能定时器时,已经获取了电源管理锁。同样,当为该定时器调用 :cpp:func:`gptimer_disable` 时,驱动程序会释放电源管理锁。
如果选择 :cpp:enumerator:`GPTIMER_CLK_SRC_XTAL` 等其他时钟源那么驱动程序不会安装电源管理锁。只要时钟源仍可提供足够的分辨率XTAL 时钟源就更适合低功耗应用。
.. _iram-safe:
IRAM 安全
^^^^^^^^^^^^^^^^^^
默认情况下,当 cache 因写入或擦除 flash 等原因而被禁用时,通用定时器的中断服务将会延迟,造成警报中断无法及时执行。在实时应用程序中通常需要避免这一情况发生。
调用 Kconfig 选项 :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` 可实现如下功能:
- 即使禁用 cache 也可使能正在运行的中断
- 将 ISR 使用的所有函数放入 IRAM [2]_
- 将驱动程序对象放入 DRAM以防意外映射到 PSRAM
这将允许中断在 cache 禁用时运行,但会增加 IRAM 使用量。
调用另一 Kconfig 选项 :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 也可将常用的 IO 控制功能放入 IRAM以便这些函数在 cache 禁用时也能执行。常用的 IO 控制功能如下:
- :cpp:func:`gptimer_start`
- :cpp:func:`gptimer_stop`
- :cpp:func:`gptimer_get_raw_count`
- :cpp:func:`gptimer_set_raw_count`
- :cpp:func:`gptimer_set_alarm_action`
.. _thread-safety:
线程安全
^^^^^^^^^^^^^^^^^^
驱动程序会保证工厂函数 :cpp:func:`gptimer_new_timer` 的线程安全,这意味着您可以从不同的 RTOS 任务中调用这一函数,而无需额外的锁保护。
由于驱动程序通过使用临界区来防止这些函数在任务和 ISR 中同时被调用,所以以下函数能够在 ISR 上下文中运行。
- :cpp:func:`gptimer_start`
- :cpp:func:`gptimer_stop`
- :cpp:func:`gptimer_get_raw_count`
- :cpp:func:`gptimer_set_raw_count`
- :cpp:func:`gptimer_set_alarm_action`
:cpp:type:`gptimer_handle_t` 作为第一个位置参数的其他函数不被视作线程安全,也就是说应该避免从多个任务中调用这些函数。
.. _kconfig-options:
Kconfig 选项
^^^^^^^^^^^^^^^^^^^^^^
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 控制放置通用定时器控制函数IRAM 或 flash的位置。了解更多信息请参考章节 :ref:`iram-safe`
- :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` 控制默认 ISR 程序在 cache 禁用时是否可以运行。了解更多信息,请参考章节 :ref:`iram-safe`
- :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` 用于启用调试日志输出。启用这一选项将增加固件二进制文件大小。
应用示例
------------------
* 示例 :example:`peripherals/timer_group/gptimer` 中列出了通用定时器的典型用例。
API 参考
-------------------
.. include-build-file:: inc/gptimer.inc
.. include-build-file:: inc/timer_types.inc
.. [1]
不同 ESP 芯片系列的通用定时器实例数量可能不同。了解详细信息,请参考《{IDF_TARGET_NAME} 技术参考手册》 > 章节定时器组 (TIMG) [`PDF <{IDF_TARGET_TRM_CN_URL}#timg>`__]。驱动程序不会禁止您申请更多的定时器,但是当所有可用的硬件资源用完时将会返回错误。在分配资源时,请务必检查返回值(例如 :cpp:func:`gptimer_new_timer`)。
.. [2]
:cpp:member:`gptimer_event_callbacks_t::on_alarm` 回调函数和这一函数调用的函数也需放在 IRAM 中,请自行处理。