gptimer.rst 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. 通用定时器
  2. =====================
  3. 简介
  4. -----------------
  5. 通用定时器是 {IDF_TARGET_NAME} 定时器组外设的驱动程序。{IDF_TARGET_NAME} 硬件定时器分辨率高,具有灵活的报警功能。定时器内部计数器达到特定目标数值的行为被称为定时器报警。定时器报警时将调用用户注册的不同定时器回调函数。
  6. 通用定时器通常在以下场景中使用:
  7. - 如同挂钟一般自由运行,随时随地获取高分辨率时间戳;
  8. - 生成周期性警报,定期触发事件;
  9. - 生成一次性警报,在目标时间内响应。
  10. 功能概述
  11. -----------------
  12. 下文介绍了配置和操作定时器的常规步骤:
  13. .. list::
  14. - :ref:`gptimer-resource-allocation` - 获取定时器句柄应设置的参数,以及如何在通用定时器完成工作时回收资源。
  15. - :ref:`set-and-get-count-value` - 如何强制定时器从起点开始计数,以及如何随时获取计数值。
  16. - :ref:`set-up-alarm-action` - 启动警报事件应设置的参数。
  17. - :ref:`gptimer-register-event-callbacks` - 如何将用户的特定代码挂载到警报事件回调函数。
  18. - :ref:`enable-and-disable-timer` - 如何使能和禁用定时器。
  19. - :ref:`start-and-stop-timer` - 通过不同报警行为启动定时器的典型使用场景。
  20. :SOC_TIMER_SUPPORT_ETM: - :ref:`gptimer-etm-event-and-task` - 定时器提供了哪些事件和任务可以连接到 ETM 通道上。
  21. - :ref:`gptimer-power-management` - 选择不同的时钟源将会如何影响功耗。
  22. - :ref:`gptimer-iram-safe` - 在 cache 禁用的情况下,如何更好地让定时器处理中断事务以及实现 IO 控制功能。
  23. - :ref:`gptimer-thread-safety` - 驱动程序保证哪些 API 线程安全。
  24. - :ref:`gptimer-kconfig-options` - 支持的 Kconfig 选项,这些选项会对驱动程序行为产生不同影响。
  25. .. _gptimer-resource-allocation:
  26. 资源分配
  27. ^^^^^^^^^^^^^^^^^^
  28. 不同的 ESP 芯片可能有不同数量的独立定时器组,每组内也可能有若干个独立定时器。[1]_
  29. 通用定时器实例由 :cpp:type:`gptimer_handle_t` 表示。后台驱动会在资源池中管理所有可用的硬件资源,这样您便无需考虑硬件所属的定时器以及定时器组。
  30. 要安装一个定时器实例,需要提前提供配置结构体 :cpp:type:`gptimer_config_t`:
  31. - :cpp:member:`gptimer_config_t::clk_src` 选择定时器的时钟源。:cpp:type:`gptimer_clock_source_t` 中列出多个可用时钟,仅可选择其中一个时钟。了解不同时钟源对功耗的影响,请查看章节 :ref:`gptimer-power-management`。
  32. - :cpp:member:`gptimer_config_t::direction` 设置定时器的计数方向,:cpp:type:`gptimer_count_direction_t` 中列出多个支持的方向,仅可选择其中一个方向。
  33. - :cpp:member:`gptimer_config_t::resolution_hz` 设置内部计数器的分辨率。计数器每滴答一次相当于 **1 / resolution_hz** 秒。
  34. - 选用 :cpp:member:`gptimer_config_t::intr_shared` 设置是否将定时器中断源标记为共享源。了解共享中断的优缺点,请参考 :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`。
  35. 完成上述结构配置之后,可以将结构传递给 :cpp:func:`gptimer_new_timer`,用以实例化定时器实例并返回定时器句柄。
  36. 该函数可能由于内存不足、参数无效等错误而失败。具体来说,当没有更多的空闲定时器(即所有硬件资源已用完)时,将返回 :c:macro:`ESP_ERR_NOT_FOUND`。可用定时器总数由 :c:macro:`SOC_TIMER_GROUP_TOTAL_TIMERS` 表示,不同的 ESP 芯片该数值不同。
  37. 如已不再需要之前创建的通用定时器实例,应通过调用 :cpp:func:`gptimer_del_timer` 回收定时器,以便底层硬件定时器用于其他目的。在删除通用定时器句柄之前,请通过 :cpp:func:`gptimer_disable` 禁用定时器,或者通过 :cpp:func:`gptimer_enable` 确认定时器尚未使能。
  38. 创建分辨率为 1 MHz 的通用定时器句柄
  39. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  40. .. code:: c
  41. gptimer_handle_t gptimer = NULL;
  42. gptimer_config_t timer_config = {
  43. .clk_src = GPTIMER_CLK_SRC_DEFAULT,
  44. .direction = GPTIMER_COUNT_UP,
  45. .resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
  46. };
  47. ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
  48. .. _set-and-get-count-value:
  49. 设置和获取计数值
  50. ^^^^^^^^^^^^^^^^^^^^^^^^^
  51. 创建通用定时器时,内部计数器将默认重置为零。计数值可以通过 :cpp:func:`gptimer_set_raw_count` 异步更新。最大计数值取决于硬件定时器的位宽,这也会在 SOC 宏 :c:macro:`SOC_TIMER_GROUP_COUNTER_BIT_WIDTH` 中有所反映。当更新活动定时器的原始计数值时,定时器将立即从新值开始计数。
  52. 计数值可以随时通过 :cpp:func:`gptimer_get_raw_count` 获取。
  53. .. _set-up-alarm-action:
  54. 设置警报动作
  55. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  56. 对于大多数通用定时器使用场景而言,应在启动定时器之前设置警报动作,但不包括简单的挂钟场景,该场景仅需自由运行的定时器。设置警报动作,需要根据如何使用警报事件来配置 :cpp:type:`gptimer_alarm_config_t` 的不同参数:
  57. - :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` 不能设置为相同的值,因为警报值和重载值相同时没有意义。
  58. - :cpp:member:`gptimer_alarm_config_t::reload_count` 代表警报事件发生时要重载的计数值。此配置仅在 :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 设置为 true 时生效。
  59. - :cpp:member:`gptimer_alarm_config_t::auto_reload_on_alarm` 标志设置是否使能自动重载功能。如果使能,硬件定时器将在警报事件发生时立即将 :cpp:member:`gptimer_alarm_config_t::reload_count` 的值重载到计数器中。
  60. 要使警报配置生效,需要调用 :cpp:func:`gptimer_set_alarm_action`。特别是当 :cpp:type:`gptimer_alarm_config_t` 设置为 ``NULL`` 时,报警功能将被禁用。
  61. .. 注解::
  62. 如果警报值已设置且定时器超过该值,则会立即触发警报。
  63. .. _gptimer-register-event-callbacks:
  64. 注册事件回调函数
  65. ^^^^^^^^^^^^^^^^^^^^^^^^
  66. 定时器启动后,可动态产生特定事件(如“警报事件”)。如需在事件发生时调用某些函数,请通过 :cpp:func:`gptimer_register_event_callbacks` 将函数挂载到中断服务例程 (ISR)。:cpp:type:`gptimer_event_callbacks_t` 中列出了所有支持的事件回调函数:
  67. - :cpp:member:`gptimer_event_callbacks_t::on_alarm` 设置警报事件的回调函数。由于此函数在 ISR 上下文中调用,必须确保该函数不会试图阻塞(例如,确保仅从函数内调用具有 ``ISR`` 后缀的 FreeRTOS API)。函数原型在 :cpp:type:`gptimer_alarm_cb_t` 中有所声明。
  68. 您也可以通过参数 ``user_data`` 将自己的上下文保存到 :cpp:func:`gptimer_register_event_callbacks` 中。用户数据将直接传递给回调函数。
  69. 此功能将为定时器延迟安装中断服务,但不使能中断服务。所以,请在 :cpp:func:`gptimer_enable` 之前调用这一函数,否则将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。了解详细信息,请查看章节 :ref:`enable-and-disable-timer`。
  70. .. _enable-and-disable-timer:
  71. 使能和禁用定时器
  72. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  73. 在对定时器进行 IO 控制之前,需要先调用 :cpp:func:`gptimer_enable` 使能定时器。此函数功能如下:
  74. * 此函数将把定时器驱动程序的状态从 **init** 切换为 **enable**。
  75. * 如果 :cpp:func:`gptimer_register_event_callbacks` 已经延迟安装中断服务,此函数将使能中断服务。
  76. * 如果选择了特定的时钟源(例如 APB 时钟),此函数将获取适当的电源管理锁。了解更多信息,请查看章节 :ref:`gptimer-power-management`。
  77. 调用 :cpp:func:`gptimer_disable` 会进行相反的操作,即将定时器驱动程序恢复到 **init** 状态,禁用中断服务并释放电源管理锁。
  78. .. _start-and-stop-timer:
  79. 启动和停止定时器
  80. ^^^^^^^^^^^^^^^^
  81. 启动和停止是定时器的基本 IO 操作。调用 :cpp:func:`gptimer_start` 可以使内部计数器开始工作,而 :cpp:func:`gptimer_stop` 可以使计数器停止工作。下文说明了如何在存在或不存在警报事件的情况下启动定时器。
  82. 将定时器作为挂钟启动
  83. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  84. .. code:: c
  85. ESP_ERROR_CHECK(gptimer_enable(gptimer));
  86. ESP_ERROR_CHECK(gptimer_start(gptimer));
  87. // Retrieve the timestamp at anytime
  88. uint64_t count;
  89. ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
  90. 触发周期性事件
  91. ~~~~~~~~~~~~~~~~~~~~~~~~~
  92. .. code:: c
  93. typedef struct {
  94. uint64_t event_count;
  95. } example_queue_element_t;
  96. static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
  97. {
  98. BaseType_t high_task_awoken = pdFALSE;
  99. QueueHandle_t queue = (QueueHandle_t)user_ctx;
  100. // Retrieve the count value from event data
  101. example_queue_element_t ele = {
  102. .event_count = edata->count_value
  103. };
  104. // Optional: send the event data to other task by OS queue
  105. // Don't introduce complex logics in callbacks
  106. // Suggest dealing with event data in the main loop, instead of in this callback
  107. xQueueSendFromISR(queue, &ele, &high_task_awoken);
  108. // return whether we need to yield at the end of ISR
  109. return high_task_awoken == pdTRUE;
  110. }
  111. gptimer_alarm_config_t alarm_config = {
  112. .reload_count = 0, // counter will reload with 0 on alarm event
  113. .alarm_count = 1000000, // period = 1s @resolution 1MHz
  114. .flags.auto_reload_on_alarm = true, // enable auto-reload
  115. };
  116. ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
  117. gptimer_event_callbacks_t cbs = {
  118. .on_alarm = example_timer_on_alarm_cb, // register user callback
  119. };
  120. ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
  121. ESP_ERROR_CHECK(gptimer_enable(gptimer));
  122. ESP_ERROR_CHECK(gptimer_start(gptimer));
  123. 触发一次性事件
  124. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  125. .. code:: c
  126. typedef struct {
  127. uint64_t event_count;
  128. } example_queue_element_t;
  129. static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
  130. {
  131. BaseType_t high_task_awoken = pdFALSE;
  132. QueueHandle_t queue = (QueueHandle_t)user_ctx;
  133. // Stop timer the sooner the better
  134. gptimer_stop(timer);
  135. // Retrieve the count value from event data
  136. example_queue_element_t ele = {
  137. .event_count = edata->count_value
  138. };
  139. // Optional: send the event data to other task by OS queue
  140. xQueueSendFromISR(queue, &ele, &high_task_awoken);
  141. // return whether we need to yield at the end of ISR
  142. return high_task_awoken == pdTRUE;
  143. }
  144. gptimer_alarm_config_t alarm_config = {
  145. .alarm_count = 1 * 1000 * 1000, // alarm target = 1s @resolution 1MHz
  146. };
  147. ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
  148. gptimer_event_callbacks_t cbs = {
  149. .on_alarm = example_timer_on_alarm_cb, // register user callback
  150. };
  151. ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
  152. ESP_ERROR_CHECK(gptimer_enable(gptimer));
  153. ESP_ERROR_CHECK(gptimer_start(gptimer));
  154. 警报值动态更新
  155. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  156. 通过更改 :cpp:member:`gptimer_alarm_event_data_t::alarm_value`,可以在 ISR 程序回调中动态更新警报值。警报值将在回调函数返回后更新。
  157. .. code:: c
  158. typedef struct {
  159. uint64_t event_count;
  160. } example_queue_element_t;
  161. static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
  162. {
  163. BaseType_t high_task_awoken = pdFALSE;
  164. QueueHandle_t queue = (QueueHandle_t)user_data;
  165. // Retrieve the count value from event data
  166. example_queue_element_t ele = {
  167. .event_count = edata->count_value
  168. };
  169. // Optional: send the event data to other task by OS queue
  170. xQueueSendFromISR(queue, &ele, &high_task_awoken);
  171. // reconfigure alarm value
  172. gptimer_alarm_config_t alarm_config = {
  173. .alarm_count = edata->alarm_value + 1000000, // alarm in next 1s
  174. };
  175. gptimer_set_alarm_action(timer, &alarm_config);
  176. // return whether we need to yield at the end of ISR
  177. return high_task_awoken == pdTRUE;
  178. }
  179. gptimer_alarm_config_t alarm_config = {
  180. .alarm_count = 1000000, // initial alarm target = 1s @resolution 1MHz
  181. };
  182. ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
  183. gptimer_event_callbacks_t cbs = {
  184. .on_alarm = example_timer_on_alarm_cb, // register user callback
  185. };
  186. ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, queue));
  187. ESP_ERROR_CHECK(gptimer_enable(gptimer));
  188. ESP_ERROR_CHECK(gptimer_start(gptimer, &alarm_config));
  189. .. only:: SOC_TIMER_SUPPORT_ETM
  190. .. _gptimer-etm-event-and-task:
  191. ETM 事件与任务
  192. ^^^^^^^^^^^^^^
  193. 定时器可以产生多种事件,这些事件可以连接到 :doc:`ETM </api-reference/peripherals/etm>` 模块。:cpp:type:`gptimer_etm_event_type_t` 中列出了定时器能够产生的事件类型。用户可以通过调用 :cpp:func:`gptimer_new_etm_event` 来获得相应事件的 ETM event 句柄。同样地,定时器还公开了一些可被其他事件触发然后自动执行的任务。:cpp:type:`gptimer_etm_task_type_t` 中列出了定时器能够支持的任务类型。 用户可以通过调用 :cpp:func:`gptimer_new_etm_task` 来获得相应任务的 ETM task 句柄。
  194. 关于如何将定时器事件和任务连接到 ETM 通道中,请参阅 :doc:`ETM </api-reference/peripherals/etm>` 文档。
  195. .. _gptimer-power-management:
  196. .. only:: not SOC_TIMER_SUPPORT_ETM
  197. .. _gptimer-power-management:
  198. 电源管理
  199. ^^^^^^^^^^^^^^^^^
  200. 当使能电源管理时(即 :ref:`CONFIG_PM_ENABLE` 已打开),系统将在进入 Light-sleep 模式之前调整 APB 频率,从而可能会改变通用定时器的计数步骤周期,导致计时不准确。
  201. 然而,驱动程序可以通过获取类型为 :cpp:enumerator:`ESP_PM_APB_FREQ_MAX` 的电源管理锁来阻止系统更改 APB 频率。每当驱动程序创建一个通用定时器实例,且该实例选择 :cpp:enumerator:`GPTIMER_CLK_SRC_APB` 作为其时钟源的时,驱动程序会确保在通过 :cpp:func:`gptimer_enable` 使能定时器时,已经获取了电源管理锁。同样,当为该定时器调用 :cpp:func:`gptimer_disable` 时,驱动程序会释放电源管理锁。
  202. 如果选择 :cpp:enumerator:`GPTIMER_CLK_SRC_XTAL` 等其他时钟源,那么驱动程序不会安装电源管理锁。只要时钟源仍可提供足够的分辨率,XTAL 时钟源就更适合低功耗应用。
  203. .. _gptimer-iram-safe:
  204. IRAM 安全
  205. ^^^^^^^^^^^^^^^^^^
  206. 默认情况下,当 cache 因写入或擦除 flash 等原因而被禁用时,通用定时器的中断服务将会延迟,造成警报中断无法及时执行。在实时应用程序中通常需要避免这一情况发生。
  207. 调用 Kconfig 选项 :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` 可实现如下功能:
  208. - 即使禁用 cache 也可使能正在运行的中断
  209. - 将 ISR 使用的所有函数放入 IRAM [2]_
  210. - 将驱动程序对象放入 DRAM(以防意外映射到 PSRAM)
  211. 这将允许中断在 cache 禁用时运行,但会增加 IRAM 使用量。
  212. 调用另一 Kconfig 选项 :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 也可将常用的 IO 控制功能放入 IRAM,以便这些函数在 cache 禁用时也能执行。常用的 IO 控制功能如下:
  213. - :cpp:func:`gptimer_start`
  214. - :cpp:func:`gptimer_stop`
  215. - :cpp:func:`gptimer_get_raw_count`
  216. - :cpp:func:`gptimer_set_raw_count`
  217. - :cpp:func:`gptimer_set_alarm_action`
  218. .. _gptimer-thread-safety:
  219. 线程安全
  220. ^^^^^^^^^^^^^^^^^^
  221. 驱动程序会保证工厂函数 :cpp:func:`gptimer_new_timer` 的线程安全,这意味着您可以从不同的 RTOS 任务中调用这一函数,而无需额外的锁保护。
  222. 由于驱动程序通过使用临界区来防止这些函数在任务和 ISR 中同时被调用,所以以下函数能够在 ISR 上下文中运行。
  223. - :cpp:func:`gptimer_start`
  224. - :cpp:func:`gptimer_stop`
  225. - :cpp:func:`gptimer_get_raw_count`
  226. - :cpp:func:`gptimer_set_raw_count`
  227. - :cpp:func:`gptimer_set_alarm_action`
  228. 将 :cpp:type:`gptimer_handle_t` 作为第一个位置参数的其他函数不被视作线程安全,也就是说应该避免从多个任务中调用这些函数。
  229. .. _gptimer-kconfig-options:
  230. Kconfig 选项
  231. ^^^^^^^^^^^^^^^^^^^^^^
  232. - :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` 控制放置通用定时器控制函数(IRAM 或 flash)的位置。了解更多信息,请参考章节 :ref:`gptimer-iram-safe`。
  233. - :ref:`CONFIG_GPTIMER_ISR_IRAM_SAFE` 控制默认 ISR 程序在 cache 禁用时是否可以运行。了解更多信息,请参考章节 :ref:`gptimer-iram-safe`。
  234. - :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` 用于启用调试日志输出。启用这一选项将增加固件二进制文件大小。
  235. 应用示例
  236. ------------------
  237. .. list::
  238. - 示例 :example:`peripherals/timer_group/gptimer` 中列出了通用定时器的典型用例。
  239. :SOC_TIMER_SUPPORT_ETM: - 示例 :example:`peripherals/timer_group/gptimer_capture_hc_sr04` 展示了如何在 ETM 模块的帮助下,用定时器捕获外部事件的时间戳。
  240. API 参考
  241. -------------------
  242. .. include-build-file:: inc/gptimer.inc
  243. .. include-build-file:: inc/gptimer_etm.inc
  244. .. include-build-file:: inc/gptimer_types.inc
  245. .. include-build-file:: inc/timer_types.inc
  246. .. [1]
  247. 不同 ESP 芯片系列的通用定时器实例数量可能不同。了解详细信息,请参考《{IDF_TARGET_NAME} 技术参考手册》 > 章节定时器组 (TIMG) [`PDF <{IDF_TARGET_TRM_CN_URL}#timg>`__]。驱动程序不会禁止您申请更多的定时器,但是当所有可用的硬件资源用完时将会返回错误。在分配资源时,请务必检查返回值(例如 :cpp:func:`gptimer_new_timer`)。
  248. .. [2]
  249. :cpp:member:`gptimer_event_callbacks_t::on_alarm` 回调函数和这一函数调用的函数也需放在 IRAM 中,请自行处理。