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)滤波
滤波分为denoise和filter两种方式。
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_level和grade。
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_ACTIVE和TOUCH_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里面完成引脚号码的查询,才能保证线程安全?结果就能稳定检测是哪号引脚被触摸了。