ESP32 IDF v5.3.1 驱动 CST816T 触摸芯片(I2C 协议)

一、芯片概述

CST816T 自电容触控芯片,采用高速 MCU 内核并内嵌 DSP 电路,结合自身的快速自电容感应技术,可广泛支持三角形在内的多种自电容图案,在其上实现单点手势和真实两点操作,实现极高灵敏度和低待机功耗。该芯片仅支持标准的 I2C 通讯协议标准,可实现 10Khz~400Khz 的可配通信速率。

二、芯片I2C驱动实现

芯片的 7bit 设备地址一般为 0x15,即设备写地址为 0x2A,读地址为 0x2B。为了保证通信的可靠性,建议最大使用 400Kbps 的通信速率。

不了解ESP32如何实现I2C的朋友们,可以先移步先查看研究下I2C在ESP32上的实现:

ESP32在 IDF v5.3.1 版本下实现 硬件I2C 和 软件I2C总线(接口封装通用简洁明了)-CSDN博客https://blog.csdn.net/qq_34885669/article/details/155885586

1. CST816T硬件配置定义

复制代码
/*定义硬件i2c相关引脚*/
#define CST816T_SCL_PIN    GPIO_NUM_41  //13
#define CST816T_SDA_PIN    GPIO_NUM_20  //14
#define CST816T_RST_PIN    GPIO_NUM_45  //15
#define CST816T_INIT_PIN   GPIO_NUM_19  //16

#define CST816T_I2C_NUM    I2C_NUM_1
#define CST816T_ADDR       0x15

// 复位设置
#define CST816T_RST_PIN_SET()   do{ gpio_set_level(CST816T_RST_PIN,1); }while(0)
#define CST816T_RST_PIN_RESET() do{ gpio_set_level(CST816T_RST_PIN,0); }while(0)

2. CST816T芯片寄存器地址定义

复制代码
#define GestureID       0x01 //手势码。0x00:无手势,0x01:上滑,0x02:下滑,0x03:左滑,0x04:右滑,0x05:单击,0x0B:双击,0x0C:长按。
#define FingerNum       0x02 //手指个数。0:无手指 1:1个手指
#define XposH           0x03 //X坐标高4位
#define XposL           0x04 //X坐标低8位
#define YposH           0x05 //Y坐标高4位
#define YposL           0x06 //Y坐标低8位
#define ChipID          0xA7 //芯片型号
#define ProjID          0xA8 //工程编
#define FwVersion       0xA9 //软件版本号
#define FactoryID       0xAA //TP厂家ID
#define BPC0H           0xB0 //BPC0值的高8位
#define BPC0L           0xB1 //BPC0值的低8位
#define BPC1H           0xB2 //BPC1值的高8位
#define BPC1L           0xB3 //BPC1值的低8位
#define SleepMode       0xE5 //值为0x03时进入休眠状态(无触摸唤醒功能)
#define ErrResetCtl     0xEA //bit1:使能大面积触摸复位功能   bit0:使能双指复位功能
#define LongPressTick   0xEB //长按时间门限,默认为100。大约1S。
#define MotionMask      0xEC //使能左右、上下、双击动作(bit0:双击 bit1:上下 bit2:左右)。
#define IrqPluseWidth   0xED //中断低脉冲输出宽度。单位1ms,可选值:1~200。默认值为10。
#define NorScanPer      0xEE //正常快速检测周期。此值会影响到LpAutoWakeTime和AutoSleepTime。单位10ms,可选值:1~30。默认值为1。
#define MotionSlAngle   0xEF //手势检测滑动分区角度控制。Angle=tan(c)*10,c为以x轴正方向为基准的角度。
#define LpScanRaw1H     0xF0 //低功耗扫描1号通道的基准值的高8位。
#define LpScanRaw1L     0xF1 //低功耗扫描1号通道的基准值的低8位。
#define LpScanRaw2H     0xF2 //低功耗扫描2号通道的基准值的高8位。
#define LpScanRaw2L     0xF3 //低功耗扫描2号通道的基准值的低8位。
#define LpAutoWakeTime  0xF4 //低功耗时自动重校正周期。单位1分钟,可选值:1~5。默认值为5。
#define LpScanTH        0xF5 //低功耗扫描唤醒门限。越小越灵敏。可选值:1~255。默认值为48。
#define LpScanWin       0xF6 //低功耗扫描量程。越大越灵敏,功耗越高。可选值:0,1,2,3。默认值为3。
#define LpScanFreq      0xF7 //低功耗扫描频率。越小越灵敏。可选值:1~255。默认值为7。
#define LpScanIdac      0xF8 //低功耗扫描电流。越小越灵敏。可选值:1~255。
#define AutoSleepTime   0xF9 //x秒内无触摸时,自动进入低功耗模式。单位1S,默认值为2S。
#define IrqCtl          0xFA //bit7:中断引脚测试,使能后自动周期性发出低脉冲。  bit6:检测到触摸时,周期性发出低脉冲。  bit5:检测到触摸状态变化时,发出低脉冲。  bit4:检测到手势时,发出低脉冲。  bit0:长按手势只发出一个低脉冲信号。
#define AutoReset       0xFB //x秒内有触摸但无有效手势时,自动复位。单位1S,为0时不启用此功能。默认为5。
#define LongPressTime   0xFC //长按x秒后自动复位。单位1S,为0时不启用此功能。默认为10。
#define IOCtl           0xFD //bit2:主控通过拉低IRQ引脚实现触控的软复位功能 0禁止 1使能   bit1:IIC引脚驱动模式 0电阻上拉,1开漏    bit0:IIC和IRQ引脚电平选择,默认为VDD电平 0:VDD 1:1.8V
#define DisAutoSleep    0xFE //默认为0,使能自动进入低功耗模式。为非0值时,禁止自动进入低功耗模式。

3. CST816T触摸相关结构体定义

复制代码
typedef struct 
{
    uint8_t chipID;     //芯片型号
    uint8_t projID;     //工程编号
    uint8_t fwVersion;  //软件版本号
    uint8_t factoryID;  //TP厂家ID
}Factory_info;

typedef struct  _CST816T_CoordinateInfo
{
    uint8_t gestureID; //手势码
    uint8_t fingerNum; //手指个数
    uint16_t x;
    uint16_t y;
}CST816T_CoordinateInfo;

typedef union _CST816T_TouchInfo
{
    CST816T_CoordinateInfo CoordinateInfo;
    uint8_t dataBuff[6];
}CST816T_TouchInfo;
  1. CST816T初始化

    /*
    函数功能:cst816t触摸IC引脚初始化函数
    */
    void cst816t_GPIOinit(void)
    {
    gpio_config_t touchGPIO_InitConfig = {0};
    touchGPIO_InitConfig.pin_bit_mask = 1ull << CST816T_RST_PIN;
    touchGPIO_InitConfig.mode = GPIO_MODE_OUTPUT_OD;
    touchGPIO_InitConfig.pull_up_en = GPIO_PULLUP_ENABLE;
    touchGPIO_InitConfig.pull_down_en = GPIO_PULLUP_DISABLE;
    ESP_ERROR_CHECK(gpio_config(&touchGPIO_InitConfig));

    复制代码
     touchGPIO_InitConfig.pin_bit_mask = 1ull << CST816T_INIT_PIN;
     touchGPIO_InitConfig.mode = GPIO_MODE_INPUT;
     touchGPIO_InitConfig.pull_up_en = GPIO_PULLUP_ENABLE;
     touchGPIO_InitConfig.pull_down_en = GPIO_PULLUP_DISABLE;
     touchGPIO_InitConfig.intr_type = GPIO_INTR_LOW_LEVEL;
     ;
     ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_EDGE));
     ESP_ERROR_CHECK(gpio_config(&touchGPIO_InitConfig));
     ESP_ERROR_CHECK(gpio_isr_handler_add(CST816T_INIT_PIN,cst816t_touchIRQHandler,(void*) CST816T_INIT_PIN)); //添加中断服务函数
     ESP_ERROR_CHECK(gpio_intr_enable(CST816T_INIT_PIN));// 使能 GPIO 中断

    }

    /*
    函数功能:cst816t触摸初始化函数
    形 参:中断引脚号
    */
    void cst816t_init(void)
    {
    esp_err_t ret = ESP_FAIL;
    cst816t_GPIOinit();
    #ifdef CST816T_HARDWARE_I2C //使用硬件I2C
    i2c_hardware_init(CST816T_I2C_NUM);
    if(ESP_OK == ret) ESP_LOGE(TAG,"cst816t hardware i2c_%d init ok !",CST816T_I2C_NUM);
    else ESP_LOGE(TAG,"cst816t hardware i2c_%d init error !",CST816T_I2C_NUM);
    #else
    i2c_software_init(CST816T_SCL_PIN,CST816T_SDA_PIN);//初始化触摸面板I2C引脚
    #endif
    CST816T_RST_PIN_RESET();
    delay_ms(10);
    CST816T_RST_PIN_SET();
    delay_ms(50);
    }

    /*
    函数功能:触摸屏触摸中断服务函数
    形 参:中断引脚号
    /
    void IRAM_ATTR cst816t_touchIRQHandler(void
    arg)
    {
    int pin = (int) arg;
    touch_IRQFlag = 1;
    }

4. 从CST816T芯片寄存器地址读取字节数据

复制代码
/*
函数功能:读取触摸IC的数据
形   参:
        dev_Addr:触摸IC的i2c地址
        reg_Addr:要读取的寄存器地址
        buff:存放读取的数据
        len:要读取的字节数
*/
void cst816t_readBytes(uint8_t dev_Addr,uint8_t reg_Addr,uint8_t *buff,uint8_t len)
{
    uint8_t i;
#ifdef CST816T_HARDWARE_I2C //使用硬件I2C
    i2c_master_write_read_device(CST816T_I2C_NUM,dev_Addr,&reg_Addr,1,buff,len,10 / portTICK_PERIOD_MS);//idf库的i2c读从机数据。
#else
    i2c_start();
    i2c_write_one_byte((dev_Addr<<1)|WRITE_BIT,ACK);
    i2c_write_one_byte(reg_Addr,ACK);
    i2c_start();
    i2c_write_one_byte((dev_Addr<<1)|READ_BIT,ACK);
    for(i=0;i<len;i++)
    {
        if(len==1||(len>1&&i==len-1)) *(buff+i) = i2c_read_one_byte(NACK); //最后一个字节读取,cst816t不会有ACK响应,所以要发NACK,否则下一次读取数据就会出现错误!!!!!
        else *(buff+i) = i2c_read_one_byte(ACK);
    }
    i2c_stop();
#endif
}

5. 给CST816T芯片寄存器地址写一个字节数据

复制代码
/*
函数功能:给触摸IC发送数据
形   参:
        dev_Addr:触摸IC的i2c地址
        reg_Addr:要发送的寄存器地址
        buff:要发送的数据
        len:要发送的数据长度
*/
void cst816t_writeOneByte(uint8_t dev_Addr,uint8_t reg_Addr,uint8_t data)
{
#ifdef CST816T_HARDWARE_I2C //使用硬件I2C
    uint8_t buff[] = {reg_Addr,data};
    i2c_master_write_to_device(CST816T_I2C_NUM,dev_Addr,buff,2,10 / portTICK_PERIOD_MS);//idf库的i2c读从机数据。
#else //使用软件模拟I2C
    i2c_start();
    i2c_write_one_byte((dev_Addr<<1)|WRITE_BIT,ACK);
    i2c_write_one_byte(reg_Addr,ACK);
    i2c_write_one_byte(data,ACK);
    i2c_stop();
#endif
}

6. 读取当前触摸的原始坐标

复制代码
/*
函数功能:一次性读取触摸的xy原始坐标(没有经过校准标定的原始触摸XY坐标)
形   参:
 dev_Addr:触摸IC设备地址
        x:坐标x的指针
        y:坐标y的指针
*/
void cst816t_getXY(uint8_t dev_Addr,uint16_t *x,uint16_t *y)
{
    uint8_t temp[4]={0};
    cst816t_readBytes(dev_Addr,XposH,temp,4); //一次性读取触摸的XY坐标
    *x = (temp[0]&0x0F)<<8 | temp[1];
    *y = (temp[2]&0x0F)<<8 | temp[3];
}

7. 读取当前触摸的信息(坐标,手势码,手指个数)

复制代码
/*
函数功能:一次性读取触摸的坐标,以及触摸手势码,手指个数。(并校准XY坐标到LCD)
形   参:
 dev_Addr:触摸IC设备地址
touchInfo:触摸信息结构体
*/
void cst816t_getTouchInfo(uint8_t dev_Addr,CST816T_TouchInfo *touchInfo)
{
    uint8_t temp=0,cnt=0,cmd;
    uint16_t tempX,tempY;
    uint16_t small_or_big = 1;
    uint8_t small_or_big_flag = (*(uint8_t*)&small_or_big);
    do
    {
        cst816t_readBytes(dev_Addr,GestureID,touchInfo->dataBuff,6);
        if(small_or_big_flag) // 如果是小端
        {
            temp = touchInfo->dataBuff[2]&0x0F;
            touchInfo->dataBuff[2] = touchInfo->dataBuff[3];
            touchInfo->dataBuff[3] = temp;

            temp = touchInfo->dataBuff[4]&0x0F;
            touchInfo->dataBuff[4] = touchInfo->dataBuff[5];
            touchInfo->dataBuff[5] = temp;
            
        }
        cnt++;
    }while((touchInfo->CoordinateInfo.x>LCD_W || touchInfo->CoordinateInfo.y>LCD_H ) && cnt<5);
    
    if(LCD_Adjustd_OK)//校准坐标
    {
        printf(" Touch(%3.0d,%3.0d) gesture=%2d cnt=%d ",touchInfo->CoordinateInfo.x,touchInfo->CoordinateInfo.y,touchInfo->CoordinateInfo.gestureID,cnt);
        tempX = touchInfo->CoordinateInfo.x;
        tempY = touchInfo->CoordinateInfo.y;
        touchInfo->CoordinateInfo.x = (uint16_t)((cal.a[1]*tempX + cal.a[2]*tempY + cal.a[0])*1.0/cal.a[6]);
        touchInfo->CoordinateInfo.y = (uint16_t)((cal.a[4]*tempX + cal.a[5]*tempY + cal.a[3])*1.0/cal.a[6]);
        if(touchInfo->CoordinateInfo.x > LCD_W) touchInfo->CoordinateInfo.x = LCD_W -1;
        if(touchInfo->CoordinateInfo.y > LCD_H) touchInfo->CoordinateInfo.y = LCD_H -1;
        printf(" LCD(%3.0d,%3.0d) LCD_Adjustd_OK=1\r\n",touchInfo->CoordinateInfo.x,touchInfo->CoordinateInfo.y);
    }
}

三、触摸校准

通过前面的驱动,相信你已经成功获取到了触摸坐标了,但是你经过仔细对比你就能发现,你实际触摸点和获取的坐标点还是有较大的偏移误差的,那么就有必要引入触摸校准了。

校准算法有很多种,目前我挑选比较通用的tslib 5 点校准算法来实现触摸校准:

复制代码
typedef struct _Calibration //触摸校准用
{
    int touch_x[5],touch_y[5]; //触摸点的坐标
    int lcd_x[5],lcd_y[5]; //屏幕点的坐标
    unsigned int a[7];
} Calibration;




/*
函数功能:计算触摸屏触摸校准参数(tslib 5点校准算法)
形   参:触摸屏实际采集的5点以及该5点对应LCD上的参考坐标结构体
触摸屏坐标到LCD坐标的映射关系:
        lcd_x = (A*touc_x+B*touc_y+C)/scaling
        lcd_y = (D*touc_x+E*touc_y+F)/scaling
*/
esp_err_t touch_calibrationParamCalculation(Calibration *calibration)
{
    uint8_t j;
	double n, x, y, x2, y2, xy, z, zx, zy;
	double det, a, b, c, e, f, i;
	float scaling = 32767.0;//定义计算放大倍数,保证计算精度。
    for ( j = 0; j < 5; j++)
    {
        calibration->touch_x[j] = touch_x[j];
        calibration->touch_y[j] = touch_y[j];
    }
    // Get sums for matrix
	n = x = y = x2 = y2 = xy = 0;
	for(j = 0;j < 5;j++) 
    {
		n  += 1.0;
		x  += (float)calibration->touch_x[j];
		y  += (float)calibration->touch_y[j];
		x2 += (float)(calibration->touch_x[j]*calibration->touch_x[j]);
		y2 += (float)(calibration->touch_y[j]*calibration->touch_y[j]);
		xy += (float)(calibration->touch_x[j]*calibration->touch_y[j]);
	}

    //获取矩阵的行列式------检查行列式是否太小
	det = n*(x2*y2 - xy*xy) + x*(xy*y - x*y2) + y*(x*xy - y*x2);
	if(det < 0.1 && det > -0.1) return -1;

    //获取逆矩阵的元素
	a = (x2*y2 - xy*xy)/det;
	b = (xy*y - x*y2)/det;
	c = (x*xy - y*x2)/det;
	e = (n*y2 - y*y)/det;
	f = (x*y - n*xy)/det;
	i = (n*x2 - x*x)/det;

    //获取 touch_x 校准的总和
	z = zx = zy = 0;
	for(j=0;j<5;j++) {
		z  += (float)calibration->lcd_x[j];
		zx += (float)(calibration->lcd_x[j]*calibration->touch_x[j]);
		zy += (float)(calibration->lcd_x[j]*calibration->touch_y[j]);
	}

    //获取 touch_x 校准参数
	calibration->a[0] = (int)(a*z + b*zx + c*zy);
	calibration->a[1] = (int)((b*z + e*zx + f*zy)*(scaling));
	calibration->a[2] = (int)((c*z + f*zx + i*zy)*(scaling));

    //获取 touch_y 校准的总和
	z = zx = zy = 0;
	for(j=0;j<5;j++) 
    {
		z += (float)calibration->lcd_y[j];
		zx += (float)(calibration->lcd_y[j]*calibration->touch_x[j]);
		zy += (float)(calibration->lcd_y[j]*calibration->touch_y[j]);
	}
    //获取 touch_y 校准参数
	calibration->a[3] = (int)(a*z + b*zx + c*zy);
	calibration->a[4] = (int)((b*z + e*zx + f*zy)*(scaling));
	calibration->a[5] = (int)((c*z + f*zx + i*zy)*(scaling));
    //赋值放大倍数
	calibration->a[6] = (int)scaling;
	//验证校准参数准确性(将触摸屏坐标带入校准函数,计算出来校准后的LCD坐标,与实际的LCD参考点坐标计算偏差)
	for(j = 0; j < 5; j ++)
    {
		x = calibration->a[0] + ((calibration->a[1]*calibration->touch_x[j] + calibration->a[2]*calibration->touch_y[j] ) / calibration->a[6]);		
		y = calibration->a[3] + ((calibration->a[4]*calibration->touch_x[j] + calibration->a[5]*calibration->touch_y[j] ) / calibration->a[6]);	
		if((abs(x - calibration->lcd_x[j]) > 5) || (abs(y - calibration->lcd_y[j]) > 5))//校准偏差大于5个像素点则认定为校准失败
		{
            printf("calibration fail! P%d offset:(%d,%d)\n",j,abs(x - calibration->lcd_x[j]),abs(y - calibration->lcd_y[j]));
            return -2;
        }
	}
    LCD_Adjustd_OK = 1;
    printf(" Calibration Parameter Calculation Completed !(LCD_Adjustd_OK=%d)\r\n",LCD_Adjustd_OK);
    printf(" InputPoins:P0(%d,%d) P1(%d,%d) P2(%d,%d) P3(%d,%d) P4(%d,%d)\n",calibration->touch_x[0],calibration->touch_y[0],calibration->touch_x[1],calibration->touch_y[1],calibration->touch_x[2],calibration->touch_y[2],calibration->touch_x[3],calibration->touch_y[3],calibration->touch_x[4],calibration->touch_y[4]);
    //printf("%10d %10d %10d \n %10d %10d %10d   %d\n",calibration->a[0],calibration->a[1],calibration->a[2],calibration->a[3],calibration->a[4],calibration->a[5],calibration->a[6]);
    printf(" X=(%d*x+%d*y+%d)/%d \r\n Y=(%d*x+%d*y+%d)/%d\r\n",calibration->a[1],calibration->a[2],calibration->a[0],calibration->a[6],calibration->a[4],calibration->a[5],calibration->a[3],calibration->a[6]);

    return  ESP_OK;
}

校准后触摸的效果:

相关推荐
zd8451015006 小时前
16bit ADC+DAC模拟量控制板第二方案设计
单片机·嵌入式硬件
星一工作室7 小时前
STM32项目分享:基于stm32的自动升降棋系统
stm32·单片机·嵌入式硬件
up向上up7 小时前
基于STM32温湿度采集Proteus仿真设计
stm32·单片机·proteus
d111111111d9 小时前
江协科技-PID基本原理-(学习笔记)-主页有所有STM32外设的笔记基本都是万字起步。
笔记·科技·stm32·单片机·嵌入式硬件·学习
wotaifuzao9 小时前
Nordic-nRF54L 系列架构全景:从蓝牙 6.0 到超低功耗设计详解
单片机·物联网·硬件架构·蓝牙·nordic
1+2单片机电子设计11 小时前
基于 STM32 的太阳能 MPPT 充电控制器设计
stm32·单片机·嵌入式硬件
Stanford_sun11 小时前
基于Zigbee的无线火灾报警系统(云平台版)
网络·嵌入式硬件·物联网·zigbee
CFZPL11 小时前
espidf用CMake文件构建项目
单片机·esp32
猪八戒1.012 小时前
9.5 【定时器】输入捕获
单片机·嵌入式硬件