esp32s3(espidf)使用脉冲计数(pcnt)计数ec11 单/双边沿计数 双通道计数

前言:pcnt是esp32的高速脉冲计数模式,用于计数编码器等波形,其可以通过硬件执行减少软件资源消耗。通常需要俩个io口来控制。

本次使用包含pcnt计数及其中断,简述了pcnt及其使用方法,介绍了自定义边沿和双通道计数(对标stm32的编码器模式)。

目录

1.认识pcnt

1.1简述

1.2特性

2.使用配置(重点)

2.1再来看编码器原理

2.2结构体配置:

(1)首先创建一个该结构体

(2)引出该成员

(3)配置成员

(4)例子:

(5)初始化

(6)开始计数

(7)中断设置

(9)效果

3.其他计数模式对标stm32 单/双边沿计数(扩展)

3.1单边沿(自定义边沿计数)

3.2双边沿

完整配置代码(直接修改可用)


1.认识pcnt

1.1简述

这里重点是 CH0 CH1 Unit0

uint代表计数单元 这里一共有四个单元(这里是esp32s3其他看手册)。

每个单元有俩个通道ch0 ch1都可以用来计数。

每个通道控制需要俩个gpio口,即input pulse引脚,control引脚。这里先不用管具体含义后面会叙述怎么用。

1.2特性

计数范围是16位,可以设置滤波(pcnt_set_filter_value())最大计数速率为40mhz

2.使用配置(重点)

先看这张图只看ch0 其他都不看

看这里

这就是前面ch0接的俩个引脚 pulse引脚和ctrl引脚(名字而已对于编码器来说不重要)

filter是滤波器也不管后面有api直接设置和开启

重点看inc_dec里面有四个寄存器来控制 知道就行后面用结构体会来详细说明

(1)pcnt_ch0_neg_mode_un :用来控制下降沿的计数模式 (接在pulse引脚上)

(2)pcnt_ch0_pos_mode_un:用来控制上升沿的计数模式(接在pulse引脚上)

(3)pcnt_ch0_hctrl_mode_un:用来控制高电平的计数模式是否反转或者不计数(接在ctrl引脚上)

(4)pcnt_ch0_lctrl_mode_un:用来控制低电平的计数模式是否反转或者不计数(接在ctrl引脚上)

我们用俩个引脚来控制计数的模式其中第一个为pulse引脚 esp32会在pulse引脚产生上升沿和下降沿时产生中断,通过检测ctrl引脚的高低电平来判断是增加计数还是减少计数。

2.1再来看编码器原理

我们定义正旋计数+1反向-1.

这里我们设置A相为pulse引脚只用于检测上升沿和下降沿。

B相为ctrl引脚只用于检测高低电平。

正旋计数时A相比B相超前90°,A相上升沿时B为低电平,A相下降沿时B为高电平。

简称:A上B低 A下B高 为正旋 (后面要用的)

反旋计数时B相比A相超前90°,A相上升沿时B为高电平,A相下降沿时B为低电平。

简称:A上B高 A下B低 为反旋(后面要用的)

这样就可以分出正反转了 下一步我们来配置。

2.2结构体配置:

主要通过该结构体来配置 看不懂不要紧跟我一起来配就行

#include <driver/pcnt.h>
#include "encoder.h"
#include "esp_err.h"
#include "soc/pcnt_reg.h"
#include "soc/soc.h"
#include "arduino.h"
typedef struct {
    int pulse_gpio_num;          //pulse引脚号
    int ctrl_gpio_num;           //ctrl引脚号
    pcnt_ctrl_mode_t lctrl_mode; //设置ctrl引脚为低电平时的计数方式
    pcnt_ctrl_mode_t hctrl_mode; //设置ctrl引脚为高电平时的计数方式
    pcnt_count_mode_t pos_mode;  //设置上升沿时的计数方式
    pcnt_count_mode_t neg_mode;  //设置为下降沿时的计数方式
    int16_t counter_h_lim;       //最大计数0-65535
    int16_t counter_l_lim;       //最小计数0-65535
    pcnt_unit_t unit;            //计数单元选择 uint0-uint4
    pcnt_channel_t channel;      //ch0还是ch1的通道
} pcnt_config_t;

(1)首先创建一个该结构体

pcnt_config_t pcnt_config_0;

(2)引出该成员

    pcnt_config_0.pulse_gpio_num =;  // 旋钮 A 相 (CLK)
    pcnt_config_0.ctrl_gpio_num = ;   // 旋钮 B 相 (DT)
    pcnt_config_0.channel = ;
    pcnt_config_0.unit = ;
    pcnt_config_0.pos_mode = ;  
    pcnt_config_0.neg_mode = ;  
    pcnt_config_0.lctrl_mode = ; 
    pcnt_config_0.hctrl_mode = ;     
    pcnt_config_0.counter_h_lim = 32767;  // 计数上限
    pcnt_config_0.counter_l_lim = -32767; // 计数下限

(3)配置成员

-------pulse_gpio_num:

pulse引脚对应A或者B相任意一相引脚这里我是A相。

-------ctrl_gpio_num:

ctrl引脚同上。

-------channel:

PCNT_CHANNEL_0对应同一个uint下的ch0通道(见1.1简述)

PCNT_CHANNEL_1对应同一个uint下的ch1通道

-------unit:

    PCNT_UNIT_0, 选择uint0-4(见1.1简述)

    PCNT_UNIT_1,

    PCNT_UNIT_2,

    PCNT_UNIT_3,

-------pos_mode:

上升沿计数方式这里我是A相

PCNT_COUNT_DIS   不计数

PCNT_COUNT_INC 上升计数即计数+1

PCNT_COUNT_DEC  下降计数即计数-1

-------neg_mode:

下降沿计数方式这里我是A相

同上

-------lctrl_mode:

低电平是否改变计数模式

PCNT_MODE_KEEP       不改变

PCNT_MODE_REVERSE 反转(上升变下降)

PCNT_MODE_DISABLE 不计数计数不加不减

-------hctrl_mode:

高电平是否改变计数模式

同上

(4)例子:

还是来看这张图

A上B低 A下B高 为正旋我们计数+1

A上B高 A下B低 为反旋我们计数-1

先看(A上B低 A下B高 为正旋我们计数+1):

这里我们要分辨正反计数就很明了了,pos_mode =PCNT_COUNT_INC 设置上升沿计数为增加计数计数+1遇到上升沿就会+1(设置为减少也行后面会说为什么先默认增加计数)

然后从左往右看 第一个A相上升时我们看到B为低电平说明我们上升沿计数上升不改变其计数的模式,所以lctrl_mode=PCNT_MODE_KEEP 让A保持其计数模式即计数+1

然后往右继续走,第一个A的下降沿时B为高电平 我们设置neg_mode = PCNT_COUNT_DEC下降沿计数-1,但是这显然不是我们要的 我们要正旋时一直+1这里显然不是我们要的。这里计数模式改成减少计数了,我们要让他增加就得切换他的计数模式即hctrl_mode=PCNT_MODE_REVERSE

让B引脚在高电平时切换计数模式从-1的计数模式->变成+1的计数模式。这样正旋我们一直计数+1了

再来看A上B高 A下B低 为反旋我们计数-1:

同理我们已经设置过了pos_mode 和neg_mode 分别为+1计数和-1计数也设置了hctrl_mode为反转计数模式使得遇到B为高电平时neg_mode 从-1计数反转为+1计数(低电平未设置)。

看反旋的图,A上升沿B为高电平要让计数-1,前面已经设置过了A为上升沿时计数+1的模式,且B为高电平时反转计数模式,这里就会刚好使得反转时A为上升沿时从-1变成+1的计数模式。很巧对吧。在A第一个下降沿时B为低电平,显然我们A下降沿时为-1的计数模式所以保持计数模式所以我们设置lctrl_mode = PCNT_MODE_KEEP使得保持-1计数。

为什么要一个+1一个-1:因为只在A上计数如果不设置+1-1就会无法判断方向,大家自行试着都设置+1试试这样会在反向时出现问题,交换上下边沿+1或者-1只是交换了极性而已。

我们总结一下

关于A引脚需要设置其上下沿的计数模式即+1还是-1或者不计数遇到上下沿就会改变编码器的计数值。(我们计数是通过A引脚的边沿来计数的)

关于B引脚是用来控制A引脚的计数方式是否需要反转A引脚的计数方式。即从+1变成-1或者反之再或者不变。(B只改变A的计数模式通过改变计数模式来判断正反旋向)

贴上配置代码

pcnt_config_t pcnt_config_0;
    pcnt_config_0.pulse_gpio_num = ENCODER_PIN_A;  // 旋钮 A 相 (CLK)
    pcnt_config_0.ctrl_gpio_num = ENCODER_PIN_B;   // 旋钮 B 相 (DT)
    pcnt_config_0.channel = PCNT_CHANNEL_0;
    pcnt_config_0.unit = PCNT_UNIT_0;
    pcnt_config_0.pos_mode = PCNT_COUNT_INC;  
    pcnt_config_0.neg_mode = PCNT_COUNT_DEC;  
    pcnt_config_0.lctrl_mode = PCNT_MODE_KEEP; 
    pcnt_config_0.hctrl_mode = PCNT_MODE_REVERSE;     
    pcnt_config_0.counter_h_lim = 32767;  // 计数上限
    pcnt_config_0.counter_l_lim = -32767; // 计数下限

(5)初始化

 pcnt_unit_config(&pcnt_config_0);  // 初始化 PCNT
 pcnt_set_filter_value(PCNT_UNIT_0, 1000);  // 设置滤波时间大小0-1023 这里不多说需要的看手册
 pcnt_filter_enable(PCNT_UNIT_0);  // 启用滤波

(6)开始计数

清除计数器并启动
    pcnt_counter_pause(PCNT_UNIT_0);
    pcnt_counter_clear(PCNT_UNIT_0);
    pcnt_counter_resume(PCNT_UNIT_0);

5-6自己看一下就好注意一定要有 pcnt_counter_clear(PCNT_UNIT_0);不然计数的

(7)中断设置

    pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_THRES_1);
    pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_THRES_0);//开始事件中断
    pcnt_set_event_value(PCNT_UNIT_0, PCNT_EVT_THRES_0, 100);//设置事件中断的阈值
    pcnt_set_event_value(PCNT_UNIT_0, PCNT_EVT_THRES_1, -100);
    pcnt_isr_service_install(0);//设置优先级
    pcnt_isr_handler_add(PCNT_UNIT_0, encoder_callback, NULL);//设置中断回调函数encoder_callback

 中断类型有这么几种事件

    PCNT_EVT_THRES_1 计数达到设定值1时中断 通过pcnt_set_event_value设置

    PCNT_EVT_THRES_0 计数达到设定值2时中断通过pcnt_set_event_value设置

    PCNT_EVT_L_LIM = 1 计数达到最大设定值中断(结构体设置里面的最大计数)

    PCNT_EVT_H_LIM = 1计数达到最小设定值中断

    PCNT_EVT_ZERO = 1 计数值达到0时中断

(8)中断回调处理

void encoder_callback(void* arg) {
    uint32_t status;
    pcnt_get_counter_value(PCNT_UNIT_0,&encoder_value);
    pcnt_get_event_status(PCNT_UNIT_0,&status);
    Serial.printf("Encoder Value: %d, Status: 0x%x\n", encoder_value, status);
    if(status&PCNT_EVT_THRES_0){
        msg_count=1;

        pcnt_counter_clear(PCNT_UNIT_0);  // 手动清除计数器值

        WRITE_PERI_REG(PCNT_INT_CLR_REG, PCNT_CNT_THR_EVENT_U0_INT_CLR);
    }else if (status&PCNT_EVT_THRES_1)
    {

        pcnt_counter_clear(PCNT_UNIT_0);  // 手动清除计数器值
        msg_count=2;
        WRITE_PERI_REG(PCNT_INT_CLR_REG, PCNT_CNT_THR_EVENT_U0_INT_CLR);
    }
    
}

 pcnt_get_event_status()获取(7)中的中断事件来判断是什么中断

这里我偷懒了一下用arduino的固件来做,方便串口打印(懒得写串口了),中断我们要判断是哪个unit的中的那个中断。用pcnt_get_event_status()获取中断标志位的值通过&的方式来判断是哪一个中断产生了并清除中断标志位。我这里通过直接写入寄存器清除(因为arduino固件的pcnt里面没有api来清除我直接操作寄存器了WRITE_PERI_REG(PCNT_INT_CLR_REG, PCNT_CNT_THR_EVENT_U0_INT_CLR);看下俩张图就知道怎么清除了还是很简单的)当然也可以用指针操作 知道地址直接置1就行了很简单!idf用户有结构体可以访问清除不多说。

(8)主函数

void setup() {
    Serial.begin(9600);
    encoder_init();
}

void loop() {
    if(msg_count==1){
        Serial.printf("msg_count=1,encoer_value=%d\n",encoder_value);
        msg_count=0;
    }else if (msg_count==2)
    {
        Serial.printf("msg_count=2,encoer_value=%d\n",encoder_value);
        msg_count=0;
    } 
    pcnt_get_counter_value(PCNT_UNIT_0,&encoder_value);/获取计数值
    Serial.printf("encoder_value:%d\n",encoder_value);
    delay(1000);
}

(9)效果

达到100或者-100时产生中断

3.其他计数模式对标stm32 单/双边沿计数(扩展)

我们知道stm32的编码器模式可以双边沿计数单边沿计数

esp32如何实现单/双边沿呢?

3.1单边沿(自定义边沿计数)

很简单我们设置A相计数的时候设置上或者下边沿不计数就好了.

3.2双边沿

uint作为一个计数单元会计数来自ch1和ch2的俩个通道的计数。但是通过前面我们知道计数只能是A作为pulse引脚来计数或者B作为pulse引脚来计数另一个作为正反转控制引脚。

所以我们只需要将A和B引脚互换原来接在ch0上的A作为pulse引脚计数改为B作为pulse引脚计数并接到ch1上面这样同样俩个引脚A在ch0上计数B在ch1上计数即可。看具体代码就知道了

pcnt_config_t pcnt_config_0;
    pcnt_config_0.pulse_gpio_num = ENCODER_PIN_A;  // 旋钮 A 相 (CLK)
    pcnt_config_0.ctrl_gpio_num = ENCODER_PIN_B;   // 旋钮 B 相 (DT)
    pcnt_config_0.channel = PCNT_CHANNEL_0;
    pcnt_config_0.unit = PCNT_UNIT_0;
    pcnt_config_0.pos_mode = PCNT_COUNT_INC;  
    pcnt_config_0.neg_mode = PCNT_COUNT_DEC;  
    pcnt_config_0.lctrl_mode = PCNT_MODE_KEEP; 
    pcnt_config_0.hctrl_mode = PCNT_MODE_REVERSE;     
    pcnt_config_0.counter_h_lim = 32767;  // 计数上限
    pcnt_config_0.counter_l_lim = -32767; // 计数下限

    pcnt_config_t pcnt_config_1;
    pcnt_config_1.pulse_gpio_num = ENCODER_PIN_B;  // 旋钮 A 相 (CLK)
    pcnt_config_1.ctrl_gpio_num = ENCODER_PIN_A;   // 旋钮 B 相 (DT)
    pcnt_config_1.channel = PCNT_CHANNEL_1;
    pcnt_config_1.unit = PCNT_UNIT_0;
    pcnt_config_1.pos_mode = PCNT_COUNT_INC;  
    pcnt_config_1.neg_mode = PCNT_COUNT_DEC;  
    pcnt_config_1.lctrl_mode =PCNT_MODE_REVERSE;  
    pcnt_config_1.hctrl_mode = PCNT_MODE_KEEP;     
    pcnt_config_1.counter_h_lim = 32767;  // 计数上限
    pcnt_config_1.counter_l_lim = -32767; // 计数下限

    pcnt_unit_config(&pcnt_config_0);  // 初始化 PCNT
    pcnt_unit_config(&pcnt_config_1);  // 初始化 PCNT

我们将AB引脚互换重新配置了一个结构图 pcnt_config_1 因为

A上B低 A下B高 为正旋我们计数+1

A上B高 A下B低 为反旋我们计数-1

对于B来说是相反的即

B上A低 B下A高 为正旋我们计数-1

B上A高 B下A低 为反旋我们计数+1

所以我们需要改变ctrl引脚在pcnt_config_1 交换(跟pcnt_config_0是反过来的)

    pcnt_config_1.lctrl_mode =PCNT_MODE_REVERSE;  
    pcnt_config_1.hctrl_mode = PCNT_MODE_KEEP; 

配置即可

(别问为什么不改pcnt_config_1.pos_mode = PCNT_COUNT_INC;  pcnt_config_1.neg_mode = PCNT_COUNT_DEC;你可以自己照着2中的分析一下就明白了)

完整配置代码(直接修改可用)

#include <driver/pcnt.h>
#include "encoder.h"
#include "esp_err.h"
#include "soc/pcnt_reg.h"
#include "soc/soc.h"
#include "arduino.h"
int16_t encoder_value = 0;
int msg_count = 0;
#define ENCODER_PIN_A 4
#define ENCODER_PIN_B 5


#define PCNT_CNT_THR_EVENT_0_INT_CLR (*((volatile uint32_t*)0x3FF40000))
void encoder_init() {
    pcnt_config_t pcnt_config_0;
    pcnt_config_0.pulse_gpio_num = ENCODER_PIN_A;  // 旋钮 A 相 (CLK)
    pcnt_config_0.ctrl_gpio_num = ENCODER_PIN_B;   // 旋钮 B 相 (DT)
    pcnt_config_0.channel = PCNT_CHANNEL_0;
    pcnt_config_0.unit = PCNT_UNIT_0;
    pcnt_config_0.pos_mode = PCNT_COUNT_INC;  
    pcnt_config_0.neg_mode = PCNT_COUNT_DEC;  
    pcnt_config_0.lctrl_mode = PCNT_MODE_KEEP; 
    pcnt_config_0.hctrl_mode = PCNT_MODE_REVERSE;     
    pcnt_config_0.counter_h_lim = 32767;  // 计数上限
    pcnt_config_0.counter_l_lim = -32767; // 计数下限


    //双通道计数时添加下列
    pcnt_config_t pcnt_config_1;
    pcnt_config_1.pulse_gpio_num = ENCODER_PIN_B;  // 旋钮 A 相 (CLK)
    pcnt_config_1.ctrl_gpio_num = ENCODER_PIN_A;   // 旋钮 B 相 (DT)
    pcnt_config_1.channel = PCNT_CHANNEL_1;
    pcnt_config_1.unit = PCNT_UNIT_0;
    pcnt_config_1.pos_mode = PCNT_COUNT_INC;  
    pcnt_config_1.neg_mode = PCNT_COUNT_DEC;  
    pcnt_config_1.lctrl_mode =PCNT_MODE_REVERSE;  
    pcnt_config_1.hctrl_mode = PCNT_MODE_KEEP;     
    pcnt_config_1.counter_h_lim = 32767;  // 计数上限
    pcnt_config_1.counter_l_lim = -32767; // 计数下限
    pcnt_unit_config(&pcnt_config_1);  // 初始化 PCNT

    pcnt_unit_config(&pcnt_config_0);  // 初始化 PCNT
    
    pcnt_set_filter_value(PCNT_UNIT_0, 1000);  // 设置滤波窗口大小
    pcnt_filter_enable(PCNT_UNIT_0);  // 启用滤波
    // 2. 清除计数器并启动
    pcnt_counter_pause(PCNT_UNIT_0);
    pcnt_counter_clear(PCNT_UNIT_0);
    pcnt_counter_resume(PCNT_UNIT_0);
    

    // 3. 注册中断
    pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_THRES_1);
    pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_THRES_0);
    pcnt_set_event_value(PCNT_UNIT_0, PCNT_EVT_THRES_0, 100);
    pcnt_set_event_value(PCNT_UNIT_0, PCNT_EVT_THRES_1, -100);
    pcnt_isr_service_install(0);
    pcnt_isr_handler_add(PCNT_UNIT_0, encoder_callback, NULL);

}

    

void encoder_callback(void* arg) {
    uint32_t status;
    pcnt_get_counter_value(PCNT_UNIT_0,&encoder_value);
    pcnt_get_event_status(PCNT_UNIT_0,&status);
    Serial.printf("Encoder Value: %d, Status: 0x%x\n", encoder_value, status);
    if(status&PCNT_EVT_THRES_0){
        msg_count=1;

        pcnt_counter_clear(PCNT_UNIT_0);  // 手动清除计数器值

        WRITE_PERI_REG(PCNT_INT_CLR_REG, PCNT_CNT_THR_EVENT_U0_INT_CLR);
    }else if (status&PCNT_EVT_THRES_1)
    {

        pcnt_counter_clear(PCNT_UNIT_0);  // 手动清除计数器值
        msg_count=2;
        WRITE_PERI_REG(PCNT_INT_CLR_REG, PCNT_CNT_THR_EVENT_U0_INT_CLR);
    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值