zoukankan      html  css  js  c++  java
  • Linux触摸驱动分析

    测试平台

    宿主机平台:Ubuntu 12.04.4 LTS

    目标机:Easy-ARM IMX283

    目标机内核:Linux 2.6.35.3

    触摸屏基础知识

    一、结构

      上图是电阻触摸屏的一个侧面剖视图。手指触摸的表面是一个硬涂层,用以保护下面的PET层。PET层是很薄的有弹性的PET薄膜,当表面被触摸时它会向下弯曲,并使得下面的两层ITO涂层能够相互接触并在该点连通电路。两个ITO层之间是约千分之一英寸厚的一些隔离支点使两层分开。最下面是一个透明的硬底层用来支撑上面的结构,通常是玻璃或者塑料。
            电阻触摸屏的多层结构会导致很大的光损失,对于手持设备通常需要加大背光源来弥补透光性不好的问题,但这样也会增加电池的消耗。电阻式触摸屏的优点是它的屏和控制系统都比较便宜,反应灵敏度也很好。

    二、触摸坐标的计算

       ITO陶瓷层分为了上下两层,中间用隔离支点分开,这两层是X层和Y层。可以看成如下结构:

    当没有触摸按下时,X层和Y层是分离的,此时就测不到电压

    其中X层上X-到X+和Y-到Y+的电阻是均匀分布的,其电路等效图如下:

     

    1、计算Y坐标,在Y+电极施加驱动电压V,Y-接地,芯片通过X+测量接触点的电压。

    由于ITO层均匀导电,触点电压与V电压之比等于触点Y坐标与屏高度之比。

    y/height = Vx/Vdrv

    y = (Vx/Vdrv) * height

    2、计算X坐标,在X+电极施加驱动电压V, X-电极接地,Y+做为引出端测量得到接触点的电压,由于ITO层均匀导电,触点电压与Vdrive电压之比等于触点X坐标与屏宽度之比。

    x = (Vy/Vdrv) * width

    测得的电压通常由ADC转化为数字信号,再进行简单处理就可以做为坐标判断触点的实际位置。另外触点压力的计算在此不再赘述。

    触摸屏驱动实现

    Linux将触摸屏驱动放在输入子系统 input 中。

    input子系统

    接下来我们回顾以下 input 子系统。

    Linux内核的输入子系统为鼠标、键盘、触摸屏、游戏杆等输入设备提供了驱动框架。当程序员要为自己的输入设备编写驱动程序时,只需要实现从设备获取输入事件即可。

    linux输入子系统的体系结构可以分为三个层面,分别为:硬件驱动层、子系统核心层、事件处理层;

    1)设备驱动层

     在给输入设备编写驱动程序时,需要为输入设备实现一个设备驱动。设备驱动包含的设备信息有:

    (1) 设备的总线类型、厂商、 产品、版本号、名称等身份信息;
    (2) 设备可产生的事件类型;
    (3) 各事件类型的分量。
    当输入设备发生输入事件时,驱动程序要把输入事件向输入子系统报告。

     

    2)事件管理层

    分为事件类、 MOUSE 类、游戏杆类型,所 以内核源码为这三种类型的输入设备分别实现了 evdev 、 mousedev 、 joydev 事件管理器。

    3)核心层,即 drivers/input/input.c 起着桥梁的作用。

    下面我们来剖析触摸屏驱动实现过程

    触摸屏驱动分析 

    1 触摸屏 平台设备注册

    板级文件中实现平台设备的注册、平台设备资源、平台设备私有数据

    #if defined(CONFIG_TOUCHSCREEN_MXS) || defined(CONFIG_TOUCHSCREEN_MXS_MODULE)
    // 平台设备私有数据(adc相关参数)
    static struct mxs_touchscreen_plat_data mx28_ts_data = {
        .x_plus_chan = LRADC_TOUCH_X_PLUS,
        .x_minus_chan = LRADC_TOUCH_X_MINUS,
        .y_plus_chan = LRADC_TOUCH_Y_PLUS,
        .y_minus_chan = LRADC_TOUCH_Y_MINUS,
        .x_plus_val = BM_LRADC_CTRL0_XPULSW,
        .x_minus_val = BF_LRADC_CTRL0_XNURSW(2),
        .y_plus_val = BF_LRADC_CTRL0_YPLLSW(1),
        .y_minus_val = BM_LRADC_CTRL0_YNLRSW,
        .x_plus_mask = BM_LRADC_CTRL0_XPULSW,
        .x_minus_mask = BM_LRADC_CTRL0_XNURSW,
        .y_plus_mask = BM_LRADC_CTRL0_YPLLSW,
        .y_minus_mask = BM_LRADC_CTRL0_YNLRSW,
    };
    
    // 平台设备资源(寻址地址空间、中断等资源)
    static struct resource mx28_ts_res[] = {
        {
         .flags = IORESOURCE_MEM,
         .start = LRADC_PHYS_ADDR,
         .end   = LRADC_PHYS_ADDR + 0x2000 - 1,
         },
        {
         .flags = IORESOURCE_IRQ,
         .start = IRQ_LRADC_TOUCH,
         .end   = IRQ_LRADC_TOUCH,
         },
        {
         .flags = IORESOURCE_IRQ,
         .start = IRQ_LRADC_CH5,
         .end   = IRQ_LRADC_CH5,
         },
    };
    
    static void __init mx28_init_ts(void)
    {
        struct platform_device *pdev;
    
        pdev = mxs_get_device("mxs-ts", 0);    //获取"mxs-ts"设备
        if (pdev == NULL || IS_ERR(pdev))
            return;
        pdev->resource = mx28_ts_res;
        pdev->num_resources = ARRAY_SIZE(mx28_ts_res);
        pdev->dev.platform_data = &mx28_ts_data;
        mxs_add_device(pdev, 3);            //添加到注册设备列表
    }
    #else
    static void __init mx28_init_ts(void)
    {
        ;
    }
    #endif

    2 触摸屏 平台驱动注册

    开发板的触摸驱动位置 driversinput ouchscreenmxs-ts.c

    static struct platform_driver mxs_ts_driver = {
        .probe        = mxs_ts_probe,
        .remove        = __devexit_p(mxs_ts_remove),
    #ifdef CONFIG_PM
        .suspend    = mxs_ts_suspend,
        .resume        = mxs_ts_resume,
    #endif
        .driver        = {
            .name    = "mxs-ts",
        },
    };
    
    static int __init mxs_ts_init(void)
    {
        return platform_driver_register(&mxs_ts_driver);    //平台驱动注册,"mxs-ts"匹配触发 mxs_ts_probe
    }

    驱动安装时触发平台驱动注册,与平台设备 "mxs-ts" 匹配后触发 mxs_ts_probe函数

    mxs_ts_probe 主要实现:

    1.通过传递的平台设备私有数据来设置 input设备驱动参数,然后通过 input_register_device 注册该触摸设备;

    2.获取平台设备资源,配置 lradc 及其中断;
    如 
    request_irq(info->touch_irq, ts_handler, IRQF_DISABLED, "mxs_ts_touch", info);

    当外部触摸时,触发 adc 中断,进入 ts_handler 中断函数,读取对应的 adc 值,然后通过上报给事件管理器

    input_report_abs(info->idev, ABS_Y, info->x); //info->x 反应y方向的变化
    input_report_abs(info->idev, ABS_X, info->y);
    input_report_abs(info->idev, ABS_PRESSURE, pressure);
    input_sync(info->idev);

    3. input 驱动机制浅析

    1 首先时 input 子系统初始化时的字符设备注册 (driversinputinput.c),这样就有了 dev/input 主设备

    static int __init input_init(void)
    {
        int err;
    
        input_init_abs_bypass();
    
        err = class_register(&input_class);
        if (err) {
            printk(KERN_ERR "input: unable to register input_dev class
    ");
            return err;
        }
    
        err = input_proc_init();
        if (err)
            goto fail1;
    
        err = register_chrdev(INPUT_MAJOR, "input", &input_fops);    //创建主设备号为13的"input"字符设备
        if (err) {
            printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
            goto fail2;
        }
    
        return 0;
    
     fail2:    input_proc_exit();
     fail1:    class_unregister(&input_class);
        return err;
    }

    下面来分析 input_register_handle 和 input_register_device

    其中 input_register_device 由设备驱动调用,生成子设备

    input_register_handle 由事件管理器调用

    在 input_register_device 中

    list_add_tail(&dev->node, &input_dev_list);    //这个dev放到input_dev_list链表中 
    list_for_each_entry(handler, &input_handler_list, node) 
      input_attach_handler(dev, handler);

    list_for_each_entry()函数会将每个input_handle从链表中取出,放到handler中,最后会调用input_attach_handler()函数,将每个input_handle的id_table进行判断,若两者支持便进行连接。

    在 input_register_handle 中

    list_add_tail(&handler->node, &input_handler_list);  //将这个input_handler放到input_handler_list链表中
    list_for_each_entry(dev, &input_dev_list, node) 
      input_attach_handler(dev, handler);

    list_for_each_entry()函数会将每个dev从链表中取出,放到dev中,最后会调用input_attach_handler()函数,将每个dev与handle的id_table进行判断,若两者支持便进行连接。

    触摸的事件管理器实现是 evdev.c

    evdev驱动模块支持时,就会 

    static int __init evdev_init(void)
    {
        return input_register_handler(&evdev_handler);
    }
     
    evdev_connect()函数,对

    input_attach_handler()函数先看是否匹配 input_match_device(handler, dev); //匹配两者

    然后调用 handler->connect(handler, dev, id); 进行连接

    static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
    {
        const struct input_device_id *id;
        int error;
    
        id = input_match_device(handler, dev);    //匹配两者
        if (!id)
            return -ENODEV;
    
        error = handler->connect(handler, dev, id);
        if (error && error != -ENODEV)
            printk(KERN_ERR
                "input: failed to attach handler %s to device %s, "
                "error: %d
    ",
                handler->name, kobject_name(&dev->dev.kobj), error);
    
        return error;
    }

    如下图所示,开发板使用的是4线电阻触摸屏,该4线连接在ADC引脚上,该引脚专门是用来接收模拟输入信号.

  • 相关阅读:
    Android-View动画
    Android-RemoteView-桌面小部件
    系统的Drawable(四)-LayerListDrawable
    Android-Drawable(三)
    系统的Drawable(二)-Selector
    系统的Drawable(一)
    Android View事件分发-从源码分析
    打游戏要存进度-备忘录模式
    Java 内部类.md
    docker 常用 命令
  • 原文地址:https://www.cnblogs.com/silencehuan/p/11248120.html
Copyright © 2011-2022 走看看