1. 硬件层
触摸屏硬件:触摸屏通常包括电容式或电阻式触摸屏,它们通过感应手指的触摸来产生信号。电容式触摸屏利用人体的电流感应工作,而电阻式触摸屏则通过压力改变电阻值来检测触摸。
传感器与接口:触摸屏包含多个触摸传感器,这些传感器通过总线接口(如I2C、SPI等)与主控芯片通信,将触摸数据(如位置、压力等)传输给软件层。
2. 软件层
输入子系统:Android内核中的输入子系统是一个关键组件,负责处理来自各种输入设备的数据,包括触摸屏。输入子系统从触摸屏驱动程序接收触摸数据,并将其转换为标准的输入事件,然后传递给上层应用程序。
触摸屏驱动程序:触摸屏驱动程序是Android内核中实现触摸屏功能的具体实现。它负责与硬件层通信,读取触摸传感器的数据,并通过输入子系统将其上报给系统。
3 . 触摸屏驱动流程
-
初始化与配置
设备树(DTS)配置:在设备树文件中配置触摸屏相关的硬件参数,如GPIO、I2C地址等。
驱动程序加载:在系统启动时,根据设备树配置加载触摸屏驱动程序。驱动程序会进行必要的初始化工作,如设置GPIO引脚、配置I2C接口等。
1.1 设置GPIO引脚:
打开 vendor\mediatek\proprietary\scripts\DrvGen.exe
选择 cendor\mediatek\proprietary\bootable\bootloader\lk\target\ $ (project) \dct\dct\codegen.dws 配置文件1.2 创建ft5x16,将供应商提供的驱动驱动资料拷贝到该目录下;
1.3 修改配置文件:CUSTOM_KERNEL_TOUCHPANEL其值由改为ft5x16,表明对应ft5x16子目录;
打开ft5x16.c文件,修改一下:
static struct i2c_board_info __initdata ft5x16_i2c_tpd={ I2C_BOARD_INFO("ft5x16", (0x70>>1))}; //"ft5x16"为设备名 ,设备地址为高7位
static struct tpd_driver_t tpd_device_driver = {
.tpd_device_name = "FT5x16",
.tpd_local_init = tpd_local_init,
.suspend = tpd_suspend,
.resume = tpd_resume,
#ifdef TPD_HAVE_BUTTON
.tpd_have_button = 1,
#else
.tpd_have_button = 0,
#endif
};
/* called when loaded into kernel */
static int __init tpd_driver_init(void) {
printk("MediaTek FT5x16 touch panel driver init\n");
/* 注册板级设备信息 */
i2c_register_board_info(IIC_PORT, &ft5x16_i2c_tpd, 1); //IIC_PORT表示i2c控制器号,由电路原理图可知TP设备连接到i2c控制器0,ft5x16_i2c_tpd为i2c设备结构,1表示该i2c_board_info个数
if(tpd_driver_add(&tpd_device_driver) < 0)
printk("add FT5x16 driver failed\n");
return 0;
}
1.4 I2C通信
新驱动编译进内核,启动内核后,我们怎样验证i2c接口能够正常通信呢?
系统启动后通过串口或adb shell进入系统命令行窗口,查询/sys/bus/i2c/devices目录下是否有0-0038信息,查询/sys/bus/i2c/drivers目录下是否存在‘ft5x16’设备名;先保证i2c能够正常通信;
1.5 中断触发
中断注册函数:
mt_eint_registration(CUST_EINT_TOUCH_PANEL_NUM, CUST_EINT_TOUCH_PANEL_TYPE, tpd_eint_interrupt_handler, 1);
//tpd_eint_interrupt_handler函数为中断回调函数
1.6 数据上报
当触摸屏产生中断的时候就会调用到该接口;然后在中断处理函数中唤醒运行在子线程中的等待队列,再通过子线程获取TP数据并上报到系统;
static DECLARE_WAIT_QUEUE_HEAD(waiter); //初始化等待队列
thread = kthread_run(touch_event_handler, 0, TPD_DEVICE); //新建线程
static int touch_event_handler(void *unused)
{
......
do
{
mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM);
set_current_state(TASK_INTERRUPTIBLE);
wait_event_interruptible(waiter,tpd_flag!=0); //等待队列进入休眠,等待唤醒
tpd_flag = 0;
set_current_state(TASK_RUNNING);
......
if (tpd_touchinfo(&cinfo, &pinfo)) //获取TP数据
{
//TPD_DEBUG("point_num = %d\n",point_num);
TPD_DEBUG_SET_TIME;
if(point_num >0)
{
for(i =0; i<point_num; i++)//only support 3 point
{
cinfo.x[i] = cinfo.x[i];
cinfo.y[i] = cinfo.y[i];
tpd_down(cinfo.x[i], cinfo.y[i], cinfo.id[i]); //上报按下数据
printk(KERN_DEBUG"----calibration----- X:%4d, Y:%4d, P:%4d \n", cinfo.x[i], cinfo.y[i], cinfo.id[i]);
}
input_sync(tpd->dev);
}
else
{
tpd_up(cinfo.x[0], cinfo.y[0]); //上报弹起数据
//TPD_DEBUG("release --->\n");
//input_mt_sync(tpd->dev);
input_sync(tpd->dev);
}
}
......
}while(!kthread_should_stop());
return 0;
}
注:如果TP获取到的数据比较乱的时候建议通过打开‘指针位置’功能进行查看,排除TP固件分辨与LCD没对应等问题;
4. TP驱动简要分析
MTK自己编写了一套TP框架,通过该框架管理TP设备,tpd_driver_add为框架的接口之一;系统通过tpd_driver_add添加驱动后会回调tpd_local_init函数;
static struct tpd_driver_t tpd_device_driver = {
.tpd_device_name = FT5x16,
.tpd_local_init = tpd_local_init, //初始化函数
.suspend = tpd_suspend,
.resume = tpd_resume,
#ifdef TPD_HAVE_BUTTON
.tpd_have_button = 1,
#else
.tpd_have_button = 0,
#endif
};
/* called when loaded into kernel */
static int __init tpd_driver_init(void) {
printk("MediaTek FT5x16 touch panel driver init\n");
i2c_register_board_info(0, &ft5x16_i2c_tpd, 1); //注册板级设备信息
if(tpd_driver_add(&tpd_device_driver) < 0) //添加驱动
printk("add FT5x16 driver failed\n");
return 0;
}
向系统注册i2c驱动后,如果找到对应的设备就会调用tpd_probe函数;
static int tpd_local_init(void)
{
TPD_DMESG("FTS I2C Touchscreen Driver (Built %s @ %s)\n", __DATE__, __TIME__);
if(i2c_add_driver(&tpd_i2c_driver)!=0) //注册i2c驱动
{
TPD_DMESG("FTS unable to add i2c driver.\n");
return -1;
}
TPD_DMESG("end %s, %d\n", __FUNCTION__, __LINE__);
tpd_type_cap = 1;
return 0;
}
static const struct i2c_device_id ft5x16_tpd_id[] = {{TPD_NAME,0},{}};
static struct i2c_driver tpd_i2c_driver = {
.driver = {
.name = TPD_NAME,
},
.probe = tpd_prob,
.remove = __devexit_p(tpd_remove),
.id_table = ft5x16_tpd_id,
.detect = tpd_detect,
};
static int __devinit tpd_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int retval = TPD_OK;
char data;
u8 report_rate=0;
int err=0;
int reset_count = 0;
u8 chip_id,i;
reset_proc:
i2c_client = client;
#ifdef MAIERXUN_TP_COM
if(touchpanel_flag){
return 0;
}
#endif
//复位
//power on, need confirm with SA
mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO);
mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ZERO);
msleep(5);
TPD_DMESG(" fts ic reset\n");
//打开TP电源
#ifdef TPD_POWER_SOURCE_CUSTOM
hwPowerOn(TPD_POWER_SOURCE_CUSTOM, VOL_3300, "TP");
#else
hwPowerOn(MT65XX_POWER_LDO_VGP2, VOL_3300, "TP");
#endif
mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO);
mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ONE);
#ifdef TPD_CLOSE_POWER_IN_SLEEP
hwPowerDown(TPD_POWER_SOURCE,"TP");
hwPowerOn(TPD_POWER_SOURCE,VOL_3300,"TP");
msleep(100);
#else /* 结束复位 */
mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO);
mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ZERO);
msleep(5);
TPD_DMESG(" fts ic reset\n");
mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO);
mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT);
mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ONE);
#endif
/* 初始化中断引脚 */
mt_set_gpio_mode(GPIO_CTP_EINT_PIN, GPIO_CTP_EINT_PIN_M_EINT);
mt_set_gpio_dir(GPIO_CTP_EINT_PIN, GPIO_DIR_IN);
mt_set_gpio_pull_enable(GPIO_CTP_EINT_PIN, GPIO_PULL_ENABLE);
mt_set_gpio_pull_select(GPIO_CTP_EINT_PIN, GPIO_PULL_UP);
/* 中断配置和注册 */
mt_eint_set_hw_debounce(CUST_EINT_TOUCH_PANEL_NUM, CUST_EINT_TOUCH_PANEL_DEBOUNCE_CN);
mt_eint_registration(CUST_EINT_TOUCH_PANEL_NUM, CUST_EINT_TOUCH_PANEL_TYPE, tpd_eint_interrupt_handler, 1); //注册中断处理函数,TP产生中断时就会回调tpd_eint_interrupt函数
mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM);
msleep(400);
err=i2c_smbus_read_i2c_block_data(i2c_client, 0x00, 1, &data);
TPD_DMESG("gao_i2c:err %d,data:%d\n", err,data);
if(err< 0 || data!=0)// reg0 data running state is 0; other state is not 0
{
TPD_DMESG("I2C transfer error, line: %d\n", __LINE__);
#ifdef TPD_RESET_ISSUE_WORKAROUND
if ( reset_count < TPD_MAX_RESET_COUNT )
{
reset_count++;
goto reset_proc;
}
#endif
//add at 20150330 by zhu
#ifdef MAIERXUN_TP_COM
touchpanel_flag=false;
#endif
return -1;
}
......
#ifdef VELOCITY_CUSTOM_FT5206
if((err = misc_register(&tpd_misc_device))) //注册混杂设备驱动
{
printk("mtk_tpd: tpd_misc_device register failed\n");
}
#endif
#ifdef TPD_AUTO_UPGRADE
printk("********************Enter CTP Auto Upgrade********************\n");
fts_ctpm_auto_upgrade(i2c_client);
#endif
thread = kthread_run(touch_event_handler, 0, TPD_DEVICE); //创建子线程,通过该子线程获取和上报数据
if (IS_ERR(thread))
{
retval = PTR_ERR(thread);
TPD_DMESG(TPD_DEVICE " failed to create kernel thread: %d\n", retval);
}
TPD_DMESG("FTS Touch Panel Device Probe %s\n", (retval < TPD_OK) ? "FAIL" : "PASS");
/* 初始化TP的P-sensor功能,暂不分析 */
#ifdef TPD_PROXIMITY
struct hwmsen_object obj_ps;
obj_ps.polling = 0;//interrupt mode
obj_ps.sensor_operate = tpd_ps_operate;
if((err = hwmsen_attach(ID_PROXIMITY, &obj_ps)))
{
APS_ERR("proxi_fts attach fail = %d\n", err);
}
else
{
APS_ERR("proxi_fts attach ok = %d\n", err);
}
#endif
#ifdef MAIERXUN_TP_COM
touchpanel_flag=true;
#endif
return 0;
}
中断处理函数
中断处理遵循中断上下文的设计原则,使得中断子程序只是简单唤醒等待队列就可以了,没有多余的操作;
static void tpd_eint_interrupt_handler(void)
{
//TPD_DEBUG("TPD interrupt has been triggered\n");
TPD_DEBUG_PRINT_INT;
tpd_flag = 1;
wake_up_interruptible(&waiter); //唤醒等待队列
}
子线程处理函数
中断与轮询:触摸屏驱动程序可以通过中断或轮询的方式从硬件层读取触摸数据。当触摸屏被触摸时,硬件会触发一个中断,驱动程序响应中断并读取数据;或者驱动程序定期轮询硬件以获取数据。
数据处理:驱动程序将读取到的原始触摸数据进行处理,转换为标准的输入事件格式(如ABS_X、ABS_Y等),并准备将其上报给输入子系统。
static int touch_event_handler(void *unused)
{
struct touch_info cinfo, pinfo;
int i=0;
struct sched_param param = { .sched_priority = RTPM_PRIO_TPD };
sched_setscheduler(current, SCHED_RR, ¶m);
#ifdef TPD_PROXIMITY
int err;
hwm_sensor_data sensor_data;
u8 proximity_status;
#endif
u8 state;
do //进入while循环进行睡眠-等待唤醒的操作
{
mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM); //中断使能(解除屏蔽)
set_current_state(TASK_INTERRUPTIBLE);
wait_event_interruptible(waiter,tpd_flag!=0); //进入睡眠等待唤醒
tpd_flag = 0;
set_current_state(TASK_RUNNING);
......
#ifdef TPD_PROXIMITY //TP的P-sensor功能,暂不分析
if (tpd_proximity_flag == 1)
{
i2c_smbus_read_i2c_block_data(i2c_client, 0xB0, 1, &state);
TPD_PROXIMITY_DEBUG("proxi_5206 0xB0 state value is 1131 0x%02X\n", state);
if(!(state&0x01))
{
tpd_enable_ps(1);
}
i2c_smbus_read_i2c_block_data(i2c_client, 0x01, 1, &proximity_status);
TPD_PROXIMITY_DEBUG("proxi_5206 0x01 value is 1139 0x%02X\n", proximity_status);
if (proximity_status == 0xC0)
{
tpd_proximity_detect = 0;
}
else if(proximity_status == 0xE0)
{
tpd_proximity_detect = 1;
}
TPD_PROXIMITY_DEBUG("tpd_proximity_detect 1149 = %d\n", tpd_proximity_detect);
if ((err = tpd_read_ps()))
{
TPD_PROXIMITY_DMESG("proxi_5206 read ps data 1156: %d\n", err);
}
sensor_data.values[0] = tpd_get_ps_value();
sensor_data.value_divide = 1;
sensor_data.status = SENSOR_STATUS_ACCURACY_MEDIUM;
if ((err = hwmsen_get_interrupt_data(ID_PROXIMITY, &sensor_data)))
{
TPD_PROXIMITY_DMESG(" proxi_5206 call hwmsen_get_interrupt_data failed= %d\n", err);
}
}
#endif
if (tpd_touchinfo(&cinfo, &pinfo)) //获取TP设备数据,并把数据保存在cinfob buf中
{
//TPD_DEBUG("point_num = %d\n",point_num);
TPD_DEBUG_SET_TIME;
if(point_num >0)
{
for(i =0; i<point_num; i++)//only support 3 point
{
printk(KERN_DEBUG"X:%4d, Y:%4d, P:%4d \n", cinfo.x[i], cinfo.y[i], cinfo.id[i]);
cinfo.x[i] = cinfo.x[i];
cinfo.y[i] = cinfo.y[i];
tpd_down(cinfo.x[i], cinfo.y[i], cinfo.id[i]); //按下数据处理
printk(KERN_DEBUG"----calibration----- X:%4d, Y:%4d, P:%4d \n", cinfo.x[i], cinfo.y[i], cinfo.id[i]);
}
input_sync(tpd->dev);
}
else
{
tpd_up(cinfo.x[0], cinfo.y[0]); //弹起数据处理
//TPD_DEBUG("release --->\n");
//input_mt_sync(tpd->dev);
input_sync(tpd->dev);
}
}
......
}while(!kthread_should_stop());
return 0;
}
事件上报与响应
事件上报:触摸屏驱动程序通过输入子系统将处理后的触摸事件上报给系统。这些事件包括触摸点的位置、压力等信息。
事件响应:上层应用程序通过监听输入事件来响应用户的触摸操作。例如,当用户用手指在屏幕上滑动时,应用程序会接收到相应的滑动事件,并据此执行相应的操作(如页面滚动)。
5. 触摸屏调试手段
1,使用系统自带的调试工具:
开发者选项:Android设备通常有一个“开发者选项”,其中包含了许多用于调试和性能优化的工具。通过开启“显示触摸操作”等选项,可以在屏幕上看到触摸的轨迹,帮助直观了解触摸操作的效果。
ADB(Android Debug Bridge):ADB是一个多功能的命令行工具,允许用户与连接的Android设备进行通信。通过ADB,可以执行各种调试命令,如查看日志、安装和调试应用等。
编写和运行测试程序:
开发者可以编写专门的测试程序来检测触摸屏的响应情况,如绘制触摸轨迹、检测触摸点的坐标和压力等。这些测试程序可以帮助识别触摸屏的硬件或软件问题。
查看和修改触摸屏配置文件:
在某些情况下,触摸屏的性能问题可能与配置文件有关。开发者可以查看和修改这些配置文件,以调整触摸屏的灵敏度、响应速度等参数。
2,getevent调试方法
getevent是Android系统中用于获取输入设备事件信息的工具,它可以捕获触摸屏、键盘等输入设备的原始事件数据。以下是使用getevent进行触摸屏调试的基本方法:
打开终端或命令提示符:
在PC上,可以通过ADB连接到Android设备,并在终端或命令提示符中执行getevent命令。
查看所有输入设备:
使用getevent -p命令列出所有可用的输入设备及其支持的事件类型。找到触摸屏对应的设备文件(如/dev/input/eventX)。
监视触摸屏事件:
使用getevent -lt /dev/input/eventX命令(其中X是触摸屏设备文件的编号)来实时监视触摸屏事件。这个命令会显示触摸屏的触摸、滑动、抬起等事件,以及相应的时间戳和事件代码。
分析事件数据:
通过分析getevent输出的数据,可以了解触摸屏的响应情况,包括触摸点的坐标、压力值、事件类型等。这有助于识别触摸屏的硬件或软件问题。
发送模拟事件:
虽然getevent主要用于获取事件信息,但可以使用sendevent命令向设备发送模拟的触摸、按键等事件。这在进行自动化测试或模拟用户操作时非常有用。
3,ADB 提供了一个更高级的 input 命令,用于模拟键盘和触摸屏输入,而不需要知道底层的设备文件和事件代码。例如:
模拟触摸按下(在屏幕的500,500位置):
adb shell input tap 500 500
模拟滑动(从屏幕的500,500位置滑动到1000,1000位置):
adb shell input swipe 500 500 1000 1000
input 命令是模拟触摸事件的一种更简单、更直观的方法,通常对于大多数开发者来说已经足够了。