• linux驱动移植LCD触摸屏驱动案例


    一、触摸屏种类

    触摸屏的基本原理是,用手指或其他物体触摸安装在显示器前端的触控屏时,所触摸的位置(以坐标形式)由触摸屏控制器检测,并通过接口(如RS-232串行口)送到CPU,从而确定输入的信息。

    触摸屏系统一般包括触摸屏控制器(卡)和触摸检测装置两个部分:

    • 触控屏控制器(卡)的主要作用是从触摸点检测装置上接收触摸信息,并将它转换成触点坐标,再送给CPU,它同时能接收CPU发来的命令并加以执行:
    • 触摸检测装置一般安装在显示器的前端,主要作用是检测用户的触摸位置,并传送给触控屏控制卡;

    1.1 电阻触摸屏

    电阻类触摸屏的关键在于材料科技。电阻屏根据引出线数多少,分为四线、五线、六线等多线电阻触摸屏。下面以四线电阻式触摸屏为例介绍。

    四线电阻式触摸屏的结构如图,在玻璃或丙烯酸基板上覆盖有两层透平,均匀导电的ITO层,分别做为X电极和Y电极,它们之间由均匀排列的透明格点分开绝缘。其中下层的ITO与玻璃基板附着,上层的ITO附着在PET薄膜上。X电极和Y电极的正负端由“导电条”(图中黑色条形部分)分别从两端引出,且X电极和Y电极导电条的位置相互垂直。引出端X-,X+,Y-,Y+一共四条线,这就是四线电阻式触摸屏名称的由来。

    当有物体接触触摸屏表面并施以一定的压力时,上层的ITO导电层发生形变与下层ITO发生接触,该结构可以等效为上图中的电路。

    计算触点的X,Y坐标分为如下两步:

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

     

     所以:

    $$x=\frac{V_{x_+}}{V_{driver}} \times width_{screen}$$

    $$y=\frac{V_{y_+}}{V_{driver}} \times height_{screen}$$

    测得的电压通常由ADC转化为数字信号,再进行简单处理就可以做为坐标判断触点的实际位置。

    四线电阻式触摸屏除了可以得到触点的X/Y坐标,还可以测得触点的压力,这是因为top layer施压后,上下层ITO发生接触,在触点上实际是有电阻存在的,如图3的Rtouch。压力越大,接触越充分,电阻越小,通过测量这个电阻的大小可以量化压力大小。

    1.2 电容触摸屏

    是利用人体的电流感应进行工作的。电容式触摸屏是是一块四层复合玻璃屏,玻璃屏的内表面和夹层各涂有一层ITO,最外层是一薄层矽土玻璃保护层,夹层ITO涂层作为工作面,四个角上引出四个电极,内层ITO为屏蔽层以保证良好的工作环境。
    当手指触摸在金属层上时,由于人体电场,用户和触控屏表面形成以一个耦合电容,对于高频电流来说,电容是直接导体,于是手指从接触点吸走一个很小的电流。这个电流分从触控屏的四角上的电极中流出,并且流经这四个电极的电流与手指到四角的距离成正比,控制器通过对这四个电流比例的精确计算,得出触摸点的位置。

    二、硬件分析

    2.1 硬件接线

    由于我们Mini2440开发板使用的S3C2440 SOC,只支持四线电阻式触摸屏。这里我们使用的LCD型号为LCD-T35(TD035STEB4),该4线连接在S3C2440的AIN4~AIN7引脚上,该引脚专门是用来接收模拟输入信号:

    引脚说明:

    • YM: (Y Minus)触摸屏的Y坐标的负线,也可以用Y -表示;
    • YP : (Y Power)触摸屏的Y坐标的正线, 也可以用Y+表示;
    • XM: (X Minus)触摸屏的Y坐标的负线, 也可以用X-表示;
    • XP : (X Power)触摸屏的Y坐标的正线, 也可以用X+表示;

    2.2 x坐标获取

    如下图,  把XP接3.3V 、XM接0V,YP和YM悬空。我们以按压X坐标的中间位置,X层和Y层便闭合,此时YP就会输出当前X坐标值的1.66V给CPU 。

    2.3 y坐标获取

    如下图, 把YP接3.3V , YM接0V,XP和XM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时XP就会输出当前X坐标值的1.66V给CPU 。

    2.4 s3c2440的触摸屏接口

    查看s3c2440手册第16章ADC & TOUCH SCREEN INTERFACE章节,触摸屏A/D转换器和触摸屏接口框图如下:

    触摸屏接口模式共有四种:

    • 普通转换模式:单转换模式是最适合的通用的ADC转换,此模式可以通过设置ADCCON完成初始化,通过读写ADCDAT0完成;
    • 分离的X/Y方向转换模式;
    • 自动顺序X/Y方向转换模式:触摸屏控制器顺序变换触摸X方向和Y方向,在该模式中触摸屏控制器会自动把X坐标写入到ADCDAT0、把Y坐标写入ADCDAT1,并且产生INT_ADC中断源给中断控制器;
    • 等待中断模式:当点击触摸屏时产生INT_TC中断信号,等待中断模式设置值为ADCTSC=0xD3,触摸屏控制器产生中断信号后,必须清除等待中断模式(XY_PST设置为0)。

    ADC的工作频率最大为2.5MHZ,需要设置寄存器ADCCON->PRSCVL更改分频系数。A/D转换时间:

    当PCLK频率在50MHz并且预分频器的值为49时,10位的转换时间为:

    $$A/D转换器频率=\frac{50MHZ}{49+1}=1MHz$$

    $$转换时间=frac{1}{1M/5}=\frac{1}{200k}=5us$$

    2.5 ADC 和触摸屏接口特殊寄存器

    ADC控制寄存器(ADCCON)

    寄存器信息:

    寄存器名

    地址

    是否读写

    描述

    复位值

    ADCCON

    0x58000000

    R/W

    ADC控制寄存器

    0x3FC4

    寄存器位信息:

    ADCCON 描述 初始状态
    ECFLG [15]

    转换结束标志位(只读)

    0 = A/D正在转换   1 = A/D转换已结束

     0
    PRSCEN [14]

    A/D转换器预分频器使能

    0 = 禁止    1 = 使能

     0
    PRSCVL [13:6]

    A/D转换器预分频值

    数值范围: 0~255

    注意:ADC频率应该设置为低于PCLK的1/5

     0xFF
    SEL_MUX [5:3]

    模拟输入通道选择

    000 = AIN0  001= AIN1  010 = AIN2  011 = AIN3
    100 = YM     101 = yp     110 = XM    111 = XP 

     0
    STDBM [2]

    待机模式选择

    0 = 正常工作模式   1 = 待机模式 

     1
    READ_ START [1]

    读启动 A/D 转换

    0 = 禁止读启动操作 1 = 使能读启动操作

    0
    ENABLE_START [0]

    使能A/D转换启动。如果 READ_START 为使能,则此值无效

    0 = 无操作      1 =  A/D 转换启动且此位在启动后被清零

    0

    ADC触摸屏控制寄存器(ADCTSC)

    寄存器信息:

    寄存器名

    地址

    是否读写

    描述

    复位值

    ADCTSC

    0x58000004

    R/W

    ADC触摸屏控制寄存器

    0x58

    寄存器位信息:

    ADCTSC 描述 初始状态
    UD_SEB [8]

    检测笔尖起落状态

    0 = 检测笔尖落下中断信号 1 = 检测笔尖抬起中断信号

     0
    YM_SEN [7]

    YM 开关使能

    0 = YM 输出驱动器禁止 1 = YM 输出驱动器使能

     0
    YP_SEN [6]

    YP 开关使能

    0 = YP 输出驱动器禁止 1 = YP 输出驱动器使能

    1
    XM_SEN [5]

    XM 开关使能

    0 = XM 输出驱动器禁止 1 = XM 输出驱动器使能

     0
    XP_SEN [4]

    XP 开关使能

    0 = XP 输出驱动器禁止 1 = XP 输出驱动器使能

     1
    PULL_UP [3]

    上拉开关使能

    0 = XP 上拉使能   1 = XP 上拉禁止

    1
    AUTO_PST [2]

    0 = XP 上拉使能 1 = XP 上拉禁止

    0 = 正常 ADC 转换    1 = 自动顺序X方向和Y方向测量

    0
    XY_PST [1:0]

     手动测量X方或Y方向

    00 = 无操作模式    01 = X 方向测量

    10 = Y 方向测量    11 = 等待中断模式

    0

    ADC转换数据寄存器(ADCDAT0)

    寄存器信息:

    寄存器名

    地址

    是否读写

    描述

    复位值

    ADCDAT0

    0x5800000C

    R

    ADC转换数据寄存器

    -

    寄存器位信息:

    ADCDAT0 描述 初始状态
    UPDOWN [15]

    等待中断模式中笔尖的起落状态

    0 = 笔尖落下态 1 = 笔尖抬起态

    -
    AUTO_PST [14]

    自动顺序 X 方向和 Y 方向转换

    0 = 正常 ADC 转换 1 = 顺序 X 方向、Y 方向测量

     -
    XY_PST [13:12]

    手动 X 方向或 Y 方向测量

    00 = 无操作模式 01 = X 方向测量

    10 = Y 方向测量 11 = 等待中断模式

    -
    保留 [11:10]

    保留

    -
    XPDATA [9:0]

    X 方向转换数值(包括正常 ADC 转换数值)

    数值范围:0 至 3FF

    -

    ADC转换数据寄存器(ADCDAT1)

    寄存器信息:

    寄存器名

    地址

    是否读写

    描述

    复位值

    ADCDAT1

    0x58000010

    R

    ADC转换数据寄存器

    -

    寄存器位信息:

    ADCDAT1 描述 初始状态
    UPDOWN [15]

    等待中断模式中笔尖的起落状态

    0 = 笔尖落下态 1 = 笔尖抬起态

    -
    AUTO_PST [14]

    自动顺序X方向和Y方向转换

    0 = 正常ADC 转换  1 = 顺序X方向、Y方向测量

     -
    XY_PST [13:12]

    手动X方向或Y方向测量

    00 = 无操作模式 01 = X 方向测量

    10 = Y 方向测量 11 = 等待中断模式

    -
    保留 [11:10]

    保留

    -
    YPDATA [9:0]

    Y方向转换数值(包括正常 ADC 转换数值)

    数值范围:0 至 3FF

    -

    三、触摸屏驱动编写步骤

    3.1 输入子系统

    对于触摸屏驱动,也是使用输入子系统框架进行编写,输入子系统相关内容在linux驱动移植-输入子系统示例里详细分析了:

    右边的驱动事件处理,内核是已经写好了的,所以我们的触摸屏只需要写具体的驱动设备,然后内核会与触摸屏驱动tsdev.c自动连接 。

    3.2 驱动入口函数

    (1) 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;

    (2) 初始化dev;

    • 通过set_bit设置支持的事件类型:按键事件EV_KEY,绝对位移事件EV_ABS;
    • 通过input_set_capability设置input设备支持的按键类型的事件包括:触摸屏笔尖按下;
    • 通过input_set_abs_params设置input设备支持的绝对位移类型的事件包括:ABS_X、ABS_Y、ABS_PRESSURE;

    (3) 然后调用input_register_device注册这个设备;

    (4) 初始化触摸屏相关的硬件:

    • 开启ADC时钟,使用clk_get 和clk_enable函数;
    • ioremap获取寄存器地址,设置寄存器ADCCON =(1<<14)|(49<<6),分频值设置为49;
    • 设置寄存器ADCDLY=0xffff,,ADC启动延时时间设为最大值,使触摸按压更加稳定;
    • 开启IRQ_TC中断、并通过IRQ_ADC中断获取XY坐标;
    • 初始化定时器,增加触摸滑动功能;
    • 最后设置寄存器ADCTSC=0x0D3,开启IRQ_TC中断;

    3.3 驱动出口函数

    • 移除定时器;
    • 释放中断;
    • iounmap注销地址;
    • 卸载驱动、注销dev;

    3.4 IRQ_TC中断函数

    • 若判断触摸屏当前为松开状态,设置寄存器ADCTSC =0xD3,即设置触摸屏为等待笔尖按下中断模式;
    • 若判断触摸屏当前为按下状态,设置为XY自动转换模式,启动一次ADC转换(ADC转换成功后,会进入IRQ_ADC中断函数);

    3.5 IRQ_ADC中断函数

    • 若判断触摸屏当前为松开状态,设置寄存器ADCTSC =0xD3,即设置触摸屏为等待笔尖按下中断模式;
    • 若判断触摸屏当前为按下状态,获取ADCDAT0/1的低10位,计算出X/Y方向坐标值。多次测量(多次设置为XY自动转换模式并启动ADC转换,转换完成就会再次进入IRQ_ADC中断函数),测量4次后判断精度,满足则上报数据,否则设置成等待笔尖抬起中断模式,10ms后启动定时器超时函数(如果10ms之后仍然还没有笔尖抬起,将会再次进行坐标测量,从而可以处理触摸滑动的问题);

    3.6 定时器超时函数

    • 若判断触摸屏当前为松开状态,设置寄存器ADCTSC =0xD3,即设置触摸屏为等待笔尖按下中断模式 ;
    • 若判断触摸屏当前为按下状态,设置为XY自动转换模式,启动一次ADC转换(ADC转换成功后,会进入IRQ_ADC中断函数);

    四、编写代码

    4.1 创建项目

    在/work/sambashare/drivers路径下创建12.lcd_touch项目,并创建lcd_touch_dev.c和Makefile文件。

    4.2 lcd_touch_dev.c

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/io.h>
    #include <linux/uaccess.h>
    #include <linux/gpio.h>
    #include <linux/irq.h>        // 包含了mach/irqs.h
    #include <linux/interrupt.h>
    #include <linux/gpio/machine.h>
    #include <mach/gpio-samsung.h>
    #include <linux/input.h>
    #include <linux/timer.h>
    #include <linux/clk.h>
    
    
    #define IRQF_SAMPLE_RANDOM       0x00000040
    
    /*
     * s3c2440 APC和触摸屏相关寄存器
     */
    struct s3c_ts_regs{
        unsigned long adccon;
        unsigned long adctsc;
        unsigned long adcdly;
        unsigned long adcdat0;
        unsigned long adcdat1;
        unsigned long adcupdn;
    };
    
    /* 定义一个input_dev结构体 */
    static struct input_dev *s3c_ts_dev;
    /* 寄存器 */
    static struct s3c_ts_regs *s3c_ts_regs;
    /* 定时器 */
    static struct timer_list s3c_ts_timer;
    
    /* 进入等待笔尖按下中断模式 */
    static void enter_wait_pen_down_mode(void)
    {
        /* 设置寄存器ADCTSC=0x0d3,开启INT_TC中断,笔尖按下触发*/
        s3c_ts_regs->adctsc = 1 << 7 | 1 << 6 |  1<<4  | 3 << 0;
    }
    
    /* 进入等待笔尖抬起中断模式 */
    static void enter_wait_pen_up_mode(void)
    {
        /* 设置寄存器ADCTSC=0x01d3,开启IRQ_TC中断,笔尖抬起触发*/
        s3c_ts_regs->adctsc = 1<< 8 | 1 << 7 | 1 << 6 |  1<<4  | 3 << 0;
    }
    
    /* 进入XY自动转换模式,ADC转换完成后,触发INT_ADC中断 */
    static void enter_measure_xy_mode(void)
    {
        /* 启动XY自动转换 */
        s3c_ts_regs->adctsc = (1<<3)|(1<<2);
         /* 启动1次ADC转换,开启一次ADC转换,当ADC转换成功该位清0 */
        s3c_ts_regs->adccon |= (1<<0);
    }
    
    
    /*
     * IRQ_TC中断处理服务
     * 笔尖按下或者抬起进入
     */
    static irqreturn_t tc_handler(int irq, void *dev_id)
    {
        //如果触摸屏当前为松开状态
       if (s3c_ts_regs->adcdat0 & (1<<15))
           {
               /*上报压力值为0,等价 input_event(s3c_ts_dev, EV_ABS, ABS_PRESSURE,  0) */
               input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
               /*上报BTN_TOUCH按键值松开,等价 input_event(s3c_ts_dev, EV_KEY, BTN_TOUCH,  0) */
               input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
               /*上报同步事件,通知系统有事件上报 */
               input_sync(s3c_ts_dev);
               /* 进入等待笔尖按下中断模式 */
               enter_wait_pen_down_mode();
    //           printk("tc_handler up\n");
           }
           else
           {
               /* 进入测量X/Y坐标模式并启动一次ADC */
               enter_measure_xy_mode();
    //           printk("tc_handler down\n");
           }
    
        return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    /*
     * 计算若干次的采样值是否存在较大误差,如果存在较大误差,放弃这次采样
     */
    static int s3c_filter_ts(int x[], int y[])
    {
    // 定义最大误差
    #define ERR_LIMIT 10
    
        int avr_x, avr_y;
        int det_x, det_y;
    
        avr_x = (x[0] + x[1])/2;
        avr_y = (y[0] + y[1])/2;
    
        det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
        det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
    
        if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
            return 0;
    
        avr_x = (x[1] + x[2])/2;
        avr_y = (y[1] + y[2])/2;
    
        det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
        det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
    
        if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
            return 0;
    
        return 1;
    }
    
    /*
     * IRQ_ADC中断处理服务 获取x、y坐标
     * 自动顺序X/Y方向转换模式,ADC转换成功后,会进入IRQ_ADC中断函数(系统自动把X坐标写入到ADCDAT0、把Y坐标写入ADCDAT1)
     */
    static irqreturn_t adc_handler(int irq, void *dev_id)
    {
        /* 计数 */
        static int cnt=0;
        /* 保存x、y  */
           static int x[4],y[4];
    
        //如果触摸屏当前为松开状态
           if (s3c_ts_regs->adcdat0 & (1<<15))
           {
               /*上报压力值为0,等价 input_event(s3c_ts_dev, EV_ABS, ABS_PRESSURE,  0) */
            input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
               /*上报BTN_TOUCH按键值松开,等价 input_event(s3c_ts_dev, EV_KEY, BTN_TOUCH,  0) */
            input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
               /*上报同步事件,通知系统有事件上报 */
            input_sync(s3c_ts_dev);
            /* 进入等待笔尖按下中断模式 */
            enter_wait_pen_down_mode();
               cnt = 0;
           }
           else
           {   // 笔尖按下后 连续测量4次
               // x坐标
               x[cnt]=s3c_ts_regs->adcdat0 & 0x3ff;
               // y
               y[cnt]=s3c_ts_regs->adcdat1 & 0x3ff;
               cnt++;
            // 4次求平均
               if(cnt == 4){
                // 计算若干次的采样值是否存在较大误差,如果存在较大误差,放弃这次采样
                   if (s3c_filter_ts(x, y)){
                       //上报X方向值
                       input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
                       //上报Y方向值
                       input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
                       //上报压力方向值
                       input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
                       //上报BTN_TOUCH按键按下
                       input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
                       //上报同步事件,通知系统有事件上报
                       input_sync(s3c_ts_dev);
                       printk("X:  %04d,y:  %04d \n",(x[0]+x[1]+x[2]+x[3])/4,(y[0]+y[1]+y[2]+y[3])/4);      //中值滤波
                   }
                   cnt = 0;
                   /* 进入等待笔尖抬起中断模式 */
                   enter_wait_pen_up_mode();
                   /* 启动定时器处理长按/滑动的情况 10ms后如果仍然没有笔尖抬起,再次进行测量坐标 */
                   mod_timer(&s3c_ts_timer, jiffies + HZ/100);
               }
               else
               {
                   /* 进入测量X/Y坐标模式并启动一次ADC */
                   enter_measure_xy_mode();
               }
           }
    
        return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    /*
     * 定时器超时函数
     * 将输入转换为转换为统一事件形式
     */
    static void s3c_ts_timer_function(struct timer_list *t)
    {
        /* 如果触摸屏当前为松开状态 */
        if (s3c_ts_regs->adcdat0 & (1<<15))
        {
            /* 上报压力值为0,等价 input_event(s3c_ts_dev, EV_ABS, ABS_PRESSURE,  0) */
            input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
               /*上报BTN_TOUCH按键值松开,等价 input_event(s3c_ts_dev, EV_KEY, BTN_TOUCH,  0) */
            input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
               /*上报同步事件,通知系统有事件上报 */
            input_sync(s3c_ts_dev);
            /* 进入等待笔尖按下中断模式 */
            enter_wait_pen_down_mode();
        }
        else
        {
             /* 进入测量X/Y坐标模式并启动一次ADC */
             enter_measure_xy_mode();
        }
    }
    
    /*
     * 入口函数
     */
    static int s3c_ts_init(void)
    {
        int err;
        struct clk * clk;
        printk("touch screen driver init\n");
    
        /* 向内核 申请input_dev结构体 */
        s3c_ts_dev = input_allocate_device();
    
        /* 设置input_dev、 输入事件code定义在include/uapi/linux/input-event-codes.h */
        set_bit(EV_KEY,s3c_ts_dev->evbit);       // 支持按键事件
        set_bit(EV_ABS,s3c_ts_dev->evbit);       // 支持绝对位移事件
        input_set_capability(s3c_ts_dev,EV_KEY,BTN_TOUCH);   //触摸屏笔尖按下
        input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);//s3c2440手册ADC是10位,所以第四个参数设置为3FF
        input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
        input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
    
        /* 注册input_dev */
        err = input_register_device(s3c_ts_dev);
        if (err) {
           printk("input touch screen driver registration failed\n");
           /* 释放驱动结构体 */
           input_free_device(s3c_ts_dev);
           return err;
        } else {
            printk("input touch screen driver registered successfully\n");
        }
    
        /* 使能时钟(设置CLKCON[15]) */
        clk = clk_get(NULL,"adc");
        clk_prepare_enable(clk);
    
        /* 设置s3c2440的ADC/ts寄存器 */
        s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
        s3c_ts_regs->adccon = (1<<14)|(49<<6);   // 预分频器使能,分频值为49  1MHZ
        s3c_ts_regs->adcdly = 0xffff;
    
        /* 注册中断,这里指定了中断线程化处理函数 */
        request_irq(IRQ_TC, tc_handler, IRQF_SAMPLE_RANDOM, "ts_tc", 0);
        request_irq(IRQ_ADC, adc_handler, IRQF_SAMPLE_RANDOM, "ts_adc", 0);
    
        /* 初始化定时器 */
        timer_setup(&s3c_ts_timer,s3c_ts_timer_function,0);
        add_timer(&s3c_ts_timer);
    
        /* 进入等待笔尖按下中断模式*/
        enter_wait_pen_down_mode();
    
        return 0;
    }
    
    /*
     * 出口函数
     */
    static void __exit s3c_ts_exit(void)
    {
        printk("touch screen driver exit\n");
    
         /* 删除定时器 */
        del_timer(&s3c_ts_timer);
    
         /* 释放中断 */
        free_irq(IRQ_TC, NULL);
        free_irq(IRQ_ADC, NULL);
    
        /* 注销虚拟地址 */
        iounmap(s3c_ts_regs);
    
         /* 卸载类下的驱动设备 */
        input_unregister_device(s3c_ts_dev);
        /* 释放驱动结构体 */
        input_free_device(s3c_ts_dev);
        return;
    }
    
    module_init(s3c_ts_init);
    module_exit(s3c_ts_exit);
    MODULE_LICENSE("GPL");

    4.3 Makefile

    KERN_DIR :=/work/sambashare/linux-5.2.8
    all:
        make -C $(KERN_DIR) M=`pwd` modules 
    clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order
    
    obj-m += lcd_touch_dev.o

    五、测试

    我们在linux驱动移植-LCD驱动分析这一节的基础上进行试验。

    5.1 配置内核

    配置内核,移除将内核自带的触摸屏驱动:

    root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig

     Device Drivers  --->

    • Input device support  -->
      • <*>.Touchscreens  -->
        • <> Samsung S3C2410/generic touchscreen input driver
    • Graphics support  --->
      • <*> Bootup logo
      • Frame buffer Devices
        • <*>Support for frame buffer devices --->
        •  <*> S3C2410 LCD framebuffer support
        •  <*> Samsung S3C framebuffer support

    保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

    mv s3c2440_defconfig ./arch/arm/configs/

    5.2 编译内核

    此时重新执行:

    make distclean
    make s3c2440_defconfig    
    make uImage V=1

    将uImage复制到tftp服务器路径下:

     cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

    5.3  烧录内核

    开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,下载内核到内存,并写NAND FLASH:

    tftp 30000000 uImage
    nand erase.part kernel
    nand write 30000000 kernel

    下载完成后,重启开发板,内核启动完成后会在显示屏上看到启动logo。

    5.4  编译LCD触摸屏驱动

    在12.lcd_touch_dev路径下编译:

    root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev# cd /work/sambashare/drivers/12.lcd_touch_dev
    root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev# make

    拷贝驱动文件到nfs文件系统:

    root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev# cp lcd_touch_dev.ko  /work/nfs_root/rootfs/

    5.5 安装驱动

    重启开发板,加载lcd驱动,执行如下命令:

    insmod lcd_touch_dev.ko

    运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:

    [root@zy:/]# cat /proc/interrupts
               CPU0       
     29:      21110       s3c  13 Edge      samsung_time_irq
     32:          0       s3c  16 Edge      s3c2410-lcd
     42:          0       s3c  26 Edge      ohci_hcd:usb1
     43:          0       s3c  27 Edge      s3c2440-i2c.0
     55:       1460   s3c-ext   7 Edge      eth0
     74:         14  s3c-level   0 Edge      s3c2440-uart
     75:        117  s3c-level   1 Edge      s3c2440-uart
     83:          0  s3c-level   9 Edge      ts_tc
     84:          0  s3c-level  10 Edge      ts_adc
     87:          0  s3c-level  13 Edge      s3c2410-wdt

    查看设备节点文件:

    [root@zy:/]# ls /dev/input -l
    total 0
    crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0

    5.6 测试

    我们滑动屏幕,从下往上滑动,驱动程序会输出如下信息:

    X:  0477,y:  0151 
    X:  0484,y:  0158 
    X:  0487,y:  0161 
    X:  0497,y:  0169 
    X:  0495,y:  0172 
    X:  0495,y:  0173 
    X:  0497,y:  0181 
    X:  0500,y:  0193 
    X:  0503,y:  0206 
    X:  0503,y:  0211 
    X:  0499,y:  0221 
    X:  0506,y:  0234 
    X:  0505,y:  0243 
    X:  0508,y:  0254 
    X:  0512,y:  0271 
    X:  0506,y:  0276 
    X:  0505,y:  0295 
    X:  0509,y:  0302 
    X:  0506,y:  0350 
    X:  0505,y:  0381 
    X:  0510,y:  0424 
    X:  0504,y:  0433 
    X:  0504,y:  0449 
    X:  0500,y:  0459 
    X:  0498,y:  0488 
    X:  0502,y:  0511 
    X:  0497,y:  0525 
    X:  0494,y:  0543 
    X:  0495,y:  0564 
    X:  0495,y:  0585 
    X:  0499,y:  0599 
    X:  0497,y:  0637 
    X:  0503,y:  0668 
    X:  0507,y:  0679 

    可以看到x坐标基本不变,y坐标逐渐增大,最大为我们设定的值0x3FF。

    实际我们可以通过从屏幕最左滑动到最右端,从而得到x坐标范围,我这里大概是80~940;

    实际我们可以通过从屏幕最下滑动到最上端,从而得到y坐标范围,我这里大概是100~685(我的触摸屏最上端大概率是损坏了,点击没有反应,正常范围应该是100~900+);

    由于我这移植的nfs文件系系统没有hexdump命令,所以就不能执行如下命令查看触摸屏的输入信息了:

    hexdump /dev/input/event0 

     六、使用tslib进行测试

    6.1 下载tslib

    直接到github上下载:

    git clone https://github.com/kergoth/tslib

    下载完成后,我直接上传到ubuntu服务器如下路径:/work/sambashare/drivers/12.lcd_touch_dev。

    解压:

    cd tslib/

    6.2 编译

    首先运行:

    ./autogen.sh                       // 如果出现如下错误./autogen.sh: 3: ./autogen.sh: autoreconf: not found 先执行apt-get install autoconf automake libtool 
    mkdir tmp                    //创建安装目录

    然后配置:

    CC=arm-linux-gcc ./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp CFLAGS="-march=armv4t -O2 -Wall -W"

    编译安装:

    make                                                //编译
    make install                                       //安装到temp目录下    

    需要注意的是编译所使用的的版本要和内核编译的版本保持一致,我是用的是arm-linux-gcc4.8.3版本。

    可以通过如下命令查看可执行文件的平台属性信息:

    root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev/tslib/tmp# arm-linux-readelf -A bin/ts_test
    Attribute Section: aeabi
    File Attributes
      Tag_CPU_name: "4T"
      Tag_CPU_arch: v4T
      Tag_ARM_ISA_use: Yes
      Tag_THUMB_ISA_use: Thumb-1
      Tag_ABI_PCS_wchar_t: 4
      Tag_ABI_FP_rounding: Needed
      Tag_ABI_FP_denormal: Needed
      Tag_ABI_FP_exceptions: Needed
      Tag_ABI_FP_number_model: IEEE 754
      Tag_ABI_align_needed: 8-byte
      Tag_ABI_align_preserved: 8-byte, except leaf SP
      Tag_ABI_enum_size: int

    6.3 配置nfs文件系统

    进入tmp, 将tmp里面的bin ,etc,include,lib4个目录下的文件拷贝到文件系统的bin ,etc,include,lib4个目录下 :

    cd tmp
    cp *  /work/nfs_root/rootfs/   -rfd

    进入nfs文件系统,修改etc/inittab文件:

    cd /work/nfs_root/rootfs/
    vim etc/inittab

    检查是否会启动:

    tty1: tty1::askfirst:-/bin/sh  #在虚拟终端tty1启动askfirst动作的shell,也就是在LCD上会出现Please press Enter to active this console.

    若有,前面加#,屏蔽掉,这条命令。

    6.4 安装驱动

    启动开发板,安装lcd_touch_dev驱动:

    insmod lcd_touch_dev.ko

    配置LCD和触摸屏环境:

    export TSLIB_TSDEVICE=/dev/input/event0           //ts设备文件(触摸屏):event0
    export TSLIB_CALIBFILE=/etc/pointercal            //校验文件(calibrate file),存放校验值
    export TSLIB_CONFFILE=/etc/ts.conf                //配置文件
    export TSLIB_PLUGINDIR=/lib/ts                    //插件文件
    export TSLIB_CONSOLEDEVICE=none                   //终端控制台设为NULL
    export TSLIB_FBDEVICE=/dev/fb0                    //fb设备文件(LCD):fb0

    或者直接写入nfs根文件系统路径下etc/profile文件中。

    6.5 测试

    运行校准程序,触摸屏依次出现5个点,依次点击之:

    [root@zy:/]# ts_calibrate 
    xres = 240, yres = 320
    Took 2 samples...
    Top left : X =  260 Y =  736
    Took 2 samples...
    Top right : X =  771 Y =  729
    Took 1 samples...
    Bot right : X =  777 Y =  243
    Took 1 samples...
    Bot left : X =  258 Y =  214
    Took 1 samples...
    Center : X =  528 Y =  505
    -21.327820 0.271702 0.000761
    366.129089 0.009783 -0.435114
    Calibration constants: -1397740 17806 49 23994636 641 -28515 65536

    生成的校准文件名为pointercal,位于/etc目录下。

    在开发板上执行如下名,就可以在lcd上绘图了:

    ts_test

    屏幕最上方会出现三个按钮,分别为“Drag”、“Draw”和“Quit”,默认是第一个,因此,用触摸笔点击任何一处,十字光标便会到那里。

    下面是点击Draw按钮并用触摸笔写字的提示信息的一小部分(我的触摸屏最上端损坏了,无法点击了,所以这里就不无法展示Draw绘画过程了,下面信息是Drag输出的,实际上和Draw没区别的):

    485.820282:    176    118      1
    485.850283:    184    120      1
    485.880283:    187    126      1
    485.910284:    189    134      1
    485.970367:    205    163      1
    486.000308:    205    165      1
    486.090298:    159    165      1
    486.120315:    157    159      1
    486.150320:    159    151      1
    486.210284:    158    156      1
    486.330266:    113    325      1
    486.360288:    113    327      1
    486.397042:    113    327      0

    第一列为timeval结构体的两个成员:tv_sec和tv_usec,中间两列分别是X和Y的坐标,最后为pressure,这里可以理解成“触摸事件”,为1表示触摸笔点击了(接触)屏幕,为0表示触摸笔离开了屏幕(这里出现很多的1是正常的,因为写字过程中笔没有离开触摸屏)。
    点击屏幕上“Quit”或按Ctrl+C可退出该程序。

    七、代码下载

    Young / s3c2440_project[drivers]

    参考文章

    [1]十三、Linux驱动之触摸屏驱动

    [2]18.Llinux-触摸屏驱动(详解)

    [3]四线电阻触摸屏工作原理的示意图

    [4]四线电阻触摸屏原理

    [5]常用低成本:四线电阻式触摸屏原理

    [6]linux根文件系统之inittab

    [7]嵌入式linux启动时运行的inittab文件(zz)

    [8]Linux移植随笔:终于解决Tslib的问题了

  • 相关阅读:
    Spring 框架的设计理念与设计模式分析
    stratos paas平台
    云计算国际标准
    如何在KVM中管理存储池
    深度学习(四) softmax函数
    深度学习(二)BP求解过程和梯度下降
    深度学习(一) BP神经网络
    提交代码到git
    Mac 安装tensorflow
    Mac 安装Git
  • 原文地址:https://www.cnblogs.com/zyly/p/16210859.html
Copyright © 2020-2023  润新知