-
开发环境:
- 开发板:JZ2440V3
- CPU:samsunS3C2440
- 内核:Linux3.4.2
- 编译工具:arm-linux-gcc 4.3.2
- LCD:4.3存液晶屏AT043TN24
- 参考文献:
触摸屏使用过程:
- 触摸屏某点被按下,产生INT_TC中断;
- 在中断处理程序中,打开定时器
- 定时器时间到,启动ADC转换,得到x和y坐标;
- ADC结束,产生ADC中断;
- 、在ADC中断处理函数里,上报(input_event),启
- 抬起,松开屏幕
1、编写基本框架(s3c_ts.c)
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
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. 硬件相关的操作 */
/* 4.1 使能时钟(CLKCON[15]) */
/* 4.2 设置S3C2440的ADC/TS寄存器 */
return 0;
}
static void s3c_ts_exit(void)
{
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");
2、分析内核自带触摸屏驱动
先打开JZ2440开发板原理图,查看触摸屏的四根引脚(TSYM、TSYP、TSXM、TSXP)分别接在ADC的——AIN4、5、6、7上面。
通过S3C2440手册搜索发现,这些引脚不需要配置什么寄存器就可以使用。
看一下内核自带的触摸屏驱动(drivers/input/touchscreen/s3c2410_ts.c),找到probe函数,看做了什么事情,
ts.clock = clk_get(dev, "adc");
if (IS_ERR(ts.clock)) {
dev_err(dev, "cannot get adc clock source\n");
return -ENOENT;
}
clk_enable(ts.clock);
这里有个使能时钟的函数:
Tips:在内核启动的时候,为了省电,会把一些不相关的模块给关掉。
怎么关?就是通过设置CLKCON寄存器或者是(Clock Gating Control Register),我们在要用任何模块之前必须把对应的位置1(打开模块时钟)。
内核中,就是通过clk_get 和clk_enable使能模块时钟的。
然后,再看芯片手册上的ADC和触摸屏接口那一章:
The 10-bit CMOS ADC (Analog to Digital Converter) is a recycling type device with 8-channel analog inputs. It converts the analog input signal into 10-bit binary digital codes at a maximum conversion rate of 500KSPS with 2.5MHz A/D converter clock. A/D converter operates with on-chip sample-and-hold function and power down mode is supported.
Touch Screen Interface can control/select pads (XP, XM, YP, YM) of the Touch Screen for X, Y position conversion. Touch Screen Interface contains Touch Screen Pads control logic and ADC interface logic with an interrupt generation logic.
这里说的就是这里面有个10位的mos ADC转换器,有8路信道。在ADC工作频率为2.5MHz时,最大转换频率是500KSPS,同时,因为Power Supply Voltage: 3.3V (最大输入电压是3.3v ),所以如果ADC的输入电压是3.3V的话,输出就是10个1(0x3ff),如果是0V的话,输出就是0。每个刻度就是3.3v/10位 10位的话就是1024 就是3.3V/1024 最小刻度是3mv
然后是我们的ADC转换时间
如果PCLK是50MHZ,而ADC最大工作频率是2.5MZ,所以要设置分频系数,把这个频率给降低下来
然后下面就是它提供的例子 the prescaler value is 49, ADC的工作频率就是A/D converter freq. = 50MHz/(49+1) = 1MHz .
转换时间就需要Conversion time = 1/(1MHz / 5cycles) = 1/200kHz = 5us (1MHZ的5个周期 就是5us )
- 再看下接口模式:
正常的转换模式(Normal Conversion Mode):正常转换模式就是一般的ADC操作,比如说你想测量某个电压。
分离的xy坐标转换模式(Separate X/Y Position Conversion Mode) :这种模式分为两个部分。一种是测量X坐标,一种是测量Y坐标。
进入X或Y坐标模式需要采取的措施是:
1.设置0x69到TSCONn寄存器
2.通过设置TSADCCONn开始转换
3.X坐标转换结束后能被中断给通知
4.从TSDATXn读出坐标转换数据
自动(连续)的XY转换模式(Auto (Sequential) X/Y Position Conversion Mode):当你进入这个模式之后,它会自动的帮你即转换x坐标也转换Y坐标
-
等待中断模式,就是等待按下产生中断模式:若想在我们按下触摸屏后让它产生中断,就要进入该模式。当触摸笔按下的时候,触摸屏会产生INT_PENn这个中断。
- 怎么进入这个模式呢?设置rADCTSC=0xd3 就可以了。
3、硬件相关代码编写
3.1 使能时钟
//函数体外定义全局变量clk
struct clk* clk;
/* 4.1 使能时钟(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
3.2 设置S3C2440的ADC控制寄存器
//函数体外定义寄存器结构体
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
//仅在本文件内使用的静态寄存器指针变量
static volatile struct s3c_ts_regs *s3c_ts_regs;
//在初始化函数内对寄存器结构体进行地址映射
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
3.3 设置ADC控制寄存器
- ADCCON
PRESCEN(bit[14]) : =1 A/D converter prescaler enable
PRSCVL(bit[13:6]): =49 A/D converter prescaler value,最大值为2.5MHz,在此我们取1MHz,所以ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
SEL_MUX(bit[5:3]):模拟信号输入信道选择,若为普通的AD转换,则可在此选择AIN0-3,我们先不设
STDBM(bit[2]):省电模式选择位,默认为0,我们用的是非省电模式,所以在此可不用设置
READ_START(bit[1]):通过读操作自动启动AD转换,我们在此不选该功能
ENABLE_START(bit[0]): 通过置1手动开启AD转换(转换完成后自动清零),先设为0
s3c_ts_regs->adccon = (1<<14)|(49<<6);
3.4 编写触摸中断处理函数(pen_down_up_irq)并注册中断(INT_TC)
当触摸屏被按下时,AD转换进入等待中断模式(通过设置ADCTSC=0xd3)
当触摸屏没有被按下时,由于上拉电阻的原因,Y_ADC处于高电平状态;当被按下时,Y_ADC的电压由于联通到y轴接地而变为低电平,此低电平就是中断的触发信号,使之产生pen down事件。
Tips:
左边的图为读取x坐标时的等效电路图:测X_ADC时,S1(XP_SEN Enable)、S2(YP_SEN Disable)、S3(XM_SEN Enable),S4(YM_SEN Disable)、S5(PULL_UP Disable);
测Y_ADC时,S1(XP_SEN Disable)、S2(YP_SEN Enable)、S3(XM_SEN Disable断开,S4(YM_SEN Enable)、S5(PULL_UP Disable)接通;
//调用该函数进入等待触摸屏按下模式
static void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc = 0xd3;
}
//调用该函数进入等待触摸屏松开模式
static void enter_wait_pen_up_mode(void)
{
s3c_ts_regs->adctsc = 0x1d3;
}
//触摸屏中断处理函数V1.0
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) //Stylus up 松开
{
printk("pen up\n");
enter_wait_pen_down_mode(); //送开的话,就进入等待下次按下的模式
}
else //Stylus down 按下
{
printk("pen down\n");
enter_wait_pen_up_mode(); //按下的话,就进入等待松开的模式
}
return IRQ_HANDLED;
}
在初始化函数中,注册中断处理函数:
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
3.5 编写退出函数
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}
4、测试
4.1 编译无触摸屏驱动的内核映像文件
//虚拟机(上位机)命令行下
cd linux3.4.2
make menuconfig //去掉原来的LCD驱动程序
make uImage
依次进入:
- Device Drivers——>
- Input device support——>
- Touchscreens——>
- < > S3C2410/S3C2440 touchscreens
- Touchscreens——>
- Input device support——>
4.2 使用新内核启动并加载自己的触摸屏驱动
//Uboot命令行
tftp 30000000 uImage 或者 nfs 30000000 虚拟机IP:网络文件系统目录/uImage
bootm 30000000
//新内核启动后的命令行下
insmod s3c_ts.ko
按下/松开触摸屏进行测试
//新内核启动后的命令行下
pen down
pen up
#
#
pen down
pen up
5、拓展——按下时启动ADC,显示坐标
修改触摸屏中断处理函数,当触摸屏被按下时,进入TC中断处理函数,自动测量模式并启动ADC转换;
AD转换完成后产生ADC中断,并在中断处理函数中完成相应任务(例如显示x、y点坐标)后,进入等待触摸屏松开模式。
5.1 修改触摸屏(TC)中断处理函数
//触摸屏中断处理函数V2.0
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) //Stylus up 松开
{
printk("pen up\n"); //触摸屏被松开
enter_wait_pen_down_mode(); //松开的话,就进入等待下次按下的模式
}
else //Stylus down 按下
{
//printk("pen down\n");
//enter_wait_pen_up_mode();
enter_measure_xy_mode(); //按下的话,就进入测量的模式
start_adc(); //启动ADC转换
enter_wait_pen_up_mode(); //进入等待触摸屏被松开
}
return IRQ_HANDLED;
}
5.2 编辑进入自动测量模式函数
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2); //断开上拉电阻,打开AUTO_PST
}
5.3 编辑启动ADC函数
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}
5.4 编辑ADC中断处理函数
//在初始化文件中编写ADC中断处理函数
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
return IRQ_HANDLED;
}
5.5 注册ADC中断处理函数
//在初始化函数中对ADC中断进行注册
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
5.6 修改退出函数
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}
5.7 测试
修改完成后,重新编译生成s3c_tc.ko驱动文件,下载安装后测试:
rmmod s3c_tc //卸载旧驱动
insmod s3c_tc.ko //安装新驱动
//碰触触摸屏
#
#
adc_irq cnt = 1, x = 330, y = 667
pen_up
adc_irq cnt = 2, x = 239, y = 739
pen_up
adc_irq cnt = 3, x = 215, y = 779
#
#
测试发现:当我们滑动触摸屏时,其输出不能连续显示。且输出的坐标也不是太精确!
6、优化——支持滑动操作、坐标更精确
6.1 使电压稳定后再进行转换——设置ADC延时
//修改初始化函数,添加ADCDLY延时
static int s3c_ts_init(void)
{
struct clk* clk;
/* 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. 硬件相关的操作 */
/* 4.1 使能时钟(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
/* 4.2 设置S3C2440的ADC/TS寄存器 */
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
/* bit[14] : 1-A/D converter prescaler enable
* bit[13:6]: A/D converter prescaler value,
* 49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
* bit[0]: A/D conversion starts by enable. 先设为0
*/
s3c_ts_regs->adccon = (1<<14)|(49<<6);
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
/* 优化措施1:
* 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
*/
s3c_ts_regs->adcdly = 0xffff;
enter_wait_pen_down_mode();
return 0;
}
6.2 多次ADC转换,求平均值
从按下至松开的这段期间,进行多次AD转换,并求其平均数。
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15)) /* 已经松开 */
{
cnt = 0;
enter_wait_pen_down_mode();
}
else
{
/* 优化措施3: 多次测量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4) //如果测量次数已经到了4次,则打印出平均值,并等待触摸屏被松开
{
printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
cnt = 0;
enter_wait_pen_up_mode();
}
else //如果测量次数不够4次,则再度启动adc转换
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
6.3 添加软件过滤功能
6.3.1 编写软件过滤函数
把四次测量的值放入数组中,从头至尾方向,依次使前两项的平均值和第3项的值进行比较,若绝对值在允许误差范围内,则表示过滤成功,返回1,否则失败,返回0
static int s3c_filter_ts(int x[], int y[])
{
#define ERR_LIMIT 10
int avr_x, avr_y;
int det_x, det_y;
avr_x = (x[0] + x[1])/2;
avr_y = (y[0] + y[1])/2;
det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
return 0;
avr_x = (x[1] + x[2])/2;
avr_y = (y[1] + y[2])/2;
det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
return 0;
return 1;
}
6.3.2 修改adc中断处理函数
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
cnt = 0;
enter_wait_pen_down_mode();
}
else
{
/* 优化措施3: 多次测量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 优化措施4: 软件过滤 */
if (s3c_filter_ts(x, y))
{
printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
}
cnt = 0;
enter_wait_pen_up_mode();
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
6.4 支持滑动操作
6.4.1 定义一个定时器
static struct timer_list ts_timer;
6.4.2 修改初始化函数,添加定时器
static int s3c_ts_init(void)
{
struct clk* clk;
/* 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. 硬件相关的操作 */
/* 4.1 使能时钟(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);
/* 4.2 设置S3C2440的ADC/TS寄存器 */
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
/* bit[14] : 1-A/D converter prescaler enable
* bit[13:6]: A/D converter prescaler value,
* 49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
* bit[0]: A/D conversion starts by enable. 先设为0
*/
s3c_ts_regs->adccon = (1<<14)|(49<<6);
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
/* 优化措施1:
* 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
*/
s3c_ts_regs->adcdly = 0xffff;
/* 优化措施5: 使用定时器处理长按,滑动的情况
*
*/
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer); //在出口函数中要删除它
enter_wait_pen_down_mode();
return 0;
}
6.4.3 修该ADC中断处理函数,添加启动定时器处理长按、滑动的情况
添加的代码,见优化措施5:
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
cnt = 0;
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
/* 优化措施3: 多次测量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 优化措施4: 软件过滤 */
if (s3c_filter_ts(x, y))
{
printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
}
cnt = 0;
enter_wait_pen_up_mode();
/* 优化措施5:启动定时器处理长按/滑动的情况 */
mod_timer(&ts_timer, jiffies + HZ/100); //添加一个10ms的定时器,时间到了就进入定时器处理函数
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
6.4.4 编辑定时器处理函数
当10ms的定时器已经到了的话,
static void s3c_ts_timer_function(unsigned long data)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) /* 如果已经松开,则等待下次按下 */
{
enter_wait_pen_down_mode();
}
else
{
/* 测量X/Y坐标 */
enter_measure_xy_mode();
start_adc();
}
}
6.4.5 修改出口函数
static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
free_irq(IRQ_ADC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
del_timer(&ts_timer); //删除定时器
}
6.4.6 编译测试
重新编译安装驱动后,使用触摸屏滑动或长按,观察输出数据:坐标可以连续输出!
7、将printk函数替换成上报事件,得到完整触摸屏驱动
7.1 修改adc中断处理函数
注释掉printk函数,添加上报事件语句:
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4], y[4];
int adcdat0, adcdat1;
/* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;
if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
cnt = 0;
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0); //压力为0
input_report_key(s3c_ts_dev, BTN_TOUCH, 0); //无触摸屏按键类事件
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
/* 优化措施3: 多次测量求平均值 */
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
/* 优化措施4: 软件过滤 */
if (s3c_filter_ts(x, y))
{
//printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
//此处仅需表示松开或按下两种压力状态,若是绘图板,则会有多个压力值
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1); //触摸屏按键类事件
input_sync(s3c_ts_dev);
}
cnt = 0;
enter_wait_pen_up_mode();
/* 启动定时器处理长按/滑动的情况 */
mod_timer(&ts_timer, jiffies + HZ/100);
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
7.2 修改TC中断处理函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
//printk("pen up\n");
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
enter_wait_pen_down_mode();
}
else
{
//测量xy坐标
enter_measure_xy_mode();
start_adc();
}
return IRQ_HANDLED;
}
7.3 编译测试
7.3.1 使用hexdump命令测试
rmmod s3c_tc
ls /dev/event* //安装新驱动前查看原来有哪些
ls:/dev/event*:No such file or directory
insmod s3c_tc.ko
ls /dev/event* //安装完新驱动后,查看新增加的是哪个event?
/dev/event0
hexdump /dev/event0
输出如下:
其输出结果的前4行分别对应adc_irq处理函数中的4个上报事件:
input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1); input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
前面的秒和微妙代表后面事件发生的时间。
第一行代表上报事件input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
type:代表EV_ABS宏的取值为3,即绝对位移;
code:代表ABS_X宏的取值为0,即x方向;
value:4个字节表示x坐标值。
第4行代表上报事件input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
type:代表EV_ABS宏的取值为1,即按键;
code:代表ABS_X宏的取值为014a,即BTN_TOUCH;
value:4个字节表示按键值1。
7.3.2 使用tslib测试
- 安装tslib
tar xzf tslib-1.4.tar.gz
cd tslib
./autogen.sh
提示错误:./autogen.sh: 4: autoreconf: not found
解决方法:sudo apt-get install autoconf automake libtool
mkdir tmp
echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install
- 复制tmp目录下的所有文件到开发板的根目录中
cp tmp /nfs_root/first_fs/ts_dir -rfd
cd /mnt/ts_dir
cp * / -rfd
- 安装驱动程序
# insmod cfbcopyarea.ko
# insmod cfbfillrect.ko
# insmod cfbimgblt.ko
# insmod s3c_lcd.ko //也可以使用内核自带的LCD驱动
//如果发生段错误,则重新编译lcd驱动后,再加载
# ls /dev/ev*
ls:/dev/event*:No such file or directory
# insmod s3c_ts.ko
# ls /dev/ev*
/dev/event0 //这是触摸屏设备
#
# ls /dev/fb0 //这是LCD设备
-
测试
- 修改/etc/ts.conf第一行(去掉#号和第一个空格)
# module_raw input 改为: module_raw input
- 添加环境变量
# export TSLIB_TSDEVICE=/dev/event0 //指定tc触摸屏设备 # export TSLIB_CALIBFILE=/etc/pointercal //校验文件 # export TSLIB_CONFFILE=/etc/ts.conf //配置文件 # export TSLIB_PLUGINDIR=/lib/ts //插件 # export TSLIB_CONSOLEDEVICE=none # export TSLIB_FBDEVICE=/dev/fb0 //指定lcd显示屏 # ts_calibrate //开始校验 错误提示:selected device is not a touchscreen I understand
-
解决办法:看到这个错误的提示,我们应该想到tslib源码里面肯定有这个错误提示的条件,这样就让我们有了思路,我们可以去找到这个文件看看他错误判断的条件是啥?
- 查找“selected device is not a touchscreen I understand”出处:
cd tslib/plugins grep -nr "selected device is not a touchscreen I understand" 匹配到二进制文件 .libs/input.so 匹配到二进制文件 .libs/input-raw.o input-raw.c:61: fprintf(stderr, "selected device is not a touchscreen I understand\n");
可以看出,该错误提示语句出自input-raw.c之手!
- 打开input-raw.c文件,找出相关段落
47 static int check_fd(struct tslib_input *i) 48 { 49 struct tsdev *ts = i->module.dev; 50 int version; 51 u_int32_t bit; 52 u_int64_t absbit; 53 54 if (! ((ioctl(ts->fd, EVIOCGVERSION, &version) >= 0) && 55 (version == EV_VERSION) && 56 (ioctl(ts->fd, EVIOCGBIT(0, sizeof(bit) * 8), &bit) >= 0) && 57 (bit & (1 << EV_ABS)) && 58 (ioctl(ts->fd, EVIOCGBIT(EV_ABS, sizeof(absbit) * 8), &absbit) >= 0) && 59 (absbit & (1 << ABS_X)) && 60 (absbit & (1 << ABS_Y)) && (absbit & (1 << ABS_PRESSURE)))) { 61 fprintf(stderr, "selected device is not a touchscreen I understand\n"); 62 return -1; 63 } 64 65 if (bit & (1 << EV_SYN)) 66 i->using_syn = 1; 67 68 return 0; 69 }
tslib通过EVIOCGVERSION来获取驱动的版本号,然后再通过EVIOCGBIT来判断设备是否为触摸屏,最后获取触摸屏的X轴(ABS_X),Y轴(ABS_Y),以及压力(ABS_PRESSURE)。
其中只要有一项内容不正确,tslib都会认为该设备不是触摸屏,而打印出“selecteddevice is not a touchscreen I understand”错误。
EVIOCGBIT ioctl处理 4个参数 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield))。 ev_type是返回的 type feature( 0是个特殊 case,表示返回设备支持的所有的type features)。 max_bytes表示返回的最大字节数。 bitfield域是指向保存结果的内存指针。 return value表示保存结果的实际字节数,如果调用失败,则返回负值。
这里用bit和EV_ABS相与来判断是不是EV_ABS事件,判断是这个事件后再判断这个事件是否又X,Y轴和压力值。
- 核查内核(linux-3.4.2/include/linux/input.h)与编译tslib的编译器(/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/linux/input.h)中的EV_VERSION版本号是否一致。
内核中的定义为: #define EV_VERSION 0x010001
编译器中的定义: #define EV_VERSION 0x010000
两者不等,所以只要将内核中的版本号改为0x010000再重新编译内核,或者将编译器中的改为0x010001,然后再重新编译tslib库也行。
另外:若在触摸屏驱动程序中没有同时上报坐标值和压力值也会引起类似的错误提醒。因为tslib同时判断的几个条件必须同时满足,方才不会报错。所以不管你的应用程序需不需要测试压力值,在你的触摸屏驱动程序中都要上报压力值。即:
input_report_abs(s3c_ts_dev, ABS_X, x);
input_report_abs(s3c_ts_dev, ABS_Y, y);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
input_sync(s3c_ts_dev);
还有一点需要提醒的是,在设置tslib环境变量的时候,一定要正确设置触摸屏(例如:event0)和显示屏(例如:fb0)的文件名,特别是当内核中安装了多个event事件的时候。
- 测试继续:
# ts_calibrate
xres = 480, yres = 272 //屏幕出现十字线,用于点击校验
Took 3 samples...
Top left : X = 713 Y = 806
。。。
Center : X = 477 Y = 524
...
Calibration constant:16972928 -18997 -162 ...
# ls /etc/pointercal
/etc/pointercal
# ts_test //十字架会随着你的手指在触摸屏上移动。
215.787878: 124 234 1
215.782448: 102 219 0
。。。
# ts_ //按下tab命令补全键,显示所有可执行测试命令
ts_harvest ts_print_raw//打印触摸点原始坐标
ts_calibrate ts_print//打印触摸点像素坐标
ts_test