# 脉冲计数器 (PCNT)

## 概述

PCNT 用于统计输入信号的上升沿和/或下降沿的数量。ESP32 集成了多个脉冲计数单元，[1] 每个单元都是包含多个通道的独立计数器。通道可独立配置为统计上升沿或下降沿数量的递增计数器或递减计数器。

PCNT 通道可检测 边沿 信号及 电平 信号。对于比较简单的应用，检测边沿信号就足够了。PCNT 通道可检测上升沿信号、下降沿信号，同时也能设置为递增计数，递减计数，或停止计数。电平信号就是所谓的 控制信号，可用来控制边沿信号的计数模式。通过设置电平信号与边沿信号的检测模式，PCNT 单元可用作 正交解码器。

每个 PCNT 单元还包含一个滤波器，用于滤除线路毛刺。

PCNT 模块通常用于：

- 对一段时间内的脉冲计数，进而计算得到周期信号的频率；

- 对正交信号进行解码，进而获得速度和方向信息。

## 功能描述

PCNT 的功能从以下几个方面进行说明：

- 分配资源 - 说明如何通过配置分配 PCNT 单元和通道，以及在相应操作完成之后，如何回收单元和通道。

- 设置通道操作 - 说明如何设置通道针对不同信号沿和电平进行操作。

- PCNT 观察点 - 说明如何配置观察点，即当计数达到某个数值时，命令 PCNT 单元触发某个事件。

- 注册事件回调函数 - 说明如何将您的代码挂载到观察点事件的回调函数上。

- 设置毛刺滤波器 - 说明如何使能毛刺滤波器并设置其时序参数。

- 使能和禁用单元 - 说明如何使能和关闭 PCNT 单元。

- 控制单元 IO 操作 - 说明 PCNT 单元的 IO 控制功能，例如使能毛刺滤波器，开启和停用 PCNT 单元，获取和清除计数。

- 电源管理 - 说明哪些功能会阻止芯片进入低功耗模式。

- 支持 IRAM 安全中断 - 说明在缓存禁用的情况下，如何执行 PCNT 中断和 IO 控制功能。

- 支持线程安全 - 列出线程安全的 API。

- 支持的 Kconfig 选项 - 列出了支持的 Kconfig 选项，这些选项可实现不同的驱动效果。

### 分配资源

PCNT 单元和通道分别用 `pcnt_unit_handle_t` 与 `pcnt_channel_handle_t` 表示。所有的可用单元和通道都由驱动在资源池中进行维护，无需了解底层实例 ID。

#### 安装 PCNT 单元

安装 PCNT 单元时，需要先完成配置 `pcnt_unit_config_t`：

- `pcnt_unit_config_t::group_id` 用于指定从哪个 PCNT 外设组（硬件实例）中分配该单元。必须是合法的组索引，范围为 `[0, PCNT_LL_INST_NUM)`。当芯片拥有多个独立 PCNT 组时（例如 ESP32-S31），可通过此字段将单元绑定到特定的组。

- `pcnt_unit_config_t::clk_src` 用于选择 PCNT 单元的时钟源。

- `pcnt_unit_config_t::low_limit` 与 `pcnt_unit_config_t::high_limit` 用于指定内部计数器的最小值和最大值。当计数器超过任一限值时，计数器将归零。

- `pcnt_unit_config_t::intr_priority` 设置中断的优先级。如果设置为 `0`，则会分配一个默认优先级的中断，否则会使用指定的优先级。

- `pcnt_unit_config_t::flags::accum_count` 用于设置是否需要软件在硬件计数值溢出的时候进行累加保存，这有助于“拓宽”计数器的实际位宽。默认情况下，计数器的位宽最高只有 16 比特。请参考 计数溢出补偿 了解如何利用此功能来补偿硬件计数器的溢出损失。

备注

同一 PCNT 组 内的所有单元共享同一硬件中断源和同一外设时钟，因此在同一组内安装多个 PCNT 单元时，需满足以下约束：

- `pcnt_unit_config_t::intr_priority` 和 `pcnt_unit_config_t::clk_src` 在同组内须一致，不一致将导致 `pcnt_new_unit()` 返回 `ESP_ERR_INVALID_ARG`。

调用函数 `pcnt_new_unit()` 并将 `pcnt_unit_config_t` 作为其输入值，可对 PCNT 单元进行分配和初始化。该函数正常运行时，会返回一个 PCNT 单元句柄。没有可用的 PCNT 单元时（即 PCNT 单元全部被占用），该函数会返回错误 `ESP_ERR_NOT_FOUND`。

如果不再需要之前创建的某个 PCNT 单元，建议通过调用 `pcnt_del_unit()` 来回收该单元，从而该单元可用于其他用途。删除某个 PCNT 单元之前，需要满足以下条件：

- 该单元处于初始状态，即该单元要么已经被 `pcnt_unit_disable()` 禁用，要么尚未使能。

- 附属于该单元的通道已全部被 `pcnt_del_channel()` 删除。

    #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 通道时，需要先初始化 `pcnt_chan_config_t`，然后调用 `pcnt_new_channel()`。对 `pcnt_chan_config_t` 配置如下所示：

- `pcnt_chan_config_t::edge_gpio_num` 与 `pcnt_chan_config_t::level_gpio_num` 用于指定 边沿 信号和 电平 信号对应的 GPIO 编号。请注意，这两个参数未被使用时，可以设置为 -1，即成为 虚拟 IO 。对于一些简单的脉冲计数应用，电平信号或边沿信号是固定的（即不会发生改变），可将其设置为虚拟 IO，然后该信号会被连接到一个固定的高/低逻辑电平，这样就可以在通道分配时回收一个 GPIO，节省一个 GPIO 管脚资源。

- `pcnt_chan_config_t::flags::virt_edge_io_level` 与 `pcnt_chan_config_t::flags::virt_level_io_level` 用于指定 边沿 信号和 电平 信号的虚拟 IO 电平，以保证这些控制信号处于确定状态。请注意，只有在 `pcnt_chan_config_t::edge_gpio_num` 或 `pcnt_chan_config_t::level_gpio_num` 设置为 -1 时，这两个参数才有效。

- `pcnt_chan_config_t::flags::invert_edge_input` 与 `pcnt_chan_config_t::flags::invert_level_input` 用于确定信号在输入 PCNT 之前是否需要被翻转，信号翻转由 GPIO 矩阵（不是 PCNT 单元）执行。

调用函数 `pcnt_new_channel()`，将 `pcnt_chan_config_t` 作为输入值并调用 `pcnt_new_unit()` 返回的 PCNT 单元句柄，可对 PCNT 通道进行分配和初始化。如果该函数正常运行，会返回一个 PCNT 通道句柄。如果没有可用的 PCNT 通道（PCNT 通道资源全部被占用），该函数会返回错误 `ESP_ERR_NOT_FOUND`。注意，为某个单元安装 PCNT 通道时，应确保该单元处于初始状态，否则函数 `pcnt_new_channel()` 会返回错误 `ESP_ERR_INVALID_STATE`。

如果不再需要之前创建的某个 PCNT 通道，建议通过调用 `pcnt_del_channel()` 回收该通道，从而该通道可用于其他用途。

    #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));

备注

PCNT 驱动不会为边沿信号或电平信号对应的 GPIO 配置内部上拉或下拉电阻。如果信号源需要确定的空闲电平，请使用 `gpio_set_pull_mode()`、`gpio_pullup_en()` 和 `gpio_pullup_dis()` 等函数显式配置 GPIO 上下拉模式。

### 设置通道操作

当输入脉冲信号切换时，PCNT 通道会增加，减少或停止计数。边沿信号及电平信号可设置为不同的计数器操作。

- `pcnt_channel_set_edge_action()` 为输入到 `pcnt_chan_config_t::edge_gpio_num` 的信号上升沿和下降沿设置操作，`pcnt_channel_edge_action_t` 中列出了支持的操作。

- `pcnt_channel_set_level_action()` 为输入到 `pcnt_chan_config_t::level_gpio_num` 的信号高电平和低电平设置操作，`pcnt_channel_level_action_t` 中列出了支持的操作。使用 `pcnt_new_channel()` 分配 PCNT 通道时，如果 `pcnt_chan_config_t::level_gpio_num` 被设置为 -1，就无需对该函数进行设置了。

    // 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));

### PCNT 观察点

PCNT 单元可被设置为观察几个特定的数值，这些被观察的数值被称为 观察点。观察点不能超过 `pcnt_unit_config_t` 设置的范围，最小值和最大值分别为 `pcnt_unit_config_t::low_limit` 和 `pcnt_unit_config_t::high_limit`。当计数器到达任一观察点时，会触发一个观察事件，如果在 `pcnt_unit_register_event_callbacks()` 注册过事件回调函数，该事件就会通过中断发送通知。关于如何注册事件回调函数，请参考 注册事件回调函数。

观察点分别可以通过 `pcnt_unit_add_watch_point()` 和 `pcnt_unit_remove_watch_point()` 进行添加和删除。常用的观察点包括 过零、最大/最小计数 以及其他的阈值。可用的观察点是有限的，如果 `pcnt_unit_add_watch_point()` 无法获得空闲硬件资源来存储观察点，会返回错误 `ESP_ERR_NOT_FOUND`。不能多次添加同一个观察点，否则将返回错误 `ESP_ERR_INVALID_STATE`。

建议通过 `pcnt_unit_remove_watch_point()` 删除未使用的观察点来回收资源。

    // 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));

备注

由于硬件上的限制，在添加一个新的观察点后，你需要调用 `pcnt_unit_clear_count()` 函数来使之生效。

### 注册事件回调函数

当 PCNT 单元的数值达到任一使能的观察点的数值时，会触发相应的事件并通过中断通知 CPU。如果要在事件触发时执行相关函数，可通过调用 `pcnt_unit_register_event_callbacks()` 将函数挂载到中断服务程序 (ISR) 上。`pcnt_event_callbacks_t` 列出了所有支持的事件回调函数：

- `pcnt_event_callbacks_t::on_reach` 用于为观察点事件设置回调函数。由于该回调函数是在 ISR 的上下文中被调用的，必须确保该函数不会阻塞调用的任务，（例如，可确保只有以 `ISR` 为后缀的 FreeRTOS API 才能在函数中调用）。`pcnt_watch_cb_t` 中声明了该回调函数的原型。

可通过 `user_ctx` 将函数上下文保存到 `pcnt_unit_register_event_callbacks()` 中，这些数据会直接传递给回调函数。

驱动程序会将特定事件的数据写入回调函数中，例如，观察点事件或观察步进事件数据被声明为 `pcnt_watch_event_data_t`：

- `pcnt_watch_event_data_t::watch_point_value` 用于保存触发事件时计数器的数值。

- `pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式，`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常，不同的过零模式意味着不同的 计数方向 和 计数步长。

注册回调函数会导致中断服务延迟安装，因此回调函数只能在 PCNT 单元被 `pcnt_unit_enable()` 使能之前调用。否则，回调函数会返回错误 `ESP_ERR_INVALID_STATE`。

    static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, const 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));

### 设置毛刺滤波器

PCNT 单元的滤波器可滤除信号中的短时毛刺，`pcnt_glitch_filter_config_t` 中列出了毛刺滤波器的配置参数：

- `pcnt_glitch_filter_config_t::max_glitch_ns` 设置了最大的毛刺宽度，单位为纳秒。如果一个信号脉冲的宽度小于该数值，则该信号会被认定为噪声而不会触发计数器操作。

可通过调用 `pcnt_unit_set_glitch_filter()` 来使能毛刺滤波器，并对上述参数进行配置。之后，还可通过调用 `pcnt_unit_set_glitch_filter()` 来关闭毛刺滤波器，并将上述参数设置为 NULL。

调用该函数时，PCNT 单元应处于初始状态。否则，函数将返回错误 `ESP_ERR_INVALID_STATE`。

备注

毛刺滤波器的时钟信息来自 APB。为确保 PCNT 单元不会滤除脉冲信号，最大毛刺宽度应大于一个 APB_CLK 周期（如果 APB 的频率为 80 MHz，则最大毛刺宽度为 12.5 ns）。使能动态频率缩放 (DFS) 后，APB 的频率会发生变化，从而最大毛刺宽度也会发生变化，这会导致计数器无法正常工作。因此，驱动会为每个 PCNT 单元安装电源锁。关于 PCNT 驱动的电源管理的更多信息，请参考 电源管理。

    pcnt_glitch_filter_config_t filter_config = {
        .max_glitch_ns = 1000,
    };
    ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));

### 使能和禁用单元

在对 PCNT 单元进行 IO 控制之前，需要通过调用函数 `pcnt_unit_enable()` 来使能该 PCNT 单元。该函数将完成以下操作：

- 将 PCNT 单元的驱动状态从 初始 切换到 使能 。

- 如果中断服务已经在 `pcnt_unit_register_event_callbacks()` 延迟安装，使能中断服务。

- 如果电源管理锁已经安装，获取该电源管理锁。请参考 电源管理 获取更多信息。

调用函数 `pcnt_unit_disable()` 会进行相反的操作，即将 PCNT 单元的驱动状态切换回 初始 状态，禁用中断服务并释放电源管理锁。

### 控制单元 IO 操作

### 启用/停用及清零

通过调用 `pcnt_unit_start()` 可启用 PCNT 单元，根据不同脉冲信号进行递增或递减计数；通过调用 `pcnt_unit_stop()` 可停用 PCNT 单元，当前的计数值会保留；通过调用 `pcnt_unit_clear_count()` 可将计数器清零。

注意 `pcnt_unit_start()` 和 `pcnt_unit_stop()` 应该在 PCNT 单元被 `pcnt_unit_enable()` 使能后调用，否则将返回错误 `ESP_ERR_INVALID_STATE`。

### 获取计数器数值

调用 `pcnt_unit_get_count()` 可随时获取当前计数器的数值。返回的计数值是一个 带符号 的整型数，其符号反映了计数的方向。

>     int pulse_count = 0;
>     ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count));

#### 计数溢出补偿

PCNT 内部的硬件计数器会在计数达到高/低门限的时候自动清零。如果你想补偿该计数值的溢出损失，以期进一步拓宽计数器的实际位宽，你可以：

1. 在安装 PCNT 计数单元的时候使能 `pcnt_unit_config_t::flags::accum_count` 选项。

2. 将高/低计数门限设置为 PCNT 观察点。

3. 现在，`pcnt_unit_get_count()` 函数返回的计数值就会包含硬件计数器当前的计数值，累加上计数器溢出造成的损失。

备注

`pcnt_unit_clear_count()` 会复位该软件累加器。

备注

启用计数溢出补偿时，建议使用尽可能大的高/低计数门限，因为它可以避免中断被频繁触发，提高系统性能，并且避免由于多次溢出而导致的补偿失败。

### 电源管理

当电源管理使能（即 CONFIG_PM_ENABLE 开启）时，系统会在进入 Light-sleep 模式之前调整 APB 的频率，这可能导致 PCNT 毛刺滤波器将有效信号误认为噪声。

为了防止这种情况发生，驱动程序可以获取类型为 `ESP_PM_APB_FREQ_MAX` 的电源管理锁，以确保 APB 频率保持不变。该锁在通过 `pcnt_unit_enable()` 使能 PCNT 单元时获取，并在通过 `pcnt_unit_disable()` 禁用单元时释放。

### 支持 IRAM 安全中断

当缓存由于写入/擦除 flash 等原因被禁用时，PCNT 中断会默认被延迟。这会导致报警中断无法及时执行，从而无法满足实时性应用的要求。

Konfig 选项 CONFIG_PCNT_ISR_IRAM_SAFE 可以实现以下功能：

1. 即使缓存被禁用也可以使能中断服务

2. 将 ISR 使用的所有函数都放入 IRAM 中 [2]

3. 将驱动对象放入 DRAM（防止驱动对象被意外映射到 PSRAM 中）

这样，在缓存被禁用时，中断也可运行，但是这也会增加 IRAM 的消耗。

另外一个 Konfig 选项 CONFIG_PCNT_CTRL_FUNC_IN_IRAM 也可以把常用的 IO 控制函数放在 IRAM 中。这样，当缓存禁用时，这些函数仍然可以执行。这些 IO 控制函数如下所示：

- `pcnt_unit_start()`

- `pcnt_unit_stop()`

- `pcnt_unit_clear_count()`

- `pcnt_unit_get_count()`

### 支持线程安全

驱动保证工厂函数 `pcnt_new_unit()` 与 `pcnt_new_channel()` 是线程安全的，因此可以从 RTOS 任务中调用这些函数，而无需使用额外的电源管理锁。

以下函数可以在 ISR 上下文中运行，驱动可以防止这些函数在任务和 ISR 中同时被调用。

- `pcnt_unit_start()`

- `pcnt_unit_stop()`

- `pcnt_unit_clear_count()`

- `pcnt_unit_get_count()`

其他以 `pcnt_unit_handle_t` 和 `pcnt_channel_handle_t` 作为第一个参数的函数被视为线程不安全函数，在多任务场景下应避免调用这些函数。

### 支持的 Kconfig 选项

- CONFIG_PCNT_CTRL_FUNC_IN_IRAM 用于确定 PCNT 控制函数的位置（放在 IRAM 还是 flash 中），请参考 支持 IRAM 安全中断 获取更多信息。

- CONFIG_PCNT_ISR_IRAM_SAFE 用于控制当缓存禁用时，默认的 ISR 句柄是否可以工作，请参考 支持 IRAM 安全中断 获取更多信息。

- CONFIG_PCNT_ENABLE_DEBUG_LOG 用于使能调试日志输出，而这会增大固件二进制文件。

## 应用示例

- peripherals/pcnt/rotary_encoder 演示了如何使用 PCNT 外设来解码由常见的旋转编码器 EC11 生成的差分信号，以及如何配置旋转编码器将系统从 light-sleep 状态唤醒。

## API 参考

### Header File

- components/esp_driver_pcnt/include/driver/pulse_cnt.h

- This header file can be included with:

  >     #include "driver/pulse_cnt.h"

- This header file is a part of the API provided by the `esp_driver_pcnt` component. To declare that your component depends on `esp_driver_pcnt`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_pcnt

  > or

  >     PRIV_REQUIRES esp_driver_pcnt

### Functions

esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_unit)

  Create a new PCNT unit, and return the handle.

  备注

  The newly created PCNT unit is put in the init state.

  参数:

    - config -- [in] PCNT unit configuration

    - ret_unit -- [out] Returned PCNT unit handle

  返回:

    - ESP_OK: Create PCNT unit successfully

    - ESP_ERR_INVALID_ARG: Create PCNT unit failed because of invalid argument (e.g. high/low limit value out of the range)

    - ESP_ERR_NO_MEM: Create PCNT unit failed because out of memory

    - ESP_ERR_NOT_FOUND: Create PCNT unit failed because all PCNT units are used up and no more free one

    - ESP_FAIL: Create PCNT unit failed because of other error

esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit)

  Delete the PCNT unit handle.

  备注

  A PCNT unit can't be in the enable state when this function is invoked. See also `pcnt_unit_disable()` for how to disable a unit.

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Delete the PCNT unit successfully

    - ESP_ERR_INVALID_ARG: Delete the PCNT unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Delete the PCNT unit failed because the unit is not in init state or some PCNT channel is still in working

    - ESP_FAIL: Delete the PCNT unit failed because of other error

esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch_filter_config_t *config)

  Set glitch filter for PCNT unit.

  备注

  This function should be called when the PCNT unit is in the init state (i.e. before calling `pcnt_unit_enable()`)

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - config -- [in] PCNT filter configuration, set config to NULL means disabling the filter function

  返回:

    - ESP_OK: Set glitch filter successfully

    - ESP_ERR_INVALID_ARG: Set glitch filter failed because of invalid argument (e.g. glitch width is too big)

    - ESP_ERR_INVALID_STATE: Set glitch filter failed because the unit is not in the init state

    - ESP_FAIL: Set glitch filter failed because of other error

esp_err_t pcnt_unit_enable(pcnt_unit_handle_t unit)

  Enable the PCNT unit.

  备注

  This function will transit the unit state from init to enable.

  备注

  This function will enable the interrupt service, if it's lazy installed in `pcnt_unit_register_event_callbacks()`.

  备注

  This function will acquire the PM lock if it's lazy installed in `pcnt_unit_set_glitch_filter()`.

  备注

  Enable a PCNT unit doesn't mean to start it. See also `pcnt_unit_start()` for how to start the PCNT counter.

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Enable PCNT unit successfully

    - ESP_ERR_INVALID_ARG: Enable PCNT unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Enable PCNT unit failed because the unit is already enabled

    - ESP_FAIL: Enable PCNT unit failed because of other error

esp_err_t pcnt_unit_disable(pcnt_unit_handle_t unit)

  Disable the PCNT unit.

  备注

  This function will do the opposite work to the `pcnt_unit_enable()`

  备注

  Disable a PCNT unit doesn't mean to stop it. See also `pcnt_unit_stop()` for how to stop the PCNT counter.

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Disable PCNT unit successfully

    - ESP_ERR_INVALID_ARG: Disable PCNT unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Disable PCNT unit failed because the unit is not enabled yet

    - ESP_FAIL: Disable PCNT unit failed because of other error

esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit)

  Start the PCNT unit, the counter will start to count according to the edge and/or level input signals.

  备注

  This function should be called when the unit is in the enable state (i.e. after calling `pcnt_unit_enable()`)

  备注

  This function is allowed to run within ISR context

  备注

  This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM` is on, so that it's allowed to be executed when Cache is disabled

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Start PCNT unit successfully

    - ESP_ERR_INVALID_ARG: Start PCNT unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Start PCNT unit failed because the unit is not enabled yet

    - ESP_FAIL: Start PCNT unit failed because of other error

esp_err_t pcnt_unit_stop(pcnt_unit_handle_t unit)

  Stop PCNT from counting.

  备注

  This function should be called when the unit is in the enable state (i.e. after calling `pcnt_unit_enable()`)

  备注

  The stop operation won't clear the counter. Also see `pcnt_unit_clear_count()` for how to clear pulse count value.

  备注

  This function is allowed to run within ISR context

  备注

  This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it is allowed to be executed when Cache is disabled

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Stop PCNT unit successfully

    - ESP_ERR_INVALID_ARG: Stop PCNT unit failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Stop PCNT unit failed because the unit is not enabled yet

    - ESP_FAIL: Stop PCNT unit failed because of other error

esp_err_t pcnt_unit_clear_count(pcnt_unit_handle_t unit)

  Clear PCNT pulse count value to zero.

  备注

  It's recommended to call this function after adding a watch point by `pcnt_unit_add_watch_point()`, so that the newly added watch point is effective immediately.

  备注

  This function is allowed to run within ISR context

  备注

  This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Clear PCNT pulse count successfully

    - ESP_ERR_INVALID_ARG: Clear PCNT pulse count failed because of invalid argument

    - ESP_FAIL: Clear PCNT pulse count failed because of other error

esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value)

  Get PCNT count value.

  备注

  This function is allowed to run within ISR context

  备注

  This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - value -- [out] Returned count value

  返回:

    - ESP_OK: Get PCNT pulse count successfully

    - ESP_ERR_INVALID_ARG: Get PCNT pulse count failed because of invalid argument

    - ESP_FAIL: Get PCNT pulse count failed because of other error

esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt_event_callbacks_t *cbs, void *user_data)

  Set event callbacks for PCNT unit.

  备注

  User registered callbacks are expected to be runnable within ISR context

  备注

  The first call to this function needs to be before the call to `pcnt_unit_enable`

  备注

  User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL.

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - cbs -- [in] Group of callback functions

    - user_data -- [in] User data, which will be passed to callback functions directly

  返回:

    - ESP_OK: Set event callbacks successfully

    - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Set event callbacks failed because the unit is not in init state

    - ESP_FAIL: Set event callbacks failed because of other error

esp_err_t pcnt_unit_add_watch_point(pcnt_unit_handle_t unit, int watch_point)

  Add a watch point for PCNT unit, PCNT will generate an event when the counter value reaches the watch point value.

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - watch_point -- [in] Value to be watched

  返回:

    - ESP_OK: Add watch point successfully

    - ESP_ERR_INVALID_ARG: Add watch point failed because of invalid argument (e.g. the value to be watched is out of the limitation set in `pcnt_unit_config_t`)

    - ESP_ERR_INVALID_STATE: Add watch point failed because the same watch point has already been added

    - ESP_ERR_NOT_FOUND: Add watch point failed because no more hardware watch point can be configured

    - ESP_FAIL: Add watch point failed because of other error

esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point)

  Remove a watch point for PCNT unit.

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - watch_point -- [in] Watch point value

  返回:

    - ESP_OK: Remove watch point successfully

    - ESP_ERR_INVALID_ARG: Remove watch point failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Remove watch point failed because the watch point was not added by `pcnt_unit_add_watch_point()` yet

    - ESP_FAIL: Remove watch point failed because of other error

esp_err_t pcnt_unit_add_watch_step(pcnt_unit_handle_t unit, int step_interval)

  Add a step notify for PCNT unit, PCNT will generate an event when the incremental(can be positive or negative) of counter value reaches the step interval.

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - step_interval -- [in] PCNT step notify interval value. Positive value means step forward, negative value means step backward.

  返回:

    - ESP_OK: Add step notify successfully

    - ESP_ERR_INVALID_ARG: Add step notify failed because of invalid argument (e.g. the value incremental to be watched is out of the limitation set in `pcnt_unit_config_t`)

    - ESP_ERR_INVALID_STATE: Add step notify failed because the step notify has already been added

    - ESP_FAIL: Add step notify failed because of other error

esp_err_t pcnt_unit_remove_all_watch_step(pcnt_unit_handle_t unit)

  Remove all step notify for PCNT unit.

  参数:

    unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

  返回:

    - ESP_OK: Remove step notify successfully

    - ESP_ERR_INVALID_ARG: Remove step notify failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Remove step notify failed because the step notify was not added by `pcnt_unit_add_watch_step()` yet

    - ESP_FAIL: Remove step notify failed because of other error

esp_err_t pcnt_unit_remove_single_watch_step(pcnt_unit_handle_t unit, int step_interval)

  Remove a step notify for PCNT unit.

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - step_interval -- [in] Step notify interval value

  返回:

    - ESP_OK: Remove step notify successfully

    - ESP_ERR_INVALID_ARG: Remove step notify failed because of invalid argument

    - ESP_ERR_INVALID_STATE: Remove step notify failed because the step notify was not added by `pcnt_unit_add_watch_step()` yet

    - ESP_FAIL: Remove step notify failed because of other error

esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *config, pcnt_channel_handle_t *ret_chan)

  Create PCNT channel for specific unit, each PCNT has several channels associated with it.

  备注

  This function should be called when the unit is in init state (i.e. before calling `pcnt_unit_enable()`)

  参数:

    - unit -- [in] PCNT unit handle created by `pcnt_new_unit()`

    - config -- [in] PCNT channel configuration

    - ret_chan -- [out] Returned channel handle

  返回:

    - ESP_OK: Create PCNT channel successfully

    - ESP_ERR_INVALID_ARG: Create PCNT channel failed because of invalid argument

    - ESP_ERR_NO_MEM: Create PCNT channel failed because of insufficient memory

    - ESP_ERR_NOT_FOUND: Create PCNT channel failed because all PCNT channels are used up and no more free one

    - ESP_ERR_INVALID_STATE: Create PCNT channel failed because the unit is not in the init state

    - ESP_FAIL: Create PCNT channel failed because of other error

esp_err_t pcnt_del_channel(pcnt_channel_handle_t chan)

  Delete the PCNT channel.

  参数:

    chan -- [in] PCNT channel handle created by `pcnt_new_channel()`

  返回:

    - ESP_OK: Delete the PCNT channel successfully

    - ESP_ERR_INVALID_ARG: Delete the PCNT channel failed because of invalid argument

    - ESP_FAIL: Delete the PCNT channel failed because of other error

esp_err_t pcnt_channel_set_edge_action(pcnt_channel_handle_t chan, pcnt_channel_edge_action_t pos_act, pcnt_channel_edge_action_t neg_act)

  Set channel actions when edge signal changes (e.g. falling or rising edge occurred). The edge signal is input from the `edge_gpio_num` configured in `pcnt_chan_config_t`. We use these actions to control when and how to change the counter value.

  参数:

    - chan -- [in] PCNT channel handle created by `pcnt_new_channel()`

    - pos_act -- [in] Action on posedge signal

    - neg_act -- [in] Action on negedge signal

  返回:

    - ESP_OK: Set edge action for PCNT channel successfully

    - ESP_ERR_INVALID_ARG: Set edge action for PCNT channel failed because of invalid argument

    - ESP_FAIL: Set edge action for PCNT channel failed because of other error

esp_err_t pcnt_channel_set_level_action(pcnt_channel_handle_t chan, pcnt_channel_level_action_t high_act, pcnt_channel_level_action_t low_act)

  Set channel actions when level signal changes (e.g. signal level goes from high to low). The level signal is input from the `level_gpio_num` configured in `pcnt_chan_config_t`. We use these actions to control when and how to change the counting mode.

  参数:

    - chan -- [in] PCNT channel handle created by `pcnt_new_channel()`

    - high_act -- [in] Action on high level signal

    - low_act -- [in] Action on low level signal

  返回:

    - ESP_OK: Set level action for PCNT channel successfully

    - ESP_ERR_INVALID_ARG: Set level action for PCNT channel failed because of invalid argument

    - ESP_FAIL: Set level action for PCNT channel failed because of other error

### Structures

struct pcnt_watch_event_data_t

  PCNT watch event data.

  Public Members

  int watch_point_value

    Watch point value that triggered the event

  pcnt_unit_zero_cross_mode_t zero_cross_mode

    Zero cross mode

struct pcnt_event_callbacks_t

  Group of supported PCNT callbacks.

  备注

  The callbacks are all running under ISR environment

  备注

  When CONFIG_PCNT_ISR_IRAM_SAFE is enabled, the callback itself and functions callbed by it should be placed in IRAM.

  Public Members

  pcnt_watch_cb_t on_reach

    Called when PCNT unit counter reaches any watch point or step notify

struct pcnt_unit_config_t

  PCNT unit configuration.

  Public Members

  int group_id

    Specify from which PCNT instance (peripheral group) to allocate the unit

  pcnt_clock_source_t clk_src

    Clock source for PCNT unit

  int low_limit

    Low limitation of the count unit, should be lower than 0

  int high_limit

    High limitation of the count unit, should be higher than 0

  int intr_priority

    PCNT interrupt priority, if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3)

  struct pcnt_unit_config_t::extra_pcnt_unit_flags flags

    Extra flags

  struct extra_pcnt_unit_flags

    Extra configuration flags for PCNT unit.

    Public Members

    uint32_t accum_count

      Whether to accumulate the count value when overflows at the high/low limit

struct pcnt_chan_config_t

  PCNT channel configuration.

  Public Members

  int edge_gpio_num

    GPIO number used by the edge signal. Set to -1 if unused

  int level_gpio_num

    GPIO number used by the level signal. Set to -1 if unused

  struct pcnt_chan_config_t::extra_pcnt_chan_flags flags

    Channel config flags

  struct extra_pcnt_chan_flags

    Extra configuration flags for PCNT channel.

    Public Members

    uint32_t invert_edge_input

      Invert the input edge signal

    uint32_t invert_level_input

      Invert the input level signal

    uint32_t virt_edge_io_level

      Virtual edge IO level, 0: low, 1: high. Only valid when edge_gpio_num is set to -1

    uint32_t virt_level_io_level

      Virtual level IO level, 0: low, 1: high. Only valid when level_gpio_num is set to -1

struct pcnt_glitch_filter_config_t

  PCNT glitch filter configuration.

  Public Members

  uint32_t max_glitch_ns

    Pulse width smaller than this threshold will be treated as glitch and ignored, in the unit of ns

### Macros

_pcnt_unit_remove_all_watch_step(unit)

_pcnt_unit_remove_single_watch_step(unit, step_interval)

_pcnt_get_remove_func(_1, _2, FUNC, ...)

pcnt_unit_remove_watch_step(...)

### Type Definitions

typedef struct pcnt_unit_t *pcnt_unit_handle_t

  Type of PCNT unit handle.

typedef struct pcnt_chan_t *pcnt_channel_handle_t

  Type of PCNT channel handle.

typedef bool (*pcnt_watch_cb_t)(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx)

  PCNT watch event callback prototype.

  备注

  The callback function is invoked from an ISR context, so it should meet the restrictions of not calling any blocking APIs when implementing the callback. e.g. must use ISR version of FreeRTOS APIs.

  Param unit:

    [in] PCNT unit handle

  Param edata:

    [in] PCNT event data, fed by the driver

  Param user_ctx:

    [in] User data, passed from `pcnt_unit_register_event_callbacks()`

  Return:

    Whether a high priority task has been woken up by this function

### Header File

- components/esp_hal_pcnt/include/hal/pcnt_types.h

- This header file can be included with:

  >     #include "hal/pcnt_types.h"

- This header file is a part of the API provided by the `esp_hal_pcnt` component. To declare that your component depends on `esp_hal_pcnt`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_hal_pcnt

  > or

  >     PRIV_REQUIRES esp_hal_pcnt

### Type Definitions

typedef int pcnt_clock_source_t

  Default type.

### Enumerations

enum pcnt_channel_level_action_t

  PCNT channel action on control level.

  Values:

  enumerator PCNT_CHANNEL_LEVEL_ACTION_KEEP

    Keep current count mode

  enumerator PCNT_CHANNEL_LEVEL_ACTION_INVERSE

    Invert current count mode (increase -> decrease, decrease -> increase)

  enumerator PCNT_CHANNEL_LEVEL_ACTION_HOLD

    Hold current count value

enum pcnt_channel_edge_action_t

  PCNT channel action on signal edge.

  Values:

  enumerator PCNT_CHANNEL_EDGE_ACTION_HOLD

    Hold current count value

  enumerator PCNT_CHANNEL_EDGE_ACTION_INCREASE

    Increase count value

  enumerator PCNT_CHANNEL_EDGE_ACTION_DECREASE

    Decrease count value

enum pcnt_unit_zero_cross_mode_t

  PCNT unit zero cross mode.

  Values:

  enumerator PCNT_UNIT_ZERO_CROSS_POS_ZERO

    start from positive value, end to zero, i.e. +N->0

  enumerator PCNT_UNIT_ZERO_CROSS_NEG_ZERO

    start from negative value, end to zero, i.e. -N->0

  enumerator PCNT_UNIT_ZERO_CROSS_NEG_POS

    start from negative value, end to positive value, i.e. -N->+M

  enumerator PCNT_UNIT_ZERO_CROSS_POS_NEG

    start from positive value, end to negative value, i.e. +N->-M

  enumerator PCNT_UNIT_ZERO_CROSS_INVALID

    invalid zero cross mode

enum pcnt_step_direction_t

  PCNT step direction.

  Values:

  enumerator PCNT_STEP_FORWARD

    step forward, e.g., [N]->[N+1]->[N+2]->...

  enumerator PCNT_STEP_BACKWARD

    step backward, e.g., [N]->[N-1]->[N-2]->...

[1]

在不同的 ESP 芯片系列中，PCNT 单元和通道的数量可能会有差异，具体信息请参考 [TRM]。驱动程序对通道申请数量不做限制，但当硬件资源用尽时，驱动程序将返回错误。因此分配资源时，应注意检查返回值，如 `pcnt_new_unit()`。

[2]

`pcnt_event_callbacks_t::on_reach` 回调函数和其调用的函数也应该放在 IRAM 中。

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。