1、先来回忆之前第12节分析的输入子系统(请点击这里)
其中输入子系统层次如下图所示:
其中事件处理层的函数是通过input_register_handler()函数注册到input_handler_list链表中
搜索input_register_handler注册函数,就可以看到都是事件处理层里的函数:
所以最终如下图所示:
右边的驱动事件处理,内核是已经写好了的,所以我们的触摸屏只需要写具体的驱动设备,然后内核会与触摸屏驱动tsdev.c自动连接。
2、本节需要用到的结构体成员如下:
struct input_dev {
void *private;
const char *name; //设备名字
const char *phys; //文件路径,比如 input/buttons
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; //表示支持哪类事件,常用有以下几种事件(可以多选)
//EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
//EV_KEY 键盘事件
//EV_REL (relative)相对坐标事件,比如鼠标
//EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应
//EV_MSC 其他事件,功能
//EV_LED LED灯事件
//EV_SND (sound)声音事件
//EV_REP 重复键盘按键事件
//(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)
//EV_FF 受力事件
//EV_PWR 电源事件
//EV_FF_STATUS 受力状态事件
unsigned long keybit[NBITS(KEY_MAX)]; //存放支持的键盘按键值
//键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)、BTN_TOUCH(触摸屏的按键)
unsigned long relbit[NBITS(REL_MAX)]; //存放支持的相对坐标值
unsigned long absbit[NBITS(ABS_MAX)]; //存放支持的绝对坐标值,存放下面4个absxxx[]
unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能
unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED
unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音
unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备
unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能
... ...
/*以下4个数组都会保存在上面成员absbit[]里,数组号为:ABS_xx ,位于include/linux/input.h */
/*比如数组0,标志就是ABS_X,以下4个的absXXX[0]就是表示绝对位移X方向的最大值、最小值... */
/*对于触摸屏常用的标志有:
ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向,比如绘图,越用力线就越粗)* /
int absmax[ABS_MAX + 1]; //绝对坐标的最大值
int absmin[ABS_MAX + 1]; //绝对坐标的最小值
int absfuzz[ABS_MAX + 1]; //绝对坐标的干扰值,默认为0,
int absflat[ABS_MAX + 1]; //绝对坐标的平焊位置,默认为0
... ...
3、本节需要用到的函数:
struct input_dev *input_allocate_device(void); //向内存中分配input_dev结构体
input_free_device(struct input_dev *dev); //释放内存中的input_dev结构体
input_register_device(struct input_dev *dev); //注册一个input_dev,若有对应的驱动事件,
则在/sys/class/input下创建这个类设备
input_unregister_device(struct input_dev *dev); //卸载/sys/class/input目录下的
input_dev这个类设备
set_bit(nr,p); //设置某个结构体成员p里面的某位等于nr,支持这个功能
/* 比如:
set_bit(EV_KEY,buttons_dev->evbit); //设置input_dev结构体buttons_dev->evbit支持EV_KEY
set_bit(KEY_S,buttons_dev->keybit); //设置input_dev结构体buttons_dev->keybit支持按键”S”
*/
input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);
//设置绝对位移的支持参数
//dev: 需要设置的input_dev结构体
//axis : 需要设置的数组号,常用的有: ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向)//min: axis方向的最小值, max:axis方向的最大值, fuzz: axis方向的干扰值, flat:axis方向的平焊位置
input_report_abs(struct input_dev *dev, unsigned int code, int value);
//上报EV_ABS事件
//该函数实际就是调用的input_event(dev, EV_ABS, code, value);
//*dev :要上报哪个input_dev驱动设备的事件
// code: EV_ABS事件里支持的哪个方向,比如X坐标方向则填入: ABS_X
//value:对应的方向的值,比如X坐标126
input_report_key(struct input_dev *dev, unsigned int code, int value);
//上报EV_KEY事件
input_sync(struct input_dev *dev); //同步事件通知,通知系统有事件上报
struct clk *clk_get(struct device *dev, const char *id);
//获得*id模块的时钟,返回一个clk结构体
//*dev:填0即可, *id:模块名字, 比如"adc","i2c"等,名字定义在clock.c中
clk_enable(struct clk *clk);
//开启clk_get()到的模块时钟,就是使能CLKCON寄存器的某个模块的位
4、电阻式触摸屏介绍:
如下图所示,2440开发板使用的是4线触摸屏,该4线连接在2440的AIN4~AIN7引脚上,该引脚专门是用来接收模拟输入信号
引脚说明:
YM:(Y Minus)触摸屏的Y坐标的负线,也可以用Y-表示
YP:(Y Power)触摸屏的Y坐标的正线,也可以用Y+表示
XM:(X Minus)触摸屏的X坐标的负线,也可以使用X-表示
YP:(X Power)触摸屏的Y坐标的正线,也可以使用X+表示
4.1 4线触摸屏包含了两个阻性层,如下图所示:
当没有触摸按下时,X和Y层是分离的,此时就测不到电压
4.2 测X坐标方向时:
如下图,把XP接3.3V,XM接0V,YP和YM悬空,我们以按压X坐标的中间位置,X层和Y层便闭合了,此时YP就会输出当前X坐标值的1.66V给CPU
4.3 测Y坐标方向时:
如下图,把YP接3.3V,YM接0V,XP和XM悬空,我们以按压X坐标的中间位置,X层和Y层便闭合了,此时XP就会输出当前X坐标值的1.66V给CPU
5、接下来开始看2440手册
如下图,2440的ADC分辨率为10位(0~0X3FFF)
如下图,若工作在普通ADC模式,则通过寄存器ADCCON->SEL_MUX来选择转换哪个引脚的模拟信号
当设置为ADC等待中断模式时,测到有屏幕笔尖触摸,就会产生INT_TC中断
其中ADC的工作频率最大为2.5MHz,需要设置寄存器ADCCON->PRSCVL更改分频系数
5.1 获取笔尖触摸按下/松开使用的是ADC等待中断模式:
当笔尖落下时,触摸屏控制器产生中断(INT_TC)信号。需要设置寄存器
ADCTSC=0xd3/0x1d3
设置寄存器ADCTSC=0x0d3/0x1d3(X 1101 0011)时(如下图):
开启YM开关,使能XP上拉,开启等待中断模式
当有笔尖按下时,X层和Y层闭合,然后会拉低XP和XM电平,输出低电平
设置为0x0d3是检测触摸低电平,设置为0x1d3是检测触摸上拉电平
(PS:ADCDAT0的bit15位用来标志笔尖是按下还是松开)
5.2 获取XY坐标时,使用的是自动X/Y方向转换模式
当ADC转换成功,X坐标值到ADCDAT0和Y坐标值到ADCDAT1后,就会产生INT_ADC中断
自动获取XY坐标时(如下图):
设置寄存器ADCTSC=0X0C(关闭XP上拉、启动自动XY方向转换)
设置寄存器ADCCON的位[0]=1(开启一次ADC转换,当ADC转换成功该位清0)
6、编写代码
步骤如下:
6.1 在init入口函数
1)分配一个input_dev结构体
2)设置input_dev的成员
->2.1)设置input_dev->ebit支持按键时间,绝对位移时间
(触摸屏:通过按键BTN_TOUCH获取按下/松开,通过绝对位移获取作弊啊)
->2.2)设置input_dev->keybit支持BTN_TOUCH触摸屏笔尖按下
->2.3)设置input_dev->absbit支持ABS_X、ABS_Y、ABS_PRESSURE
input_set_abs_params(ts.dev,ABS_X,0,0x3FF,0,0);
input_set_abs_params(ts.dev,ABS_Y,0,0x3FF,0,0); //0x3FF:最大值为10位ADC
input_set_abs_params(ts.dev,ABS_PRESSURE,0,1,0,0); //压力最多就是1
3)注册input_dev驱动设备到内核中
4)设置触摸屏相关的硬件
->4.1)开启ADC时钟,使用clk_get()和clk_enable()函数
->4.2)ioremap获取寄存器地址,设置寄存器ADCCON=(1<<14)|(49<<6),分频
->4.3)设置寄存器ADCDLY=0xffff,ADC启动延时时间设为最大值,使触摸按压更加稳定
->4.4)开启IRC_TC笔尖中断、开启IRQ_ADC中断获取XY坐标
->4.5)初始化定时器,增加触摸滑动功能
->4.6)最后设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断
6.2 在出口函数中:
1)注销内核里的input_dev
2)释放中断、删除定时器、iounmap注销地址
3)释放input_dev
6.3 在IRQ_TC中断函数中:
1)若判断笔尖为松开,设置寄存器ADCTSC=0xD3(按下中断)
2)若判断笔尖按下,设置为XY自动转换模式,启动一次ADC转换,ADC转换成功,会进入ADC中断
6.4 在IRQ_ADC中断函数中:
1)获取ADCDAT0的位[9:0],来算出XY方向坐标值
2)测量n次值保存在数组中,然后再次设置为XY自动转换模式,启动ADC
(PS:要启动ADC转换之前,必须设置一次XY为自动转换模式,不然获取的数据会不准)
3)采集完毕,使用快速排序将n次值排序后,以最小值为基准,如有误差非常大的数,则舍弃,如果没有则打印数组的中间值,实现中值滤波。
4)打印数据后,必须设置寄存器ADCTSC=0x1D3(松开中断IRQ_TC)
(PS:在ADC采样模式下是判断不到ADCDAT0的bit15位的,因为ADCDAT0已被自动设置为X坐标的采样值)
5)设置定时器10ms超时时间
6.5 在定时器超时函数中:
1)判断ADCDAT0的bit15位,若还在按下再次启动ADC转换(实现触摸滑动功能)
2)若松开,设置寄存器ADCTSC=0xD3(按下中断)
最终代码如下:
#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) { //bit[8]:0-检测按下 s3c_ts_regs->adctsc = 0xd3; //等待触摸笔按下模式 } static void enter_wait_pen_up_mode(void) { //bit[8]:1-检测松开 s3c_ts_regs->adctsc = 0x1d3; //等待触摸笔松开模式 } static void enter_measure_xy_mode(void)//测量xy模式 { s3c_ts_regs->adctsc = (1<<3) | (1<<2);//禁止上拉,自动测量x、y } static void start_adc(void) { s3c_ts_regs->adccon |= (1<<0);//启动ADC } static int s3c_filter_ts(int x[], int y[])//软件过滤 { #define ERR_LIMIT 10//误差不超过多人,可以修改 int avr_x, avr_y; int det_x, det_y;//误差值 /* 第一个数与第二数求平均值,如果第三个数比平均值大10则认为是错误值 */ 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; //如果x或者y的差值大于10,则认为它是一个错误的值 /* 第二个数与第三数求平均值,如果第四个数比平均值大10则认为是错误值 */ 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; //如果x或者y的差值大于10,则认为它是一个错误的值 return 1; } static void s3c_ts_timer_function(unsigned long data) //定时器处理函数 { //ADCDAT0的第15位可以判断按下或者松开的模式 //1-松开,0-按下 if (s3c_ts_regs->adcdat0 & (1<<15))//如果已经松开 { /* 已经松开 */ input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0); //上报事件(实质:input_event()),按下的压力值,0--松开 input_report_key(s3c_ts_dev, BTN_TOUCH, 0); //按键类事件(实质:input_event()),0--松开 input_sync(s3c_ts_dev); enter_wait_pen_down_mode();//等待按下模式,这样才可以处理下一次 } else { /* 测量X/Y坐标 */ enter_measure_xy_mode();//进入测量x、y模式 start_adc();//转换成功启动ADC,ADC不可能瞬间完成 } } static irqreturn_t pen_down_up_irq(int irq, void *dev_id)//按下触摸笔中断处理函数 { //ADCDAT0的第15位可以判断按下或者松开的模式
上一篇:S3C2440 USB总线驱动分析(十八)
下一篇:S3C2440 热拔插驱动 hotplug_uevent机制 (三十三)
推荐阅读最新更新时间:2024-11-23 06:28
设计资源 培训 开发板 精华推荐
- FEBFAN23SV65_LVA,基于 FAN23SV65 15A 同步降压稳压器的评估板,具有超声波模式、内部线性稳压器和宽输入范围
- DC1016A-B,LT6558 演示板,5V 三路高速视频放大器
- SG1843交直流开关转换器典型应用电路
- AS5055-DB-1.0,基于AS5055磁性旋转编码器的演示板
- MAXREFDES1091:效率为91%的小尺寸,12V / 250mA,无光反激式DC-DC转换器
- 用于便携式消费电子产品的 2.5W、1 通道、D 类音频功率放大器
- 使用 LT1054CS8 数字可编程负电源的典型应用
- 【填坑】又是一个 NFC 卡片
- 使用 ROHM Semiconductor 的 BU4916 的参考设计
- LTC1930ES5/LTC1931ES5 演示板、1.2 MHz、DC/DC 转换器