文章目录
前言一、触摸屏驱动二、源码实例分析三、实验结果前言
本文基于S3C2440开发板。
一、触摸屏驱动
输入子系统其实就是一个文件input.c,Linux系统支持的输入设备繁多,例如键盘、鼠标、触摸屏、手柄或者是一些输入设备像体感输入等等,为了让上层app使用相同的方法获取不同的输入设备数据,引入了输入子系统的概念,input子系统解决了不同的输入类设备的输入事件与应用层之间的数据传输,使得应用层能够获取到各种不同的输入设备的输入事件,input输入子系统能够囊括所有的不同种类的输入设备,在应用层都能够感知到所有发生的输入事件。
如果想要添加自己的输入设备驱动程序到这个输入子系统中,首先需要用
input_allocate_device()
申请一个结构体input_dev
,然后填充实例化这个结构体,就是进行一些事件的设置,设置完后,用input_register_device
进行注册进内核,那如果有数据产生了要怎么传数据给app呢?,这里需要使用到input_event()
上报发生的事件,这时候app就可以读到数据,读到的数据是一个结构体input_event
。
具体的结构体和函数的原型触摸屏驱动使用的就是这个输入子系统:
①声明一个
input_dev
结构体指针s3c_ts_dev:static struct input_dev *s3c_ts_dev;
②分配一个input_dev结构体
s3c_ts_dev = input_allocate_device();
③填充结构体
s3c_ts_dev
,设置可以产生哪类,和这类事件中的哪些事件:
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);
⑤进行一些硬件的操作,地址的iormap,注册中断函数等等。
⑥有数据产生,就可以使用
input_event()
上报数据。
struct input_dev *input_allocate_device(void){struct input_dev *dev;dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);if (dev) {dev->cdev.class = &input_class;dev->cdev.groups = input_dev_attr_groups;class_device_initialize(&dev->cdev);mutex_init(&dev->mutex);INIT_LIST_HEAD(&dev->h_list);INIT_LIST_HEAD(&dev->node);__module_get(THIS_MODULE);}return dev;}
struct input_dev {void *private;const char *name;const char *phys;const char *uniq;struct input_id id;unsigned long evbit[NBITS(EV_MAX)];unsigned long keybit[NBITS(KEY_MAX)];unsigned long relbit[NBITS(REL_MAX)];unsigned long absbit[NBITS(ABS_MAX)];unsigned long mscbit[NBITS(MSC_MAX)];unsigned long ledbit[NBITS(LED_MAX)];unsigned long sndbit[NBITS(SND_MAX)];unsigned long ffbit[NBITS(FF_MAX)];unsigned long swbit[NBITS(SW_MAX)];unsigned int keycodemax;unsigned int keycodesize;void *keycode;int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);struct ff_device *ff;unsigned int repeat_key;struct timer_list timer;int state;int sync;int abs[ABS_MAX + 1];int rep[REP_MAX + 1];unsigned long key[NBITS(KEY_MAX)];unsigned long led[NBITS(LED_MAX)];unsigned long snd[NBITS(SND_MAX)];unsigned long sw[NBITS(SW_MAX)];int absmax[ABS_MAX + 1];int absmin[ABS_MAX + 1];int absfuzz[ABS_MAX + 1];int absflat[ABS_MAX + 1];int (*open)(struct input_dev *dev);void (*close)(struct input_dev *dev);int (*flush)(struct input_dev *dev, struct file *file);int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);struct input_handle *grab;struct mutex mutex;/* serializes open and close operations */unsigned int users;struct class_device cdev;union {/* temporarily so while we switching to struct device */struct device *parent;} dev;struct list_headh_list;struct list_headnode;};
nt input_register_device(struct input_dev *dev){static atomic_t input_no = ATOMIC_INIT(0);struct input_handler *handler;const char *path;int error;set_bit(EV_SYN, dev->evbit);/** If delay and period are pre-set by the driver, then autorepeating* is handled by the driver itself and we don't do it in input.c.*/init_timer(&dev->timer);if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {dev->timer.data = (long) dev;dev->timer.function = input_repeat_key;dev->rep[REP_DELAY] = 250;dev->rep[REP_PERIOD] = 33;}if (!dev->getkeycode)dev->getkeycode = input_default_getkeycode;if (!dev->setkeycode)dev->setkeycode = input_default_setkeycode;list_add_tail(&dev->node, &input_dev_list);snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);if (!dev->cdev.dev)dev->cdev.dev = dev->dev.parent;error = class_device_add(&dev->cdev);if (error)return error;path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);printk(KERN_INFO "input: %s as %s\n",dev->name ? dev->name : "Unspecified device", path ? path : "N/A");kfree(path);list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();return 0;}
struct input_event {struct timeval time;__u16 type;__u16 code;__s32 value;};
static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat){dev->absmin[axis] = min;dev->absmax[axis] = max;dev->absfuzz[axis] = fuzz;dev->absflat[axis] = flat;dev->absbit[LONG(axis)] |= BIT(axis);}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value){input_event(dev, EV_ABS, code, value);}static inline void input_report_key(struct input_dev *dev, unsigned int code, int value){input_event(dev, EV_KEY, code, !!value);}
二、源码实例分析
1,按下(作为一个高效的系统,按下一般产生某个中断,不会是用轮询方式检测按下。)产生中断。2,在“按下”中断处理程序里面,启动 ADC 转换 X、Y 坐标电压值。3,这是启动 ADC,启动 ADC 不会瞬间完成。一般来说启动就不管了。ADC 结束,产生 ADC中断(与‘按下’中断不同)。4,在 ADC 中断处理函数里面,用 input_event()来上报。这个过程有个缺点,就是按下后只会启动一次,按下不松开时“粘点”划动,这样就不会新“按下”中断产生。所以第 4 步里,上报后,启动“定时器”(处理长按、滑动的过程中也可以连续不断的转换这个 坐标电压值,上报出来)。5,定时器 时间到,再到 第 2 步,再次启动 ADC 。就是按下时产生一个“粘点”中断, 过了一段时间(定时器),就会再启动一次 ADC中断,这样就能处理“粘点”滑动的过程。6,松开。#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>#include <asm/plat-s3c24xx/ts.h>#include <asm/arch/regs-adc.h>#include <asm/arch/regs-gpio.h>/* 寄存器结构体,这些寄存器的地址都是连续的,不需要进行留存 */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) //xy连续自动转换模式{s3c_ts_regs->adctsc = (1<<3)|(1<<2);}static void start_adc(void) //启动adc{s3c_ts_regs->adccon |= (1<<0);}static int s3c_filter_ts(int x[], int y[]) //软件过滤数据{#define ERR_LIMIT 10int 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) //系统定时器10ms中断{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(); // 继续进行ADC测量,测量结束后就产生中断}}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); //最后一个参数为0,代表上报的是松开的数据input_report_key(s3c_ts_dev, BTN_TOUCH, 0);input_sync(s3c_ts_dev); //上报结束enter_wait_pen_down_mode(); //进入等待按下模式}else{//printk("pen down\n");//enter_wait_pen_up_mode();enter_measure_xy_mode();//这里判断结果为按下后,设置为xy自动连续转换模式,并使能启动adc,测量结束后会产生adc中断start_adc();}return IRQ_HANDLED;}static irqreturn_t adc_irq(int irq, void *dev_id) //adc转换结束后就产生中断,在这个中断中进行事件的上报{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);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); //1s/100=10ms,设置系统时钟的中断为10ms后}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); //将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");
三、实验结果
1,先查看有哪个 event 设备节点:ls /dev/event*
2, insmod s3c_ts.ko 。
3,再查看设备节点:ls /dev/event*
4,hexdump /dev/event* 。 先加载的是触摸屏时,dev/event0 就对应于触摸屏。
秒微秒 type code value
000000029a4 0000 8625 0008 0003 0000 0172 0000 (上报的x数据)
000001029a4 0000 8631 0008 0003 0001 027c 0000 (上报的y数据)
000002029a4 0000 8634 0008 0003 0018 0001 0000 (上报的压力数据)
000003029a4 0000 8638 0008 0001 014a 0001 0000 (上报的哪个按键数据)
000004029a4 0000 863c 0008 0000 0000 0000 0000 (同步事件)
000005029a4 0000 c85e 0008 0003 0000 0171 0000
000006029a4 0000 c874 0008 0003 0001 027d 0000
000007029a4 0000 c87b 0008 0000 0000 0000 0000
000008029a4 0000 ed37 0008 0003 0018 0000 0000
000009029a4 0000 ed48 0008 0001 014a 0000 0000
00000a029a4 0000 ed4a 0008 0000 0000 0000 0000
0003 – 绝对位移。 Code 0000 表示 X 方向(ABS_X)。
0001 表示 Y 方向(ABS_Y) 。
0018
表示压力。(ABS_PRESSURE)。
Value 0172 0000 就是 X 坐标值。
Code 014a是指哪个按键“BTN_TOUCH”。