文章目录

  • 前言
  • 一、触摸屏驱动
  • 二、源码实例分析
  • 三、实验结果

前言

本文基于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_head    h_list;struct list_head node;
};
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

0000000 29a4 0000 8625 0008 0003 0000 0172 0000 (上报的x数据)
0000010 29a4 0000 8631 0008 0003 0001 027c 0000 (上报的y数据)
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

0003 – 绝对位移。 Code 0000 表示 X 方向(ABS_X)。
0001 表示 Y 方向(ABS_Y) 。
0018
表示压力。(ABS_PRESSURE)。
Value 0172 0000 就是 X 坐标值。
Code 014a是指哪个按键“BTN_TOUCH”。

linux驱动程序 ---- 触摸屏相关推荐

  1. Linux下触摸屏驱动程序分析

    [摘要: 本文以 linux 3.5--Exynos4412仄台,剖析 触摸屏 驱动焦点内容.Linux下触摸屏驱动(以ft5x06_ts为例)须要懂得以下学问: 1. I2C协定 2. Exynos ...

  2. Linux驱动程序编写

    工作需要写了我们公司一块网卡的Linux驱动程序.经历一个从无到有的过程,深感技术交流的重要.Linux作为挑战微 软垄断的强有力武器,日益受到大家的喜爱.真希望她能在中国迅速成长.把程序文档贴出来, ...

  3. linux驱动程序是什么,简述一个Linux驱动程序的主要流程与功能

    1. 简述一个Linux驱动程序的主要流程与功能. 2. 请列举一个软件中时间换空间或者空间换时间的例子. void swap(int a,int b) { int c; c=a;a=b;b=a; } ...

  4. 编写linux驱动程序步骤

    一.建立Linux驱动框架(装载.卸载Linux驱动) Linux内核在使用驱动时首先要装载驱动,在装载过程中进行一些初始化动作(建立设备文件.分配内存等),在驱动程序中需提供相应函数来处理驱动初始化 ...

  5. Linux驱动程序学习步骤

     了解linux驱动程序技巧学习的方法很重要,学习linux操作系统时,你可能会遇到关于驱动方面的问题, 这里将介绍学习linux驱动程序的方法,在这里拿出来和大家分享一下. 1.学会写简单的make ...

  6. poll接口《来自Linux驱动程序开发实例》

    您所在的位置:读书频道 > 操作系统 > Linux > 1.2.7 poll接口 1.2.7 poll接口 2012-05-22 13:38 冯国进 机械工业出版社 我要评论(0) ...

  7. 异步通知《来自Linux驱动程序开发实例》

    您所在的位置:读书频道 > 操作系统 > Linux > 1.2.8 异步通知 1.2.8 异步通知 2012-05-22 13:38 冯国进 机械工业出版社 我要评论(0) 字号: ...

  8. 基于块的linux驱动程序,基于块的Linux驱动程序 块设备驱动 centos内核编译过程 操作系统课程设计...

    操作系统的课程设计,本人也是一头雾水地做完了课程设计,在这里贴下操作过程,放下当时参考的一篇CSDN文章链接:https://blog.csdn.net/cxy_chen/article/detail ...

  9. 第一个linux驱动程序

    本章将进行实例的学习,第一个linux驱动程序:统计单词个数.本例子的目的不是讲解如何统计单词个数,而是该算法的实现技术:Linux驱动.Linux系统将每一个驱动都映射成一个文件,这些文件称为设备文 ...

最新文章

  1. 机器学习(MACHINE LEARNING)MATLAB模拟退火算法【SA】
  2. ros消息服务器,ROS服务和消息
  3. 正则表达式:Pattern 与Matcher
  4. Pandas高级教程之:category数据类型
  5. node.js Promise简单介绍
  6. IBM研究院计画5年改变人类生活创新预测
  7. 博客搬家到CSDN:http://blog.csdn.net/yeweiouyang
  8. VS2019 团队资源管理器--Git的使用(二)
  9. utc时间怎么转换北京时间?
  10. oracle 从后往前,oracle从后往前截取字符串 oracle截取字符串后三位
  11. 压缩软件如何删除压缩包密码(zip、rar、7-zip),忘记密码如何删除密码?
  12. 麒麟Linux系统根目录与单目录扩容详解,适用于大多数的centeros系统
  13. #最全面# Python 下将 opencv MAT ( numpy ndarray ) 彩色 或 灰度 图像转化为 QImage 对象
  14. ValueError: Length mismatch: Expected axis has 2 elements, new values have 1 elements
  15. KISSY基础篇乄KISSY之Node(2)
  16. 学历代表过去,只有学习力才能代表将来,尊重经验的人,才能少走弯路
  17. 此时不应有 \Microsoft 或其它的解决办法
  18. STM32 IIC协议 读写EEPROM
  19. 2022出海拉美:墨西哥电商市场现状及网红营销特点
  20. 鸿蒙系统样貌,EMUI11是如何炼成的?揭示鸿蒙OS未来的样貌

热门文章

  1. 视觉SLAM:模型介绍、算法框架及应用场景
  2. 详解linux中的backlog
  3. python分布_python 伯努利分布详解
  4. html 修改内容弹窗,javascript如何设置弹窗?
  5. SQL——递归CTE
  6. 做文案必备的基础能力是什么?
  7. QT 关于邮箱格式的正则表达式判断
  8. ocaml快餐教程(2) - 定义变量与函数
  9. Windows系统键盘各键作用和快捷方式
  10. html作业给父亲的三行书信,给爸爸妈妈的一封信书信作文(精选3篇)