ESP32S3学习——Pulse Counter (PCNT)脉冲计数
不就输入捕获?
芯片:esp32s3
开发环境:espidfv4.4
一、官网相关资料(又是英文T_T)
1)介绍
PCNT(脉冲计数器)模块设计用于计数输入信号的上升沿和/或下降沿的数量。每个脉冲计数器单元都有一个16位带符号计数器寄存器和两个通道,可以配置为递增或递减计数器。每个通道都有一个接受待检测信号边缘的信号输入,以及一个可用于启用或禁用信号输入的控制输入。输入具有可选滤波器,可用于丢弃信号中不需要的毛刺。
关键词:计数上升下降沿,有两个16为带输出的计数器,可递增递减;滤波器
2)功能概述
API可以分为四个部分
-
配置-描述计数器的配置参数以及如何设置计数器。
-
操作计数器-提供有关暂停、测量和清除计数器的控制功能的信息。
-
滤波脉冲-描述了滤波脉冲和计数器控制信号的选项。
-
使用中断-介绍如何在计数器的特定状态下触发中断。
3)配置流程
PCNT模块有8个独立的计数“单元”,编号从0到7。在API中,使用pcnt_unit_t来引用它们。每个单元有两个独立的通道,编号为0和1,并用pcnt_channel_t指定。共16个通道
使用pcnt_config_t为每个单元的通道单独提供配置,包括:
-
此配置所指的单元和通道编号。
-
脉冲输入和脉冲门输入的GPIO数。
-
两对参数:pcnt_ctrl_mode_t和pcnt_count_mode_t,用于定义计数器如何根据控制信号的状态做出反应,以及如何在脉冲的正/负边缘进行计数。
-
两个极限值(最小值/最大值),用于在脉冲计数达到特定极限时建立观察点并触发中断。
然后,通过调用函数pcnt_unit_config(),以上面的pcnt_config_t作为输入参数,来完成特定通道的设置。要禁用配置中的脉冲或控制输入引脚,使用PCNT_pin_NOT_USED而不是GPIO编号。
4)操作计数器
-
使用pcnt_unit_config()进行设置后,计数器立即开始工作。可以通过调用pcnt_get_counter_value()来检查累积脉冲计数。
-
有几个函数可以控制计数器的操作:pcnt_counter_pause()、pcnt_coounter_resume()和pcnt_cocounter_clear()
-
还可以通过调用pcnt_set_mode(),使用pcnt_unit_config()动态更改先前设置的计数器模式。
-
如果需要,可以使用pcnt_set_pin()“动态”更改脉冲输入引脚和控制输入引脚。要禁用特定输入,请提供PCNT_PIN_NOT_USED作为函数参数,而不是GPIO编号。
-
为了使计数器不错过任何脉冲,脉冲持续时间应长于一个APB_CLK周期(12.5ns)。脉冲是在APB_CLK时钟的边缘上采样的,如果落在边缘之间,则可能会被错过。这适用于使用或不使用文件管理器的计数器操作。
5)过滤脉冲
-
PCNT单元在每个脉冲和控制输入上都有滤波器,增加了忽略信号中短故障的选项。 通过调用pcnt_set_filter_value(),在APB_CLK时钟周期中提供被忽略脉冲的长度。可以使用pcnt_get_filter_value()检查当前过滤器设置。APB_CLK时钟运行频率为80 MHz。 通过调用pcnt_filter_enable()/pcnt_filter_disable(),过滤器进入运行/挂起状态。
6) 使用中断
pcnt_evt_type_t中定义了五个计数器状态监视事件,它们能够触发中断。该事件发生在脉冲计数器达到特定值时:
-
最小或最大计数值:如配置中所述,在pcnt_config_t中提供counter_l_lim或counter_h_lim
-
使用函数pcnt_set_event_value()设置阈值0或阈值1值。
-
脉冲计数=0
要注册、启用或禁用中断以服务上述事件,请调用pcnt_isr_register()、pcnt_intr_enable()。和pcnt_intr_disable()。要在达到阈值时启用或禁用事件,还需要调用函数pcnt_event_enable()和pcnt_eevent_disable()。为了检查当前设置的阈值,请使用函数pcnt_get_event_value()。
7)示例代码
带有控制信号和事件中断的脉冲计数器示例:peripherals/pcnt/pulse_count_event.
解析旋转编码器产生的信号: peripherals/pcnt/rotary_encoder.
三、上手敲代码(官方加注释)
-
带有控制信号和事件中断的脉冲计数器示例:peripherals/pcnt/pulse_count_event.
-
头文件:#include "driver/pcnt.h"
-
readme: GPIO4(ledc)产生1hz的脉冲,GPIO1 8作为输入脚进行计数;GPIO5为控制脚,悬空(内部上拉)-计数增加;接地-计数减小
中断
* - reaches 'thresh1' or 'thresh0' value,
* - reaches 'l_lim' value or 'h_lim' value,
* - will be reset to zero.
ESP32S3学习——Pulse Counter (PCNT)脉冲计数
不就输入捕获?
芯片:esp32s3
开发环境:espidfv4.4
## 一、官网相关资料(又是英文T_T)
### 1)介绍
PCNT(脉冲计数器)模块设计用于**计数输入信号的上升沿和/或下降沿**的数量。每个脉冲计数器单元都有一个1**6位带符号计数器寄存器**和**两个通道**,可以配置为**递增或递减计数器**。每个通道都有一个**接受待检测信号边缘的信号输入**,以及一个可用于启用或禁用信号输入的控制输入。输入具有**可选滤波器**,可用于丢弃信号中不需要的毛刺。
关键词:计数上升下降沿,有两个16为带输出的计数器,可递增递减;滤波器
### 2)功能概述
#### API可以分为四个部分
- 配置-描述计数器的配置参数以及如何设置计数器。
- 操作计数器-提供有关暂停、测量和清除计数器的控制功能的信息。
- 滤波脉冲-描述了滤波脉冲和计数器控制信号的选项。
- 使用中断-介绍如何在计数器的特定状态下触发中断。
## 3)配置流程
#### PCNT模块有8个独立的计数“单元”,编号从0到7。在API中,使用pcnt_unit_t来引用它们。每个单元有两个独立的通道,编号为0和1,并用pcnt_channel_t指定。共16个通道
#### 使用pcnt_config_t为每个单元的通道单独提供配置,包括:
- 此配置所指的单元和通道编号。
- 脉冲输入和脉冲门输入的GPIO数。
- 两对参数:pcnt_ctrl_mode_t和pcnt_count_mode_t,用于定义计数器如何根据控制信号的状态做出反应,以及如何在脉冲的正/负边缘进行计数。
- 两个极限值(最小值/最大值),用于在脉冲计数达到特定极限时建立观察点并触发中断。
#### 然后,通过调用函数pcnt_unit_config(),以上面的pcnt_config_t作为输入参数,来完成特定通道的设置。要禁用配置中的脉冲或控制输入引脚,使用PCNT_pin_NOT_USED而不是GPIO编号。
## 4)操作计数器
- 使用pcnt_unit_config()进行设置后,计数器立即开始工作。可以通过调用pcnt_get_counter_value()来检查累积脉冲计数。
- 有几个函数可以控制计数器的操作:pcnt_counter_pause()、pcnt_coounter_resume()和pcnt_cocounter_clear()
- 还可以通过调用pcnt_set_mode(),使用pcnt_unit_config()动态更改先前设置的计数器模式。
- 如果需要,可以使用pcnt_set_pin()“动态”更改脉冲输入引脚和控制输入引脚。要禁用特定输入,请提供PCNT_PIN_NOT_USED作为函数参数,而不是GPIO编号。
- 为了使计数器不错过任何脉冲,脉冲持续时间应长于一个APB_CLK周期(12.5ns)。脉冲是在APB_CLK时钟的边缘上采样的,如果落在边缘之间,则可能会被错过。这适用于使用或不使用文件管理器的计数器操作。
## 5)过滤脉冲
- PCNT单元在每个脉冲和控制输入上都有滤波器,增加了忽略信号中短故障的选项。
通过调用pcnt_set_filter_value(),在APB_CLK时钟周期中提供被忽略脉冲的长度。可以使用pcnt_get_filter_value()检查当前过滤器设置。APB_CLK时钟运行频率为80 MHz。
通过调用pcnt_filter_enable()/pcnt_filter_disable(),过滤器进入运行/挂起状态。
## 6) 使用中断
#### pcnt_evt_type_t中定义了五个计数器状态监视事件,它们能够触发中断。该事件发生在脉冲计数器达到特定值时:
- 最小或最大计数值:如配置中所述,在pcnt_config_t中提供counter_l_lim或counter_h_lim
- 使用函数pcnt_set_event_value()设置阈值0或阈值1值。
- 脉冲计数=0
#### 要注册、启用或禁用中断以服务上述事件,请调用pcnt_isr_register()、pcnt_intr_enable()。和pcnt_intr_disable()。要在达到阈值时启用或禁用事件,还需要调用函数pcnt_event_enable()和pcnt_eevent_disable()。为了检查当前设置的阈值,请使用函数pcnt_get_event_value()。
## 7)示例代码
带有控制信号和事件中断的脉冲计数器示例:[peripherals/pcnt/pulse_count_event](https://github.com/espressif/esp-idf/tree/v4.4.2/examples/peripherals/pcnt/pulse_count_event).
解析旋转编码器产生的信号: [peripherals/pcnt/rotary_encoder](https://github.com/espressif/esp-idf/tree/v4.4.2/examples/peripherals/pcnt/rotary_encoder).
## 三、上手敲代码(官方加注释)
1) 带有控制信号和事件中断的脉冲计数器示例:[peripherals/pcnt/pulse_count_event](https://github.com/espressif/esp-idf/tree/v4.4.2/examples/peripherals/pcnt/pulse_count_event).
2) 头文件:\#include "driver/pcnt.h"
3) readme: GPIO4(ledc)产生1hz的脉冲,GPIO1 8作为输入脚进行计数;GPIO5为控制脚,悬空(内部上拉)-计数增加;接地-计数减小
中断
\* - reaches 'thresh1' or 'thresh0' value,
\* - reaches 'l_lim' value or 'h_lim' value,
\* - will be reset to zero.
`#include "freertos/FreeRTOS.h"`
`#include "freertos/task.h"`
`#include "freertos/queue.h"`
`#include "driver/ledc.h"`
`#include "driver/pcnt.h"`
`#include "esp_attr.h"`
`#include "esp_log.h"`
`static const char *TAG = "example";`
`/**`
* `TEST CODE BRIEF`
`*`
* `Use PCNT module to count rising edges generated by LEDC module.`
`*`
* `Functionality of GPIOs used in this example:`
* - `GPIO18 - output pin of a sample 1 Hz pulse generator,`
* - `GPIO4 - pulse input pin,`
* - `GPIO5 - control input pin.`
`*`
* `Load example, open a serial port to view the message printed on your screen.`
`*`
* `To do this test, you should connect GPIO18 with GPIO4.`
* `GPIO5 is the control signal, you can leave it floating with internal pull up,`
* `or connect it to ground. If left floating, the count value will be increasing.`
* `If you connect GPIO5 to GND, the count value will be decreasing.`
`*`
* `An interrupt will be triggered when the counter value:`
* - `reaches 'thresh1' or 'thresh0' value,`
* - `reaches 'l_lim' value or 'h_lim' value,`
* - `will be reset to zero.`
`*/`
`#define PCNT_H_LIM_VAL 10`
`#define PCNT_L_LIM_VAL -10`
`#define PCNT_THRESH1_VAL 5`
`#define PCNT_THRESH0_VAL -5`
`#define PCNT_INPUT_SIG_IO 4 // Pulse Input GPIO`
`#define PCNT_INPUT_CTRL_IO 5 // Control GPIO HIGH=count up, LOW=count down`
`#define LEDC_OUTPUT_IO 18 // Output GPIO of a sample 1 Hz pulse generator`
`xQueueHandle pcnt_evt_queue; // A queue to handle pulse counter events`
`/* A sample structure to pass events from the PCNT`
* `interrupt handler to the main program.`
`*/`
`typedef struct {`
`int unit; // the PCNT unit that originated an interrupt`
`uint32_t status; // information on the event type that caused the interrupt`
`} pcnt_evt_t;`
`/* Decode what PCNT's unit originated an interrupt`
* `and pass this information together with the event type`
* `the main program using a queue.`
`*/`
`static void IRAM_ATTR pcnt_example_intr_handler(void *arg)
{
int pcnt_unit = (int)arg;
pcnt_evt_t evt;
evt.unit = pcnt_unit;
/* Save the PCNT event type that caused an interrupt`
`to pass it to the main program */`
`pcnt_get_event_status(pcnt_unit, &evt.status);`
`xQueueSendFromISR(pcnt_evt_queue, &evt, NULL);`
`}`
`/* Configure LED PWM Controller`
* `to output sample pulses at 1 Hz with duty of about 10%`
`*/`
`static void ledc_init(void)`
`{`
`// Prepare and then apply the LEDC PWM timer configuration`
`ledc_timer_config_t ledc_timer;`
`ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE;`
`ledc_timer.timer_num = LEDC_TIMER_1;`
`ledc_timer.duty_resolution = LEDC_TIMER_10_BIT;`
`ledc_timer.freq_hz = 1; // set output frequency at 1 Hz`
`ledc_timer.clk_cfg = LEDC_AUTO_CLK;`
`ledc_timer_config(&ledc_timer);`
`// Prepare and then apply the LEDC PWM channel configuration`
`ledc_channel_config_t ledc_channel;`
`ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE;`
`ledc_channel.channel = LEDC_CHANNEL_1;`
`ledc_channel.timer_sel = LEDC_TIMER_1;`
`ledc_channel.intr_type = LEDC_INTR_DISABLE;`
`ledc_channel.gpio_num = LEDC_OUTPUT_IO;`
`ledc_channel.duty = 100; // set duty at about 10%`
`ledc_channel.hpoint = 0;`
`ledc_channel_config(&ledc_channel);`
`}`
`/* Initialize PCNT functions:`
* - `configure and initialize PCNT`
* - `set up the input filter`
* - `set up the counter events to watch`
`*/
static void pcnt_example_init(int unit)
{
/* Prepare configuration for the PCNT unit */
pcnt_config_t pcnt_config = {
// Set PCNT input signal and control GPIOs
.pulse_gpio_num = PCNT_INPUT_SIG_IO,//GPIO4输入
.ctrl_gpio_num = PCNT_INPUT_CTRL_IO,//GPIO5控制
.channel = PCNT_CHANNEL_0,//0通道
.unit = unit,//函数参数,单元
// What to do on the positive / negative edge of pulse input?
//上升沿/下降沿做什么
.pos_mode = PCNT_COUNT_INC, // 上升沿递增Count up on the positive edge
.neg_mode = PCNT_COUNT_DIS, // 下降沿保持Keep the counter value on the negative edge
// What to do when control input is low or high?
//控制引脚--高低电平时坐什么
.lctrl_mode = PCNT_MODE_REVERSE, // 低电平反转技术方向Reverse counting direction if low
.hctrl_mode = PCNT_MODE_KEEP, // 高电平保持Keep the primary counter mode if high
// Set the maximum and minimum limit values to watch
//设置技术最大小值
.counter_h_lim = PCNT_H_LIM_VAL,
.counter_l_lim = PCNT_L_LIM_VAL,
};
/* Initialize PCNT unit */`
`// 初始化结构体`
`pcnt_unit_config(&pcnt_config);`
`/* Configure and enable the input filter */`
`// 配置使能滤波,第二个参数为时间,时钟个数计数,时钟为apb`
`pcnt_set_filter_value(unit, 100);`
`pcnt_filter_enable(unit);`
`/* Set threshold 0 and 1 values and enable events to watch */
// 配置使能阈值01
pcnt_set_event_value(unit, PCNT_EVT_THRES_1, PCNT_THRESH1_VAL);
pcnt_event_enable(unit, PCNT_EVT_THRES_1);
pcnt_set_event_value(unit, PCNT_EVT_THRES_0, PCNT_THRESH0_VAL);
pcnt_event_enable(unit, PCNT_EVT_THRES_0);
/* Enable events on zero, maximum and minimum limit values */`
`// 使能0,最大小值`
`pcnt_event_enable(unit, PCNT_EVT_ZERO);`
`pcnt_event_enable(unit, PCNT_EVT_H_LIM);`
`pcnt_event_enable(unit, PCNT_EVT_L_LIM);`
`/* Initialize PCNT's counter */`
`// 初始化计数器,暂停-》清零`
`pcnt_counter_pause(unit);`
`pcnt_counter_clear(unit);`
`/* Install interrupt service and add isr callback handler */`
`// 添加中断回调`
`pcnt_isr_service_install(0);`
`pcnt_isr_handler_add(unit, pcnt_example_intr_handler, (void *)unit);`
`/* Everything is set up, now go to counting */`
`// 开始计数`
`pcnt_counter_resume(unit);`
`}`
`void app_main(void)`
`{`
`int pcnt_unit = PCNT_UNIT_0;`
`/* Initialize LEDC to generate sample pulse signal */`
`// 初始化ledc,配置timer和通道`
`ledc_init();`
`/* Initialize PCNT event queue and PCNT functions */`
`// 初始化pcnt对了`
`pcnt_evt_queue = xQueueCreate(10, sizeof(pcnt_evt_t));`
`// pcnt初始化`
`pcnt_example_init(pcnt_unit);`
`int16_t count = 0;`
`pcnt_evt_t evt;`
`portBASE_TYPE res;`
`while (1) {`
`/* Wait for the event information passed from PCNT's interrupt handler.`
* `Once received, decode the event type and print it on the serial monitor.`
`//接受中断事件数据`
`res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS);`
`if (res == pdTRUE) {//接收到数据`
`// 获取计数值`
`pcnt_get_counter_value(pcnt_unit, &count);`
`ESP_LOGI(TAG, "Event PCNT unit[%d]; cnt: %d", evt.unit, count);`
`// status为中断返回的状态`
`if (evt.status & PCNT_EVT_THRES_1) {`
`ESP_LOGI(TAG, "THRES1 EVT");`
`}`
`if (evt.status & PCNT_EVT_THRES_0) {`
`ESP_LOGI(TAG, "THRES0 EVT");`
`}`
`if (evt.status & PCNT_EVT_L_LIM) {`
`ESP_LOGI(TAG, "L_LIM EVT");`
`}`
`if (evt.status & PCNT_EVT_H_LIM) {`
`ESP_LOGI(TAG, "H_LIM EVT");`
`}`
`if (evt.status & PCNT_EVT_ZERO) {`
`ESP_LOGI(TAG, "ZERO EVT");`
`}`
`} else {//一直打印计数值`
`pcnt_get_counter_value(pcnt_unit, &count);`
`ESP_LOGI(TAG, "Current counter value :%d", count);`
`}`
`}`
`}`