WDS2期第16课 1 2 3 触摸屏 TS 打开ADC时钟 设置ADC的寄存器 采集电压 处理异常值 filter 上报事件 LCD和触摸对应 需要tslib库 校准触摸屏


还是用的输入子系统,总线驱动设备框架。
参考配套光盘的 drivers\input\touchscreen\s3c2410_ts.c
触摸屏工作原理,电阻屏利用的是两个欧姆定律,一层通电另一层ADC采集膜对应电压。
在这里插入图片描述
触摸屏使用过程:

  1. 按下,产生中断
  2. 在中断处理程序中启动ADC采集x、y电压
  3. ADC转换结束,产生ADC中断
  4. 在ADC中断函数里边,上报input_event,启动定时器
  5. 定时时间到,再次启动ADC采集电压,2.3.4.5(处理长按和滑动操作)
  6. 松开

触摸屏硬件连接,这几个引脚就只能用作ADC的,要么ADC要么touch screen,
在这里插入图片描述

打开ADC时钟 设置寄存器CLKCON bit15为1

内核为了省电,在上电时,很多外设都是关闭的,s3c需要通过设置寄存器CLKCON打开,这里ts应该设置bit15为1
在这里插入图片描述
参考drivers\input\touchscreen\s3c2410_ts.c怎么设置的硬件相关操作,s3c需要通过设置寄存器CLKCON打开,这里ts应该设置bit15为1

drivers\input\touchscreen\s3c2410_ts.c
static int __init s3c2410ts_probe(struct platform_device *pdev)
{
	adc_clock = clk_get(NULL, "adc");

arch\arm\mach-s3c2410\clock.c
{
		.name		= "adc",
		.id		= -1,
		.parent		= &clk_p,
		.enable		= s3c2410_clkcon_enable,
		.ctrlbit	= S3C2410_CLKCON_ADC, // bit15
	},
	
include\asm-arm\arch-s3c2410\regs-clock.h
#define S3C2410_CLKCON_ADC	     (1<<15)

arch\arm\mach-s3c2410\clock.c
int s3c2410_clkcon_enable(struct clk *clk, int enable)
{
	unsigned int clocks = clk->ctrlbit; // .ctrlbit	= S3C2410_CLKCON_ADC, // bit15
	unsigned long clkcon;

	clkcon = __raw_readl(S3C2410_CLKCON);

	if (enable)
		clkcon |= clocks;			   // clkcon | clk->ctrlbit 修改bit15
	else
		clkcon &= ~clocks;

	/* ensure none of the special function bits set */
	clkcon &= ~(S3C2410_CLKCON_IDLE|S3C2410_CLKCON_POWER);

	__raw_writel(clkcon, S3C2410_CLKCON); // 写入寄存器

	return 0;

adc的分辨率,1bit刻度表示3.3/(2^10-1)=0.003v=3mv

在这里插入图片描述

设置ADC的ADC/TS的寄存器,

在这里插入图片描述

ADCCON寄存器

在这里插入图片描述
dmesg查看外设时钟为50MHz,
在这里插入图片描述

ADCTSC寄存器 等待中断模式

等待中断模式,
在这里插入图片描述
在这里插入图片描述

ADCDAT0寄存器

在这里插入图片描述

程序一 实现按下松开

#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 <linux/interrupt.h>
#include <asm/plat-s3c24xx/ts.h>

#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>


struct adc_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 adc_regs *adcregs;

// 进入等待松开模式
static void enter_wait_pen_up_mode(void)
{
    adcregs->adctsc = 0x1d3; // bit8 1 = Detect Stylus Up Interrupt Signal.
}
// 进入等待按下模式
static void  enter_wait_pen_down_mode(void)
{
    adcregs->adctsc = 0xd3;  // bit8 0 = Detect Stylus Down Interrupt Signal
}
// 触摸按下松开进入中断函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    printk("pen down/up.\n");
    // 分辨按下还是松开 判断adcdat0 bit15
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        printk("pen_up.\n");
        enter_wait_pen_down_mode(); // 进入等待按下模式
    } else {                          // 按下
        printk("pen_down.\n");    
        enter_wait_pen_up_mode();   // 进入等待松开模式
    }

	return IRQ_HANDLED;
}

static int s3c_ts_init(void)
{
    struct clk * adc_clock; // adc的时钟,需要打开,内核上电关闭了

    // 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->evbit); // 点击,类button
    // void input_set_abs_params(input_dev *, int axis, int min, int max, int fuzz, int flat)
    //  ADC最大值最小值
	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时钟 CLKCON[15]
    adc_clock = clk_get(NULL, "adc");
	clk_enable(adc_clock);  // 打开时钟    
    // 4.2设置s3c的ADC/TS寄存器
    adcregs = ioremap(0x58000000, sizeof(struct adc_regs));
        // bit[14]  : 1 将pclck预分配
        // bit[13:6]: 分频系数 49
        //      ADCCLK=PCLK/(49+1)=50/(49+1)=1MHz
        // bit[0]   : 0 转换开始信号,现在不设置
    adcregs->adccon = (1<<14) | (49<<6);
    
    // 注册中断 中断号 isr 。。。
    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, \
                "ts_pen", NULL);
    // 进入等待中断模式
    enter_wait_pen_down_mode();// 进入等待按下模式

    return 0;
}
static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    adcregs = iounmap(adcregs);
    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");

测试触摸 按下和松开 (程序一)

  1. make 触摸驱动程序,cp xxx.ko nfs
  2. make menuconfig 在内核镜像中去掉触摸驱动
    在这里插入图片描述
    在这里插入图片描述
  3. make uImage 重新编译内核
  4. 从新的内核镜像启动
  5. inmod ts.ko 插入触摸屏模块
    点击触摸屏可以看到结果
    在这里插入图片描述

触摸屏ADC自动的ADC转换模式

等待TS中断时上拉电阻要使能(默认),ADC测量的时候上拉电阻要失能,
在这里插入图片描述
在这里插入图片描述
启动ADC,
在这里插入图片描述
x、y电压转换值存放在ADCDAT0 1 10bit,
在这里插入图片描述

程序二 测量x y电压打印出来

#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 <linux/interrupt.h>
#include <asm/plat-s3c24xx/ts.h>

#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>


struct adc_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 adc_regs *adcregs;

// 进入等待松开中断模式
static void enter_wait_pen_up_mode(void)
{
    adcregs->adctsc = 0x1d3; // bit8 1 = Detect Stylus Up Interrupt Signal.
}
// 进入等待按下中断模式
static void enter_wait_pen_down_mode(void)
{
    adcregs->adctsc = 0xd3;  // bit8 0 = Detect Stylus Down Interrupt Signal
}
// 使adc进入 Auto(Sequential) X/Y Position Conversion Mode
static void enter_measure_xy_mode(void)
{
    //      为啥不是 |= 可能其它位自动设置吧 
    adcregs->adctsc = (1<<3) | (1<<2);  // 1 = Auto Sequential measurement of X-position, Y-position.
}
// 启动adc,开始转换
static void start_adc(void)
{
    adcregs->adccon |= (1<<0);
}

// adc采集完成会有进入adc isr
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    // 测量值放在 ADCDAT0  ADCDAT1 打印电压测量值
    printk("irq cnt=%d, x=%d, y=%d\n", ++cnt, \
            adcregs->adcdat0 & (0x3ff), \
            adcregs->adcdat1 & (0x3ff));
    // 测量完 打印之后等待松开,否则测量一次,不明白为什么???
    enter_wait_pen_up_mode();

	return IRQ_HANDLED;
}
// 触摸按下松开进入中断函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    printk("pen down/up.\n");
    // 分辨按下还是松开 判断adcdat0 bit15
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        printk("pen_up.\n");
        enter_wait_pen_down_mode(); // 进入等待按下模式
    } else {                          // 按下
        // printk("pen_down.\n");    
        // enter_wait_pen_up_mode();   // 进入等待松开模式
        enter_measure_xy_mode(); // 进入测量模式
        start_adc(); // 启动adc开始转换,转换需要时间,但是不等,
                     // 设置了adc中断,转换完成后会进入adc_irq
    }
	return IRQ_HANDLED;
}

static int s3c_ts_init(void)
{
    struct clk * adc_clock; // adc的时钟,需要打开,内核上电关闭了

    // 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->evbit); // 点击,类button
    // void input_set_abs_params(input_dev *, int axis, int min, int max, int fuzz, int flat)
    //  ADC最大值最小值
	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时钟 CLKCON[15]
    adc_clock = clk_get(NULL, "adc");
	clk_enable(adc_clock);  // 打开时钟    
    // 4.2设置s3c的ADC/TS寄存器
    adcregs = ioremap(0x58000000, sizeof(struct adc_regs));
        // bit[14]  : 1 将pclck预分配
        // bit[13:6]: 分频系数 49
        //      ADCCLK=PCLK/(49+1)=50/(49+1)=1MHz
        // bit[0]   : 0 转换开始信号,现在不设置
    adcregs->adccon = (1<<14) | (49<<6);
    
    // 注册中断 中断号 触摸屏中断 isr 。。。
    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, \
                "ts_pen", NULL);
    // 注册ADC中断
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, \
                "adc", NULL);
    // 进入等待中断模式
    enter_wait_pen_down_mode();// 进入等待按下中断模式

    return 0;
}
static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    adcregs = iounmap(adcregs);
    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");

问题,触摸屏返回值不连续,也无法滑动长按

依次点击但是x的值不连续,滑动也不测量,
在这里插入图片描述
采集结果不稳定的结果原因有:

  1. 电压建立不稳定就开始采集,所以可以延时稳定后再采集,有寄存器ADCDLY支持;
    在这里插入图片描述
  2. adc完成前 已经松开,丢弃此次结果。按下松开时间间隔短语adc采集时间,导致采集信息不准确,丢弃;寄存器可以判断按下松开
    在这里插入图片描述
  3. 求多次测量的平均值,比如4次
  4. 简单滤波,根据x y 4次采样得到的数组计算正常的情况,1 2 3 4
    3与1、2平均值的差值应该<ERR_LIMIT && 4与2、3平均值的差值应该<ERR_LIMIT

程序三 处理异常值(电压稳定再采集、剔除中间弹起、4次平均值、filter)

#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 <linux/interrupt.h>
#include <asm/plat-s3c24xx/ts.h>

#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>


struct adc_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 adc_regs *adcregs;

// 进入等待松开中断模式
static void enter_wait_pen_up_mode(void)
{
    adcregs->adctsc = 0x1d3; // bit8 1 = Detect Stylus Up Interrupt Signal.
}
// 进入等待按下中断模式
static void enter_wait_pen_down_mode(void)
{
    adcregs->adctsc = 0xd3;  // bit8 0 = Detect Stylus Down Interrupt Signal
}
// 使adc进入 Auto(Sequential) X/Y Position Conversion Mode
static void enter_measure_xy_mode(void)
{
    //      为啥不是 |= 可能其它位自动设置吧 
    adcregs->adctsc = (1<<3) | (1<<2);  // 1 = Auto Sequential measurement of X-position, Y-position.
}
// 启动adc,开始转换
static void start_adc(void)
{
    adcregs->adccon |= (1<<0);
}

// 根据x y多次采样得到的数组计算正常的情况
//  1 2 3 4  3与1、2平均值的差值应该<ERR_LIMIT && 
//           4与2、3平均值的差值应该<ERR_LIMIT
static int ts_filter(const int x[], const int y[])
{
#define ERR_LIMIT   10 // 像素误差极限
    int avr_x, avr_y;
    int dst_x, dst_y;
    avr_x = (x[0] + x[1]) / 2;
    avr_y = (x[0] + x[1]) / 2;
    dst_x = x[2] > avr_x ? x[2] - avr_x : avr_x - x[2];
    dst_y = y[2] > avr_y ? y[2] - avr_y : avr_y - y[2];
    if (dst_x > ERR_LIMIT || dst_x > ERR_LIMIT) // 3与1、2
        return 0;

    avr_x = (x[1] + x[2]) / 2;
    avr_y = (x[1] + x[3]) / 2;
    dst_x = x[3] > avr_x ? x[3] - avr_x : avr_x - x[3];
    dst_y = y[3] > avr_y ? y[3] - avr_y : avr_y - y[3];
    if (dst_x > ERR_LIMIT || dst_x > ERR_LIMIT) // 4与2、3
        return 0;
    
    return 1;   // 符合正确情况
}

// adc采集完成会有进入adc isr
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4]; // 保存4次结果 求均值用

    // // 优化措施2: adc完成前 已经松开,丢弃此次结果
    // if (adcregs->adcdat0 & (1<<15)) { // 松开
    //     // 等待下次按下
    //     enter_wait_pen_down_mode();
    // } else {
    //     // 测量值放在 ADCDAT0  ADCDAT1 打印电压测量值
    //     printk("irq cnt=%d, x=%d, y=%d\n", ++cnt, \
    //             adcregs->adcdat0 & (0x3ff), \
    //             adcregs->adcdat1 & (0x3ff));
    //     // 测量完 打印之后等待松开,否则测量一次,不明白为什么???
    //     enter_wait_pen_up_mode();
    // }

    // // 优化措施3: 多次求平均值 (之前的优化也在)
    // if (adcregs->adcdat0 & (1<<15)) { // 松开
    //     // 等待下次按下
    //     enter_wait_pen_down_mode();
    // } else {
    //     x[cnt] = adcregs->adcdat0 & (0x3ff);
    //     y[cnt] = adcregs->adcdat1 & (0x3ff);
    //     ++cnt;
    //     if (cnt == 4) {
    //         cnt = 0;
    //         // 测量值放在 ADCDAT0  ADCDAT1 打印电压测量值
    //         printk("irq cnt=%d, x=%d, y=%d\n", ++cnt, \
    //                 (x[0]+x[1]+x[2]+x[3])/4, \
    //                 (y[0]+y[1]+y[2]+y[3])/4);
    //         // 测量完 打印之后等待松开,否则测量一次,不明白为什么???
    //         enter_wait_pen_up_mode();
    //     } else {
    //         enter_measure_xy_mode(); // 进入测量模式
    //         start_adc();            
    //     }
    // }    

    // 优化措施4:  自定义过滤异常的值 ts_filter (之前的优化也在)
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        cnt = 0;    
        // 等待下次按下
        enter_wait_pen_down_mode();
    } else {
        x[cnt] = adcregs->adcdat0 & (0x3ff);
        y[cnt] = adcregs->adcdat1 & (0x3ff);
        ++cnt;
        if (cnt == 4) {
            cnt = 0;
            if (ts_filter(x, y)) { // 自定义过滤方法filter
                // 测量值放在 ADCDAT0  ADCDAT1 打印电压测量值
                printk("irq cnt=%d, x=%d, y=%d\n", ++cnt, \
                        (x[0]+x[1]+x[2]+x[3])/4, \
                        (y[0]+y[1]+y[2]+y[3])/4);
                // 测量完 打印之后等待松开,否则测量一次,不明白为什么???
                enter_wait_pen_up_mode();
            }
        } else {
            enter_measure_xy_mode(); // 进入测量模式
            start_adc();            
        }
    }    

	return IRQ_HANDLED;
}
// 触摸按下松开进入中断函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    printk("pen down/up.\n");
    // 分辨按下还是松开 判断adcdat0 bit15
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        printk("pen_up.\n");
        enter_wait_pen_down_mode(); // 进入等待按下模式
    } else {                          // 按下
        // printk("pen_down.\n");    
        // enter_wait_pen_up_mode();   // 进入等待松开模式
        enter_measure_xy_mode(); // 进入测量模式
        start_adc(); // 启动adc开始转换,转换需要时间,但是不等,
                     // 设置了adc中断,转换完成后会进入adc_irq
    }
	return IRQ_HANDLED;
}

static int s3c_ts_init(void)
{
    struct clk * adc_clock; // adc的时钟,需要打开,内核上电关闭了

    // 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->evbit); // 点击,类button
    // void input_set_abs_params(input_dev *, int axis, int min, int max, int fuzz, int flat)
    //  ADC最大值最小值
	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时钟 CLKCON[15]
    adc_clock = clk_get(NULL, "adc");
	clk_enable(adc_clock);  // 打开时钟    
    // 4.2设置s3c的ADC/TS寄存器
    adcregs = ioremap(0x58000000, sizeof(struct adc_regs));
        // bit[14]  : 1 将pclck预分配
        // bit[13:6]: 分频系数 49
        //      ADCCLK=PCLK/(49+1)=50/(49+1)=1MHz
        // bit[0]   : 0 转换开始信号,现在不设置
    adcregs->adccon = (1<<14) | (49<<6);
    
    // 注册中断 中断号 触摸屏中断 isr 。。。
    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, \
                "ts_pen", NULL);
    // 注册ADC中断
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, \
                "adc", NULL);
    
    // 优化措施1: 延时测量,配置延时寄存器ADCDLY
    // 使得电压稳定后再发出IRQ_TC中断
    adcregs->adcdly = 0xffff; // 延时最大值
    
    // 进入等待中断模式
    enter_wait_pen_down_mode();// 进入等待按下中断模式

    return 0;
}
static void s3c_ts_exit(void)
{
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);
    adcregs = iounmap(adcregs);
    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");

程序四 处理滑动长按 用定时器timer

按下触摸屏,
发生IRQ_TC中断,设置成测量模式并启动ADC,
ADC转换完成发生中断,各种异常值处理优化措施,如果4次判断都是按下 则上报事件 并 则启动定时器,
定时时间到若还是按下则测量并启动ADC转换,不是按下则等待下次按下。

#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 <linux/interrupt.h>
#include <asm/plat-s3c24xx/ts.h>

#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>


struct adc_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 adc_regs *adcregs;
static struct timer_list ts_timer; // 用于处理需要多次中断的 ts滑动长按

// 进入等待松开中断模式
static void enter_wait_pen_up_mode(void)
{
    adcregs->adctsc = 0x1d3; // bit8 1 = Detect Stylus Up Interrupt Signal.
}
// 进入等待按下中断模式
static void enter_wait_pen_down_mode(void)
{
    adcregs->adctsc = 0xd3;  // bit8 0 = Detect Stylus Down Interrupt Signal
}
// 使adc进入 Auto(Sequential) X/Y Position Conversion Mode
static void enter_measure_xy_mode(void)
{
    //      为啥不是 |= 可能其它位自动设置吧 
    adcregs->adctsc = (1<<3) | (1<<2);  // 1 = Auto Sequential measurement of X-position, Y-position.
}
// 启动adc,开始转换
static void start_adc(void)
{
    adcregs->adccon |= (1<<0);
}

// 根据x y多次采样得到的数组计算正常的情况
//  1 2 3 4  3与1、2平均值的差值应该<ERR_LIMIT && 
//           4与2、3平均值的差值应该<ERR_LIMIT
static int ts_filter(const int x[], const int y[])
{
#define ERR_LIMIT   10 // 像素误差极限
    int avr_x, avr_y;
    int dst_x, dst_y;
    avr_x = (x[0] + x[1]) / 2;
    avr_y = (x[0] + x[1]) / 2;
    dst_x = x[2] > avr_x ? x[2] - avr_x : avr_x - x[2];
    dst_y = y[2] > avr_y ? y[2] - avr_y : avr_y - y[2];
    if (dst_x > ERR_LIMIT || dst_x > ERR_LIMIT) // 3与1、2
        return 0;

    avr_x = (x[1] + x[2]) / 2;
    avr_y = (x[1] + x[3]) / 2;
    dst_x = x[3] > avr_x ? x[3] - avr_x : avr_x - x[3];
    dst_y = y[3] > avr_y ? y[3] - avr_y : avr_y - y[3];
    if (dst_x > ERR_LIMIT || dst_x > ERR_LIMIT) // 4与2、3
        return 0;
    
    return 1;   // 符合正确情况
}
// 定时时间到 处理函数
static void ts_timer_function(unsigned long data)
{
    if (adcregs->adcdat0 & (1<<15)) { // 已经松开
        // 等待下次按下
        enter_wait_pen_down_mode();
    } else {                          // 依然是按下
        enter_measure_xy_mode(); // 进入测量模式
        start_adc();           
    }
}

// adc采集完成会有进入adc isr
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4]; // 保存4次结果 求均值用

    // 优化措施4:  自定义过滤异常的值 ts_filter (之前的优化也在)
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        cnt = 0;
        // 等待下次按下
        enter_wait_pen_down_mode();
    } else {
        x[cnt] = adcregs->adcdat0 & (0x3ff);
        y[cnt] = adcregs->adcdat1 & (0x3ff);
        ++cnt;
        if (cnt == 4) {
            cnt = 0;
            if (ts_filter(x, y)) { // 自定义过滤方法filter
                // 测量值放在 ADCDAT0  ADCDAT1 打印电压测量值
                printk("irq cnt=%d, x=%d, y=%d\n", ++cnt, \
                        (x[0]+x[1]+x[2]+x[3])/4, \ 
                        (y[0]+y[1]+y[2]+y[3])/4); 
                // 测量完 打印之后等待松开,否则测量一次,不明白为什么???
                enter_wait_pen_up_mode();

                // 启动定时器
                mod_timer(&ts_timer, jiffies+HZ/100); // 10ms
            }
        } else {
            enter_measure_xy_mode(); // 进入测量模式
            start_adc();            
        }
    }    

	return IRQ_HANDLED;
}
// 触摸按下松开进入中断函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    printk("pen down/up.\n");
    // 分辨按下还是松开 判断adcdat0 bit15
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        printk("pen_up.\n");
        enter_wait_pen_down_mode(); // 进入等待按下模式
    } else {                          // 按下
        // printk("pen_down.\n");    
        // enter_wait_pen_up_mode();   // 进入等待松开模式
        enter_measure_xy_mode(); // 进入测量模式
        start_adc(); // 启动adc开始转换,转换需要时间,但是不等,
                     // 设置了adc中断,转换完成后会进入adc_irq
    }
	return IRQ_HANDLED;
}

static int s3c_ts_init(void)
{
    struct clk * adc_clock; // adc的时钟,需要打开,内核上电关闭了

    // 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->evbit); // 点击,类button
    // void input_set_abs_params(input_dev *, int axis, int min, int max, int fuzz, int flat)
    //  ADC最大值最小值
	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时钟 CLKCON[15]
    adc_clock = clk_get(NULL, "adc");
	clk_enable(adc_clock);  // 打开时钟    
    // 4.2设置s3c的ADC/TS寄存器
    adcregs = ioremap(0x58000000, sizeof(struct adc_regs));
        // bit[14]  : 1 将pclck预分配
        // bit[13:6]: 分频系数 49
        //      ADCCLK=PCLK/(49+1)=50/(49+1)=1MHz
        // bit[0]   : 0 转换开始信号,现在不设置
    adcregs->adccon = (1<<14) | (49<<6);
    
    // 注册中断 中断号 触摸屏中断 isr 。。。
    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, \
                "ts_pen", NULL);
    // 注册ADC中断
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, \
                "adc", NULL);
    
    // 优化措施1: 延时测量,配置延时寄存器ADCDLY
    // 使得电压稳定后再发出IRQ_TC中断
    adcregs->adcdly = 0xffff; // 延时最大值
    
    // 优化措施5:用定时器处理滑动长按
    //  
    init_timer(&ts_timer);
    ts_timer.function = ts_timer_function;
    add_timer(&ts_timer);

    // 进入等待中断模式
    enter_wait_pen_down_mode();// 进入等待按下中断模式

    return 0;
}
static void s3c_ts_exit(void)
{
    del_timer(&ts_timer);
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);
    adcregs = iounmap(adcregs);
    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");

// 按下触摸屏,
// 发生IRQ_TC中断,设置成测量模式并启动ADC,
// ADC转换完成发生中断,各种异常值处理优化措施,
//      如果4次判断都是按下 则上报事件 并 则启动定时器
// 定时时间到若还是按下则测量并启动ADC转换,不是按下则等待下次按下

程序五 将打印改成上报事件 上报完毕

#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 <linux/interrupt.h>
#include <asm/plat-s3c24xx/ts.h>

#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>


struct adc_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 adc_regs *adcregs;
static struct timer_list ts_timer; // 用于处理需要多次中断的 ts滑动长按

// 进入等待松开中断模式
static void enter_wait_pen_up_mode(void)
{
    adcregs->adctsc = 0x1d3; // bit8 1 = Detect Stylus Up Interrupt Signal.
}
// 进入等待按下中断模式
static void enter_wait_pen_down_mode(void)
{
    adcregs->adctsc = 0xd3;  // bit8 0 = Detect Stylus Down Interrupt Signal
}
// 使adc进入 Auto(Sequential) X/Y Position Conversion Mode
static void enter_measure_xy_mode(void)
{
    //      为啥不是 |= 可能其它位自动设置吧 
    adcregs->adctsc = (1<<3) | (1<<2);  // 1 = Auto Sequential measurement of X-position, Y-position.
}
// 启动adc,开始转换
static void start_adc(void)
{
    adcregs->adccon |= (1<<0);
}

// 根据x y多次采样得到的数组计算正常的情况
//  1 2 3 4  3与1、2平均值的差值应该<ERR_LIMIT && 
//           4与2、3平均值的差值应该<ERR_LIMIT
static int ts_filter(const int x[], const int y[])
{
#define ERR_LIMIT   10 // 像素误差极限
    int avr_x, avr_y;
    int dst_x, dst_y;
    avr_x = (x[0] + x[1]) / 2;
    avr_y = (x[0] + x[1]) / 2;
    dst_x = x[2] > avr_x ? x[2] - avr_x : avr_x - x[2];
    dst_y = y[2] > avr_y ? y[2] - avr_y : avr_y - y[2];
    if (dst_x > ERR_LIMIT || dst_x > ERR_LIMIT) // 3与1、2
        return 0;

    avr_x = (x[1] + x[2]) / 2;
    avr_y = (x[1] + x[3]) / 2;
    dst_x = x[3] > avr_x ? x[3] - avr_x : avr_x - x[3];
    dst_y = y[3] > avr_y ? y[3] - avr_y : avr_y - y[3];
    if (dst_x > ERR_LIMIT || dst_x > ERR_LIMIT) // 4与2、3
        return 0;
    
    return 1;   // 符合正确情况
}
// 定时时间到 处理函数
static void ts_timer_function(unsigned long data)
{
    if (adcregs->adcdat0 & (1<<15)) { // 已经松开
        // 等待下次按下
        enter_wait_pen_down_mode();
    } else {                          // 依然是按下
        enter_measure_xy_mode(); // 进入测量模式
        start_adc();           
    }
}

// adc采集完成会有进入adc isr
static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int cnt = 0;
    static int x[4], y[4]; // 保存4次结果 求均值用

    // 优化措施4:  自定义过滤异常的值 ts_filter (之前的优化也在)
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        cnt = 0;
        // 上报事件
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0); // 多级压力 1表示按下
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0); // 1表示按下        
        input_sync(s3c_ts_dev); // 上报完毕
        // 等待下次按下
        enter_wait_pen_down_mode();
    } else {
        x[cnt] = adcregs->adcdat0 & (0x3ff);
        y[cnt] = adcregs->adcdat1 & (0x3ff);
        ++cnt;
        if (cnt == 4) {
            cnt = 0;
            if (ts_filter(x, y)) { // 自定义过滤方法filter
                // 测量值放在 ADCDAT0  ADCDAT1 打印电压测量值
                // printk("irq cnt=%d, x=%d, y=%d\n", ++cnt, \
                //         (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); // 多级压力 1表示按下
                input_report_key(s3c_ts_dev, BTN_TOUCH, 1);    // 1表示按下
                input_sync(s3c_ts_dev); // 上报完毕
                // 测量完 打印之后等待松开,否则测量一次,不明白为什么???
                enter_wait_pen_up_mode();

                // 启动定时器
                mod_timer(&ts_timer, jiffies+HZ/100); // 10ms
            }
        } else {
            enter_measure_xy_mode(); // 进入测量模式
            start_adc();            
        }
    }    

	return IRQ_HANDLED;
}
// 触摸按下松开进入中断函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
    printk("pen down/up.\n");
    // 分辨按下还是松开 判断adcdat0 bit15
    if (adcregs->adcdat0 & (1<<15)) { // 松开
        // printk("pen_up.\n");
        // 上报事件
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0); // 多级压力 1表示按下
        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(); // 进入测量模式
        start_adc(); // 启动adc开始转换,转换需要时间,但是不等,
                     // 设置了adc中断,转换完成后会进入adc_irq
    }
	return IRQ_HANDLED;
}

static int s3c_ts_init(void)
{
    struct clk * adc_clock; // adc的时钟,需要打开,内核上电关闭了

    // 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->evbit); // 点击,类button
    // void input_set_abs_params(input_dev *, int axis, int min, int max, int fuzz, int flat)
    //  ADC最大值最小值
	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时钟 CLKCON[15]
    adc_clock = clk_get(NULL, "adc");
	clk_enable(adc_clock);  // 打开时钟    
    // 4.2设置s3c的ADC/TS寄存器
    adcregs = ioremap(0x58000000, sizeof(struct adc_regs));
        // bit[14]  : 1 将pclck预分配
        // bit[13:6]: 分频系数 49
        //      ADCCLK=PCLK/(49+1)=50/(49+1)=1MHz
        // bit[0]   : 0 转换开始信号,现在不设置
    adcregs->adccon = (1<<14) | (49<<6);
    
    // 注册中断 中断号 触摸屏中断 isr 。。。
    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, \
                "ts_pen", NULL);
    // 注册ADC中断
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, \
                "adc", NULL);
    
    // 优化措施1: 延时测量,配置延时寄存器ADCDLY
    // 使得电压稳定后再发出IRQ_TC中断
    adcregs->adcdly = 0xffff; // 延时最大值
    
    // 优化措施5:用定时器处理滑动长按
    //  
    init_timer(&ts_timer);
    ts_timer.function = ts_timer_function;
    add_timer(&ts_timer);

    // 进入等待中断模式
    enter_wait_pen_down_mode();// 进入等待按下中断模式

    return 0;
}
static void s3c_ts_exit(void)
{
    del_timer(&ts_timer);
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);
    adcregs = iounmap(adcregs);
    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");

// 按下触摸屏,
// 发生IRQ_TC中断,设置成测量模式并启动ADC,
// ADC转换完成发生中断,各种异常值处理优化措施,
//      如果4次判断都是按下 则上报事件 并 则启动定时器
// 定时时间到若还是按下则测量并启动ADC转换,不是按下则等待下次按下

测试程序五

  1. 装在模块前 查看有哪些事件 ls /dev/even*
  2. 然后 insmod xxx.ko
  3. 装在模块后 查看有哪些事件 ls /dev/even* 新增加的对应触摸屏even
  4. hexdump /dev/even0 打开并读取
    在这里插入图片描述

将LCD和触摸对应起来 需要tslib库 校准触摸屏 生成校验文件 有很多测试程序

在这里插入图片描述

  1. 解压 tar xzf tslib1.4.tar.gz
  2. cd tslib
  3. ./autogen.sh
  4. mkdir temp 存放临时结果
  5. 。。。。。

可能出现段错误,内核配置后 需要重新编译驱动程序,跟装载过程有关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值