一、概述
????????在STM32上配置输入捕获很简单,因为有硬件支持,同时有很好的库函数。而在ESP32-S2这个芯片是没有输入捕获这个功能,但是这个芯片有定时器和脉冲计数器,我们可以借助这两个功能实现输入捕获。所谓输入捕获就是捕捉输入信号的脉冲宽度与频率大小。
????????ESP32 芯片提供两组硬件定时器,每组包含两个通用硬件定时器。 所有定时器均为 64 位通用定时器,包括 16 位预分频器和 64 位自动重载向上/向下计数器。 定时器初始化 - 启动定时器前应设置的参数,以及每个设置提供的具体功能。
???????脉冲计数器(PCNT),用于统计输入信号的上升沿和/或下降沿的数量。ESP32-S2 集成了多个脉冲计数单元,1?每个单元都是包含多个通道的独立计数器。通道可独立配置为统计上升沿或下降沿数量的递增计数器或递减计数器。PCNT 通道可检测?边沿?信号及?电平?信号。对于比较简单的应用,检测边沿信号就足够了。PCNT 通道可检测上升沿信号、下降沿信号,同时也能设置为递增计数,递减计数,或停止计数。电平信号就是所谓的?控制信号,可用来控制边沿信号的计数模式。通过设置电平信号与边沿信号的检测模式,PCNT 单元可用作?正交解码器。
? ? ? ? 频率为周期的倒数?,而一分钟时间内的脉冲个数等于频率。例如下图,一分钟内脉冲个数为6个,频率为6Hz。
?????????占空比为一个周期内高电平的时间与一个周期总时间的比值? ? 占空比 =??t0_h? /? T。
二、程序设计
? ? ? ? 频率捕获程序设计,已知一分钟内的脉冲个数为频率,则需要配置一个计数器和一个一分钟的定时器。当计数器开始计数时开启定时器,一分钟后进入定时器中断,获取计数值。
? ? ? ? 占空比捕获程序设计,跟根据公式知道要计算高电平时间和周期总时间,则需要一个外部中断和一个定时器。当上升沿到来时清空定时器值,开始计数,下降沿到来获取定时器时间,这个时间为一个周期内高电平的时间,当第二个上升沿到来获取定时器时间,这个时间为一个周期的时间。
? ? ? ? 代码如下:
#include "bsp_capture.h"
extern EventGroupHandle_t xEventGroup_Handle;
esp_timer_handle_t esp_timer_handle;
input_capture_t cap = {0};
static void IRAM_ATTR exti_isr_handler(void *arg)
{
static uint8_t status = 0;
static uint8_t count = 0;
uint8_t gpio_level = gpio_get_level(EXTI_GPIO_PIN);
if(status == 0 && gpio_level)
{
timer_set_counter_value(TIMER_GROUP,TIMER_INDEX,0x00000000ULL);
status = 1;
}
else if(status == 1 && !gpio_level)
{
timer_get_counter_value(TIMER_GROUP,TIMER_INDEX,&cap.t0_h_time);
status = 2;
}
else if(status == 2 && gpio_level)
{
timer_get_counter_value(TIMER_GROUP,TIMER_INDEX,&cap.cycle_time);
status = 0;
count++;
if(count >= 3)
{
status = 3;
BaseType_t xHigherPriorityTaskWoken, xResult;
xResult = xEventGroupSetBitsFromISR(xEventGroup_Handle,
GET_DUTY_EVENT,
&xHigherPriorityTaskWoken);
if(xResult)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
}
static void EXTI_Init(void)
{
const gpio_config_t exti_config = {
.intr_type = GPIO_INTR_ANYEDGE,
.mode = GPIO_MODE_INPUT,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pin_bit_mask = (1ULL << EXTI_GPIO_PIN),
};
gpio_config(&exti_config);
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL2);
gpio_isr_handler_add(EXTI_GPIO_PIN,exti_isr_handler,NULL);
}
static void Hardware_Timer_Init(void)
{
const timer_config_t timer_config = {
.alarm_en = TIMER_ALARM_DIS, //到达计数值启动报警(计数值溢出,进入中断)
.counter_en = TIMER_PAUSE, //调用timer_init()后不启动计数,调用timer_start()才开始计数
.counter_dir = TIMER_COUNT_UP, //向上计数
.auto_reload = TIMER_AUTORELOAD_EN, //使能自动重装载
.divider = 8, //分频值
};
timer_init(TIMER_GROUP,TIMER_INDEX,&timer_config);
timer_set_counter_value(TIMER_GROUP,TIMER_INDEX,0x00000000ULL);
timer_start(TIMER_GROUP,TIMER_INDEX);
}
static void Pcnt_Init(void)
{
const pcnt_config_t pcnt_config = {
.pulse_gpio_num = PCNT_INPUT_SIG_IO,
.ctrl_gpio_num = PCNT_INPUT_CTRL_IO,
.channel = PCNT_CHANNEL_0,
.unit = PCNT_UNIT,
.pos_mode = PCNT_COUNT_INC, //计算正边沿
.neg_mode = PCNT_COUNT_DIS, //计算反边沿
.lctrl_mode = PCNT_MODE_REVERSE, //如果计数方向低,则反向计数
.hctrl_mode = PCNT_MODE_KEEP, //如果高,则保持主计数器模式
.counter_h_lim = PCNT_H_LIM_VAL, //设置最大值
.counter_l_lim = PCNT_L_LIM_VAL,
};
pcnt_unit_config(&pcnt_config);
pcnt_set_filter_value(PCNT_UNIT, 100); // 第二个参数的注释为PCNT信号的滤波值,计数器在APB_CLK周期的任何持续时间比这短的脉冲将被忽略,当过滤器被启用时。
pcnt_filter_enable(PCNT_UNIT);
pcnt_counter_pause(PCNT_UNIT); //暂停计数
pcnt_counter_clear(PCNT_UNIT); //清除计数
pcnt_counter_resume(PCNT_UNIT); //开始计数
}
//软件定时器回调函数
static void esp_timer_cb(void *arg)
{
static uint8_t count = 0;
BaseType_t xHigherPriorityTaskWoken, xResult;
pcnt_get_counter_value(PCNT_UNIT,&cap.frequency); //每秒钟的脉冲个数就是频率
pcnt_counter_clear(PCNT_UNIT);
count++;
if(count >= 3)
{
xResult = xEventGroupSetBitsFromISR(xEventGroup_Handle,
GET_FREQUENCY_EVENT,
&xHigherPriorityTaskWoken);
if(xResult)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
static void Software_Timer_Init(void)
{
const esp_timer_create_args_t fw_timer = {
.callback = &esp_timer_cb,
.arg = NULL,
.name = "esp_timer",
};
esp_timer_create(&fw_timer,&esp_timer_handle);
esp_timer_start_periodic(esp_timer_handle,1000 * 1000);
}
void capture_duty_install_service(void)
{
Hardware_Timer_Init();
EXTI_Init();
}
void capture_duty_uninstall_service(void)
{
gpio_isr_handler_remove(EXTI_GPIO_PIN);
timer_deinit(TIMER_GROUP,TIMER_INDEX);
}
void capture_frequency_install_service(void)
{
Pcnt_Init();
Software_Timer_Init();
}
void capture_frequency_uninstall_service(void)
{
esp_timer_stop(esp_timer_handle);
esp_timer_delete(esp_timer_handle);
}
#ifndef _BSP_CAPTURE_H_
#define _BSP_CAPTURE_H_
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "driver/timer.h"
#include "esp_timer.h"
#include "driver/pcnt.h"
#define EXTI_GPIO_PIN GPIO_NUM_10
#define TIMER_GROUP TIMER_GROUP_0
#define TIMER_INDEX TIMER_0
#define TIMER_INIR TIMER_INTR_T0
#define PCNT_UNIT PCNT_UNIT_0
#define PCNT_H_LIM_VAL 30000
#define PCNT_L_LIM_VAL -30000
#define PCNT_INPUT_SIG_IO GPIO_NUM_10 //脉冲输入GPIO
#define PCNT_INPUT_CTRL_IO GPIO_NUM_NC //控制GPIO Control GPIO HIGH = count up, LOW = count down
#define GET_DUTY_EVENT (0X01 << 0)
#define GET_FREQUENCY_EVENT (0X01 << 1)
typedef struct
{
uint64_t t0_h_time;
uint64_t cycle_time;
int16_t frequency;
}input_capture_t;
extern input_capture_t cap;
void capture_duty_install_service(void);
void capture_duty_uninstall_service(void);
void capture_frequency_install_service(void);
void capture_frequency_uninstall_service(void);
#endif
????????pwm程序
#include "bsp_pwm.h"
void PWM_Init(void)
{
const ledc_channel_config_t pwm_channel_config = {
.gpio_num = PWM_GPIO_PIN,
.speed_mode = PWM_MODE,
.channel = PWM_CHABNNEL,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = TIMER_NUM,
.duty = 0,
.hpoint = 0,
};
ledc_channel_config(&pwm_channel_config);
const ledc_timer_config_t pwm_timer_config = {
.duty_resolution = LEDC_TIMER_BIT,
.freq_hz = FREQUENCY,
.speed_mode = PWM_MODE,
.timer_num = TIMER_NUM,
.clk_cfg = LEDC_USE_APB_CLK,
};
ledc_timer_config(&pwm_timer_config);
ledc_fade_func_install(0);
}
void PWM_Set_Duty(uint32_t duty)
{
if(duty > 100)
{
duty = 100;
}
duty = (float)duty / 100 * LEDC_MAX_DUTY;
ledc_set_duty(PWM_MODE,PWM_CHABNNEL,duty);
ledc_update_duty(PWM_MODE,PWM_CHABNNEL);
}
#ifndef _BSP_PWM_H_
#define _BSP_PWM_H_
#include <math.h>
#include "driver/ledc.h"
#define FREQUENCY 500
#define TIMER_NUM LEDC_TIMER_0
#define PWM_GPIO_PIN GPIO_NUM_6
#define PWM_CHABNNEL LEDC_CHANNEL_0
#define PWM_MODE LEDC_LOW_SPEED_MODE
#define LEDC_TIMER_BIT LEDC_TIMER_10_BIT
#define LEDC_MAX_DUTY (int)(pow(2,LEDC_TIMER_BIT)-1) //根据定时器位数,计算出可用分辨率的最大值
void PWM_Init(void);
void PWM_Set_Duty(uint32_t duty);
#endif
? ? ? ? 主程序
#include "bsp_capture.h"
#include "bsp_pwm.h"
#include "esp_log.h"
static const char *TAG = "cap_test";
EventGroupHandle_t xEventGroup_Handle = NULL;
void capture_stak(void *arg)
{
EventBits_t uxBits;
capture_duty_install_service();
PWM_Init();
PWM_Set_Duty(50);
while(1)
{
uxBits = xEventGroupWaitBits(xEventGroup_Handle,
GET_DUTY_EVENT | GET_FREQUENCY_EVENT,
pdTRUE,
pdFALSE,
portMAX_DELAY);
if(uxBits & GET_DUTY_EVENT)
{
ESP_LOGI(TAG,"t0_h:%llu T:%llu duty:%.1f%%\n",cap.t0_h_time,cap.cycle_time,(float)cap.t0_h_time / cap.cycle_time * 100);
capture_duty_uninstall_service();
capture_frequency_install_service();
}
else if(uxBits & GET_FREQUENCY_EVENT)
{
ESP_LOGI(TAG,"frequency :%d Hz\n",-cap.frequency);
capture_frequency_uninstall_service();
}
}
}
void app_main(void)
{
xEventGroup_Handle = xEventGroupCreate();
xTaskCreate(capture_stak,"capture_stak",1024 * 5,NULL,5,NULL);
}
三、总结
? ? ? ? 设置PWM输出 频率为500Hz 、占空比为1%的脉冲波形,软件捕获结果如下图。
???????? 设置PWM输出 频率为500Hz 、占空比为90%的脉冲波形,软件捕获结果如下图。
????????设置PWM输出 频率为19KHz 、占空比为10%的脉冲波形,软件捕获结果如下图。(问题:PWM占空比低于百分之10捕获后值不对)
设置PWM输出 频率为19KHz 、占空比为90%的脉冲波形,软件捕获结果如下图。
? ? ? ? 从数据采集过程中发现PWM频率越低,捕获效果越好,PWM频率越高,捕获效果越差。 工程示例,提取码:gwoxhttps://pan.baidu.com/s/1mZyTcogycGl1XSUEPd20fA
|