ESP32-S2 PWM输入捕获

一、概述

        在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

  • 12
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值