历史上的今天
今天是:2025年11月12日(星期三)
2022年11月12日 | mini2440的触摸屏驱动----使用input子系统实现
2022-11-12 来源:csdn
没说程序之前先上图一张,这个图方便理解input子系统下的触摸屏的实现。

关于input子系统的实现在前几篇博客中已经实现了input子系统的按键驱动。
下面开始说触摸屏的驱动程序。
在说之前先结合上面的图说一下几个主要的函数的调用情况。
个中断函数的调用次序:
先是stylus_updown TC中断处理函数,
如果要是压下则调用touch_timer_fire来启动ADC转换,
在ADC转换结束之后调用stylus_action。
程序中前面定义的宏和变量:
/* For ts.dev.id.version */
#define S3C2410TSVERSION 0x0101
/*定义一个WAIT4INT宏,该宏将对ADC触摸屏控制寄存器进行操作
S3C2410_ADCTSC_YM_SEN这些宏都定义在regs-adc.h中*/
#define WAIT4INT(x) (((x)<<8) |
S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN |
S3C2410_ADCTSC_XY_PST(3))
/*定义一个AUTOPST宏,将ADC触摸屏控制寄存器设置成自动转换模式*/
#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN |
S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))
/*设备名称*/
static char *s3c2410ts_name = "s3c2410 TouchScreen";
/*定义一个输入设备来表示我们的触摸屏设备*/
static struct input_dev *dev;
/*用于记录转换后的X坐标值和Y坐标值*/
static long xp;
static long yp;
/*用于计数对触摸屏压下或抬起时模拟输入转换的次数*/
static int count;
/*定义一个外部的信号量ADC_LOCK,因为ADC_LOCK在ADC驱动程序中已申明
这样就能保证ADC资源在ADC驱动和触摸屏驱动中进行互斥访问*/
extern struct semaphore ADC_LOCK;
/*做为一个标签,只有对触摸屏操作后才对X和Y坐标进行转换*/
static int OwnADC = 0;
static void __iomem *base_addr;
老规矩:从init走你
static int __init s3c2410ts_init(void)
{
struct input_dev *input_dev;
/*从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,
因为ADC的转换频率跟时钟有关。 系统的一些时钟定义
在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
adc_clock = clk_get(NULL, "adc");
/*普通的错误处理*/
if (!adc_clock) {
printk(KERN_ERR "failed to get adc clock sourcen");
return -ENOENT;
}
/*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/
clk_enable(adc_clock);
/*
将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。
注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,
S3C2410_PA_ADC是ADC控制器的基地址,定义在mach-s3c2410/include/mach/map.h中,
0x20是虚拟地址长度大小
*/
base_addr=ioremap(S3C2410_PA_ADC,0x20);
//普通的错误验证。
if (base_addr == NULL) {
printk(KERN_ERR "Failed to remap register blockn");
return -ENOMEM;
}
/* Configure GPIOs */
/*初始化ADC控制寄存器和ADC触摸屏控制寄存器*/
s3c2410_ts_connect();
//不懂这是做什么。
iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),
base_addr+S3C2410_ADCCON);
iowrite32(0xffff, base_addr+S3C2410_ADCDLY);
iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
/* Initialise input stuff */
/*给输入设备申请空间,input_allocate_device定义在input.h中*/
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR "Unable to allocate the input device !!n");
return -ENOMEM;
}
/*初始化input子系统的dev结构体*/
dev = input_dev;
/*下面初始化输入设备,即给输入设备结构体input_dev的成员设置值。
evbit字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,
BIT宏实际就是对1进行位操作,定义在linux/bitops.h中*/
dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
/*keybit字段用于描述按键的类型,在input.h中定义了很多,这里用BTN_TOUCH类型来表示触摸屏的点击*/
dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
/*对于触摸屏来说,使用的是绝对坐标系统。这里设置该坐标系统中X和Y坐标的最小值和最大值(0-1023范围)
ABS_X和ABS_Y就表示X坐标和Y坐标,ABS_PRESSURE就表示触摸屏是按下还是抬起状态*/
input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
//ABS_PRESSURE表示触摸屏是按下还是抬起状态。
input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);
/*以下是设置触摸屏输入设备的身份信息,直接在这里写死。
这些信息可以在驱动挂载后在/proc/bus/input/devices中查看到*/
dev->name = s3c2410ts_name;
dev->id.bustype = BUS_RS232;
dev->id.vendor = 0xDEAD;
dev->id.product = 0xBEEF;
dev->id.version = S3C2410TSVERSION;
/* Get irqs */
/*申请ADC中断,AD转换完成后触发。这里使用共享中断IRQF_SHARED是因为该中断号在ADC驱动中也使用了,
最后一个参数1是随便给的一个值,因为如果不给值设为NULL的话,中断就申请不成功*/
if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,
"s3c2410_action", 1)) {
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !n");
iounmap(base_addr);
return -EIO;
}
/*申请触摸屏中断,对触摸屏按下或提笔时触发*/
if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,
"s3c2410_action", dev)) {
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !n");
iounmap(base_addr);
return -EIO;
}
printk(KERN_INFO "%s successfully loadedn", s3c2410ts_name);
/* All went ok, so register to the input system */
/*!!!!把触摸屏设备的描述结构体dev注册进input子系统中*/
input_register_device(dev);
return 0;
}
这段函数的主要功能是:启用ADC所需要的时钟、映射IO口、初始化寄存器、申请中断、初始化输入设备、将输入设备注册到输入子系统。同时注意这句话:/*!!!!把触摸屏设备的描述结构体dev注册进input子系统中*/input_register_device(dev);很帮助真正的理解。还有别忘了根本:/*初始化input子系统的dev结构体*/。同时,/*从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,因为ADC的转换频率跟时钟有关。 系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/adc_clock = clk_get(NULL, "adc");不跑裸机好多年,时钟感觉距离好远!
exit退出函数:
static void __exit s3c2410ts_exit(void)
{
/*屏蔽和释放中断*/
disable_irq(IRQ_ADC);
disable_irq(IRQ_TC);
free_irq(IRQ_TC,dev);
free_irq(IRQ_ADC,dev);
/*屏蔽并销毁时钟*/
if (adc_clock) {
clk_disable(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
//释放input设备。
input_unregister_device(dev);
/*释放虚拟地址映射空间*/
iounmap(base_addr);
}
关于先释放input设备,还是先释放地址我觉得还是源程序对。
下面开始说几个中断函数:
/*TC中断的处理函数*/
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
/*用于记录这一次AD转换后的值*/
unsigned long data0;
unsigned long data1;
/*用于记录触摸屏操作状态是按下还是抬起*/
int updown;
if (down_trylock(&ADC_LOCK) == 0) {
OwnADC = 1;
/*读的是状态*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*记录这一次对触摸屏是压下还是抬起,该状态保存在数据寄存器的第15位,所以与上S3C2410_ADCDAT0_UPDOWN*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) {
//如果要是压下则调用touch_timer_fire来启动ADC转换。
touch_timer_fire(0);
} else {
/*如果是抬起状态,就结束了这一次的操作,所以就释放ADC资源的占有*/
OwnADC = 0;
up(&ADC_LOCK);
}
}
return IRQ_HANDLED;
}
关键点在这里:if (updown) {//如果要是压下则调用touch_timer_fire来启动ADC转换。touch_timer_fire(0);
touch_timer_fire函数:
//AD转换函数
static void touch_timer_fire(unsigned long data)
{
/*用于记录这一次AD转换后的值*/
unsigned long data0;
unsigned long data1;
/*用于记录触摸屏操作状态是按下还是抬起*/
int updown;
//在未进行判定之前先读取一次状态。
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*判断这一次对触摸屏是压下还是抬起,该状态保存在数据寄存器的第15位,所以与上S3C2410_ADCDAT0_UPDOWN*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
//开始判断触摸屏的状态。
if (updown) {
/*如果状态是按下,并且ADC已经转换了就报告事件和数据*/
if (count != 0) {
long tmp;
tmp = xp;
xp = yp;
yp = tmp;
/*取4次平均值*/
xp >>= 2;
yp >>= 2;
/*
上报坐标
报告X,Y的绝对坐标值
*/
input_report_abs(dev, ABS_X, xp);
input_report_abs(dev, ABS_Y, yp);
/*报告触摸屏的状态,1表明触摸屏被按下*/
input_report_key(dev, BTN_TOUCH, 1);
/*报告按键事件,键值为1(代表触摸屏对应的按键被按下)*/
input_report_abs(dev, ABS_PRESSURE, 1);
/*结束报告*/
input_sync(dev);
}
//归零
xp = 0;
yp = 0;
count = 0;
/* 启动AD 转换*/
//设置为自动转换模式
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
//启动AD转换
iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else {
count = 0;
/*报告按键事件,键值为0(代表触摸屏对应的按键被释放)*/
input_report_key(dev, BTN_TOUCH, 0);
/*报告触摸屏的状态,0表明触摸屏没被按下*/
input_report_abs(dev, ABS_PRESSURE, 0);
//结束报告
input_sync(dev);
/*将触摸屏重新设置为等待中断状态*/
iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
/*如果触摸屏抬起,就意味着这一次的操作结束,所以就释放ADC资源的占有*/
if (OwnADC) {
OwnADC = 0;
up(&ADC_LOCK);
}
}
}
/* 启动AD 转换*///设置为自动转换模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);//启动AD转换 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
这里先留一个疑问。我们来看最后一个中断函数:
/*ADC中断服务程序,AD转换完成后触发执行*/
static irqreturn_t stylus_action(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
if (OwnADC) {
/*读取这一次AD转换后的值,注意这次主要读的是坐标*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*记录这一次通过AD转换后的X坐标值和Y坐标值,根据数据手册可知,X和Y坐标转换数值
分别保存在数据寄存器0和1的第0-9位,所以这里与上S3C2410_ADCDAT0_XPDATA_MASK就是取0-9位的值*/
xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
/*计数这一次AD转换的次数*/
count++;
//这里说如果转换次数小于4,则重新启动转换。为什么?
if (count < (1<<2)) {
/* 再一次启动AD转换*/
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else {
/*这里不懂*/
mod_timer(&touch_timer, jiffies+1);
iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
}
}
return IRQ_HANDLED;
}
到这里整个的input子系统实现触摸屏的主体部分也就结束了。还有好多细节部分和要注意的部分,由于一个脑残的同学让我给他百度论文,思路断了,所以不写了。不过在源码里都有体现了。
史海拾趣
|
1、离开舒适区 不断寻求挑战激励自己。提防自己,不要躺倒在舒适区。舒适区只是避风港,不是安乐窝。它只是你心中准备迎接下次挑战之前刻意放松自己和恢复元气的地方。 2、把握好情绪 人开心的时候,体内就会发生奇妙的变化,从而获得 ...… 查看全部问答> |
|
最近完成了signal板的设计和PCB绘制, 并已经发到PCB厂家做板, 该板实现的功能:一块数据采集板,上面具有双通道的模拟量输入和调理。 预计一周后板子制成。… 查看全部问答> |
|
最近在搞usb更新nboot,eboot,logo,nk, 芯片是sirf_A4, 下载软体是别人写的utility,自己增加了更新logo的更能,改了eboot 代码,问题如下: 1.先更新logo,再新其它的image(包括logo) ok. 2.先更新eboot(nboot,nk),再更新logo程序就会 ...… 查看全部问答> |
|
呵呵。书本上很多理论介绍P0口。把P0口当稀奇。所以很多初学者,是谈P0色变啊。。首先40个引脚的51内核单片机 32个IO口包括 P0口 都可以在程序控制下输出高低电平。。。。但P0口有什么稀奇的。。稀奇之处在于它输出高电平 ...… 查看全部问答> |
|
为什么变频器不用单极性PWM技术? 照理论上来说,单极性PWM相对于双极性PWM技术,在减少开关频率纹波和减少PWM死区时间的非线性影响这两方面都是有先天优势的,但是为什么现在的变频器上都不用呢?单极性PWM有什么致命的不足吗??????… 查看全部问答> |
|
以前一直不大相信MCU的内部看门狗,可用软件开关的不可靠。 一个独立看门狗 + 窗口型看门狗,看起来比以前随意喂狗要好,对程序的设计也和以前有不同的要求。 现在主要是关心 ...… 查看全部问答> |
|
随着电力电子技术的蓬勃发展,供电系统中增加了大量的非线性负荷,从低压小容量的家用电器到大容量的工业交流变换器的广泛应用,引起了电网电压、电流波形的畸变,威胁到电力系统安全、稳定、经济运行。在国家一些重要项目的建设中电网质量的监测 ...… 查看全部问答> |




