10-S3C2440驱动学习(六)嵌入式linux-触摸屏设备驱动

2019-07-12 20:11发布

触摸屏子系统是通过input子系统来实现,对应设备节点 /dev/input/eventn,熟悉套路后重点放在硬件程序的编写。

一、内核自带触摸屏驱动S3c2410_ts的简单分析

S3c2410_ts.c (driversinput ouchscreen) 内核自带三星的触摸屏驱动 (1)入口函数: 注册一个平台driver static int __init s3c2410ts_init(void) { //     init_MUTEX(&gADClock);        returnplatform_driver_register(&s3c2410ts_driver); } (2)platform_driver结构体 static struct platform_drivers  3c2410ts_driver = {       .driver         = {               .name  = "s3c2410-ts",               .owner = THIS_MODULE,       },       .probe         = s3c2410ts_probe,       .remove         = s3c2410ts_remove, }; 内核中有同名设备的时候,probe函数s3c2410ts_probe会被调用。 (3)static int __init     s3c2410ts_probe(structplatform_device *pdev)做什么了呢?        //分配一个 input_dev结构体 input_dev =input_allocate_device(); //设置    能产生哪些事件 按键类事件,绝对位置类事件 ts.dev =input_dev;         ts.dev->evbit[0]= BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);         ts.dev->keybit[LONG(BTN_TOUCH)]= BIT(BTN_TOUCH); //设置绝对位置的参数        input_set_abs_params(ts.dev,ABS_X, 0, 0x3FF, 0, 0);//绝对位移方向        input_set_abs_params(ts.dev,ABS_Y, 0, 0x3FF, 0, 0);        input_set_abs_params(ts.dev,ABS_PRESSURE, 0, 1, 0, 0);
       ts.dev->private= &ts;        ts.dev->name= s3c2410ts_name;        ts.dev->id.bustype= BUS_RS232;        ts.dev->id.vendor= 0xDEAD;        ts.dev->id.product= 0xBEEF;        ts.dev->id.version= S3C2410TSVERSION; //注册        input_register_device(ts.dev); (4)touch_timer_fire 当事件发生的时候,上报事件 上报事件:input_report_abs--》input_event

二、参考S3c2410_ts从零写S3C2440触摸屏驱动

(1)先写出基本框架
(2)分配一个input_dev结构体 s3c_ts_dev = input_allocate_device();
(3)设置 
1 能产生哪类事件
set_bit(EV_KEY, s3c_ts_dev->evbit);   //能够产生按键类事件
set_bit(EV_ABS, s3c_ts_dev->evbit);  //能够产生触摸屏事件

2 能产生这类事件里的哪些事件 
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);   //能够产生按键类事件里的触摸屏事件
//x y 压力方向
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);//最小值0 最大值3ff(参考2440手册触摸屏10位ADC)
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); //压力的用力度,分级,这里简单 要么0 要么1,要么按下 要么松开 参考:
1th (4)注册 
input_register_device(s3c_ts_dev); (5)硬件相关的操作 5.1首先要对触摸屏硬件了解,才能知道怎么设置硬件。 触摸屏: 触摸屏是欧姆定律的悄秒应用。触摸屏是很薄的两层膜,LCD与触摸屏是俩不同的东西。 按下后,触电会链接到一起。

5.2坐标只不过是电压值,和LCD完全无关。如何对应起来LCD和触摸屏是应用程序的事。 矫正的时候 知不是在LCD和触摸屏之间建立联系。 5.3触摸屏使用过程
为什么有定时器,是因为应用中可能存在,长按滑动。 5.4四个测点的硬件连接
手册:
5.5硬件相关操作代码 读2440手册,看例子怎么写的 5.5.1使能时钟 ts.clock = clk_get(dev, "adc");
clk_enable(ts.clock);


为了省电,内核上电的时候会对一些没用模块关闭掉,暂时不使用的模块。
clk_get:


clk_enable会调用到s3c2410_clkcon_enable,这个函数就是把相应位使能。
代码: //使能时钟(CLKCON[15]) 
clk = clk_get(NULL, "adc");
clk_enable(clk);
5.5.2 查看芯片手册AD和触摸屏部分 2440内部是10位的AD转换器。 最大工作频率2.5M。 最大电压3.3V

AD与触摸屏内部结构:. 8:1  8路选择一路进行ADC转换
工作频率: 刚才提到最大工作频率是2.5M,所以要对PCLK进行分配。转换一次需要5个时钟周期,5us
触摸屏接口模式:
模式1:正常的转换模式,一般的ADC操作,测量某个电压等。 模式2:分离XY坐标,转换模式。 里面又分为2个模式。X模式,Y模式。进入测量X坐标模式,转换结果存在ADCDAT0,并产生中断。测量Y同理也会产生中断。 模式3:自动的连续转换模式: 即转换X,又转换Y。x:ADCDAT0,y:ADCDAT1,最后产生一个中断 模式4:等待中断模式 等待触摸笔按下模式,按下笔后,产生中断。触摸笔按下的时候会产生interrupt (INT_TC)中断。 接下来开始设置触摸屏相关寄存器及相关函数: a:时钟 struct clk* clk; /* 4.1 使能时钟(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);

b:操作寄存器
寄存器名称长度 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;
//设置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);
enter_wait_pen_down_mode(); //根据芯片手册 工作在模式4.
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;
}


使能相当于开关闭合。 默认程序处在enter_wait_pen_down_mode模式,等待触摸笔按下,按下后触发pen_down_up_irq中断处理函数被执行。 static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) //松开,进入等待按下模式,循环
{
printk("pen up ");
enter_wait_pen_down_mode();
}
else                                                 //按下 等待松开模式。
{
printk("pen down ");
enter_wait_pen_up_mode();
}
return IRQ_HANDLED;
}
出口函数: 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);
}
2th
已经可以正常检测到按下和松开,接下来是在按下的时候,启动位置测量。 以上完成了一个简单的触摸屏触发中断的例子 触摸屏按下的时候 我们不打印了,而是进行ADC转换,去获取位置信息。 else
{
//printk("pen down ");
//enter_wait_pen_up_mode();
enter_measure_xy_mode();
start_adc();
}
ADC是需要时间的,我们不能死等,ADC完成之后会出发中断,因此注册如下中断 request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
printk("adc_irq cnt = %d, x = %d, y = %d ", ++cnt, s3c_ts_regs->adcdat0 & 0x3ff, s3c_ts_regs->adcdat1 & 0x3ff);
enter_wait_pen_up_mode();
return IRQ_HANDLED;
}
按下触摸屏进入pen_down_up_irq处理函数,进入测量XY模式,然后启动ADC; 看2440芯片手册 测量XY模式:
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
启动ADC:
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}
AD转换完成,进入中断程序adc_irq static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
printk("adc_irq cnt = %d, x = %d, y = %d ", ++cnt, s3c_ts_regs->adcdat0 & 0x3ff, s3c_ts_regs->adcdat1 & 0x3ff);
enter_wait_pen_up_mode();//等待松开模式
return IRQ_HANDLED;
}

到此可以打印出位置信息来,还需要优化(1)值不是很精确,没做均值处理(2)无法处理滑动 优化错施1:  * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
*/
s3c_ts_regs->adcdly = 0xffff;
优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */ adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;


if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
enter_wait_pen_down_mode();
}
else
{
printk("adc_irq cnt = %d, x = %d, y = %d ", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
enter_wait_pen_up_mode();
}

优化措施3:多次AD转换求平均 /* 优化措施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 { // printk("adc_irq cnt = %d, x = %d, y = %d ", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff); /* 优化措施3: 多次测量求平均值 */ x[cnt] = adcdat0 & 0x3ff; y[cnt] = adcdat1 & 0x3ff; ++cnt; if (cnt == 4) { printk("x = %d, y = %d ", (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(); } }优化措施4:增加软件过滤,多次AD选合适值 if (cnt == 4) { /* 优化措施4: 软件过滤 */ if (s3c_filter_ts(x, y)) { printk("x = %d, y = %d ", (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: 使用定时器处理长按,滑动的情况
static struct timer_list ts_timer;
/* 优化措施5: 使用定时器处理长按,滑动的情况

*/
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer);
del_timer(&ts_timer);
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();
}
}

/* 启动定时器处理长按/滑动的情况 */
mod_timer(&ts_timer, jiffies + HZ/100);

最后 打印坐标改为上报事件 /* 优化措施4: 软件过滤 */
if (s3c_filter_ts(x, y))
{
//printk("x = %d, y = %d ", (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);
}
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();
}
1. ls /dev/event* 
2. insmod s3c_ts.ko
3. ls /dev/event* 
4. hexdump /dev/event0
                       秒            微秒      type code    value
0000000 29a4 0000 8625 0008 0003 0000 0172 0000
0000010 29a4 0000 8631 0008 0003 0001 027c 0000
0000020 29a4 0000 8634 0008 0003 0018 0001 0000
0000030 29a4 0000 8638 0008 0001 014a 0001 0000
0000040 29a4 0000 863c 0008 0000 0000 0000 0000
0000050 29a4 0000 c85e 0008 0003 0000 0171 0000
0000060 29a4 0000 c874 0008 0003 0001 027d 0000
0000070 29a4 0000 c87b 0008 0000 0000 0000 0000
0000080 29a4 0000 ed37 0008 0003 0018 0000 0000
0000090 29a4 0000 ed48 0008 0001 014a 0000 0000
00000a0 29a4 0000 ed4a 0008 0000 0000 0000 0000
之后我们编译tslib export TSLIB_TSDEVICE=/dev/event1       //触摸屏是哪一个
export TSLIB_CALIBFILE=/etc/pointercal   // 校验文件位置    校验后在  /etc/pointercal 生成校验文件 export TSLIB_CONFFILE=/etc/ts.conf     // 配置文件位置
export TSLIB_PLUGINDIR=/lib/ts               //插件位置
export TSLIB_CONSOLEDEVICE=none 
export TSLIB_FBDEVICE=/dev/fb0             //显示屏

到此触摸屏驱动移植成功。

三、代码及测试

1、完整触摸屏代码
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct s3c_ts_regs { unsigned long adccon; unsigned long adctsc; unsigned long adcdly; unsigned long adcdat0; unsigned long adcdat1; unsigned long adcupdn; }; static struct input_dev *s3c_ts_dev; static volatile struct s3c_ts_regs *s3c_ts_regs; static struct timer_list ts_timer; 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 void enter_measure_xy_mode(void) { s3c_ts_regs->adctsc = (1<<3)|(1<<2); } static void start_adc(void) { s3c_ts_regs->adccon |= (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; } static void s3c_ts_timer_function(unsigned long data) { 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 { /* 测量X/Y坐标 */ enter_measure_xy_mode(); start_adc(); } } static irqreturn_t pen_down_up_irq(int irq, void *dev_id) { if (s3c_ts_regs->adcdat0 & (1<<15)) { //printk("pen up "); 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("pen down "); //enter_wait_pen_up_mode(); enter_measure_xy_mode(); start_adc(); } return IRQ_HANDLED; } 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 ", ++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 ", (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; } 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; } 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); } module_init(s3c_ts_init); module_exit(s3c_ts_exit); MODULE_LICENSE("GPL");
2、采用tslib测试