文章目录
- 一、PCNT
- 1、PCNT
- 2、核心能力
- 3、ESP32_S3_PCNT组成结构
- 二、PCNT原理
- 1、PCNT框图
- 2、硬件滤波模块 filter
- 3、多路选择 + 增减逻辑单元 inc_dec
- 4、加法器 adder 全局控制
- 5、阈值比较与中断输出模块
- 6、工程设计注意点
- 三、PCNT API
- 1、头文件/组件
- 头文件:
- 组件:
- 2、枚举类型
- 1. pcunt_count_mode_t 信号边沿计数动作
- 2. pcnt_ctrl_mode_t Ctrl 方向电平策略
- 3. pcnt_unit_id_t PCNT 单元编号
- 4. pcnt_event_type_t 中断事件类型
- 3、配置结构体
- 1. pcnt_unit_config_t 单元全局配置
- 2. pcnt_channel_config_t 单通道引脚与计数规则配置
- 3. 回调事件数据 pcnt_event_data_t
- 4. 回调结构体 pcnt_event_callbacks_t
- 4、创建 / 销毁资源API
- 5、运行控制类API
- 6、中断回调配置函数
- 7、动态阈值配置类
- 8、开发流程
- 四、示例代码
- 1、示例代码
- 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 信号边沿计数动作
typedefenum{PCNT_COUNT_DIS=0,// 该边沿不计数PCNT_COUNT_INC,// 触发边沿时计数值 +1PCNT_COUNT_DEC// 触发边沿时计数值 -1}pcnt_count_mode_t;2. pcnt_ctrl_mode_t Ctrl 方向电平策略
typedefenum{PCNT_CTRL_MODE_KEEP=0,// 保持sig边沿预设的增减规则PCNT_CTRL_MODE_REVERSE,// 反转sig边沿的增减方向PCNT_CTRL_MODE_INC,// Ctrl当前电平强制本次+1PCNT_CTRL_MODE_DEC// Ctrl当前电平强制本次-1}pcnt_ctrl_mode_t;3. pcnt_unit_id_t PCNT 单元编号
typedefenum{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 中断事件类型
typedefenum{PCNT_EVT_H_LIM,// 计数到达高上限阈值PCNT_EVT_L_LIM,// 计数到达低下限阈值PCNT_EVT_THRES_0,// 计数匹配阈值0PCNT_EVT_THRES_1,// 计数匹配阈值1PCNT_EVT_ZERO// 计数归零}pcnt_event_type_t;3、配置结构体
1. pcnt_unit_config_t 单元全局配置
typedefstruct{pcnt_unit_id_tunit_id;// 指定PCNT单元编号intlow_limit;// 计数下限(-32768~32767),触发L_LIM事件inthigh_limit;// 计数上限(-32768~32767),触发H_LIM事件}pcnt_unit_config_t;2. pcnt_channel_config_t 单通道引脚与计数规则配置
typedefstruct{intsig_gpio_num;// SIG脉冲输入GPIO编号intctrl_gpio_num;// CTRL方向控制GPIO,填-1表示不使用方向脚pcnt_count_mode_tpos_mode;// SIG上升沿计数动作pcnt_count_mode_tneg_mode;// SIG下降沿计数动作pcnt_ctrl_mode_thctrl_mode;// CTRL引脚高电平时的计数策略pcnt_ctrl_mode_tlctrl_mode;// CTRL引脚低电平时的计数策略uint32_tfilter_ns;// 硬件滤波时间(单位ns),0=关闭滤波,过滤窄毛刺}pcnt_channel_config_t;3. 回调事件数据 pcnt_event_data_t
typedefstruct{pcnt_event_type_ttype;// 触发的具体事件类型}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
esp_err_tpcnt_new_unit(constpcnt_unit_config_t*config,pcnt_unit_handle_t*ret_handle);//创建并初始化一个 PCNT 单元,返回句柄esp_err_tpcnt_del_unit(pcnt_unit_handle_tunit);//禁用单元、释放硬件资源esp_err_tpcnt_new_channel(pcnt_unit_handle_tunit,constpcnt_channel_config_t*config);//为指定 Unit 配置 CH0/CH1 通道参数5、运行控制类API
esp_err_tpcnt_unit_enable(pcnt_unit_handle_tunit);//使能单元,开始硬件计数esp_err_tpcnt_unit_disable(pcnt_unit_handle_tunit);//暂停单元计数esp_err_tpcnt_unit_clear_count(pcnt_unit_handle_tunit);//硬件清零当前计数值esp_err_tpcnt_unit_get_count(pcnt_unit_handle_tunit,int*count);//读取当前实时计数值6、中断回调配置函数
esp_err_tpcnt_unit_register_event_callbacks(pcnt_unit_handle_tunit,constpcnt_event_callbacks_t*cbs,void*user_data);//注册事件中断回调函数,可传入自定义用户参数esp_err_tpcnt_set_event_watchpoint(pcnt_unit_handle_tunit,pcnt_event_type_tevt_type,bool enable);//单独开启 / 关闭某一类事件的中断触发7、动态阈值配置类
esp_err_tpcnt_set_thres0(pcnt_unit_handle_tunit,intthres);//动态修改阈值 0 数值esp_err_tpcnt_set_thres1(pcnt_unit_handle_tunit,intthres);//动态修改阈值 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、示例代码
#include<stdio.h>#include"freertos/FreeRTOS.h"#include"freertos/task.h"#include"driver/pulse_cnt.h"#include"esp_log.h"staticconstchar*TAG="PCNT_HALL";#defineHALL_GPIO4#definePCNT_HIGH_LIMIT32767#definePCNT_LOW_LIMIT-32768pcnt_unit_handle_tpcnt_hall_unit=NULL;voidpcnt_hall_init(void){// PCNT单元配置pcnt_unit_config_tunit_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_tfilter_cfg={.max_glitch_ns=2000,};ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_hall_unit,&filter_cfg));// PCNT通道配置:edge引脚接霍尔信号,level不使用pcnt_chan_config_tchan_cfg={.edge_gpio_num=HALL_GPIO,.level_gpio_num=-1};pcnt_channel_handle_tpcnt_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));// 启用PCNTESP_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));}voidhall_read_task(void*arg){intpulse_val;staticintlast_pulse=0;while(1){ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_hall_unit,&pulse_val));intdelta=pulse_val-last_pulse;last_pulse=pulse_val;ESP_LOGI(TAG,"Hall Pulse: %d, Delta:%d",pulse_val,delta);vTaskDelay(pdMS_TO_TICKS(100));}}voidapp_main(void){pcnt_hall_init();xTaskCreate(hall_read_task,"hall_task",2048,NULL,5,NULL);}