文章目录
- 一、PCNT
-
- [ 1、PCNT](# 1、PCNT)
- [ 2、核心能力](# 2、核心能力)
- [ 3、ESP32_S3_PCNT组成结构](# 3、ESP32_S3_PCNT组成结构)
- 二、PCNT原理
-
- [ 1、PCNT框图](# 1、PCNT框图)
- [ 2、硬件滤波模块 filter](# 2、硬件滤波模块 filter)
- [ 3、多路选择 + 增减逻辑单元 inc_dec](# 3、多路选择 + 增减逻辑单元 inc_dec)
- [ 4、加法器 adder 全局控制](# 4、加法器 adder 全局控制)
- [ 5、阈值比较与中断输出模块](# 5、阈值比较与中断输出模块)
- [ 6、工程设计注意点](# 6、工程设计注意点)
- [三、PCNT API](#三、PCNT API)
-
- [ 1、头文件/组件](# 1、头文件/组件)
-
- [ 头文件:](# 头文件:)
- [ 组件:](# 组件:)
- [ 2、枚举类型](# 2、枚举类型)
-
- [ 1. pcunt_count_mode_t 信号边沿计数动作](# 1. pcunt_count_mode_t 信号边沿计数动作)
- [ 2. pcnt_ctrl_mode_t Ctrl 方向电平策略](# 2. pcnt_ctrl_mode_t Ctrl 方向电平策略)
- [ 3. pcnt_unit_id_t PCNT 单元编号](# 3. pcnt_unit_id_t PCNT 单元编号)
- [ 4. pcnt_event_type_t 中断事件类型](# 4. pcnt_event_type_t 中断事件类型)
- [ 3、配置结构体](# 3、配置结构体)
-
- [ 1. pcnt_unit_config_t 单元全局配置](# 1. pcnt_unit_config_t 单元全局配置)
- [ 2. pcnt_channel_config_t 单通道引脚与计数规则配置](# 2. pcnt_channel_config_t 单通道引脚与计数规则配置)
- [ 3. 回调事件数据 pcnt_event_data_t](# 3. 回调事件数据 pcnt_event_data_t)
- [ 4. 回调结构体 pcnt_event_callbacks_t](# 4. 回调结构体 pcnt_event_callbacks_t)
- [ 4、创建 / 销毁资源API](# 4、创建 / 销毁资源API)
- [ 5、运行控制类API](# 5、运行控制类API)
- [ 6、中断回调配置函数](# 6、中断回调配置函数)
- [ 7、动态阈值配置类](# 7、动态阈值配置类)
- [ 8、开发流程](# 8、开发流程)
- 四、示例代码
-
- [ 1、示例代码](# 1、示例代码)
- [ 2、执行结果](# 2、执行结果)
一、PCNT
1、PCNT
PCNT(Pulse Counter,脉冲计数控制器)是ESP32-S3内置的专用硬件脉冲计数外设,专为高频脉冲采集、正交编码器测速、脉冲信号统计设计。
ESP32-S3搭载4个独立PCNT单元,每个单元包含2路脉冲输入通道,支持正交解码、单边计数、双向增减计数,可同时采集多路脉冲设备信号。
2、核心能力
对外部 GPIO 脉冲上升 / 下降沿做硬件计数,不需要 CPU 干预。
支持方向控制信号,自动加 / 减计数。
设置阈值,到达阈值产生中断。
计数可过滤输入噪声(输入滤波)。
16 位计数器,范围:‑32768 ~ +32767。
3、ESP32_S3_PCNT组成结构
PCNT 有四个称为"单元"的独立脉冲计数控制器,这些单元拥有自己的寄存器。
PCNT 模块仅有一个时钟,为 APB_CLK。
每个单元有两个通道(ch0 和 ch1),可以独立配置为递增或递减计数。
每个通道有两个输入信号:
一个脉冲输入信号(如 sig_ch0_un 为单元 n ch0 的脉冲输入信号。
一个控制信号(如 ctrl_ch0_un 为单元 n ch0 的控制信号。

二、PCNT原理
1、PCNT框图

2、硬件滤波模块 filter
输入:sig脉冲信号、ctrl方向控制信号分别独立滤波。
配置寄存器:PCNT_FILTER_EN_Un(滤波使能)、PCNT_FILTER_THRES_Un(滤波阈值)。
作用:宽度小于阈值的高频毛刺直接过滤,硬件消抖,无需软件防抖,适配机械编码器抖动场景。
3、多路选择 + 增减逻辑单元 inc_dec
配置寄存器分组:
边沿动作配置:
PCNT_CHx_POS_MODE_Un(sig 上升沿动作:加 / 减 / 不计数)。
PCNT_CHx_NEG_MODE_Un(sig 下降沿动作:加 / 减 / 不计数)。
方向控制配置:
PCNT_CHx_HCTRL_MODE_Un(ctrl 高电平时计数策略)。
PCNT_CHx_LCTRL_MODE_Un(ctrl 低电平时计数策略)。
工作逻辑:
sig提供脉冲边沿触发时机,ctrl电平决定本次计数是+1、-1、忽略。
正交编码器模式下,A 相接 sig、B 相接 ctrl,可天然实现正反转自动增减计数。
4、加法器 adder 全局控制
输入来源:
CH0、CH1 两路inc_dec的计数增量共同送入加法器累加。
全局控制信号:
PCNT_CNT_PAUSE_Un:暂停计数冻结当前值。
PCNT_PULSE_CNT_RST_Un:硬件清零计数器(CLR 端口)。
关键优势:
双通道同时接入编码器 AB 相可实现四倍频正交计数,单通道仅能二倍频。
5、阈值比较与中断输出模块
加法器输出的pulse_cnt同时送入 5 组比较器:

6、工程设计注意点
1.滤波优先开启:
机械编码器务必配置filter阈值,从硬件层面抑制触点抖动;
2.计数模式选型:
普通单端脉冲计数:仅用 CH0,ctrl引脚悬空配置为无效;
正交编码器测速:CH0+CH1 双通道接入 AB 相,实现四倍频提升测量精度;
3.溢出防护:
PCNT 原生 16 位计数范围-32768~+32767,利用高低限中断做软件累计值扩展,突破位数限制;
4.低 CPU 占用:
全链路计数、滤波、比较均为硬件逻辑,CPU 仅需定时读取数值或响应阈值中断,不占用运算资源。
三、PCNT API
1、头文件/组件
头文件:
#include "driver/pulse_cnt.h"
组件:
REQUIRES esp_driver_pcnt
2、枚举类型
1. pcunt_count_mode_t 信号边沿计数动作
c
typedef enum {
PCNT_COUNT_DIS = 0, // 该边沿不计数
PCNT_COUNT_INC, // 触发边沿时计数值 +1
PCNT_COUNT_DEC // 触发边沿时计数值 -1
} pcnt_count_mode_t;
2. pcnt_ctrl_mode_t Ctrl 方向电平策略
c
typedef enum {
PCNT_CTRL_MODE_KEEP = 0, // 保持sig边沿预设的增减规则
PCNT_CTRL_MODE_REVERSE, // 反转sig边沿的增减方向
PCNT_CTRL_MODE_INC, // Ctrl当前电平强制本次+1
PCNT_CTRL_MODE_DEC // Ctrl当前电平强制本次-1
} pcnt_ctrl_mode_t;
3. pcnt_unit_id_t PCNT 单元编号
c
typedef enum {
PCNT_UNIT_0, PCNT_UNIT_1, PCNT_UNIT_2, PCNT_UNIT_3,
PCNT_UNIT_4, PCNT_UNIT_5, PCNT_UNIT_6, PCNT_UNIT_7
} pcnt_unit_id_t;
4. pcnt_event_type_t 中断事件类型
c
typedef enum {
PCNT_EVT_H_LIM, // 计数到达高上限阈值
PCNT_EVT_L_LIM, // 计数到达低下限阈值
PCNT_EVT_THRES_0, // 计数匹配阈值0
PCNT_EVT_THRES_1, // 计数匹配阈值1
PCNT_EVT_ZERO // 计数归零
} pcnt_event_type_t;
3、配置结构体
1. pcnt_unit_config_t 单元全局配置
c
typedef struct {
pcnt_unit_id_t unit_id; // 指定PCNT单元编号
int low_limit; // 计数下限(-32768~32767),触发L_LIM事件
int high_limit; // 计数上限(-32768~32767),触发H_LIM事件
} pcnt_unit_config_t;
2. pcnt_channel_config_t 单通道引脚与计数规则配置
c
typedef struct {
int sig_gpio_num; // SIG脉冲输入GPIO编号
int ctrl_gpio_num; // CTRL方向控制GPIO,填-1表示不使用方向脚
pcnt_count_mode_t pos_mode; // SIG上升沿计数动作
pcnt_count_mode_t neg_mode; // SIG下降沿计数动作
pcnt_ctrl_mode_t hctrl_mode; // CTRL引脚高电平时的计数策略
pcnt_ctrl_mode_t lctrl_mode; // CTRL引脚低电平时的计数策略
uint32_t filter_ns; // 硬件滤波时间(单位ns),0=关闭滤波,过滤窄毛刺
} pcnt_channel_config_t;
3. 回调事件数据 pcnt_event_data_t
c
typedef struct {
pcnt_event_type_t type; // 触发的具体事件类型
} pcnt_event_data_t;
4. 回调结构体 pcnt_event_callbacks_t
typedef struct {
// 阈值/上下限/零值事件回调,中断上下文执行
bool (*on_reach)(pcnt_unit_handle_t unit, const pcnt_event_data_t *event_data, void *user_data);
} pcnt_event_callbacks_t;
4、创建 / 销毁资源API
c
esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_handle);
//创建并初始化一个 PCNT 单元,返回句柄
esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit);
//禁用单元、释放硬件资源
esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_channel_config_t *config);
//为指定 Unit 配置 CH0/CH1 通道参数
5、运行控制类API
c
esp_err_t pcnt_unit_enable(pcnt_unit_handle_t unit);
//使能单元,开始硬件计数
esp_err_t pcnt_unit_disable(pcnt_unit_handle_t unit);
//暂停单元计数
esp_err_t pcnt_unit_clear_count(pcnt_unit_handle_t unit);
//硬件清零当前计数值
esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *count);
//读取当前实时计数值
6、中断回调配置函数
c
esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt_event_callbacks_t *cbs, void *user_data);
//注册事件中断回调函数,可传入自定义用户参数
esp_err_t pcnt_set_event_watchpoint(pcnt_unit_handle_t unit, pcnt_event_type_t evt_type, bool enable);
//单独开启 / 关闭某一类事件的中断触发
7、动态阈值配置类
c
esp_err_t pcnt_set_thres0(pcnt_unit_handle_t unit, int thres);
//动态修改阈值 0 数值
esp_err_t pcnt_set_thres1(pcnt_unit_handle_t unit, int thres);
//动态修改阈值 1 数值
8、开发流程
1.定义并填充 pcnt_unit_config_t,调用 pcnt_new_unit 创建单元句柄。
2.填充 pcnt_channel_config_t,调用 pcnt_new_channel 配置 CH0/CH1。
3.可选:pcnt_unit_register_event_callbacks 注册阈值中断回调。
4.可选:pcnt_set_thres0/thres1 设置自定义触发阈值。
5.pcnt_unit_enable 启动硬件计数。
6.业务循环:pcnt_unit_get_count 读取数值做运算;溢出中断内做软件累计扩展。
7.任务退出:pcnt_unit_disable → pcnt_del_unit 释放资源。
四、示例代码
1、示例代码
c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/pulse_cnt.h"
#include "esp_log.h"
static const char *TAG = "PCNT_HALL";
#define HALL_GPIO 4
#define PCNT_HIGH_LIMIT 32767
#define PCNT_LOW_LIMIT -32768
pcnt_unit_handle_t pcnt_hall_unit = NULL;
void pcnt_hall_init(void)
{
// PCNT单元配置
pcnt_unit_config_t unit_cfg = {
.high_limit = PCNT_HIGH_LIMIT,
.low_limit = PCNT_LOW_LIMIT
};
ESP_ERROR_CHECK(pcnt_new_unit(&unit_cfg, &pcnt_hall_unit));
// 毛刺滤波,霍尔容易有抖动,设置滤波
pcnt_glitch_filter_config_t filter_cfg = {
.max_glitch_ns = 2000,
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_hall_unit, &filter_cfg));
// PCNT通道配置:edge引脚接霍尔信号,level不使用
pcnt_chan_config_t chan_cfg = {
.edge_gpio_num = HALL_GPIO,
.level_gpio_num = -1
};
pcnt_channel_handle_t pcnt_chan = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_hall_unit, &chan_cfg, &pcnt_chan));
// 动作配置:上升沿计数+1,下降沿无动作
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan,
PCNT_CHANNEL_EDGE_ACTION_INCREASE, // 上升沿
PCNT_CHANNEL_EDGE_ACTION_HOLD // 下降沿
));
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan,
PCNT_CHANNEL_LEVEL_ACTION_KEEP,
PCNT_CHANNEL_LEVEL_ACTION_KEEP
));
// 启用PCNT
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_hall_unit));
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_hall_unit));
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_hall_unit));
}
void hall_read_task(void *arg)
{
int pulse_val;
static int last_pulse = 0;
while (1)
{
ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_hall_unit, &pulse_val));
int delta = pulse_val - last_pulse;
last_pulse = pulse_val;
ESP_LOGI(TAG, "Hall Pulse: %d, Delta:%d", pulse_val, delta);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void)
{
pcnt_hall_init();
xTaskCreate(hall_read_task, "hall_task", 2048, NULL, 5, NULL);
}
2、执行结果
