大家把图中那个电阻当成是我们的触摸屏,当要测量X坐标时,YM、YP悬空,我们按下触摸屏时,上下两层膜便产生一个触点,通俗点说我们就可以把图中的电阻想象成了一个滑动变阻器(变位器),我们按下触摸屏时产生的触点就可以当成变位器的第三个管脚,触点所在位置的电压值就可以通过计算计算出来了,我们再将得出的电压值进行AD转换得到我们需要的X坐标。这个就是触摸屏的基本工作原理了。
我所使用的s3c2440的ADC和触摸屏接口结构中会产生两个中断信号,一个是INT_TC(触摸屏被按下产生的中断),另一个是INT_ADC(ADC转换完成产生的中断)。
触摸屏驱动的编写我们可以根据触摸屏的使用过程来逐步完成
触摸屏使用过程
(1)当没有按下时,触摸屏控制器处于等待中断模式(这里等待的是INT_TC中断)。
(2)一旦被按下就会产生INT_TC中断
(3)在触摸屏中断处理函数里,使其启用下面两种转换模式中的一种:一种是分离的x/y轴坐标转化模式,另一种是连续的x/y轴转换模式。
(4)一旦转换完,就会产生INT_ADC中断
(5)在ADC中断处理函数中上报ADC转换值
但是这样是有一个问题的,因为我们按下一次,只会产生一个中断,如果我们滑动的话,就不会产生任何效果。为了处理滑动,我们可以在(5)中添加如下功能:启用定时器,每隔一段时间就启用一次AD转化,这样就可以得到按下点的实时信息了。
(6)松开
第一大部分:触摸屏驱动程序的入口函数s3c_ts_init();
第一步:因为触摸屏是工作在输入子系统上的,因此我们需要在初始化函数中完成分配input_dev结构体,设置参数,注册结构体等步骤。
/*分配一个input_dev 结构体*/
s3c_ts_dev = input_allocate_device();
/*设置*/
/*能产生哪类事件*/
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);
第二步:另外我们还需要配置clkcon寄存器
struct clk* clk;
clk = clk_get(NULL, "abc");
clk_enable(clk);
clkcon寄存器的作用就是选择使能哪个模块,如需要该模块便将该模块所对应在
clkcon寄存器中的位设置为1,其目的是为了省电,内核在启动的时候会把一些不需要的模块关掉,设置
clkcon寄存器,所以当我们需要使用某一个模块的功能的时候需要把它打开。
第三步:既然要产生中断,很明显我们需要申请中断触摸屏中断和ADC结束产生的中断,也就是上文所提到的当触摸屏被按下时产生的中断TRQ_TS,申请中断函数为
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);
request_irq申请中断函数的原型为
int request_irq(unsigned int
irq, irq_handler_t
handler,
unsigned long
irqflags, const char *
devname, void *
dev_id)
irq:要申请的硬件中断号
handler:向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它
irqflags:中断处理的属性(中断触发方式)。
若设置了IRQF_DISABLED ,则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽。
若设置了IRQF_SHARED ,则表示多个设备共享中断。
若设置了IRQF_SAMPLE_RANDOM,表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)
devname:设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称。
dev_id:在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
第四步:映射需要配置到的寄存器,因为为了便于操作,我们可以将触摸屏驱动需要设置的所有寄存器都放在同一个结构体中,如下:
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
然后在入口函数中映射
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
该结构体第一个寄存器地址为0x58000000
第五步:设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
ADCDLY:ADC start delay register,也就是延迟ADC转换
s3c_ts_regs->adcdly = 0xffff;
第六步:使用定时器处理长按,滑动的情况
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer);
第七步:一切设置完毕后等待触摸屏被按下
enter_wait_pen_down_mode(void);
第二大部分:填充第一大部分s3c_ts_init()函数中用橙色字体的各类函数
1、enter_wait_pen_down_mode(void)(等待按下函数)
static void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc = 0xd3;
}
查看我所使用的触摸屏手册可以得到下面内容
可以看到当我们需要设置等待中断模式时只需要把adctsc寄存器设置为0xd3即可
2、s3c_ts_timer_function(定时器处理函数)
static void s3c_ts_timer_function(unsigned long data)
{
if (s3c_ts_regs->adcdat0 & (1<<15)) /*判断是否按下,adcdat0第十五位如果是1为松开,是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
{
/* 测量X/Y坐标 */
enter_measure_xy_mode();
start_adc();
}
}
3、enter_measure_xy_mode()(坐标测量函数)
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}
4、start_adc()(开始adc转换)
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}
接下来是比较重头的两个中断处理函数了
5、pen_down_up_irq()(当触摸屏按下时的中断处理函数)
当我们按下触摸屏进入到我们的中断处理函数 pen_down_up_irq()中后我们还需要判断是否仍旧是按下状态
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if(
s3c_ts_regs->adcdat0 & (1<<15))
/*判断是否按下,adcdat0第十五位如果是1为松开,是0为按下*/
{
// printk("pen upn");
enter_wait_pen_down_mode(); /*等待按下处理函数*/
}
else
{
//printk("pen downn");
//enter_wait_pen_up_mode();
enter_measure_xy_mode(); /*测量xy坐标函数*/
start_adc(); /*adc转换函数*/
}
return IRQ_HANDLED;
}
6、adc_irq()(AD转换完成后产生的ADC中断)
static irqreturn_t adc_irq(int irq, void *dev_id)
{
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 = %dn", ++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 = %dn", (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);
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
第三大部分:出口函数
出口函数的作用就是释放在入口函数中申请的各个内存空间而已
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);
}
到这里一个相对简单的触摸屏驱动程序就基本算是完成了,接下来需要做的就是利用tslib来对触摸屏驱动程序进行调试,这部分的内容如果放在这里讲就显得有点累赘了,朋友们可以去度娘上查查资料或者直接参照韦东山老师第二期视频的第16课的第三节“触摸屏驱动程序之使用TSLIB测试”这一节的内容
谢谢大家观看,希望各位多多提出本帖不足,热烈欢迎大家进帖讨论和分享学习经验。