Skip to content

Latest commit

 

History

History
331 lines (212 loc) · 20.4 KB

File metadata and controls

331 lines (212 loc) · 20.4 KB

脉冲计数器 (PCNT)

概述

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

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

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

PCNT 模块通常用于:

  • 对一段时间内的脉冲计数,进而计算得到周期信号的频率;
  • 对正交信号进行解码,进而获得速度和方向信息。

功能描述

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

分配资源

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

安装 PCNT 单元

安装 PCNT 单元时,需要先完成配置 :cpp:type:`pcnt_unit_config_t`

Note

由于所有 PCNT 单元共享一个中断源,安装多个 PCNT 单元时请确保每个单元的中断优先级 :cpp:member:`pcnt_unit_config_t::intr_priority` 一致。

调用函数 :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 单元之前,需要满足以下条件:

#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: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` 回收该通道,从而该通道可用于其他用途。

#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 通道会增加,减少或停止计数。边沿信号及电平信号可设置为不同的计数器操作。

// 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 单元可被设置为观察几个特定的数值,这些被观察的数值被称为 观察点。观察点不能超过 :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:`pcnt-register-event-callbacks`

观察点分别可以通过 :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` 删除未使用的观察点来回收资源。

// 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));
.. only:: not SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE

    .. note::

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

注册事件回调函数

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

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

驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`

注册回调函数会导致中断服务延迟安装,因此回调函数只能在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能之前调用。否则,回调函数会返回错误 :c:macro:`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 单元的滤波器可滤除信号中的短时毛刺,:cpp:type:`pcnt_glitch_filter_config_t` 中列出了毛刺滤波器的配置参数:

可通过调用 :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:`pcnt-power-management`

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 控制之前,需要通过调用函数 :cpp:func:`pcnt_unit_enable` 来使能该 PCNT 单元。该函数将完成以下操作:

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

控制单元 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`

获取计数器数值

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

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

计数溢出补偿

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

  1. 在安装 PCNT 计数单元的时候使能 :cpp:member:`pcnt_unit_config_t::accum_count` 选项。
  2. 将高/低计数门限设置为 :ref:`pcnt-watch-points`.
  3. 现在,:cpp:func:`pcnt_unit_get_count` 函数返回的计数值就会包含硬件计数器当前的计数值,累加上计数器溢出造成的损失。

Note

:cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。

电源管理

使能电源管理 (即 :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 安全中断

当缓存由于写入/擦除 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_new_unit`:cpp:func:`pcnt_new_channel` 是线程安全的,因此您可以从 RTOS 任务中调用这些函数而无需使用额外的电源管理锁。 以下函数可以在 ISR 上下文中运行,驱动可以防止这些函数在任务和 ISR 中同时被调用。

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

支持的 Kconfig 选项

应用实例

API 参考

.. include-build-file:: inc/pulse_cnt.inc
.. include-build-file:: inc/pcnt_types.inc

[1]在不同的 ESP 芯片系列中,PCNT 单元和通道的数量可能会有差异,具体信息请参考 [TRM]。驱动不会禁止用户申请更多的 PCNT 单元和通道,但是当单元和通道资源全部被占用时,再调用单元和通道会返回错误。因此分配资源时,应注意检查返回值,如 :cpp:func:`pcnt_new_unit`
[2]:cpp:member:`pcnt_event_callbacks_t::on_reach` 回调函数和其调用的函数也应该放在 IRAM 中。