- 开发环境:
- 开发板: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)
{
s3c_ts_dev = input_allocate_device();
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
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);
input_register_device(s3c_ts_dev);
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上面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AnJd88Vm-1627315066794)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210724163001.png)]
通过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转换时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eY78GCUy-1627315066797)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210724170349.png)]
如果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 使能时钟
struct clk* clk;
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控制寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctM2H8cE-1627315066801)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725154619.png)]
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)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KqNOrul-1627315066804)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725165524.png)]
当触摸屏没有被按下时,由于上拉电阻的原因,Y_ADC处于高电平状态;当被按下时,Y_ADC的电压由于联通到y轴接地而变为低电平,此低电平就是中断的触发信号,使之产生pen down事件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYCIWAa7-1627315066809)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725202306.png)]
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;
}
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
printk("pen up\n");
enter_wait_pen_down_mode();
}
else
{
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
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)中断处理函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15))
{
printk("pen up\n");
enter_wait_pen_down_mode();
}
else
{
enter_measure_xy_mode();
start_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);
}
5.3 编辑启动ADC函数
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}
5.4 编辑ADC中断处理函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vq6HXUS1-1627315066810)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725203338.png)]
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中断处理函数
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延时
static int s3c_ts_init(void)
{
struct clk* clk;
s3c_ts_dev = input_allocate_device();
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
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);
input_register_device(s3c_ts_dev);
clk = clk_get(NULL, "adc");
clk_enable(clk);
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
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);
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;
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
{
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 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
{
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;
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
{
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 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;
s3c_ts_dev = input_allocate_device();
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);
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);
input_register_device(s3c_ts_dev);
clk = clk_get(NULL, "adc");
clk_enable(clk);
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
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);
s3c_ts_regs->adcdly = 0xffff;
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;
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
{
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 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();
mod_timer(&ts_timer, jiffies + HZ/100);
}
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
{
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;
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
{
x[cnt] = adcdat0 & 0x3ff;
y[cnt] = adcdat1 & 0x3ff;
++cnt;
if (cnt == 4)
{
if (s3c_filter_ts(x, y))
{
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))
{
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
{
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
输出如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtcvUt1t-1627315066811)(https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210725225531.png)]
其输出结果的前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测试
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
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之手!
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
|