参考资料:
1.【韦东山】嵌入式Linux应用开发完全手册(旧内核未包含新协议和新API接口——本文的实验平台电阻式触摸屏)
2.【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.4——目前主流开发流程:电容式触摸屏
3.触摸屏-测试过程
触摸屏的种类
触摸屏主要有电容式、电阻式、红外线式、超声波式。目前较多应用的是电容式。
电阻式触摸屏
电阻式触摸屏其实就是一种传感器,虽然已经用的不多了,但是还是有过很多的LCD模块采用电阻式触摸屏,这种屏幕可以用四线、五线、七线或八线来产生屏幕偏置电压,同时读回触摸点的电压(A/D转换)。由于其只能一个点响应操作,电阻屏就是单点触摸。 (电阻式触摸屏电压值和坐标值成正比的,所以需要去校准它。校准就是去计算(0, 0)坐标点的电压值是多少)
电阻式硬件接口主要分为两种:1.SoC内置电阻式触摸屏控制器(与ADC模块有关),2. 通过I2C接口发送给主机Soc。
电容式触摸屏
电容式触摸屏可以支持多点触摸(也可以单点触摸)。多个区块支持多点触摸让电容触摸屏坐标计算变复杂了,但是这个复杂性被电容触摸IC吸收了,还是通过数字接口和主机SoC通信报告触摸信息(触摸点数、每个触摸点的坐标等)。
电容式硬件接口:电容式触摸屏其实是一个I2C从设备,通过I2C接口发送给主机Soc。
触摸屏裸机程序
- 初始化ADC/TS指令
- 设置TS处于等待中断模式
- 设置中断寄存器INTSUBMSK和INTMSK
- 按下,进入TS中断:a. 进入自动采集模式 ,b. 启动ADC
- ADC中断:a. 读数据 ,b. 再次进入“等待中断模式”,c. 启动定时器处理长按或滑动
- 定时器中断:a. 若松开,结束, b. 若按下,重新启动
具体源码参考韦东山第一期视频
以下对视频中涉及的难度和编程架构进行分析:
- 由于2440硬件只能把nand flash前4K内容复制到片内RAM中,故需修改Makefile将重定位前的代码前移。
objs = start.o init.o nand_flash.o \ /* start.o init.o nand_flash.o 文件大小总和小于4K,后续程序才会成功执行 */
led.o uart.o main.o exception.o interrupt.o timer.o nor_flash.o my_printf.o string_utils.o lib1funcs.o
- 定时器中断的注册与卸载框架(操作系统任务调度的一种思想,与时间片的轮询类似)
typedef void(*timer_func)(void); // 定义一个定时器函数指针,执行定时任务
typedef struct timer_desc { // 定义一个定时器描述结构体
char *name;
timer_func fp;
}timer_desc, *p_timer_desc;
timer_desc timer_array[TIMER_NUM]; // TIMER_NUM数量
/* 注册一个定时器任务 */
int register_timer(char *name, timer_func fp)
{
int i;
for (i = 0; i < TIMER_NUM; i++)
{
if (!timer_array[i].fp) // 定时器函数不为空,赋值
{
timer_array[i].name = name;
timer_array[i].fp = fp;
return 0;
}
}
return -1;
}
/* 根据定时器任务名,卸载定时器 */
void unregister_timer(char *name)
{
int i;
for (i = 0; i < TIMER_NUM; i++)
{
if (!strcmp(timer_array[i].name, name))
{
timer_array[i].name = NULL;
timer_array[i].fp = NULL;
return 0;
}
}
return -1;
}
/* 定时器中断执行函数 */
void timer_irq(void)
{
int i;
for (i = 0; i < TIMER_NUM; i++)
{
if (timer_array[i].fp)
{
timer_array[i].fp();
}
}
}
-
触摸屏的校准(可参考tslib开源库)
tslib中的5点校准法是上述3点校准法的延伸。五点校准法 ---------------------------- | | | +(A) (B)+ | | | | | | | | +(E) | | | | | | | | +(D) (C)+ | | | ----------------------------
void ts_calibrate(void)
{
int a_ts_x, a_ts_y;
int b_ts_x, b_ts_y;
int c_ts_x, c_ts_y;
int d_ts_x, d_ts_y;
int e_ts_x, e_ts_y;
/* X轴方向 */
int ts_s1, ts_s2;
int lcd_s;
/* Y轴方向 */
int ts_d1, ts_d2;
int lcd_d;
/* 获得LCD的参数: fb_base, xres, yres, bpp */
get_lcd_params(&fb_base, &xres, &yres, &bpp);
/* 对于ABCDE, 循环: 显示"+"、点击、读ts原始值 */
/* A(50, 50) */
get_calibrate_point_data(50, 50, &a_ts_x, &a_ts_y);
/* B(xres-50, 50) */
get_calibrate_point_data(xres-50, 50, &b_ts_x, &b_ts_y);
/* C(xres-50, yres-50) */
get_calibrate_point_data(xres-50, yres-50, &c_ts_x, &c_ts_y);
/* D(50, yres-50) */
get_calibrate_point_data(50, yres-50, &d_ts_x, &d_ts_y);
/* E(xres/2, yres/2) */
get_calibrate_point_data(xres/2, yres/2, &e_ts_x, &e_ts_y);
/* 确定触摸屏数据XY是否反转 */
g_ts_xy_swap = is_ts_xy_swap(a_ts_x, a_ts_y, b_ts_x, b_ts_y);
if (g_ts_xy_swap)
{
/* 对调所有点的XY坐标 */
swap_xy(&a_ts_x, &a_ts_y);
swap_xy(&b_ts_x, &b_ts_y);
swap_xy(&c_ts_x, &c_ts_y);
swap_xy(&d_ts_x, &d_ts_y);
swap_xy(&e_ts_x, &e_ts_y);
}
/* 确定公式的参数并保存 */
ts_s1 = b_ts_x - a_ts_x;
ts_s2 = c_ts_x - d_ts_x;
lcd_s = xres-50 - 50;
ts_d1 = d_ts_y - a_ts_y;
ts_d2 = c_ts_y - b_ts_y;
lcd_d = yres-50-50;
// 主要为获取以下全局变量,为后续公式(参考三点校准图) :tmp_x = g_kx * (ts_x - g_ts_xc) + g_lcd_xc;
// tmp_y = g_ky * (ts_y - g_ts_yc) + g_lcd_yc; 全局变量的计算原理参考视频
g_kx = ((double)(2*lcd_s)) / (ts_s1 + ts_s2);
g_ky = ((double)(2*lcd_d)) / (ts_d1 + ts_d2);
g_ts_xc = e_ts_x;
g_ts_yc = e_ts_y;
g_lcd_xc = xres/2;
g_lcd_yc = yres/2;
}
- 电阻触摸屏通过ADC时,需AD 芯片的常用滤波处理,使触摸值到达稳定状态。
a. 对于触摸屏要多次测量求平均值
b. 要丢弃非法值(以LCD分辨率作为判断准备)
c. 校准时一定要点准
触摸屏驱动程序
- 按下,产生中断
- 在中断处理程序里,启动ADC转换X,Y坐标(电压值)
- ADC结束,产生ADC中断
- 在ADC中断处理函数里,上报(input_event),启动定时器
- 定时器时间到,启动ADC转换X,Y坐标(电压值),处理长按和滑动
- 松开
通用Touch_Screen代码框架:
static struct input_dev *s3c_ts_dev;
static int s3c_ts_init(void)
{
/* 1. 分配一个input_dev结构体 */
s3c_ts_dev = input_allocate_device();
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
/* 2.2 能产生这类事件里的哪些事件 */
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
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);
/* 3. 注册 */
input_register_device(s3c_ts_dev);
/* 4. 硬件相关的操作 */
return 0;
}
static void s3c_ts_exit(void)
{
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");
tslib校准(直接将上传的电压值通过校准转换成坐标值)