# MS-RTOS TOUCH 驱动开发

本章将介绍 MS-RTOS 触摸屏驱动开发及测试。

触摸屏又称触控面板,它是一种把触摸位置转化成坐标数据的输入设备,根据触摸屏的检测原理,主要分为电阻式触摸屏和电容式触摸屏。对于5寸电容屏,可查阅《电容触控芯片GT9157 Datasheet》及《gt91x编程指南》配套资料获知。对于7寸电容屏,可查阅《电容触摸芯片GT911》相关的数据手册,7寸电容屏的驱动原理与5寸电容屏的类似,仅写入触摸芯片的配置参数有细节差异。

# 1. 触摸屏基础知识

# 1.1 GT9157 触控芯片

若您把电容触摸屏与液晶面板分离开来,在触摸面板的背面,可看到它的边框有一些电路走线,它们就是触摸屏ITO层引出的XY轴信号线,这些信号线分别引出到GT9157芯片的Driving channels及Sensing channels引脚中。也正是因为触摸屏有这些信号线的存在,所以手机厂商追求的屏幕无边框是比较难做到的。

touch_arch

(1)芯片对外引出的信号线

信号线 说明
AVDD、AVDD18、DVDD12、VDDDIO、GND 电源和地
Driving channels 激励信号输出的引脚,一共有0-25个引脚,它连接到电容屏ITO层引出的各个激励信号轴
Sensing channels 信号检测引脚,一共有0-13个引脚,它连接到电容屏ITO层引出的各个电容量检测信号轴
I2C I2C通信信号线,包含SCL与SDA,外部控制器通过它与GT9157芯片通讯,配置GT9157的工作方式或获取坐标信号
INT 中断信号,GB9157芯片通过它告诉外部控制器有新的触摸事件
RSTB 复位引脚,用于复位GT9157芯片;在上电时还与INT引脚配合设置IIC通讯的设备地址

(2)上电时序与I2C设备地址

GT9157触控芯片有两个备选的I2C通讯地址,这是由芯片的上电时序设定的。上电时序有Reset引脚和INT引脚生成,若Reset引脚从低电电平转变到高电平期间,INT引脚为高电平的时候,触控芯片使用的I2C设备地址为0x28/0x29(8位写、读地址),7位地址为0x14;若Reset引脚从低电电平转变到高电平期间,INT引脚一直为低电平,则触控芯片使用的I2C设备地址为0xBA/0xBB(8位写、读地址),7位地址为0x5D。

(3)寄存器配置

GT9157芯片需要通过外部主控芯片加载寄存器配置,设定它的工作模式,这些配置通过I2C信号线传输到GT9157,它的配置寄存器地址都由两个字节来表示,这些寄存器的地址从0x8047-0x8100,一般来说,我们实际配置的时候会按照GT9157生产厂商给的默认配置来控制芯片,仅修改部分关键寄存器。

  • 配置版本寄存器

    0x8047配置版本寄存器,它包含有配置文件的版本号,若新写入的版本号比原版本大,或者版本号相等,但配置不一样时,才会更新配置文件到寄存器中。其中配置文件是指记录了寄存器0x8048-0x80FE控制参数的一系列数据。

    为了保证每次都更新配置,我们一般把配置版本寄存器设置为"0x00",这样版本号会默认初始化为'A',这样每次我们修改其它寄存器配置的时候,都会写入到GT9157中。

  • X、Y分辨率

    0x8048-0x804B寄存器用于配置触控芯片输出的XY坐标的最大值,为了方便使用,我们把它配置得跟液晶面板的分辨率一致,这样就能使触控芯片输出的坐标一一对应到液晶面板的每一个像素点了。

  • 触点个数

    0x804C触点个数寄存器用于配置它最多可输出多少个同时按下的触点坐标,这个极限值跟触摸屏面板有关,如我们本章实验使用的触摸面板最多支持5点触控。

  • 模式切换

    0x804D模式切换寄存器中的X2Y位可以用于交换XY坐标轴;而INT触发方式位可以配置不同的触发方式,当有触摸信号时,INT引脚会根据这里的配置给出触发信号。

  • 配置校验

    0x80FF配置校验寄存器用于写入前面0x8047-0x80FE寄存器控制参数字节之和的补码,GT9157收到前面的寄存器配置时,会利用这个数据进行校验,若不匹配,就不会更新寄存器配置。

  • 配置更新

    0x8100配置更新寄存器用于控制GT9157进行更新,传输了前面的寄存器配置并校验通过后,对这个寄存器写1,GT9157会更新配置。

# 1.2 触摸屏读坐标流程

上电、配置完寄存器后,GT9157就会开监测触摸屏,若我们前面的配置使INT采用中断上升沿报告触摸信号的方式,整个读取坐标信息的过程如下:

(1)待机时INT引脚输出低电平;

(2)有坐标更新时,INT引脚输出上升沿;

(3)INT输出上升沿后,INT 脚会保持高直到下一个周期(该周期可由配置Refresh_Rate 决定)。外部主控器在检测到INT的信号后,先读取状态寄存器(0x814E)中的number of touch points位获当前有多少个触摸点,然后读取各个点的坐标数据,读取完后将 buffer status位写为 0。外部主控器的这些读取过程要在一周期内完成,该周期由0x8056地址的Refresh_Rate寄存器配置;

(4)上一步骤中INT输出上升沿后,若主控未在一个周期内读走坐标,下次 GT9157 即使检测到坐标更新会再输出一个 INT 脉冲但不更新坐标;

(5)若外部主控一直未读走坐标,则 GT9 会一直输出 INT 脉冲。

# 2. 触摸屏驱动框架

# 2.1 驱动相关数据结构

(1)ms_io_device_t

/*
 * Private Info
 */
typedef struct {
    char         *i2c_bus;
    ms_uint8_t    i2c_addr;
} privinfo_t;

/*
 * FrameBuffer Device
 */
typedef struct {
    privinfo_t      priv;
    ms_io_device_t  dev;
} touch_screen_dev_t;

(2)ms_io_driver_t

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t ts_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = __ts_open,
    	.read   = __ts_read,
        .close  = __ts_close,
        .ioctl  = __ts_ioctl,
    	.poll   = __ts_poll,
};

/*
 * Device driver
 */
static ms_io_driver_t stm32_ts_drv = {
        .nnode = {
            .name = "ts_gt9157",
        },
        .ops  = &ts_drv_ops,
};

# 2.2 驱动的注册和卸载

(1)触摸屏驱动开发流程:

  • 获取必要的软硬件开发资源,了解设备的基本特性;

  • 参照手册的相关流程和代码规范,编写寄存器相关宏定义,封装通用硬件操作接口;

  • 申请必要的系统资源,根据默认参数初始化硬件的工作模式,实现中断处理函数;

  • 实现 ms_io_driver_ops_t 中的必要操作接口,并向 MS-RTOS IO 系统注册驱动和设备节点;

  • 检查代码质量和代码风格,编写测试程序;

(2)驱动的注册和卸载接口:

ms_err_t ms_io_driver_register(ms_io_driver_t *drv);
ms_err_t ms_io_driver_unregister(ms_io_driver_t *drv); //此接口暂不开放

(3)设备节点的注册和卸载接口:

ms_err_t ms_io_device_register(ms_io_device_t *dev, const char *dev_path, 
                               const char *drv_name, ms_ptr_t ctx);
ms_err_t ms_io_device_unregister(ms_io_device_t *dev);

# 2.3 触摸屏 ioctl 命令

(1)ms_touch_event_t

应用程序可以通过 read 接口读取触摸屏设备文件,从而获取触摸事件的信息,也可以通过 select 接口来监听触摸屏设备,当有触摸事件发送时在读取触摸事件并做相应的处理。一般而言,触摸屏设备会有操作系统的UI模块负责管理。触摸事件的结构体定义如下所示:

#define MS_TOUCH_MAX_POINT    5

typedef struct {
    uint8_t     touch_detected; // Total number of active touches detected
    ms_uint16_t touch_x[MS_TOUCH_MAX_POINT]; // Touch X[0], X[1] coordinates on 12 bits 
    ms_uint16_t touch_y[MS_TOUCH_MAX_POINT]; // Touch Y[0], Y[1] coordinates on 12 bits
    ms_uint8_t  touch_weight[MS_TOUCH_MAX_POINT]; // Weight property of each touches
    ms_uint8_t  touch_event_id[MS_TOUCH_MAX_POINT]; // Event id of each touches 
    ms_uint8_t  touch_area[MS_TOUCH_MAX_POINT]; // Touch area of each touches  
    ms_uint32_t gesture_id; // Type of gesture detected
} ms_touch_event_t;

# 2.4 触摸屏驱动示例

基于 STM32 HAL 库开发驱动,需要实现的接口如下:

接口 描述
BSP_TS_Init 触摸屏硬件初始化
BSP_TS_DeInit 触摸屏硬件复位/反初始化
BSP_TS_InitEx 触摸屏相关特性初始化
BSP_TS_GetState 获取触摸屏数据/状态信息

LCD 驱动示例,仅作为参考:

#define __MS_IO
#include "config.h"
#include "ms_kern.h"
#include "ms_io_core.h"

/*
 * Private info
 */
typedef struct {
    ms_pollfd_t *slots[1];
} privinfo_t;

/*
 * Open device
 */
static int stm32_touch_open(ms_ptr_t ctx, ms_io_file_t *file, int oflag, ms_mode_t mode)
{
    int ret;

    if (ms_atomic_inc(MS_IO_DEV_REF(file)) == 1) {
        BSP_TS_Init(BSP_CFG_LCD_WIDTH, BSP_CFG_LCD_HEIGHT);
        ret = 0;

    } else {
        ms_atomic_dec(MS_IO_DEV_REF(file));
        ms_thread_set_errno(EBUSY);
        ret = -1;
    }

    return ret;
}

/*
 * Close device
 */
static int stm32_touch_close(ms_ptr_t ctx, ms_io_file_t *file)
{
    if (ms_atomic_dec(MS_IO_DEV_REF(file)) == 0) {
        BSP_TS_DeInit();
    }

    return 0;
}

/*
 * Control device
 */
static int stm32_touch_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, void *arg)
{
    ms_thread_set_errno(EINVAL);

    return -1;
}

/*
 * Read device
 */
static ssize_t stm32_touch_read(ms_ptr_t ctx, ms_io_file_t *file, ms_ptr_t buf, size_t len)
{
    ms_touch_event_t *event = (ms_touch_event_t *)buf;
    TS_StateTypeDef state;

    BSP_TS_GetState(&state);

    event->touch_detected = state.touchDetected;
    event->touch_x[0] = state.touchX[0];
    event->touch_y[0] = state.touchY[0];

    return sizeof(ms_touch_event_t);
}

/*
 * Check device readable
 */
static ms_bool_t stm32_touch_readable_check(ms_ptr_t ctx)
{
    privinfo_t *priv = ctx;

    (void)priv;

    return MS_FALSE;
}

/*
 * Poll device
 */
static int stm32_touch_poll(ms_ptr_t ctx, ms_io_file_t *file, ms_pollfd_t *fds, ms_bool_t setup)
{
    privinfo_t *priv = ctx;

    return ms_io_poll_helper(fds, priv->slots, MS_ARRAY_SIZE(priv->slots), setup, ctx,
                             stm32_touch_readable_check, MS_NULL, MS_NULL);;
}

/*
 * Device operating function set
 */
static const ms_io_driver_ops_t stm32_touch_drv_ops = {
        .type   = MS_IO_DRV_TYPE_CHR,
        .open   = stm32_touch_open,
        .read   = stm32_touch_read,
        .ioctl  = stm32_touch_ioctl,
        .close  = stm32_touch_close,
        .poll   = stm32_touch_poll,
};

/*
 * Device driver
 */
static ms_io_driver_t stm32_touch_drv = {
        .nnode = {
            .name = "stm32_touch",
        },
        .ops = &stm32_touch_drv_ops,
};

/*
 * Register touch screen device driver
 */
ms_err_t stm32_touch_drv_register(void)
{
    return ms_io_driver_register(&stm32_touch_drv);
}

/*
 * Register touch screen device file
 */
ms_err_t stm32_touch_dev_register(const char *path)
{
    static privinfo_t priv;
    static ms_io_device_t dev;

    return ms_io_device_register(&dev, path, "stm32_touch", &priv);
}

# 3. 触摸屏应用程序

# 3.1 MS-RTOS 已经支持的触摸屏

触摸屏类别 具体型号
gt9xx GT9157、GT911、GT615

# 3.1 监听触摸事件

打开 触摸屏 设备文件,监听触摸事件,并打印触摸点的坐标:

#include <ms_rtos.h>
#include <stdlib.h>
#include <driver/ms_drv_touch.h>

#define TOUCH_DEVICE_PATH    "/dev/touch0"

int main (int argc, char **argv)
{
    int				 i;
    int              fd;
    int              ret;
    fd_set           fds_set;
    ms_touch_event_t event;
    ms_uint32_t 	 test_count = GPIO_TEST_COUNT;

    fd = ms_io_open(TOUCH_DEVICE_PATH, O_RDONLY, 0666);
    if (fd < 0) {
        ms_printf("[error]: open file %s failed!\n", TOUCH_DEVICE_PATH);
        return  (-1);
    }
    
    while (test_count--) {
        FD_ZERO(&fds_set);
        FD_SET(fd, &fds_set);

        ret = select(fd + 1, &fds_set, NULL, NULL, NULL);
        if (ret < 0) {
            ms_printf("[error]: select file %s failed!\n", TOUCH_DEVICE_PATH);
            break;
        } else {
            ret = ms_io_read(fd, &event, sizeof(ms_touch_event_t));
            if (ret < 0) {
                ms_printf("[error]: read file %s failed!\n", TOUCH_DEVICE_PATH);
            } else {
                ms_printf("[touch event]: %d point\n", event.touch_detected);
                for (i = 0; i < event.touch_detected; i++) {
                    ms_printf("x[i]: %d\n", event.touch_x[i]);
                    ms_printf("y[i]: %d\n", event.touch_y[i]);
                }
                ms_printf("\n");
            }
        }
    }

    ms_io_close(fd);
    
    return  (0);
}

# 附录(Appendix)

1. Reference

电容触摸屏:https://blog.csdn.net/flyleaf91/article/details/52325522

2. FAQ

(1)触摸屏方向颠倒的问题?

问题分析:

这个问题一般出现在更换触摸屏的时候,因为每个型号的屏在电压的流向上的不同,导致在A/D采样后德到的坐标是不同的。可以形象的解释下,比如X轴的坐标,A屏的电压是从XP到XM的,在XM点来采样,而B屏的电压时从XM到XP的,在XP点采样。那么同样的驱动,在A、B两屏采到的点,肯定是不同的。

解决方法:

检查触摸屏配置参数,设置正确的 x/y 坐标方向。

(2)触摸屏抖动的问题?

问题分析:

触摸屏抖动的现象体现在当笔尖按在一个点不动时,LCD显示的笔尖附近会有一个跳动的框框。或是,在拖动笔尖时,在笔尖的位置附近会有一个跳动的框框。或是,触摸屏经过校正后,仍然不能准确的显示笔尖的位置。

解决方法:

这个问题的可能性有以下几点:

  • 首先确定触摸屏、LCD所需的供电电压是否稳定,如果纹波较大(电压跳动的太厉害),则会导致A/D采样的不稳定,当然采集到的坐标值也就会有问题。

  • 阀值法,在触摸屏按下的时候,采集了一个坐标点后,从第二次坐标点的采集开始,每采集一个坐标点,就和前一次采集的点进行比较,如果相差很大,则认为采集的是野点(无效),否则为有效的点。这一过程就是触摸屏的滤波函数,类似于键盘的去抖动。当然,两次坐标的比较值需要实际的调整才能得出。

  • 增加采样次数,通过更多次的采样来得到准确的有效坐标。

  • 触摸屏周围是否有干扰,比如高频信号源的影响。

(3)TP局部区域可以使用?

问题分析:

TP可以正常使用,但是按下区域和响应区域成镜像反,例如按左边区域右边响应,按右边区域左边响应。TP局部区域可以使用只是按下去不准确,但是中断正常,报点位置镜像反,引起此现象可能是TP固件太老,与当前驱动不匹配引起的 。

解决方法:

TP固件不匹配,升级TP固件。

(4)busy线长期处于忙等待状态?

问题分析:

送完指令,等待busy拉低的过程中,busy信号始终为高。

解决方法:

仔细分析spec,发现在busy状态下,tsc2046依然需要clk来完成AD转换工作,所以在等待busy信号的时候,要同时继续保持CLK信号。

(5)不断产生pendown中断?

问题分析:

在第一次触摸屏幕,pendown中断产生,并完成测量后,即使放开触摸屏,依然连续不断的有pendown中断信号产生,反复进入测量过程。反复调试发现该pin电压为低的原因在于在上一次测量中,最后一次测量的是Z1,在触摸屏放开的时候,测量Z1的过程中,X+为低电平。而后回到等待中断状态的时候,X+ pin 由于外部电容的原因,被上拉电阻重新拉高为高电平需要一段的时间,在此之前,如果打开中断,就会误判,错误的收到中断信号。

解决方法:

有几种办法可以解决:减小外部电容;将中断由电平触发改为下降沿触发;在完成测量之后,打开中断之前,延迟一段时间,等待X+ Pin回到高电平状态。

(6)CPU占用率超高?

问题分析:

触摸笔压下后,CPU占用率迅速攀高到一个不合理的地步,松开后降低。通常这种情况都是由于使用了不合理的查询手段来获取采样数据,例如忙等待AD转换的结束,采样频率过快等等。

解决方法:

理论上,所有这类IO设备都应该采用中断驱动的方式来获取数据。很遗憾的是,有些内置的触摸屏控制模块,转换结束后并不产生中断信号,只是设置一个状态寄存器,需要由软件查询得到。这种情况下,如果转换完成时间不定,又没有较高精度的定时中断源,只能牺牲相应速度,在每次查询间隔之间睡眠一段足够长的时间,让出CPU。 通常来说,在jiffies值为10ms间隔的系统上,最快每秒查询50-100次,也基本能够满足像手写输入这样的应用的需求了。