【ESP32s3-espidf】电容[Touch]触摸引脚

        ESP32S3自带电容触摸引脚的代码比较简单,配置代码放在这文章方便以后直接copy。这个电容引脚作用是测试一些功能时可以替代外置的物理按键电路,人手控制一些代码的开关。我用ESP-IDF环境编译。

一、结构原理和一些重要函数API

        1.1 引脚和结构

        可以设置为Touch的引脚为GPIO1~14。        

        电容触摸模块是物理硬件,乐鑫已经给我们做好,放在CPU外面。右边的图是硬件FSM(有限状态机),作为Touch模块的核心控制器,我们就是对这个东西进行配置控制即可,有限状态机详细原理不用了解。其他denoise unit、filter unit、scan_ctrl等硬件也可以灵活选择或不用管。

T0引脚不能对外使用,没有引脚焊盘。T0作用是作为空白对照组,理论上影响T0引脚的噪声因素如温度也会影响其他引脚,所以Touch模块的降噪算法就是减去T0感知到的噪声。

        1.2 采样测量原理和测量值

        FSM设备会释放标准的三角波给引脚电容,相当于给电容进行周期性的充放电。FSM控制开始充放电,同时FSM内部的脉冲计数器和时间计数器启动,测量完成N次充放电的任务需要多少us时间。充电时要让电压升到指定阈值才算测量成功,而人手触摸引脚会导致电容增大,充电时间变长。一次测量任务持续时间应小于1ms。

        读数的返回结果就是时间值,即:耗时多长完成N次充放电。

        因此,读出来的raw value越大反映了充电时间较长,人手接触面积越大,读数值越大。官方的其他API给我们配置的就是各种能影响采样时长的参数,比如电压上下限(电容充到多高的电压(DREFH)算是完成充电,放到多低的电压(DREFL)算是完成放电)、斜率控制(充电电流的大小(slope)也能影响采样时间)、采样次数(charge_discharge_times)。这些杂项我们可以不用配置,下文1.3会简单提及。

        1.3  其他功能函数API的简单介绍

        (1)滤波

        滤波分为denoisefilter两种方式。

        denoise就是依靠T0悬空引脚测量的干扰值来指示全局干扰的情况,相当于空白对照组来排除共模干扰,代码中任何情形都推荐使用此功能。由于这是硬件,需要配置set_config()、enable()的代码进行启动。后面还会重点讲这个,因为esp官方讲得很少。

        filter是IIR滤波器,也是物理滤波,滤去高频噪声,用数学公式平滑数据。这个滤波会涉及后面读数API中的smooth()和benchmark()读数。smooth()读数是一系列经过IIR滤波的移动平均数值。benchmark()数值是从启动至今的平均值,移动平均的窗口比smooth数值更长,几乎不受单次的触摸测量值的影响。

        本质上这两个读数跟raw_value没什么区别。

        (2)FSM的启用模式

        FSM想象成三角波发生器,有两种启用方式,timer定时启用,或者软件启用测量。

        timer定时器就好理解,比如控制1秒内启动多少次touch的测量,测量频次更密的话会让测量响应更快。这个模式用于一直检测着哪个引脚被触发,但会让三角波一直做充放电容的活,耗电。

        软件启动就是按需使用,使用官方函数API:touch_pad_read_raw_data()的时候才启用FSM,其他时候不启用,省电。

        如果不启用FSM,不发生三角波,哪怕传感器一直探测着也探不到任何信号。

        (3)其他杂项

        touch_pad_set_idle_channel_connect()用来设置待机状态下电容引脚连着什么地方,要么连高阻态(电平可能不稳定,但电容电压能保持着,随时快速响应测量),要么连着GND,GND的话引脚每次测量完成后都电压清零(保证状态的稳定,但是每次测量都要从0开始升电压)。

        touch_pad_waterproof_enable()比较有意思,乐鑫考虑到引脚防潮或湿水的状况。

        touch_pad_proximity_set_count()将最多3个引脚的读数累计,这个功能猜是用于“接近模式”,手指不用碰到金属体,只要靠近也能引起电容变化。

        sleep,ESP32在低功耗睡眠模式下,能通过触摸引脚唤醒。

        scan模式,FSM 自行对多个触摸传感器按照一定顺序轮流测量,每次只测量一个,这种模式可检测哪个管脚被触摸。

二、不带中断的代码配置

        又称为软件检测算法,这种方式用于需要读具体触摸数字的场合,依赖MCU不断轮询。是否触发触摸由程序员自己定义。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/touch_pad.h"

touch_pad_t pin_touch = 2; // GPIO2脚
void touch_use()
{
    touch_pad_init();
    touch_pad_config(pin_touch);

    touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
    touch_pad_fsm_start();
    vTaskDelay(40/portTICK_PERIOD_MS);
}
void touch_read()
{
    uint32_t buff=0;
    while(1)
    {   
         touch_pad_read_raw_data(pin_touch,&buff);
         printf("touch value is:%ld\n",buff);
         vTaskDelay(500/portTICK_PERIOD_MS);
    }
}
void app_main(void)
{
    touch_use();
    xTaskCreate(touch_read,"task1",1024*3,NULL,5,NULL);
}

        这儿的fsm_mode()的参数更换成软件开启更适合。因为这个也是靠软件轮询。

三、带中断的代码配置

        又叫硬件检测算法用于检测是否有触摸,不care触摸数值,注意这是靠硬件检测,不消耗MCU资源,不需要轮询。

        这一块重点讲。我要实现功能是2个引脚做触摸输入,2号引脚触发“+”功能,11号引脚触发“-”功能。并且将touch_pad的降噪稳定措施拉满。

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/touch_pad.h"

TaskHandle_t task1=NULL;
touch_pad_t touch_pad_t pin_touch2 = TOUCH_PAD_NUM2, pin_touch11=TOUCH_PAD_NUM11; // GPIO2和11脚

void ISR_touch_cb()
{
    int pad_num=0;
    pad_num=touch_pad_get_current_meas_channel();
    xTaskNotifyFromISR(task2,pad_num,eSetValueWithOverwrite,NULL);
}

void touch_use()
{
    touch_pad_init();
    touch_pad_config(pin_touch2);
    touch_pad_config(pin_touch11);//若要更改touch的default设定,就在这句的下面开始更改
    
    /*开启T0触摸盘的降噪denoise功能,类似实时减去共模噪声*/
    touch_pad_denoise_t denoisecnf={
        .cap_level=TOUCH_PAD_DENOISE_CAP_L2,
        .grade=TOUCH_PAD_DENOISE_BIT4,
    };
    touch_pad_denoise_set_config(&denoisecnf);
    touch_pad_denoise_enable();

    /*IIR滤波,跟denoise不同,这是对历史数据进行数学平滑,对数学公式里的系数进行配置,影响不大*/
    touch_filter_config_t filter_info = {
        .mode = TOUCH_PAD_FILTER_IIR_8,           // Test jitter and filter 1/4.
        .debounce_cnt = 1,      // 1 time count.
        .noise_thr = 0,         // 50%
        .jitter_step = 4,       // use for jitter mode.
        .smh_lvl = TOUCH_PAD_SMOOTH_IIR_2,
    };
    touch_pad_filter_set_config(&filter_info);
    touch_pad_filter_enable();

    touch_pad_isr_register(ISR_touch_cb,NULL,TOUCH_PAD_INTR_MASK_ACTIVE);   //中断触发条件INTR_MASK_ACTIVE,所有enabled的channels中有任何一个达到阈值就触发中断
    touch_pad_intr_enable(TOUCH_PAD_INTR_MASK_ACTIVE);

    touch_pad_set_idle_channel_connect(TOUCH_PAD_CONN_GND);  //将其余不使用的touch pad接地,以防悬空的不稳定状态影响正在运作的触摸盘

    touch_pad_set_channel_mask(1UL<<pin_touch2);//将已激活的2号和11号触摸引脚添加到scan池,FSM就只会轮流检测池内的引脚,并且每次只检测一个引脚
    touch_pad_set_channel_mask(1UL<<pin_touch11);

    touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);//硬件检测模式,FSM一直启用运作
    touch_pad_fsm_start();
    vTaskDelay(40/portTICK_PERIOD_MS);
    uint32_t denoisedata;  
    touch_pad_denoise_read_data(&denoisedata);  //相当于读T0盘raw value的作用,用这个数据来决定denoise结构体的参数
    printf("denosise value is:%ld\n",denoisedata);
    uint32_t bchmk;
    touch_pad_read_benchmark(pin_touch2,&bchmk);//我的机器大概是18000
    printf("benchmark2 is:%ld\n",bchmk);
    touch_pad_set_thresh(pin_touch2,bchmk*0.4);//意味着,只要下次触摸值raw value大于18000+18000*0.4,就触发中断
    touch_pad_read_benchmark(pin_touch11,&bchmk);//我的机器大概是23000
    printf("benchmark11 is:%ld\n",bchmk);
    touch_pad_set_thresh(pin_touch11,bchmk*0.2);
}

void touch_read()
{
    int pad_num;
    while(1)
    {   
        if(0!=xTaskNotifyWait(0xffffffff,0,&pad_num,portMAX_DELAY))
        {   
            switch (pad_num)
            {
            case 2:
                duty+=4;
                break;  
            case 11:
                duty-=4;
                break;
            }
            printf("pad_num is:%d,duty is%d \n",pad_num,duty);            
        }
    }
}

void app_main(void)
{
    touch_use();
    xTaskCreate(touch_read,"task1",1024*2,NULL,2,&task1);
}

        3.1 中断阈值        

        硬件中断不关心电容读数多少,只想知道触发/不触发中断,所以程序员要自己定义触摸读数达到多少阈值threshold才触发中断。我们不触摸时,电容最小,读数也是最小的;触摸时,若读数大于benchmark+threshold+噪声】时,就触发中断,touch_pad的INTR_MASK状态变成了active。若手离开引脚,读数从高降低到小于上述公式数值时,touch_pad的INTR_MASK状态变成了inactive。根据这种变化,可以触发入手中断,也可以离手中断。

        噪声就是T0引脚的噪声,系统自己控制,我们不用管。benchmark也是触摸传感器感受到的物理状态,自己会动态调整,我们不用管。我们要设置的是threshold。threshold要根据实际测录的经验来设置。上电后未触摸时读一次原始benchmark值作为参考值,这个每台设备、每个引脚都不同,我的2号引脚是18000左右(见下图),11号引脚是23000左右。我也实测过触摸时raw_value读数,一般能达到25000-70000(浅触摸是25000,用力触摸时手指接触面积增大能到70000),假如是25000的话,比2号引脚benchmark读数18000多了7000,相当于benchmark*0.4,因此我将benchmark*0.4的值设为2号引脚的threshold值。

        3.2 touch_pad_denoise_t(T0引脚的降噪功能)

        重点讲的是配置的两个参数cap_levelgrade

        cap_level指capacitance level,电容大小。触摸传感器 T0还能选择不同的电容器值!看下图,cap_L0的电容值最小,CAP_L7最大。看上面3.1的图,当我为T0传感器选CAP_L2电容时,该传感器读数是denosise value is:20056。选更大的电容的话,这个给读数会上升。问题就是选多大电容合适?这个要看本项目我想测量的T2引脚和T11引脚,他们的benchmark数值分别是18000和23000,因此T0的benchmark选在20000附近合适,大概是他们的平均值。因为T0的电容大小与其他引脚的大小相当的话,噪声扰动也会较准确传到给其他引脚。

        grade指T0采样数值变化的幅度(官方手册叫分辨率),可设置4位、8位、10位、12位四档。我实测多次了T0的采样值,偏离原始采样值denosise value is:20056的幅度就是20-40的量级,因此我设置grade为4位或8位即可,2的4次方是16。

        3.3 touch_filter_config_t(IIR滤波器)

        这是物理硬件完成的数学算法。背后的数学是统计学一元多项滞后的公式,对多个raw_value采样值和历史IIR滤波值赋予不同权重,算出来的。对结构体的配置过程就是权重系数的配置,我认为这儿不用花时间研究,用中规中矩的配置就行。

        3.4 touch_pad_isr_register()

        里面有个参数touch_pad_intr_mask_t intr_mask值得注意。intr_mask的值是从以下枚举值中选出:

typedef enum {
    TOUCH_PAD_INTR_MASK_DONE = BIT(0),      /*!<Measurement done for one of the enabled channels. */
    TOUCH_PAD_INTR_MASK_ACTIVE = BIT(1),    /*!<Active for one of the enabled channels. */
    TOUCH_PAD_INTR_MASK_INACTIVE = BIT(2),  /*!<Inactive for one of the enabled channels. */
    TOUCH_PAD_INTR_MASK_SCAN_DONE = BIT(3), /*!<Measurement done for all the enabled channels. */
    TOUCH_PAD_INTR_MASK_TIMEOUT = BIT(4),   /*!<Timeout for one of the enabled channels. */
#if SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED
    TOUCH_PAD_INTR_MASK_PROXI_MEAS_DONE = BIT(5),   /*!<For proximity sensor, when the number of measurements reaches the set count of measurements, an interrupt will be generated. */
} touch_pad_intr_mask_t;

        举例,假如设置为TOUCH_PAD_INTR_MASK_DONE,表示其中一个引脚要整个采样过程完成后发现超过threshold才能触发中断。TOUCH_PAD_INTR_MASK_ACTIVE,表示其中一个引脚只要采样过程有一次值超过threshold就能触发中断。而硬件检测中断模式下,只能用TOUCH_PAD_INTR_MASK_ACTIVETOUCH_PAD_INTR_MASK_INACTIVE两种,前者是刚触碰就中断,后者是离手时才中断。其他几种不能使用,注意

        3.5 touch_pad_set_channel_mask()

        这也是调用触摸传感模块中的一个scan_ctrl硬件,将已激活的2号和11号触摸引脚添加到scan池,FSM就只会轮流检测池内的引脚,并且每次只检测一个引脚。官方说,要启用了这个函数,才能用后面的touch_pad_get_current_meas_channel()函数判断哪个引脚发生中断。经实践,发现不用touch_pad_set_channel_mask()也可以检测哪号引脚发生中断。、

        注意参数需要按这种格式1UL<<pin_touch2写,不能直接写pin_touch2。

        3.6 还有个已解决的故障想分享

        本项目我想用2个引脚做触摸输入,2号引脚触发“+”功能,11号引脚触发“-”功能。一开始我将touch_pad_get_current_meas_channel()函数放在touch_read()里面,接收来自ISR函数发来的tasknotify通知时才执行touch_pad_get_current_meas_channel()查询。结果touch_pad无法准确判断是触摸2号引脚还是11号引脚,乱了,这问题缠绕了我很久。

        经思考,将touch_pad_get_current_meas_channel()函数放入中断服务函数ISR_touch_cb()里面,在中断的context里面完成引脚号码的查询,才能保证线程安全?结果就能稳定检测是哪号引脚被触摸了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值