TOUCH 触摸屏驱动
本章将介绍 MS-RTOS 触摸屏驱动开发及测试。
触摸屏又称触控面板,它是一种把触摸位置转化成坐标数据的输入设备,根据触摸屏的检测原理,主要分为电阻式触摸屏和电容式触摸屏。对于 5 寸电容屏,可查阅《电容触控芯片GT9157 Datasheet》及《gt91x编程指南》配套资料获知。对于 7 寸电容屏,可查阅《电容触摸芯片GT911》相关的数据手册,7 寸电容屏的驱动原理与 5 寸电容屏的类似,仅写入触摸芯片的配置参数有细节差异。
1. 触摸屏基础知识
1.1 GT9157 触控芯片
若您把电容触摸屏与液晶面板分离开来,在触摸面板的背面,可看到它的边框有一些电路走线,它们就是触摸屏 ITO 层引出的 XY 轴信号线,这些信号线分别引出到 GT9157 芯片的 Driving channels 及 Sensing channels 引脚中。也正是因为触摸屏有这些信号线的存在,所以手机厂商追求的屏幕无边框是比较难做到的。
(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
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 次,也基本能够满足像手写输入这样的应用的需求了。