一、芯片概述
CST816T 自电容触控芯片,采用高速 MCU 内核并内嵌 DSP 电路,结合自身的快速自电容感应技术,可广泛支持三角形在内的多种自电容图案,在其上实现单点手势和真实两点操作,实现极高灵敏度和低待机功耗。该芯片仅支持标准的 I2C 通讯协议标准,可实现 10Khz~400Khz 的可配通信速率。
二、芯片I2C驱动实现
芯片的 7bit 设备地址一般为 0x15,即设备写地址为 0x2A,读地址为 0x2B。为了保证通信的可靠性,建议最大使用 400Kbps 的通信速率。
不了解ESP32如何实现I2C的朋友们,可以先移步先查看研究下I2C在ESP32上的实现:
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;
-
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,®_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;
}
校准后触摸的效果:
