1. 原理概述

    1.首先要区分lcd和触摸屏,lcd是一个屏幕,触摸屏是贴在lcd上的两层膜。

    2.四线电阻触摸屏的原理:触摸屏就是上下两层膜,比如上层代表x轴(XM:负端,XP:正端),下层代表y轴(YM:负端,YP:正端)。当读取x轴坐标时,XP接3.3v,XM接地,从YM读取按下点的电压值作为模拟输入信号,再经过AD转换后就得到了x轴坐标。同理,当读取y轴坐标时,YP接3.3v,YM接地,从XM读取按下点的电压值作为模拟输入,再经过AD转换后就得到了y轴坐标。

    触摸屏驱动程序采用中断方式对触摸笔的按下状态进行检测,如果检测到触摸笔按下将产生中断并触发一个事件通知一个工作线程开始采集数据。

    ADC触摸屏驱动的工作流程如图 21所示。

    图 21 ADC触摸屏工作流程

     

  2. 技术实现

  3. ADC触摸屏驱动框架分析

    ADC触摸屏驱动目录如图 31所示。

    图 31 ADC触摸屏驱动所在目录

    bspnuc970/driver_module/touch_drv/src/touch_drv.c文件主要提供SylixOS的ADC触摸屏驱动。主要关注函数如程序清单 31所示。

    程序清单 31

    INT  __tsDrv (VOID) 
    INT  __tsDevCreate (PCHAR         pcName) 
    static void __tsThread (TS_DEV   *ptsDev) 
    static irqreturn_t __tsIsr (void  *arg)
  4. 框架的流程图

    整个框架的流程如图 33所示。

    图 33 ADC触摸屏驱动框架

  5. 代码实现

  6. BSP中驱动配置

    根据NUC970相关芯片手册,配置寄存器地址并定义touch screen设备结构。如程序清单 32所示。

    程序清单 32

    /*********************************************************************************************************
      touch screen 设备结构
    *********************************************************************************************************/
    typedef struct {
        LW_DEV_HDR                 TS_devhdr;                               /*  设备头                      */
        touchscreen_event_notify   TS_tData;                                /*  采集到的数据                */
        BOOL                       TS_bIsReadRel;                           /*  是否读取的 release 操作     */
        LW_HANDLE                  TS_hThread;                              /*  扫描线程                    */
        LW_SEL_WAKEUPLIST          TS_selwulList;                           /*  select() 等待链             */
        LW_SPINLOCK_DEFINE        (TS_slLock);                              /*  自旋锁                      */
    } TS_DEV;
  7. __tsDrv

    该函数注册一系列触摸屏设备驱动程序,包括:

    1)    驱动程序中的建立函数__tsOpen;

    2)    驱动程序中的关闭函数__tsClose;

    3)    驱动程序中的读函数__tsRead;

    具体实现如程序清单 33所示。

    程序清单 33

    INT  __tsDrv (VOID)
    {
    ……
    
        /*
         *  安装触摸屏驱动,LW_NULL为待实现功能
         */
        touch_dev_num = iosDrvInstall(__tsOpen,
                                  LW_NULL,
                                  __tsOpen,
                                  __tsClose,
                                  __tsRead,
                                  LW_NULL,
                                  LW_NULL);
    ……
    }
  8. __tsDevCreate

    该函数创建一个触摸屏设备,并将__tsDrv中注册的驱动程序与设备名绑定。创建成功后对该设备的open,close,read操作将调用__tsDrv中注册的驱动程序__tsOpen,__tsClose,__tsRead。具体实现如程序清单 34所示。

    程序清单 34

    INT  __tsDevCreate (PCHAR     pcName)
    {
    ……
        iTemp = (INT)iosDevAdd(&ptsDev->TS_devhdr,
                               pcName,
                               touch_dev_num);
    ……
    }
  9. __tsThread

    该函数是在__tsOpen中创建的触摸屏服务线程,其在执行过程中等待__tsIsr中断函数发送二进制信号量,收到信号后执行后续操作。具体实现如程序清单 35所示。

    程序清单 35

    static void __tsThread (TS_DEV  *ptsDev)
    {
    ……
            /*
             *  等待INT_TC中断,进入中断服务函数,发送二进制信号量
             */
            API_SemaphoreBPend(adc_mst, LW_OPTION_WAIT_INFINITE);
    ……
            /*
             *  Menu Start Conversion
             *  (开始转换)
             */
            uiRegVal = readl(REG_ADC_IER) | ADC_IER_MIEN;
            writel(uiRegVal, REG_ADC_IER);
    
            uiRegVal = readl(REG_ADC_CTL) | ADC_CTL_MST;
            writel(uiRegVal, REG_ADC_CTL);
    
            if (__tsGetXY(&ix, &iy)) {
                /*
                 *  当前有点击操作.
                 */
                LW_SPIN_LOCK_QUICK(&ptsDev->TS_slLock, &iregInterLevel);
                ptsDev->TS_tData.kstat |= MOUSE_LEFT;
                ptsDev->TS_tData.xmovement = ix;
                ptsDev->TS_tData.ymovement = iy;
                LW_SPIN_UNLOCK_QUICK(&ptsDev->TS_slLock, iregInterLevel);
                SEL_WAKE_UP_ALL(&ptsDev->TS_selwulList,
                                SELREAD);                                   /*  释放所有等待读的线程        */
            } else {
                /*
                 *  当前没有点击操作.
                 */
                if (ptsDev->TS_tData.kstat & MOUSE_LEFT) {
                    LW_SPIN_LOCK_QUICK(&ptsDev->TS_slLock, &iregInterLevel);
                    ptsDev->TS_tData.kstat &= (~MOUSE_LEFT);
                    LW_SPIN_UNLOCK_QUICK(&ptsDev->TS_slLock, iregInterLevel);
                }
    
                if (ptsDev->TS_bIsReadRel == LW_FALSE) {                      /*  没有读取到释放操作          */
                    SEL_WAKE_UP_ALL(&ptsDev->TS_selwulList,
                                    SELREAD);                               /*  释放所有等待读的线程        */
                }
            }
        }
    }
    static INT  __tsGetXY (INT  *pX, INT  *pY)
    {
        /*
         *  等待INT_ADC中断,进入中断服务函数,发送二进制信号量adc_sem
         */
        API_SemaphoreBPend(adc_sem, LW_OPTION_WAIT_INFINITE);
    
        *pX = abs_xp;
        *pY = abs_yp;
    
        return send_press;
    }
  10. __tsIsr

    该函数是由__tsOpen注册的中断服务函数,在中断服务函数中进行判断,判断不同中断信号进行不同的中断处理,并给__tsThread发送二进制信号量。具体实现如程序清单 36所示。

    程序清单 36

    static irqreturn_t __tsIsr (void *arg)
    {
    ……
        /*
         *  检测触摸转换是否完成
         */
        if(uisr & ADC_ISR_TF) {
            writel(ADC_ISR_TF, REG_ADC_ISR);                                /*  清除标志位                  */
    
            /*
             *  若转换完成,赋值并使touchGetXY返回1
             *  否则清零并使touchGetXY返回0
             */
            uiRegVal = readl(REG_ADC_XYDATA);
    
            abs_xp        = (uiRegVal >> ADC_XYDATA_XBIT) & 0xFFF;
            abs_yp        = (uiRegVal >> ADC_XYDATA_YBIT) & 0xFFF;
            send_press    = TF_ON;
    
            printk("%d    abs_xp = %d, abs_yp = %d \n", __LINE__, abs_xp, abs_yp);
    
            API_SemaphoreBPost(adc_sem);                                    /*  释放一个信号给等待任务      */
        } else {
            abs_xp        = XDATA_CLR;
            abs_yp        = YDATA_CLR;
            send_press    = TF_OFF;
    
            API_SemaphoreBPost(adc_sem);
        }
    
        /*
         *  检测是否触摸按下 / 松开
         */
        if((uisr & ADC_ISR_PEUEF) && (uier & ADC_IER_PEUEIEN)) {
            uiRegVal = ADC_ISR_PEUEF | ADC_ISR_PEDEF;
            writel(uiRegVal, REG_ADC_ISR);
        } else if((uisr & ADC_ISR_PEDEF) && (uisr & ADC_IER_PEDEIEN)) {
            uiRegVal = readl(REG_ADC_ISR) | ADC_ISR_PEUEF | ADC_ISR_PEDEF;
            writel(uiRegVal, REG_ADC_ISR);
    
            API_SemaphoreBPost(adc_mst);
        }
    
        /*
         *  检测所有转换是否完成
         */
        if(uisr & ADC_ISR_MF) {
            uiRegVal = readl(REG_ADC_ISR) | ADC_ISR_MF;
            writel(uiRegVal, REG_ADC_ISR);
        }
    
        return  (LW_IRQ_HANDLED);
    }
  11. __tsOpen

    Open函数调用Init函数初始化并创建触摸屏服务线程。具体实现如程序清单 37所示。

    程序清单 37

    static LONG  __tsOpen (TS_DEV  *ptsDev,
                            PCHAR    pcName,
                            INT      iFlags,
                            INT      iMode)
    {
    ……
        	__tsInit();
    ……
      /*
         *  创建触摸屏服务线程
         */
          ptsDev->TS_hThread = API_ThreadCreate("t_touch",
                                             (PTHREAD_START_ROUTINE)__tsThread,
                                             &threadattr, LW_NULL);
    ……
    }
  12. __tsInit

    Init函数中创建二进制信号量,注册中断服务函数。具体实现如程序清单 38所示。

    程序清单 38

    static void  __tsInit (void)
    {
    ……
        API_SemaphoreBCreate("ad_thread_sem",
                              0,
                              LW_OPTION_WAIT_FIFO,
                              &adc_sem);
        API_SemaphoreBCreate("ad_thread_mst",
                              0,
                              LW_OPTION_WAIT_FIFO,
                              &adc_mst);
    ……
        /*
         *  安装ADC 中断服务程序,
         */
        API_InterVectorConnect(VIC_CHANNEL_ADC,
                               (PINT_SVR_ROUTINE)__tsIsr,
                               (PVOID)NULL,
                               "touchscr");
        API_InterVectorEnable(VIC_CHANNEL_ADC);
    ……
    }
  13. __tsClose

    Close函数用于删除之前创建的信号量和线程。具体实现如程序清单 39所示。

    程序清单 39

    static INT __tsClose (TS_DEV  *ptsDev)
    {
    ……
                /*
                 *  Disable Pen Down Event
                 */
                writel(CLEAR_ALL, REG_ADC_IER);
    
                API_ThreadForceDelete(&ptsDev->TS_hThread, LW_NULL);
    
                API_SemaphoreBDelete(&adc_sem);
                API_SemaphoreBDelete(&adc_mst);
    ……
    }
  14. __tsRead

    Read函数得到ADC值并上报。具体实现如程序清单 310所示。

    程序清单 310

    static ssize_t __tsRead (TS_DEV                        *ptsDev,
                             touchscreen_event_notify      *pnotify,
                             size_t                         stNbyte)
    {
        INTREG           iregInterLevel;
    
        if (stNbyte == 0) {
            return  (ERROR_NONE);
        }
    
        LW_SPIN_LOCK_QUICK(&ptsDev->TS_slLock, &iregInterLevel);
    
        pnotify->ctype   = ptsDev->TS_tData.ctype;
        pnotify->kstat   = ptsDev->TS_tData.kstat;
        pnotify->xanalog = ptsDev->TS_tData.xanalog;
        pnotify->yanalog = ptsDev->TS_tData.yanalog;
    
        printk("%d    ix = %d    iy = %d\n", __LINE__, ptsDev->TS_tData.xanalog, ptsDev->TS_tData.yanalog);
    
        if (ptsDev->TS_tData.kstat & MOUSE_LEFT) {                          /*  读取到点击事件              */
            ptsDev->TS_bIsReadRel = LW_FALSE;                               /*  需要确保应用读到释放操作    */
        } else {
            ptsDev->TS_bIsReadRel = LW_TRUE;                                /*  已经读取到释放操作          */
        }
    
        LW_SPIN_UNLOCK_QUICK(&ptsDev->TS_slLock, iregInterLevel);
    
        return  (sizeof(touchscreen_event_notify));
    }
  15. 内核模块加载

    当ADC触摸屏驱动完成后,编译生成touch_drv.ko文件,通过tftp服务下载至/lib/modules/路径下,通过执行modulereg touch_drv.ko命令动态加载至内核,如图 34所示:

    图 34 modulereg touch_drv.ko动态加载

    加载成功后,通过执行modules命令可以查看当前已加载.ko模块,如图 35所示:

    图 35查看当前已加载.ko模块测试

    在正常加载的情况下,启动任意一个QT程序,进行触控操作,如果正确响应触控事件,说明基本完成移植。