NS2009是一款带I2C接口的4线制电阻式触摸屏控制电路,内含12位分辨率A/D转换器。NS2009能通过执行两次A/D转换查出被按的屏幕位置,除此之外,还可以测量加在触摸屏上的压力。在2.7V的典型工作状态下,功耗可小于0.75mW。可应用于移动电话(手机等)、触摸屏显示器、个人数字助理(PDA)便携式设备、POS机终端设备,等等。
开发平台:Linux操作系统
内核版本:3.10
摘要:本文基于ns2009驱动程序,主要内容是ns2009电阻触摸屏的采样、滤波。
1.ns2009驱动程序相关数据结构
struct ns2009_ts
{
spinlock_t irq_lock;
struct i2c_client *client;
struct input_dev *input_dev;
wait_queue_head_t irq_wait;
struct timer_list timer;
int (*get_pen_status)(void);
int irq_is_disable;
bool stopped;
unsigned int irq;
};
这个结构体包含了i2c子系统、输入子系统、定时器和中断等一些信息,这些东西是实现按键坐标从采集到上报到应用层必不可少的组件。
struct ns2009_data
{
unsigned int x;
unsigned int y;
unsigned int z;
};
保存按键坐标、压力值的结构体,输入子系统将该结构体保存的内容上报到应用层。
为了保证触摸的准确度需要对采集到的坐标进行滤波,这里的滤波方案如下:触摸一次取5个坐标,然后冒泡排序、滤波丢弃漂移较大的坐标、去头去尾再求平均值,相关代码如下:
(1)冒泡排序算法
/*按照从小到大进行冒泡排序*/
static inline void bubble_sort(int a[], int n)
{
int i = 0,j = 0;
int temp = 0;
for(i=0; i<n-1; i++)
{
int isSorted = 1;
for(j = 0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
isSorted = 0;
temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
if(isSorted)
break;
}
}
(2)滤波算法
static inline int tsc_filter(const u32 *x, const u32 *y, const int cread)
{
int tmp = 0;
int new_value, i = 0;
int value = 0;
for(i= 1; i < cread; i++) {
value = x[i-1];
new_value = x[i];
tmp = new_value - value;
if (tmp > ERR_LIMIT)//漂移较大的点直接丢弃
return 0;
}
tmp = 0;
value = 0;
new_value = 0;
for(i= 1; i < cread; i++) {
value = y[i-1];
new_value = y[i];
tmp = new_value - value;
if (tmp > ERR_LIMIT)//漂移较大的点直接丢弃
return 0;
}
return 1;
}
(3)使用滤波算法读取坐标的函数
static int ns2009_read_coordinates(struct ns2009_ts *ts, struct ns2009_data *data, unsigned int *z1, unsigned int *z2)
{
int i = 0;
unsigned int temp_x = 0, temp_y = 0;
//unsigned int xsum = 0, ysum = 0;
unsigned int yvals[READ_NUM], xvals[READ_NUM];
//unsigned int cread = READ_NUM;
//u8 ADC_ON = PWRDOWN;
u8 ADC_OFF = PWRUP;
rtp_i2c_write_bytes(ts->client, &ADC_OFF, 1);
//usleep_range(50, 80);
for(i = 0; i < READ_NUM; i++) {
xvals[i] = ns2009_xfer(ts, READ_X);
yvals[i] = ns2009_xfer(ts, READ_Y);
if (!is_valid_coor(xvals[i], yvals[i]))
continue;
}
bubble_sort(xvals, READ_NUM);
bubble_sort(yvals, READ_NUM);
if(!tsc_filter(xvals, yvals, READ_NUM)) {
ns2009_dbg("Coor quality too bad\n");
return -1;
}
if (READ_NUM <= 3) {
for (i = 0; i < READ_NUM; i++) {
temp_x += xvals[i];
temp_y += yvals[i];
}
temp_x /= READ_NUM;
temp_y /= READ_NUM;
} else {
for(i = 1; i < READ_NUM - 1; i++) {
temp_x += xvals[i];
temp_y += yvals[i];
}
temp_x /= READ_NUM - 2;
temp_y /= READ_NUM - 2;
}
//data->x = XADC_MAX - temp_x;
data->x = temp_x;
data->y = temp_y;
*z1 = ns2009_xfer(ts, READ_Z1);
*z2 = ns2009_xfer(ts, READ_Z2);
return 0;
}
由于读取触摸屏坐标是一个复杂且耗时的过程,所以不能直接在硬中断里面读取。在linux内核中为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点, Linux 将中断处理程序分解为两个半部:顶半部( top half)和底半部( bottom half)。顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作。底半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是底半部和顶半部的最大不同,因为顶半部往往被设计成不可中断。底半部则相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务程序中执行。linux实现底半部的机制主要有tasklet、工作队列和软中断。
因此,电阻触摸屏驱动读取触摸屏坐标的函数应该要放在软中断里面执行。在本触摸屏驱动代码中,注册中断的函数为:
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
参数说明
irq:中断号
handler:硬件中断处理函数
thread_fn:软件中断处理函数,thread_fn将会在内核线程里执行
irqflag:中断的触发标志
devname:设备名
dev_id:给中断函数的传参
该驱动可以参考Linux内核ts2007.c驱动程序来写,基本上把上面的滤波、读取加上,再改一下注册中断的方式就行。