7.1.1 原理:从触摸点检测装置上接收触摸信息,并将它转换为触点坐标,再发送给CPU处理,它同时能接收CPU发来的命令并加以执行
7.1.2 主要类型: 1]矢量压力传感式触摸屏--淘汰
2]电阻式触摸屏--定位准,价格高怕刮伤易损
3]电容式触摸屏--设计合理但图像失真
4]红外线式触摸屏--便宜外框易碎易产生光干扰,曲面湿疹
5]表面声波式触摸屏--解决其它类型缺陷清晰不易损坏但水滴灰尘变迟钝
3.电阻屏原理:10-20ms反应速度
7.2 S3C2440 ADC 使用
S3C2440触摸屏控制器和ADC(模数转换控制器)结合在一起,因此介绍S3C2440的ADC以及触摸屏接口
7.2.1 触摸屏接口概述
1]S3C2440:8通道模拟输入10位CMOS模数转换器(ADC),ADC将模拟信号转换为10位二进制数字码。在2.5MHz的A/D转换器时钟下
2]ADC: 模拟信号转换为10位二进制数字码,最大转化速率:500KSPS
3]A/D器支持片上采样和保持功能,支持掉电模式
4]S3C2440的AIN[7]和AIN[5]用于连接触摸屏的模拟信号输入,触摸屏接口电路一般由触摸屏、4个外部晶体管和一个外部电压源组成
***************插入触摸屏接口电路示意图*****************
5]触摸屏接口的控制和选择信号有nYPON,YMON,nXPONT和XMON,他们连接切换X坐标和Y坐标转换的外部晶体管。模拟输入引脚(AIN[7],AIN[5])则连接到触摸屏引脚
6]触摸屏控制接口包括一个外部晶体管控制逻辑和具有路数产生逻辑的ADC接口逻辑
7.2.2 S3C2440触摸屏接口操作
***************A/D转换器和触摸屏接口的功能框图******
1] A/D转换器是循环类型,上拉电阻接在VDDA-ADC和AIN[7]之间。
2] 触摸屏X+引脚应该街道S3C2440的AIN[7],Y+接到S3C2440的AIN[5]接口
3] 图中可知ADC和触摸屏接口只有一个A/D转换器,可通过设置寄存器选择对哪路模拟信号采样,
两个中断信号:INT_ADC和INT_TC,INT_ADC表示A/D转换器已经转换完毕,而INT_TC表示触摸屏被按下
4] 使用触摸屏时引脚XP,XM,YP,YM被用于和触摸屏相连接,只剩下AIN[3:0]工4个引脚用于一般ADC输入
5] 当不适用触摸屏时,XP,XM,YP和YM这4个引脚也可用于一般的ADC输入
1.触摸屏控制器工作模式:触笔位置通过模拟信号传给A/D转换器,A/D转换完成保存在相应寄存器
1]等待中断模式
2]分离x/y轴坐标模式
3]自动x/y轴坐标转换模式
4]普通转换模式
****************控制器工作模式*********************
2.S3C2440触摸屏接口专用寄存器
主要有:
1]ADCCON:设置触摸屏A/D转换方式,普通A/D转换有8输入通道(AIN0-AIN7).AIN5&AIN7作为触摸屏x和y方向输入通道如下
********************ADCCON控制器插图****************
2]ADCTSC:选择工作模式,设置ADCTSC控制寄存器的值设置触摸屏引脚,
******************ADCTSC控制寄存器***************
3]ADCDAT0&ADCDAT1:
设置要转换的坐标和保存坐标的转换结果,X轴结果写到ADCDAT0的XPDAT,转换环比产生相应中断,Y保存在ADCDAT1的YPDAT,转换完,昌盛相应中断
**********************ADCDAT0&ADCDAT1插入图片************
4]ADCDLY:只有前16位有效,正常转换模式下,独立X/Y位置转换模式和自动X/Y位置转换模式下,X/Y位置转换延迟值
在等待终端模式中有触笔按下,次寄存器在简写几毫秒内,为自动X/Y位置转换产生中断信号,
好处:等待中断时候,可进行AD转换
7.3 2.6内核触摸屏驱动分析(s3c2410_ts.c)
7.3.1 s3c2410ts_probe 分析:探测函数
1)完成硬件资源获取,2)GPIO口初始化,3)中断申请,4)注册驱动程序等
static int s3c2410ts_probe(struct platform_device *pdev)
{
struct s3c2410_ts_mach_info *info; //保存特定数据(分辨率,分频比,延时等)
struct device *dev = &pdev->dev;
struct input_dev *input_dev;
struct resource *res;
int ret = -EINVAL;
/* Initialise input stuff */
memset(&ts, 0, sizeof(struct s3c2410ts));
ts.dev = dev;
info = dev_get_platdata(dev);//从传进来的平台数据获取硬件特定数据
if (!info) {
dev_err(dev, "no platform data, cannot attach\n");
return -EINVAL;
}
dev_dbg(dev, "initialising touchscreen\n");
ts.clock = clk_get(dev, "adc");//获取时钟,挂载APB ,BUS上的外围设备,需时钟控制,ADC就是
if (IS_ERR(ts.clock)) {
dev_err(dev, "cannot get adc clock source\n");
return -ENOENT;
}
ret = clk_prepare_enable(ts.clock); //时钟就绪
if (ret) {
dev_err(dev, "Failed! to enabled clocks\n");
goto err_clk_get;
}
dev_dbg(dev, "got and enabled clocks\n");
ts.irq_tc = ret = platform_get_irq(pdev, 0);//获取中断
if (ret < 0) {
dev_err(dev, "no resource for interrupt\n");
goto err_clk;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "no resource for registers\n");
ret = -ENOENT;
goto err_clk;
}
//映射IO内存,IO内存不能直接访问,必须进行映射,为IO内存分配虚拟地址,以_iomem进行说明,但不能直接访问,需要使用专用数据如iowrite320()
ts.io = ioremap(res->start, resource_size(res));
if (ts.io == NULL) {
dev_err(dev, "cannot map registers\n");
ret = -ENOMEM;
goto err_clk;
}
/* inititalise the gpio */ //设置触摸屏的4个GPIO为特殊功能
if (info->cfg_gpio)
info->cfg_gpio(to_platform_device(ts.dev));
ts.client = s3c_adc_register(pdev, s3c24xx_ts_select,
s3c24xx_ts_conversion, 1);
if (IS_ERR(ts.client)) {
dev_err(dev, "failed to register adc client\n");
ret = PTR_ERR(ts.client);
goto err_iomap;
}
/* Initialise registers */
if ((info->delay & 0xffff) > 0) //设置ADC延迟,在等待终端模式下表示产生INT_TC的时间间隔
writel(info->delay & 0xffff, ts.io + S3C2410_ADCDLY);
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC); //按照等待中断的模式设置TSC
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(dev, "Unable to allocate the input device !!\n");
ret = -ENOMEM;
goto err_iomap;
}
ts.input = input_dev;
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0);
ts.input->name = "S3C24XX TouchScreen";
ts.input->id.bustype = BUS_HOST;
ts.input->id.vendor = 0xDEAD;
ts.input->id.product = 0xBEEF;
ts.input->id.version = 0x0102;
ts.shift = info->oversampling_shift;
ts.features = platform_get_device_id(pdev)->driver_data;
ret = request_irq(ts.irq_tc, stylus_irq, 0,
"s3c2410_ts_pen", ts.input);
if (ret) {
dev_err(dev, "cannot get TC interrupt\n");
goto err_inputdev;
}
dev_info(dev, "driver attached, registering input device\n");
/* All went ok, so register to the input system */
ret = input_register_device(ts.input);
if (ret < 0) {
dev_err(dev, "failed to register input device\n");
ret = -EIO;
goto err_tcirq;
}
return 0;
err_tcirq:
free_irq(ts.irq_tc, ts.input);
err_inputdev:
input_free_device(ts.input);
err_iomap:
iounmap(ts.io);
err_clk:
clk_disable_unprepare(ts.clock);
del_timer_sync(&touch_timer);
err_clk_get:
clk_put(ts.clock);
return ret;
}
struct s3c2410ts {
struct s3c_adc_client *client; //设备信息
struct device *dev; //定义输入设备
struct input_dev *input;
struct clk *clock;
void __iomem *io;
unsigned long xp; //x轴坐标
unsigned long yp; //y轴坐标
int irq_tc;
int count; //统计采样次数
int shift; //用于根据采样次数求平均值
int features;
};
设置事件类型:EV_KEY,EV_MOUSE,EV_ABS(触摸屏绝对坐标事件),每种事件有不同类型的编码,ABS_X,ABS_Y,等。
2. touch_timer_fire 分析
1]stylus down 的时间,touch_timer_fire()函数在中断函数stylus_updown被调用,此时缓存区无数据,ts.count=0,故只是设置A/D转换模式,然后开启A/D转换器
2]stylus_action()填满缓冲区,作为中断后半段函数稍后被调用,此时ts.count 等于shift,算出平均值,交给事件处理层(Event Handler)处理,主要:填写缓冲区,唤醒等待输入数据的进程
static void touch_timer_fire(struct timer_list *unused)
{
unsigned long data0; //保存x轴坐标
unsigned long data1; //保存y轴坐标
bool down; //保存触笔按下,按下为1
data0 = readl(ts.io + S3C2410_ADCDAT0); //读取x轴坐标
data1 = readl(ts.io + S3C2410_ADCDAT1); //读取y轴坐标
down = get_down(data0, data1); //测试触笔是否按下
if (down) {
if (ts.count == (1 << ts.shift)) { //多次采样已经完成
ts.xp >>= ts.shift; //求平均值
ts.yp >>= ts.shift;
//调试信息
dev_dbg(ts.dev, "%s: X=%lu, Y=%lu, count=%d\n",
__func__, ts.xp, ts.yp, ts.count);
//下面两句是报告X,Y的绝对坐标值
input_report_abs(ts.input, ABS_X, ts.xp);
input_report_abs(ts.input, ABS_Y, ts.yp);
//报告按键事件,键值为1,(代表触摸屏对应的按键被按下)
input_report_key(ts.input, BTN_TOUCH, 1);
//等待接收方收到数据后回复确认,用于同步
input_sync(ts.input);
//如果触笔刚刚按下,那么ts.count的值为0,此时需清空之前保存的数值。当触笔在屏幕上拖动,则会不停采样并报告坐标值
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
}
s3c_adc_start(ts.client, 0, 1 << ts.shift);
} else { //触笔抬起
ts.xp = 0;
ts.yp = 0;
ts.count = 0;
//报告按键事件,键值为1,代表触摸屏对应按键释放
input_report_key(ts.input, BTN_TOUCH, 0);
//?旧版带有触摸屏的状态报告,此处无?
input_sync(ts.input);
//进入s3c2410触摸屏提供的等待中断模式,等待触笔按下ts.io == base_addr
writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC);
}
}
3]stylus抬起,等缓冲区填满后被调用,这时判断出stylus up 报告该事件,重新等待新的stylus down
5 s3c2410ts_remove 分析
static int s3c2410ts_remove(struct platform_device *pdev)
{
free_irq(ts.irq_tc, ts.input); //释放AD,触摸屏中断,中断
del_timer_sync(&touch_timer); //删除计时器同步
clk_disable_unprepare(ts.clock);//关闭时钟
clk_put(ts.clock);
input_unregister_device(ts.input);//注销输入设备
iounmap(ts.io);//取消内存映射
return 0;
}
7.4 内核输入子系统
驱动和应用交互----linux输入子系统
7.4.1 概述
组成:输入子系统核心层,驱动层,事件处理层组成
输入事件:(鼠标,键盘,joystick等)driver->inputcore->eventhandler->userspace到达用户空间
触摸,按键,键盘,鼠标都可以利用input接口实现设备驱动
驱动核心工作:向系统报告按键,触摸屏,键盘,鼠标等输入事件,不必关心文件操作接口,经过inputcore和eventhandler
7.4.2 输入设备结构体
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
//设置输入设备的事件类型,EV_KEY是最简单的事件类型,通过input_report_key报告输入系统
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet;
//用户所有输入设备,报告产生的数据作为扫描码。
unsigned int keycodemax; //数组大小
unsigned int keycodesize; //数组中数据大小,单位bytes
void *keycode; //映射扫描码到输入系统的键码
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
struct input_dev_poller *poller;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
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 __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
ktime_t timestamp[INPUT_CLK_MAX];
};7.4.3 输入链路的创建过程
包括:硬件设备注册,inputhandler两部分
1.硬件设备注册:
drivers将底层硬件对用户输入的响应转换为标准输入使劲再向上发送input core,两个接口
input_register_device&input_unregister_device
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(dev, "Unable to allocate the input device !!\n");
ret = -ENOMEM;
goto err_iomap;
}
ts.input = input_dev;
ts.input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
ts.input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0); ts.input->name = "S3C24XX TouchScreen";
ts.input->id.bustype = BUS_HOST;
ts.input->id.vendor = 0xDEAD;
ts.input->id.product = 0xBEEF;
ts.input->id.version = 0x0102;
7.5.2 注册input handler
设备结点:event handler 调用input core创建,创建之前,event handler需要先注册一类设备的输入事件处理函数以及相关接口
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
由此可知:一类input handler 可以和多个硬件设备相关联,创建多个设备结点
一个设备也可能与多个input handler相关联,创建多个设备结点
7.4.4 使用input子系统
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/irq.h>
#include <asm/io.h>
static struct input_dev *button_dev;
static irqreturn_t button_interrupt(int irq, void *dummy)
{
input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);
input_sync(button_dev);
return IRQ_HANDLED;
}
static int __init button_init(void)
{
int error;
//注册中断处理函数
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL)) {
printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);
return -EBUSY;
}
button_dev = input_allocate_device();
if (!button_dev) {
printk(KERN_ERR "button.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_irq;
}
button_dev->evbit[0] = BIT_MASK(EV_KEY); //设置输入设备是按键
button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);//按键
error = input_register_device(button_dev); //注册输入设备
if (error) {
printk(KERN_ERR "button.c: Failed to register device\n");
goto err_free_dev;
}
return 0;
err_free_dev:
input_free_device(button_dev);
err_free_irq:
free_irq(BUTTON_IRQ, button_interrupt);
return error;
}
static void __exit button_exit(void)
{
input_unregister_device(button_dev);//注销输入设备
free_irq(BUTTON_IRQ, button_interrupt);//释放中断
} module_init(button_init);
module_exit(button_exit);
7.4.5 编写输入设备驱动需要完成的工作
1.模块加载函数中告知input子系统它可以报告的事情
set_bit(EV_KEY,button_dev.evbit);
set_bit(BTN_0,button_dev.keybit);
2.在模块加载函数中注册输入设备
input_register_device(&button_dev);
3.在键被按下/抬起,触摸屏被触摸或抬起或移动、鼠标被抬起/移动/单击时,通过input_report_xxx()函数报告发生的事件及对应的键值/坐标等状态
主要事件类型:EV_KEY,EV_REL(相对值,光标移动,报告的是相对最后一次的偏移),EV_ABS(绝对值,如触摸屏,遥控杆,他们工作在绝对坐标系统)
void input_report_key()
void input_report_rel()
void input_report_abs()
input_sync()告知工作已完成用作同步
如下
input_report_abs(input_dev,ABS_X,x); //x坐标
input_report_abs(input_dev,ABS_Y,y); //y坐标
input_report_abs(input_dev,ABS_PRESSURE,pres);//压力
input_sync(input_dev); //同步
4.在模块卸载函数中注销输入设备
注销输入设备
input_unregister_device(struct input_dev *dev);
7.5 驱动移植和内核编译
1.修改初始化源码 arch/arm/mach-s3c2440/mach-smdk2440.c
2.修改arch/arm/plat-s3c24xx/devs.c
3.添加头文件
如果没有reg-adc.h则需要移植添加,并添加如下内容
#define S3C2410_ADCTSC_XY_PST_N (0x0<<0) //误操作模式
#define S3C2440_ADCTSC_XY_PST_X (0x1<<0) //对X坐标进行转换
#define S3C2410_ADCTSC_XY_PST_Y (0x2<<0) //对Y坐标进行转换
#define S3C2440_ADCTSC_XY_PST_W (0x3<<0) //等待中断模式
编写时成440_ts.h 复制到include/asm/arch-s3c2410/,用于定义S3C2440触摸屏的平台数据结构
内容如下
#ifndef __ASM_ARM_S3C2440_TS_H
#define __ASM_ARM_S3C2440_TS_H
struct s3c2440_ts_mach_info {
int delay;
int presc;
int oversampling_shift; //采样次数
};
void __init set_s3c2440ts_infi(struct s3c2440_ts_mach_info *hard_s3c2440ts_info); //用于设置私有数据的函数
#endif //__ASM_ARM_S3C2440_TS_H*/
7.5.2修改硬件驱动源码s3c2440_ts.c
1.拷贝相近类型源文件
2.修改触摸迫性的私有硬件结构体
3.初始化触摸屏并注册该触摸屏并注册该触摸屏输入设备
4.注册S3C2440ts触摸屏驱动
7.5.3 修改Kconfig和Makefile
1.修改drivers/input/touchscreen/Kconfig,添加如下内容
config TOUCHSREEN_S3C2440
tristate "Samsung S3c2440 touchscreen input driver"
depends on ARH_S3c2440 && INPUT && INPUT_TOUCHCREEN
select SERIO
help
Say Y here if you have the s3c2440 touchscreen.
If unsure, say N.
To compile this driver as a module, choose M here;the module will be called s3c2440_ts.
config TOUCHSCREEN_S3C2440_DEBUG
boolean "Samsung S3c2440 touchscreen debug messages"
depends on TOUCHSCREEN_S3C2440
help
Select this if you wan debug messages
2.修改drivers/input/touchscreen/Makefile,加入要编译的源码
obj-$(CONFIG_S3C2440_TOUCHSCREEN) +=s3c2440_ts.o
7.5.4 配置编译内核
1.把触摸屏驱动编译进内核,选中两个Kconfig
2.选择Eventdebugging进行内核调试
7.5.5 触摸屏测试程序设计
1.输入事件接口使用的输入设备的标准接口,传递的数据结构是struct input_event 定义在
include/linux/input.h
strcut input_event{
struct timeval time; //时间戳
unsigned short type; //事件类型,触摸屏中驱动定义成绝对输入设备(EV-ABS);
unsigned short code;//事件代码,触摸屏中定义了ABS_PRESSURE,ABS_X,ABS_Y
unsigned int value; //返回值
}
代码如下
#include <iostream>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/ioctl.h>
#include<pthread.h>
#include<fcntl.h>
#include<linux/input.h>
#define TS_DEV "/dev/input/event0"
static int ts_fd=-1;
static int init_device(void){
if((ts_fd = open(TS_DEV,O_RDONLY))<0){
printf("open error:%s",TS_DEV);
return -1;
}
return 0;
}
using namespace std;
int main()
{
int i;
struct input_event data;
if(init_device()<0){
return -1;
}
for(;;){
read(ts_fd,&data,sizeof(data)); //continue read data
if(data.type != EV_ABS)
printf("wrong type:%d\n",data.type);
printf("event=%s,value=%d\n",data.code==ABS_X?"ABS_X":data.code==ABS_Y?"ABS_Y":data.code==ABS_PRESSURE?"ABS_PRESSURE":"unknown",data.value);
}
return 0;
}