diff --git a/docs/en/api-reference/peripherals/pcnt.rst b/docs/en/api-reference/peripherals/pcnt.rst index 239b44fc59..cd4cdbc484 100644 --- a/docs/en/api-reference/peripherals/pcnt.rst +++ b/docs/en/api-reference/peripherals/pcnt.rst @@ -18,25 +18,26 @@ Typically, a PCNT module can be used in scenarios like: Functional Overview ------------------- -Description of the PCNT functionality is divided into into the following sections: +Description of the PCNT functionality is divided into the following sections: -- `Resource Allocation <#resource-allocation>`__ - covers how to allocate PCNT units and channels with properly set of configurations. It also covers how to recycle the resources when they finished working. +- :ref:`allocating-resource` - covers how to allocate PCNT units and channels with properly set of configurations. It also covers how to recycle the resources when they finished working. +- :ref:`set-up-channel-actions` - covers how to configure the PCNT channel to behave on different signal edges and levels. +- :ref:`watch-points` - describes how to configure PCNT watch points (i.e., tell PCNT unit to trigger an event when the count reaches a certain value). +- :ref:`callbacks-register-event` - describes how to hook your specific code to the watch point event callback function. +- :ref:`set-glitch-filter` - describes how to enable and set the timing parameters for the internal glitch filter. +- :ref:`enable-and-disable-unit` - describes how to enable and disable the PCNT unit. +- :ref:`unit-io-control` - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value. +- :ref:`managing-power` - describes what functionality will prevent the chip from going into low power mode. +- :ref:`iram-safety` - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache. +- :ref:`thread-safe` - lists which APIs are guaranteed to be thread safe by the driver. +- :ref:`kconfig-option` - lists the supported Kconfig options that can be used to make a different effect on driver behavior. -- `Set Up Channel Actions <#set-up-channel-actions>`__ - covers how to configure the PCNT channel to behave on different signal edges and levels. -- `Watch Points <#watch-points>`__ - describes how to configure PCNT watch points (i.e., tell PCNT unit to trigger an event when the count reaches a certain value). -- `Register Event Callbacks <#register-event-callbacks>`__ - describes how to hook user specific code to the watch point event callback function. -- `Set Glitch Filter <#set-glitch-filter>`__ - describes how to enable and set the timing parameters for the internal glitch filter. -- `Enable and Disable Unit <#enable-and-disable-unit>`__ - describes how to enable and disable the PCNT unit. -- `Unit IO Control <#unit-io-control>`__ - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value. -- `Power Management <#power-management>`__ - describes what functionality will prevent the chip from going into low power mode. -- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the PCNT 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. +.. _allocating-resource: Resource Allocation ^^^^^^^^^^^^^^^^^^^ -The PCNT unit and channel are represented by :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` respectively. All available units and channels are maintained by the driver in a resource pool, so the user doesn't need to know the exact underlying instance ID. +The PCNT unit and channel are represented by :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` respectively. All available units and channels are maintained by the driver in a resource pool, so you do not need to know the exact underlying instance ID. Install PCNT Unit ~~~~~~~~~~~~~~~~~ @@ -67,14 +68,14 @@ If a previously created PCNT unit is no longer needed, it's recommended to recyc Install PCNT Channel ~~~~~~~~~~~~~~~~~~~~ -To install a PCNT channel, users must initialize a :cpp:type:`pcnt_chan_config_t` structure in advance, and then call :cpp:func:`pcnt_new_channel`. The configuration fields of the :cpp:type:`pcnt_chan_config_t` structure are described below: +To install a PCNT channel, you must initialize a :cpp:type:`pcnt_chan_config_t` structure in advance, and then call :cpp:func:`pcnt_new_channel`. The configuration fields of the :cpp:type:`pcnt_chan_config_t` structure are described below: -- :cpp:member:`pcnt_chan_config_t::edge_gpio_num` and :cpp:member:`pcnt_chan_config_t::level_gpio_num` specify the GPIO numbers used by **edge** type signal and **level** type signal. Please note, either of them can be assigned to `-1` if it's not actually used, and thus it will become a **virtual IO**. For some simple pulse counting applications where one of the level/edge signals signals is fixed (i.e., never changes), users can reclaim a GPIO by setting the signal as a virtual IO on channel allocation. Setting the level/edge signal as a virtual IO will cause that signal to be internally routed to a fixed High/Low logic level, thus allowing users to save a GPIO for other purposes. +- :cpp:member:`pcnt_chan_config_t::edge_gpio_num` and :cpp:member:`pcnt_chan_config_t::level_gpio_num` specify the GPIO numbers used by **edge** type signal and **level** type signal. Please note, either of them can be assigned to `-1` if it's not actually used, and thus it will become a **virtual IO**. For some simple pulse counting applications where one of the level/edge signals is fixed (i.e., never changes), you can reclaim a GPIO by setting the signal as a virtual IO on channel allocation. Setting the level/edge signal as a virtual IO will cause that signal to be internally routed to a fixed High/Low logic level, thus allowing you to save a GPIO for other purposes. - :cpp:member:`pcnt_chan_config_t::virt_edge_io_level` and :cpp:member:`pcnt_chan_config_t::virt_level_io_level` specify the virtual IO level for **edge** and **level** input signal, to ensure a deterministic state for such control signal. Please note, they are only valid when either :cpp:member:`pcnt_chan_config_t::edge_gpio_num` or :cpp:member:`pcnt_chan_config_t::level_gpio_num` is assigned to `-1`. - :cpp:member:`pcnt_chan_config_t::invert_edge_input` and :cpp:member:`pcnt_chan_config_t::invert_level_input` are used to decide whether to invert the input signals before they going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware. - :cpp:member:`pcnt_chan_config_t::io_loop_back` is for debug only, which enables both the GPIO's input and output paths. This can help to simulate the pulse signals by function :cpp:func:`gpio_set_level` on the same GPIO. -Channel allocating and initialization is done by calling a function :cpp:func:`pcnt_new_channel` with the above :cpp:type:`pcnt_chan_config_t` input parameter plus a PCNT unit handle returned from :cpp:func:`pcnt_new_unit`. This function will return a PCNT channel handle if it runs correctly. Specifically, when there are no more free PCNT channel within the unit (i.e. channel resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT channels within the unit is recorded by :c:macro:`SOC_PCNT_CHANNELS_PER_UNIT` for reference. Note that, when install a PCNT channel for a specific unit, one should ensure the unit is in the init state, otherwise this function will return :c:macro:`ESP_ERR_INVALID_STATE` error. +Channel allocating and initialization is done by calling a function :cpp:func:`pcnt_new_channel` with the above :cpp:type:`pcnt_chan_config_t` as an input parameter plus a PCNT unit handle returned from :cpp:func:`pcnt_new_unit`. This function will return a PCNT channel handle if it runs correctly. Specifically, when there are no more free PCNT channel within the unit (i.e. channel resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT channels within the unit is recorded by :c:macro:`SOC_PCNT_CHANNELS_PER_UNIT` for reference. Note that, when install a PCNT channel for a specific unit, one should ensure the unit is in the init state, otherwise this function will return :c:macro:`ESP_ERR_INVALID_STATE` error. If a previously created PCNT channel is no longer needed, it's recommended to recycle the resources by calling :cpp:func:`pcnt_del_channel`. Which in return allows the underlying channel hardware to be used for other purposes. @@ -90,10 +91,12 @@ If a previously created PCNT channel is no longer needed, it's recommended to re pcnt_channel_handle_t pcnt_chan = NULL; ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan)); +.. _set-up-channel-actions: + Set Up Channel Actions ^^^^^^^^^^^^^^^^^^^^^^ -The PCNT will increase/decrease/hold its internal count value when the input pulse signal toggles. User can set different actions for edge signal and/or level signal. +The PCNT will increase/decrease/hold its internal count value when the input pulse signal toggles. You can set different actions for edge signal and/or level signal. - :cpp:func:`pcnt_channel_set_edge_action` function is to set specific actions for rising and falling edge of the signal attached to the :cpp:member:`pcnt_chan_config_t::edge_gpio_num`. Supported actions are listed in :cpp:type:`pcnt_channel_edge_action_t`. - :cpp:func:`pcnt_channel_set_level_action` function is to set specific actions for high and low level of the signal attached to the :cpp:member:`pcnt_chan_config_t::level_gpio_num`. Supported actions are listed in :cpp:type:`pcnt_channel_level_action_t`. This function is not mandatory if the :cpp:member:`pcnt_chan_config_t::level_gpio_num` is set to `-1` when allocating PCNT channel by :cpp:func:`pcnt_new_channel`. @@ -105,12 +108,14 @@ The PCNT will increase/decrease/hold its internal count value when the input pul // keep the counting mode when the control signal is high level, and reverse the counting mode when the control signal is low level ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE)); +.. _watch-points: + Watch Points ^^^^^^^^^^^^ -Each PCNT unit can be configured to watch several different values that you're interested in. The value to be watched is also called **Watch Point**. The watch point itself can't exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`. When the counter reaches either watch point, a watch event will be triggered and notify user by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See `Register Event Callbacks <#register-event-callbacks>`__ for how to register event callbacks. +Each PCNT unit can be configured to watch several different values that you're interested in. The value to be watched is also called **Watch Point**. The watch point itself can't exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`. When the counter reaches either watch point, a watch event will be triggered and notify you by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See :ref:`callbacks-register-event` for how to register event callbacks. -The watch point can be added and removed by :cpp:func:`pcnt_unit_add_watch_point` and :cpp:func:`pcnt_unit_remove_watch_point`. The commonly used watch points are: **zero cross**, **maximum / minimum count** and other threshold values. The number of available watch point is limited, :cpp:func:`pcnt_unit_add_watch_point` will return error :c:macro:`ESP_ERR_NOT_FOUND` if it can't find any free hardware resource to save the watch point. User can't add the same watch point for multiple times, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`. +The watch point can be added and removed by :cpp:func:`pcnt_unit_add_watch_point` and :cpp:func:`pcnt_unit_remove_watch_point`. The commonly used watch points are: **zero cross**, **maximum / minimum count** and other threshold values. The number of available watch point is limited, :cpp:func:`pcnt_unit_add_watch_point` will return error :c:macro:`ESP_ERR_NOT_FOUND` if it can't find any free hardware resource to save the watch point. You can't add the same watch point for multiple times, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`. It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remove_watch_point` to recycle the watch point resources. @@ -121,14 +126,16 @@ It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remov // add high limit watch point ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, EXAMPLE_PCNT_HIGH_LIMIT)); +.. _callbacks-register-event: + Register Event Callbacks ^^^^^^^^^^^^^^^^^^^^^^^^ When PCNT unit reaches any enabled watch point, specific event will be generated and notify the CPU by interrupt. If you have some function that want to get executed when event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`pcnt_unit_register_event_callbacks`. All supported event callbacks are listed in the :cpp:type:`pcnt_event_callbacks_t`: -- :cpp:member:`pcnt_event_callbacks_t::on_reach` sets a callback function for watch point 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:`pcnt_watch_cb_t`. +- :cpp:member:`pcnt_event_callbacks_t::on_reach` sets a callback function for watch point event. As this function is called within the ISR context, you 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:`pcnt_watch_cb_t`. -User can save their own context to :cpp:func:`pcnt_unit_register_event_callbacks` as well, via the parameter ``user_ctx``. This user data will be directly passed to the callback functions. +You can save their own context to :cpp:func:`pcnt_unit_register_event_callbacks` as well, via the parameter ``user_ctx``. This user data will be directly passed to the callback functions. In the callback function, the driver will fill in the event data of specific event. For example, the watch point event data is declared as :cpp:type:`pcnt_watch_event_data_t`: @@ -155,6 +162,8 @@ Registering callback function will result in lazy installation of interrupt serv QueueHandle_t queue = xQueueCreate(10, sizeof(int)); ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue)); +.. _set-glitch-filter: + Set Glitch Filter ^^^^^^^^^^^^^^^^^ @@ -162,13 +171,13 @@ The PCNT unit features filters to ignore possible short glitches in the signals. - :cpp:member:`pcnt_glitch_filter_config_t::max_glitch_ns` sets the maximum glitch width, in nano seconds. If a signal pulse's width is smaller than this value, then it will be treated as noise and won't increase/decrease the internal counter. -User can enable the glitch filter for PCNT unit by calling :cpp:func:`pcnt_unit_set_glitch_filter` with the filter configuration provided above. Particularly, user can disable the glitch filter later by calling :cpp:func:`pcnt_unit_set_glitch_filter` with a `NULL` filter configuration. +You can enable the glitch filter for PCNT unit by calling :cpp:func:`pcnt_unit_set_glitch_filter` with the filter configuration provided above. Particularly, you can disable the glitch filter later by calling :cpp:func:`pcnt_unit_set_glitch_filter` with a `NULL` filter configuration. This function should be called when the the unit is in the init state. Otherwise, it will return :c:macro:`ESP_ERR_INVALID_STATE` error. .. note:: - The glitch filter is clocked from APB. For the counter not to miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (usually 12.5 ns if APB equals 80MHz). As the APB frequency would be changed after DFS (Dynamic Frequency Scaling) enabled, which means the filter won't work as expect in that case. So the driver will install a PM lock for PCNT unit during the first time user enables the glitch filter. For more information related to power management strategy used in PCNT driver, please see `Power Management <#power-management>`__. + The glitch filter is clocked from APB. For the counter not to miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (usually 12.5 ns if APB equals 80MHz). As the APB frequency would be changed after DFS (Dynamic Frequency Scaling) enabled, which means the filter won't work as expect in that case. So the driver will install a PM lock for PCNT unit during the first time you enable the glitch filter. For more information related to power management strategy used in PCNT driver, please see :ref:`managing-power`. .. code:: c @@ -177,14 +186,16 @@ This function should be called when the the unit is in the init state. Otherwise }; ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config)); +.. _enable-and-disable-unit: + Enable and Disable Unit ^^^^^^^^^^^^^^^^^^^^^^^ -Before doing IO control to the PCNT unit, user needs to enable it first, by calling :cpp:func:`pcnt_unit_enable`. Internally, this function will: +Before doing IO control to the PCNT unit, you need to enable it first, by calling :cpp:func:`pcnt_unit_enable`. Internally, this function will: * switch the PCNT driver state from **init** to **enable**. * enable the interrupt service if it has been lazy installed in :cpp:func:`pcnt_unit_register_event_callbacks`. -* acquire a proper power management lock if it has been lazy installed in :cpp:func:`pcnt_unit_set_glitch_filter`. See also `Power management <#power-management>`__ for more information. +* acquire a proper power management lock if it has been lazy installed in :cpp:func:`pcnt_unit_set_glitch_filter`. See also :ref:`managing-power` for more information. On the contrary, calling :cpp:func:`pcnt_unit_disable` will do the opposite, that is, put the PCNT driver back to the **init** state, disable the interrupts service and release the power management lock. @@ -192,6 +203,8 @@ On the contrary, calling :cpp:func:`pcnt_unit_disable` will do the opposite, tha ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit)); +.. _unit-io-control: + Unit IO Control ^^^^^^^^^^^^^^^ @@ -210,7 +223,7 @@ Note, :cpp:func:`pcnt_unit_start` and :cpp:func:`pcnt_unit_stop` should be calle Get Count Value ~~~~~~~~~~~~~~~ -User can check current count value at any time by calling :cpp:func:`pcnt_unit_get_count`. +You can check current count value at any time by calling :cpp:func:`pcnt_unit_get_count`. .. note:: @@ -221,12 +234,16 @@ User can check current count value at any time by calling :cpp:func:`pcnt_unit_g int pulse_count = 0; ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count)); +.. _managing-power: + 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 behavior of PCNT glitch filter and leading to valid signal being treated as noise. -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 user enables the glitch filter by :cpp:func:`pcnt_unit_set_glitch_filter`, the driver will guarantee that the power management lock is acquired after the PCNT unit is enabled by :cpp:func:`pcnt_unit_enable`. Likewise, the driver releases the lock after :cpp:func:`pcnt_unit_disable` is called. +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 you enable the glitch filter by :cpp:func:`pcnt_unit_set_glitch_filter`, the driver will guarantee that the power management lock is acquired after the PCNT unit is enabled by :cpp:func:`pcnt_unit_enable`. Likewise, the driver releases the lock after :cpp:func:`pcnt_unit_disable` is called. + +.. _iram-safety: IRAM Safe ^^^^^^^^^ @@ -248,10 +265,12 @@ There's another Kconfig option :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` that can put - :cpp:func:`pcnt_unit_clear_count` - :cpp:func:`pcnt_unit_get_count` +.. _thread-safe: + Thread Safety ^^^^^^^^^^^^^ -The factory function :cpp:func:`pcnt_new_unit` and :cpp:func:`pcnt_new_channel` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks. +The factory functions :cpp:func:`pcnt_new_unit` and :cpp:func:`pcnt_new_channel` are guaranteed to be thread safe by the driver, which means, you can call them 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. - :cpp:func:`pcnt_unit_start` @@ -259,13 +278,15 @@ The following functions are allowed to run under ISR context, the driver uses a - :cpp:func:`pcnt_unit_clear_count` - :cpp:func:`pcnt_unit_get_count` -Other functions that take the :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_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 the :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` as the first positional parameter, are not treated as thread safe. This means you should avoid calling them from multiple tasks. + +.. _kconfig-option: Kconfig Options ^^^^^^^^^^^^^^^ -- :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` controls where to place the PCNT control functions (IRAM or Flash), see `IRAM Safe <#iram-safe>`__ for more information. -- :ref:`CONFIG_PCNT_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_PCNT_CTRL_FUNC_IN_IRAM` controls where to place the PCNT control functions (IRAM or Flash), see :ref:`iram-safety` for more information. +- :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see :ref:`iram-safety` for more information. - :ref:`CONFIG_PCNT_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size. Application Examples @@ -284,4 +305,4 @@ API Reference Different ESP chip series might have different number of PCNT units and channels. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#pcnt>`__] for details. The driver won't forbid you from applying for more PCNT units and channels, 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:`pcnt_new_unit`). .. [2] - :cpp:member:`pcnt_event_callbacks_t::on_reach` callback and the functions invoked by itself should also be placed in IRAM, users need to take care of them by themselves. + :cpp:member:`pcnt_event_callbacks_t::on_reach` callback and the functions invoked by itself should also be placed in IRAM, you need to take care of them by themselves. \ No newline at end of file diff --git a/docs/zh_CN/api-reference/peripherals/pcnt.rst b/docs/zh_CN/api-reference/peripherals/pcnt.rst index c4d1cf83db..b4de1d614b 100644 --- a/docs/zh_CN/api-reference/peripherals/pcnt.rst +++ b/docs/zh_CN/api-reference/peripherals/pcnt.rst @@ -1 +1,308 @@ -.. include:: ../../../en/api-reference/peripherals/pcnt.rst \ No newline at end of file +脉冲计数器 (PCNT) +================= + +概述 +----- + +PCNT 用于统计输入信号的上升沿和/或下降沿的数量。{IDF_TARGET_NAME} 集成了多个脉冲计数单元,[1]_ 每个单元都是包含多个通道的独立计数器。通道可独立配置为统计上升沿或下降沿数量的递增计数器或递减计数器。 + +PCNT 通道可检测 **边沿** 信号及 **电平** 信号。对于比较简单的应用,检测边沿信号就足够了。PCNT 通道可检测上升沿信号、下降沿信号,同时也能设置为递增计数,递减计数,或停止计数。电平信号就是所谓的 **控制信号**,可用来控制边沿信号的计数模式。通过设置电平信号与边沿信号的检测模式,PCNT 单元可用作 **正交解码器**。 + +每个 PCNT 单元还包含一个滤波器,用于滤除线路毛刺。 + +PCNT 模块通常用于: + +- 对一段时间内的脉冲计数,进而计算得到周期信号的频率; +- 对正交信号进行解码,进而获得速度和方向信息。 + +功能描述 +-------- + +PCNT 的功能从以下几个方面进行说明: + +- :ref:`allocating-resource` - 说明如何通过配置分配 PCNT 单元和通道,以及在相应操作完成之后,如何回收单元和通道。 +- :ref:`set-up-channel-actions` - 说明如何设置通道针对不同信号沿和电平进行操作。 +- :ref:`watch-points` - 说明如何配置观察点,即当计数达到某个数值时,命令 PCNT 单元触发某个事件。 +- :ref:`callbacks-register-event` - 说明如何将您的代码挂载到观察点事件的回调函数上。 +- :ref:`set-glitch-filter` - 说明如何使能毛刺滤波器并设置其时序参数。 +- :ref:`enable-and-disable-unit` - 说明如何使能和关闭 PCNT 单元。 +- :ref:`unit-io-control` - 说明 PCNT 单元的 IO 控制功能,例如使能毛刺滤波器,开启和停用 PCNT 单元,获取和清除计数。 +- :ref:`managing-power` - 说明哪些功能会阻止芯片进入低功耗模式。 +- :ref:`iram-safety` - 说明在缓存禁用的情况下,如何执行 PCNT 中断和 IO 控制功能。 +- :ref:`thread-safe` - 列出线程安全的 API。 +- :ref:`kconfig-option` - 列出了支持的 Kconfig 选项,这些选项可实现不同的驱动效果。 + +.. _allocating-resource: + +分配资源 +^^^^^^^^^^^^^ + +PCNT 单元和通道分别用 :cpp:type:`pcnt_unit_handle_t` 与 :cpp:type:`pcnt_channel_handle_t` 表示。所有的可用单元和通道都由驱动在资源池中进行维护,无需了解底层实例 ID。 + +安装 PCNT 单元 +~~~~~~~~~~~~~~~~~~ + +安装 PCNT 单元时,需要先完成配置 :cpp:type:`pcnt_unit_config_t`: + +- :cpp:member:`pcnt_unit_config_t::low_limit` 与 :cpp:member:`pcnt_unit_config_t::high_limit` 用于指定内部计数器的最小值和最大值。当计数器超过任一限值时,计数器将归零。 + +调用函数 :cpp:func:`pcnt_new_unit` 并将 :cpp:type:`pcnt_unit_config_t` 作为其输入值,可对 PCNT 单元进行分配和初始化。该函数正常运行时,会返回一个 PCNT 单元句柄。没有可用的 PCNT 单元时(即 PCNT 单元全部被占用),该函数会返回错误 :c:macro:`ESP_ERR_NOT_FOUND`。可用的 PCNT 单元总数记录在 :c:macro:`SOC_PCNT_UNITS_PER_GROUP` 中,以供参考。 + +如果不再需要之前创建的某个 PCNT 单元,建议通过调用 :cpp:func:`pcnt_del_unit` 来回收该单元,从而该单元可用于其他用途。删除某个 PCNT 单元之前,需要满足以下条件: + +- 该单元处于初始状态,即该单元要么已经被 :cpp:func:`pcnt_unit_disable` 禁用,要么尚未使能。 +- 附属于该单元的通道已全部被 :cpp:func:`pcnt_del_channel` 删除。 + +.. code:: c + + #define EXAMPLE_PCNT_HIGH_LIMIT 100 + #define EXAMPLE_PCNT_LOW_LIMIT -100 + + pcnt_unit_config_t unit_config = { + .high_limit = EXAMPLE_PCNT_HIGH_LIMIT, + .low_limit = EXAMPLE_PCNT_LOW_LIMIT, + }; + pcnt_unit_handle_t pcnt_unit = NULL; + ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit)); + +安装 PCNT 通道 +~~~~~~~~~~~~~~~~~~~ + +安装 PCNT 通道时,需要先初始化 :cpp:type:`pcnt_chan_config_t`,然后调用 :cpp:func:`pcnt_new_channel`。对 :cpp:type:`pcnt_chan_config_t` 配置如下所示: + +- :cpp:member:`pcnt_chan_config_t::edge_gpio_num` 与 :cpp:member:`pcnt_chan_config_t::level_gpio_num` 用于指定 **边沿** 信号和 **电平** 信号对应的 GPIO 编号。请注意,这两个参数未被使用时,可以设置为 `-1`,即成为 **虚拟 IO** 。对于一些简单的脉冲计数应用,电平信号或边沿信号是固定的(即不会发生改变),可将其设置为虚拟 IO,然后该信号会被连接到一个固定的高/低逻辑电平,这样就可以在通道分配时回收一个 GPIO,节省一个 GPIO 管脚资源。 +- :cpp:member:`pcnt_chan_config_t::virt_edge_io_level` 与 :cpp:member:`pcnt_chan_config_t::virt_level_io_level` 用于指定 **边沿** 信号和 **电平** 信号的虚拟 IO 电平,以保证这些控制信号处于确定状态。请注意,只有在 :cpp:member:`pcnt_chan_config_t::edge_gpio_num` 或 :cpp:member:`pcnt_chan_config_t::level_gpio_num` 设置为 `-1` 时,这两个参数才有效。 +- :cpp:member:`pcnt_chan_config_t::invert_edge_input` 与 :cpp:member:`pcnt_chan_config_t::invert_level_input` 用于确定信号在输入 PCNT 之前是否需要被翻转,信号翻转由 GPIO 矩阵 (不是 PCNT 单元) 执行。 +- :cpp:member:`pcnt_chan_config_t::io_loop_back` 仅用于调试,它可以使能 GPIO 的输入和输出路径。这样,就可以通过调用位于同一 GPIO 上的函数 :cpp:func:`gpio_set_level` 来模拟脉冲信号。 + +调用函数 :cpp:func:`pcnt_new_channel`,将 :cpp:type:`pcnt_chan_config_t` 作为输入值并调用 :cpp:func:`pcnt_new_unit` 返回的 PCNT 单元句柄,可对 PCNT 通道进行分配和初始化。如果该函数正常运行,会返回一个 PCNT 通道句柄。如果没有可用的 PCNT 通道(PCNT 通道资源全部被占用),该函数会返回错误 :c:macro:`ESP_ERR_NOT_FOUND`。可用的 PCNT 通道总数记录在 :c:macro:`SOC_PCNT_CHANNELS_PER_UNIT`,以供参考。注意,为某个单元安装 PCNT 通道时,应确保该单元处于初始状态,否则函数 :cpp:func:`pcnt_new_channel` 会返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。 + +如果不再需要之前创建的某个 PCNT 通道,建议通过调用 :cpp:func:`pcnt_del_channel` 回收该通道,从而该通道可用于其他用途。 + +.. code:: c + + #define EXAMPLE_CHAN_GPIO_A 0 + #define EXAMPLE_CHAN_GPIO_B 2 + + pcnt_chan_config_t chan_config = { + .edge_gpio_num = EXAMPLE_CHAN_GPIO_A, + .level_gpio_num = EXAMPLE_CHAN_GPIO_B, + }; + pcnt_channel_handle_t pcnt_chan = NULL; + ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan)); + +.. _set-up-channel-actions: + +设置通道操作 +^^^^^^^^^^^^^^ + +当输入脉冲信号切换时,PCNT 通道会增加,减少或停止计数。边沿信号及电平信号可设置为不同的计数器操作。 + +- :cpp:func:`pcnt_channel_set_edge_action` 为输入到 :cpp:member:`pcnt_chan_config_t::edge_gpio_num` 的信号上升沿和下降沿设置操作,:cpp:type:`pcnt_channel_edge_action_t` 中列出了支持的操作。 +- :cpp:func:`pcnt_channel_set_level_action` 为输入到 :cpp:member:`pcnt_chan_config_t::level_gpio_num` 的信号高电平和低电平设置操作,:cpp:type:`pcnt_channel_level_action_t` 中列出了支持的操作。使用 :cpp:func:`pcnt_new_channel` 分配 PCNT 通道时,如果 :cpp:member:`pcnt_chan_config_t::level_gpio_num` 被设置为 `-1`,就无需对该函数进行设置了。 + +.. code:: c + + // decrease the counter on rising edge, increase the counter on falling edge + ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE)); + // keep the counting mode when the control signal is high level, and reverse the counting mode when the control signal is low level + ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE)); + +.. _watch-points: + +配置观察点 +^^^^^^^^^^ + +PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值被称为 **观察点**。观察点不能超过 :cpp:type:`pcnt_unit_config_t` 设置的范围,最小值和最大值分别为 :cpp:member:`pcnt_unit_config_t::low_limit` 和 :cpp:member:`pcnt_unit_config_t::high_limit`。当计数器到达任一观察点时,会触发一个观察事件,如果在 :cpp:func:`pcnt_unit_register_event_callbacks` 注册过事件回调函数,该事件就会通过中断通知您。关于如何注册事件回调函数,请参考 :ref:`callbacks-register-event`。 + +观察点分别可以通过 :cpp:func:`pcnt_unit_add_watch_point` 和 :cpp:func:`pcnt_unit_remove_watch_point` 进行添加和删除。常用的观察点包括 **过零**, **最大/最小计数** 以及其他的阈值。可用的观察点是有限的,如果 :cpp:func:`pcnt_unit_add_watch_point` 无法获得空闲硬件资源来存储观察点,会返回错误 :c:macro:`ESP_ERR_NOT_FOUND`。不能多次添加同一个观察点,否则将返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。 + +建议通过 :cpp:func:`pcnt_unit_remove_watch_point` 删除未使用的观察点来回收资源。 + +.. code:: c + + // add zero across watch point + ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, 0)); + // add high limit watch point + ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, EXAMPLE_PCNT_HIGH_LIMIT)); + +.. _callbacks-register-event: + +注册事件回调函数 +^^^^^^^^^^^^^^^^^^^^ + +当 PCNT 单元的数值达到任一使能的观察点的数值时,会触发相应的事件并通过中断通知 CPU。如果您想在事件触发时执行相关函数,可通过调用 :cpp:func:`pcnt_unit_register_event_callbacks` 将函数挂载到中断服务程序 (ISR) 上。:cpp:type:`pcnt_event_callbacks_t` 列出了所有支持的事件回调函数: + +- :cpp:member:`pcnt_event_callbacks_t::on_reach` 用于为观察点事件设置回调函数。由于该回调函数是在 ISR 的上下文中被调用的,必须确保该函数不会阻塞调用的任务,(例如,可确保只有以 ``ISR`` 为后缀的 FreeRTOS API 才能在函数中调用)。:cpp:type:`pcnt_watch_cb_t` 中声明了该回调函数的原型。 + +可通过 ``user_ctx`` 将函数上下文保存到 :cpp:func:`pcnt_unit_register_event_callbacks` 中,这些数据会直接传递给回调函数。 + +驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`: + +- :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点数值。 +- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式,:cpp:type:`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向** 和 **计数步长**。 + +注册回调函数会导致中断服务延迟安装,因此回调函数只能在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能之前调用。否则,回调函数会返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。 + +.. code:: c + + static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx) + { + BaseType_t high_task_wakeup; + QueueHandle_t queue = (QueueHandle_t)user_ctx; + // send watch point to queue, from this interrupt callback + xQueueSendFromISR(queue, &(edata->watch_point_value), &high_task_wakeup); + // return whether a high priority task has been waken up by this function + return (high_task_wakeup == pdTRUE); + } + + pcnt_event_callbacks_t cbs = { + .on_reach = example_pcnt_on_reach, + }; + QueueHandle_t queue = xQueueCreate(10, sizeof(int)); + ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue)); + +.. _set-glitch-filter: + +设置毛刺滤波器 +^^^^^^^^^^^^^^^^^ + +PCNT 单元的滤波器可滤除信号中的短时毛刺,:cpp:type:`pcnt_glitch_filter_config_t` 中列出了毛刺滤波器的配置参数: + +- :cpp:member:`pcnt_glitch_filter_config_t::max_glitch_ns` 设置了最大的毛刺宽度,单位为纳秒。如果一个信号脉冲的宽度小于该数值,则该信号会被认定为噪声而不会触发计数器操作。 + +可通过调用 :cpp:func:`pcnt_unit_set_glitch_filter` 来使能毛刺滤波器,并对上述参数进行配置。之后,还可通过调用 :cpp:func:`pcnt_unit_set_glitch_filter` 来关闭毛刺滤波器,并将上述参数设置为 `NULL`。 + +调用该函数时,PCNT 单元应处于初始状态。否则,函数将返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。 + +.. note:: + + 毛刺滤波器的时钟信息来自 APB。为确保 PCNT 单元不会滤除脉冲信号,最大毛刺宽度应大于一个 APB_CLK 周期(如果 APB 的频率为 80 MHz,则最大毛刺宽度为 12.5 ns)。使能动态频率缩放 (DFS) 后,APB 的频率会发生变化,从而最大毛刺宽度也会发生变化,这会导致计数器无法正常工作。因此,第一次使能毛刺滤波器时,驱动会为 PCNT 单元安装 PM 锁。关于 PCNT 驱动的电源管理的更多信息,请参考 :ref:`managing-power`。 + +.. code:: c + + pcnt_glitch_filter_config_t filter_config = { + .max_glitch_ns = 1000, + }; + ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config)); + +.. _enable-and-disable-unit: + +使能和禁用单元 +^^^^^^^^^^^^^^^^^ + +在对 PCNT 单元进行 IO 控制之前,需要通过调用函数 :cpp:func:`pcnt_unit_enable` 来使能该 PCNT 单元。该函数将完成以下操作: + +* 将 PCNT 单元的驱动状态从 **初始** 切换到 **使能** 。 +* 如果中断服务已经在 :cpp:func:`pcnt_unit_register_event_callbacks` 延迟安装,使能中断服务。 +* 如果电源管理锁已经在 :cpp:func:`pcnt_unit_set_glitch_filter` 延迟安装,获取该电源管理锁。请参考 :ref:`managing-power` 获取更多信息。 + +调用函数 :cpp:func:`pcnt_unit_disable` 会进行相反的操作,即将 PCNT 单元的驱动状态切换回 **初始** 状态,禁用中断服务并释放电源管理锁。 + +.. code::c + + ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit)); + +.. _unit-io-control: + +控制单元 IO +^^^^^^^^^^^^^^^ + +启用/停用及清零 +^^^^^^^^^^^^^^^^^^ + +通过调用 :cpp:func:`pcnt_unit_start` 可启用 PCNT 单元,根据不同脉冲信号进行递增或递减计数;通过调用 :cpp:func:`pcnt_unit_stop` 可停用 PCNT 单元,当前的计数值会保留;通过调用 :cpp:func:`pcnt_unit_clear_count` 可将计数器清零。 + +注意 :cpp:func:`pcnt_unit_start` 和 :cpp:func:`pcnt_unit_stop` 应该在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能后调用,否则将返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。 + +.. code::c + + ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit)); + ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit)); + +获取计数器数值 +^^^^^^^^^^^^^^^^^^^ + +通过调用 :cpp:func:`pcnt_unit_get_count` 可随时获取当前计数器的数值。 + +.. note:: + + 返回的计数器数值是一个 **带符号** 的整数,符号代表计数方向。计数器的数值大于等于最大值或小于等于最小值时,计数器会溢出。 + + .. code:: c + + int pulse_count = 0; + ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count)); + +.. _managing-power: + +电源管理 +^^^^^^^^^^ + +使能电源管理 (即 :ref:`CONFIG_PM_ENABLE` 开启) 后,在进入 Light-sleep 模式之前,系统会调整 APB 的频率。这会改变 PCNT 毛刺滤波器的参数,从而可能导致有效信号被滤除。 + +驱动通过获取 :cpp:enumerator:`ESP_PM_APB_FREQ_MAX` 类型的电源管理锁来防止系统修改 APB 频率。每当通过 :cpp:func:`pcnt_unit_set_glitch_filter` 使能毛刺滤波器时,驱动可以保证系统在 :cpp:func:`pcnt_unit_enable` 使能 PCNT 单元后获取电源管理锁。而系统调用 :cpp:func:`pcnt_unit_disable` 之后,驱动会释放电源管理锁。 + +.. _iram-safety: + +支持 IRAM 安全中断 +^^^^^^^^^^^^^^^^^^^^^^ + +当缓存由于写入/擦除 flash 等原因被禁用时,PCNT 中断会默认被延迟。这会导致报警中断无法及时执行,从而无法满足实时性应用的要求。 + +Konfig 选项 :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` 可以实现以下功能: + +1. 即使缓存被禁用也可以使能中断服务 +2. 将 ISR 使用的所有函数都放入 IRAM 中 [2]_ +3. 将驱动对象放入 DRAM (防止驱动对象被意外映射到 PSRAM 中) + +这样,在缓存被禁用时,中断也可运行,但是这也会增加 IRAM 的消耗。 + +另外一个 Konfig 选项 :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` 也可以把常用的 IO 控制函数放在 IRAM 中。这样,当缓存禁用时,这些函数仍然可以执行。这些 IO 控制函数如下所示: + +- :cpp:func:`pcnt_unit_start` +- :cpp:func:`pcnt_unit_stop` +- :cpp:func:`pcnt_unit_clear_count` +- :cpp:func:`pcnt_unit_get_count` + +.. _thread-safe: + +支持线程安全 +^^^^^^^^^^^^^ + +驱动保证工厂函数 :cpp:func:`pcnt_new_unit` 与 :cpp:func:`pcnt_new_channel` 是线程安全的,因此您可以从 RTOS 任务中调用这些函数而无需使用额外的电源管理锁。 +以下函数可以在 ISR 上下文中运行,驱动可以防止这些函数在任务和 ISR 中同时被调用。 + +- :cpp:func:`pcnt_unit_start` +- :cpp:func:`pcnt_unit_stop` +- :cpp:func:`pcnt_unit_clear_count` +- :cpp:func:`pcnt_unit_get_count` + +其他以 :cpp:type:`pcnt_unit_handle_t` 和 :cpp:type:`pcnt_channel_handle_t` 作为第一个参数的函数被视为线程不安全函数,在多任务场景下应避免调用这些函数。 + +.. _kconfig-option: + +支持的 Kconfig 选项 +^^^^^^^^^^^^^^^^^^^^^^ + +- :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` 用于确定 PCNT 控制函数的位置 (放在 IRAM 还是 flash 中),请参考 :ref:`iram-safety` 获取更多信息。 +- :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` 用于控制当缓存禁用时,默认的 ISR 句柄是否可以工作,请参考 :ref:`iram-safety` 获取更多信息。 +- :ref:`CONFIG_PCNT_ENABLE_DEBUG_LOG` 用于使能调试日志输出,而这会增大固件二进制文件。 + +应用实例 +------------ + +* 对旋转编码器的正交信号进行解码的实例请参考::example:`peripherals/pcnt/rotary_encoder`。 + + +API 参考 +-------------- + +.. include-build-file:: inc/pulse_cnt.inc +.. include-build-file:: inc/pcnt_types.inc + +.. [1] + 在不同的 ESP 芯片系列中,PCNT 单元和通道的数量可能会有差异,具体信息请参考 [`TRM <{IDF_TARGET_TRM_CN_URL}#pcnt>`__]。驱动不会禁止用户申请更多的 PCNT 单元和通道,但是当单元和通道资源全部被占用时,再调用单元和通道会返回错误。因此分配资源时,应注意检查返回值,如 :cpp:func:`pcnt_new_unit`。 + +.. [2] + :cpp:member:`pcnt_event_callbacks_t::on_reach` 回调函数和其调用的函数也应该放在 IRAM 中。 \ No newline at end of file