Bootstrap

【Android驱动04】触摸屏touchscreen驱动移植以及调试方法

1. 硬件层

触摸屏硬件:触摸屏通常包括电容式或电阻式触摸屏,它们通过感应手指的触摸来产生信号。电容式触摸屏利用人体的电流感应工作,而电阻式触摸屏则通过压力改变电阻值来检测触摸。
传感器与接口:触摸屏包含多个触摸传感器,这些传感器通过总线接口(如I2C、SPI等)与主控芯片通信,将触摸数据(如位置、压力等)传输给软件层。
在这里插入图片描述2. 软件层

输入子系统:Android内核中的输入子系统是一个关键组件,负责处理来自各种输入设备的数据,包括触摸屏。输入子系统从触摸屏驱动程序接收触摸数据,并将其转换为标准的输入事件,然后传递给上层应用程序。
触摸屏驱动程序:触摸屏驱动程序是Android内核中实现触摸屏功能的具体实现。它负责与硬件层通信,读取触摸传感器的数据,并通过输入子系统将其上报给系统。

3 . 触摸屏驱动流程

  1. 初始化与配置

    设备树(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 命令是模拟触摸事件的一种更简单、更直观的方法,通常对于大多数开发者来说已经足够了。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;