文章目录
还是用的输入子系统,总线驱动设备框架。
参考配套光盘的
drivers\input\touchscreen\s3c2410_ts.c
,
触摸屏工作原理,电阻屏利用的是两个欧姆定律,一层通电另一层ADC采集膜对应电压。

触摸屏使用过程:
- 按下,产生中断
- 在中断处理程序中启动ADC采集x、y电压
- ADC转换结束,产生ADC中断
- 在ADC中断函数里边,上报input_event,启动定时器
- 定时时间到,再次启动ADC采集电压,2.3.4.5(处理长按和滑动操作)
- 松开
触摸屏硬件连接,这几个引脚就只能用作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");
测试触摸 按下和松开 (程序一)
make
触摸驱动程序,cp xxx.ko nfs
make menuconfig
在内核镜像中去掉触摸驱动
make uImage
重新编译内核- 从新的内核镜像启动
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的值不连续,滑动也不测量,
采集结果不稳定的结果原因有:
- 电压建立不稳定就开始采集,所以可以延时稳定后再采集,有寄存器ADCDLY支持;
- adc完成前 已经松开,丢弃此次结果。按下松开时间间隔短语adc采集时间,导致采集信息不准确,丢弃;寄存器可以判断按下松开
- 求多次测量的平均值,比如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转换,不是按下则等待下次按下
测试程序五
- 装在模块前 查看有哪些事件
ls /dev/even*
- 然后
insmod xxx.ko
- 装在模块后 查看有哪些事件
ls /dev/even*
新增加的对应触摸屏even hexdump /dev/even0
打开并读取
将LCD和触摸对应起来 需要tslib库 校准触摸屏 生成校验文件 有很多测试程序
- 解压
tar xzf tslib1.4.tar.gz
cd tslib
./autogen.sh
mkdir temp
存放临时结果- 。。。。。
可能出现段错误,内核配置后 需要重新编译驱动程序,跟装载过程有关。