Part17: 触摸屏驱动程序(基于输入子系统实现)

0 电阻屏原理

一张图足矣,如下
电阻屏原理
备注:可结合《嵌入式Linux应用开发完全手册》理解

电阻屏的原理不难理解,但有两点需注意
1.使用电阻屏前,需要校验以确定坐标转换关系
2.需根据s3c2440手册对应章节编写触摸屏相关硬件操作
由于下面使用自动测量X/Y坐标模式,YM\YP XM/XP 还有上拉电阻的开闭自动完成

下面直接看代码,看看触摸屏基本驱动程序如何编写(基于输入子系统实现)
备注:代码已上传到https://blog.youkuaiyun.com/qq_42800075/article/details/105670841

1 基本框架:打印测量的电压值
头文件:s3c_ts.h(适用本博文全部代码,只贴一遍)
#ifndef _S3C_TS_H
#define _S3C_TS_H

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <plat/adc.h>
#include <plat/regs-adc.h>
#include <plat/ts.h>

struct s3c_ts_regs{
    unsigned long adccon;
 unsigned long adctsc;
 unsigned long adcdly;
 unsigned long adcdat0;
 unsigned long adcdat1;
 unsigned long adcupdn;
};

#endif

TS驱动文件:s3c_ts.c

#include "s3c_ts.h"

static struct s3c_ts_regs *s3c_ts_regs;
static struct input_dev *s3c_ts_dev;

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 irqreturn_t adc_irq(int irq, void *dev_id)
{
      static int cnt = 0;
      printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, s3c_ts_regs->adcdat0 & 0x3ff, 
 	   s3c_ts_regs->adcdat1 & 0x3ff);
      enter_wait_pen_up_mode();
      return IRQ_HANDLED;
}
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    if(s3c_ts_regs->adcdat0 & (1<<15))
    {
        printk("pen up\n");  // 松开,等待按下
  	enter_wait_pen_down_mode();
    }else{
        enter_measure_xy_mode();  // 按下,进入自动测量模式,且开启ADC转换
        start_adc();
    }
    return IRQ_HANDLED;
}
static int s3c_ts_init(void)
{
    struct clk* clk;
    /* 1 分配一个input_dev 结构体*/
    s3c_ts_dev = input_allocate_device();
    
    /* 2 设置input_dev */
    set_bit(EV_KEY, s3c_ts_dev->evbit);
    set_bit(EV_ABS, s3c_ts_dev->evbit);
    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 使能ADC时钟 */
    clk = clk_get(NULL, "adc");
    clk_enable(clk);
    /* 4.2 ioremap */
    ioremap(0x58000000, sizeof(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);
    /* 4.3 注册中断 */
    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);
    /* 4.4 进入等待按下模式 */
    enter_wait_pen_down_mode();
    
    return 0;
}
static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    iounmap(s3c_ts_regs);
    input_unregister_device(s3c_ts_dev);
    input_free_device(s3c_ts_dev);
}

module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");
Makefile文件(备注:适用这篇博文全部代码,只贴一遍)
KERN_DIR = /work/system/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules
 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
obj-m += s3c_ts.o
程序执行流程:
	触笔点击触发中断,进入自动测量xy坐标模式且开启ADC转换,待转换完成后,触发ADC中断,
	打印xy坐标的“电压值”(注意不是屏幕的实际坐标值)并进入等待触笔松开模式。如果松开,触发触摸屏
	中断,但只打印“pen up“, 然后进入等待触笔点击模式。
	注意:触摸屏中断 与 ADC 中断 是不同的

套路:
1)申请一个input_dev结构体: input_allocate_device
2)设置input_dev(支持哪类事件、哪类事情的哪些事件)set_bit / input_set_abs_params
3)   在入口函数中注册设备驱动程序:input_register_device()
4)硬件相关操作:a. 使能ADC b.ioremap 即 设置ADC输入时钟源频率  c.注册中断 d.进入等待按键按下模式
5)编写硬件相关函数:1.触摸屏中断函数中判断按下/松开,并动作(这里只是打印,后面会启动ADC转换)
			      2.进入等待按键按下或松开模式的函数
6)在出口函数中注销设备驱动程序: 逆序释放(free_irq/iounmap/input_unregister_device/input_free_device)
7)  module_init() / module_exit() --->实际将入口/出口函数放在内核初始化段,别忘了 MODULE_LICENSE("GPL")
2 完善一:电压稳定后再发出IRQ_TC中断
问题:上面代码经测试存在不稳定的电压值输出(如从左往右,y的电压值应该依次增大,但出现减小的一些值)
解决:待ADC转换输出的电压值稳定后再发出IRQ_TC中断,再启动测量
代码修改如下,其它不变
// 在s3c_ts.c 的 s3c_ts_init()入口函数增加
/* 优化错施1: 
  * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
  */
 s3c_ts_regs->adcdly = 0xffff;
 
 /* 4.4 进入等待按下模式 */
    enter_wait_pen_down_mode();
测试效果:
	输出的电压值基本没有不正常的跳动值(已测试验证)
3 完善二:若触笔已经松开, 则丢弃此次测量结果
问题:除了异常值,发现触笔松开了也打印出一些电压值
解决:如果测量结束后(即在adr_irq中断处理里),如果触笔已松开,则丢弃这次测量结果
代码修改如下,其它不变
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    int adcdat0, adcdat1;
    /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;
    
    if(s3c_ts_regs->adcdat0 & (1<<15))
    {
  	enter_wait_pen_down_mode();  // 如果转换结束后,触笔已松开,不打印,只需进入等待点击模式即可
    }else{
        printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, s3c_ts_regs->adcdat0 & 0x3ff, 
                s3c_ts_regs->adcdat1 & 0x3ff);
        enter_wait_pen_up_mode();
    }
    return IRQ_HANDLED;
}
测试效果:
	触笔松开时,能够不打印电压值
4 完善三:同一点多次测量取平均值
问题:点击同一点时,电压值浮动稍微大。且靠近两点的电压值变化幅度有些会较大
解决:对于某一点测量,多次测量后取平均值,这样同一点的电压值变化不大,基本稳定。
代码修改如下,其它没变
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;
        enter_wait_pen_down_mode();
    }else{
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)  // 如果测量了4次,取平均值后再打印
        {
            printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
            cnt = 0;
            enter_wait_pen_up_mode();
       }
       else           // 如果测量<4次,只需再进入测量即可
       {
           enter_measure_xy_mode();
           start_adc();
        }  
    }
    return IRQ_HANDLED;
}
测试效果:
	点击同一点,电压值稳定。且相邻点的电压值变化幅度小,稳定
5 完善四:软件过滤,去除异常电压值
优化:对于一次4次测量结果中,如果某些值变化较大,则丢弃此次的4次测量结果
 	怎样判断某些值变化较大呢?
	 具体来讲,就是如果测量的第3个值超出前两个测量的平均值10,或者,第4个值
 	超出前面两个测量的平均值10,则都认为此次测量结果不稳定,无效
 	备注1:10 只是个粗略值,可多次实验取一个较好的
 	备注2:上面的优化很粗糙,可参考tslib 的优化策略
static int s3c_filter_ts(int x[], int y[])
{
#define ERR_LIMIT 10
 int 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 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;
        enter_wait_pen_down_mode();
    }else{
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)  // 如果测量了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);
            }
            cnt = 0;
            enter_wait_pen_up_mode();
       }
       else           // 如果测量<4次,只需再进入测量即可
       {
           enter_measure_xy_mode();
           start_adc();
        }  
    }
    return IRQ_HANDLED;
}
实验效果:
	相比上次,发现相邻点的电压值变化幅度为1,即输出的电压值更可靠
6 完善五:支持长按、滑动测量(使用定时器)
问题:触笔点击某一个点并不会连续输出打印电压值,且滑动时不会连续打印电压值
解决:增加一个定时器,在触笔点击触发的中断函数里开启定时器,如果时间到了,
  	  在定时器中断函数中判断是否还按下,如果是则继续测量并打印,滑动同理。
static void s3c_ts_timer_function(unsigned long data)
{
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开, 等待按下 */
        enter_wait_pen_down_mode();
    }
   else
   {
       /* 未松开,则继续测量X/Y坐标 */
       enter_measure_xy_mode();
       start_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;
        enter_wait_pen_down_mode();
    }else{
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)  // 如果测量了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);
            }
            cnt = 0;
            enter_wait_pen_up_mode();

	    /* 启动定时器处理长按/滑动的情况 */
  	    mod_timer(&timer_ts, jiffies + HZ/100);
       }
       else           // 如果测量<4次,只需再进入测量即可
       {
           enter_measure_xy_mode();
           start_adc();
        }  
    }
    return IRQ_HANDLED;
}

static struct time_list timer_ts; // 定义一个定时器
static int s3c_ts_init(void)
{		
	/* ....  */
	init_timer(&timer_ts);
    	timer_ts.function = s3c_ts_timer_function;
    	add_timer(&timer_ts);
    	
    	return 0;
}
测试效果:
	长按和滑动时均有连续电压值输出,且电压值稳定变化
7 完善六:把打印改为上报事件,并用tslib测试
修改三处,代码如下
// 在触摸屏中断处理函数中,修改如下
/* 优化措施4: 软件过滤 */
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    /* ...  */
            if(s3c_filter_ts(x, y))
            {
       		// 修改一:点击测量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);  // 注意同步,表示上面上报的数据(x,y,压力值,按下)是一组数据
            }
   /* ... */
}           
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    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{
        enter_measure_xy_mode();
        start_adc();
    }
    return IRQ_HANDLED;
}
static void s3c_ts_timer_function(unsigned long data)
{
    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();
    }
}

下面是tslib库的安装与测试:
1)tslib的安装
a. 编译
tar xzf tslib-1.4.tar.gz
cd tslib
./autogen.sh
tslib安装错误
解决办法:
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool

b. 安装
mkdir tmp
echo “ac_cv_func_malloc_0_nonnull=yes” >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install

最终结果:
tslib安装成功

2) 将tslib下的tmp目录复杂到开发板的根文件系统目录下
cd tmp
cp * -rfd   ~/hardware/first_fs(这个改成你自己的根文件系统目录)
注意:复制时使用rfd三个选项,r: 递归复制  f:force 强制复制    d:复制时保持符号链接

3)一些配置
1.修改 /etc/ts.conf第1行(去掉#号和第一个空格):
# module_raw input
改为:
module_raw input
2. 注意:下面的event0 要看插入触摸屏驱动模块后确定(ls -l /dev/event* ),是哪个事件,我这里是event0
export TSLIB_TSDEVICE=/dev/event0     
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0   
备注:fb0是LCD设备,可使用内核提供的

4)测试
测试一:因为电阻屏都需要校验,故先测验 ts_calibrate以生成校验文件,如下图所示

tslib的测试一:生成校验文件

测试二:ts_test  
1.十字架跟着触笔走
2.绘图

绘图测试

--->  原谅字丑:)
然后其它的测试就不一 一细讲啦
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值