一、写ts驱动步骤(新手稍微看下即可,内容有点搞)
1、复制头文件;
2、写入口函数跟出口函数
3、分配一个input_dev结构体,
在头文件下插入:static struct input_dev *ts_dev;
在init中分配:ts_dev = input_allocate_device();
4、注册:在init中注册:input_register_device(ts_dev);
5、设置:
1、能产生哪类事件:
set_bit(EV_KEY, ts_dev->evbit);按键类事件
set_bit(EV_ABS, ts_dev->evbit);触摸屏事件
2、能产生这类事件的哪些事件:
set_bit(BTN_TOUCH, ts_dev->keybit);按键类里的触摸屏事件
input_set_abs_params(ts_dev, ABS_X, 0, 0xfff, 0, 0);
input_set_abs_params(ts_dev, ABS_Y, 0, 0xfff, 0, 0);
input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
***********************************************************************************
所有的触摸屏程序几乎都是上面的框架,可直接采用
***********************************************************************************
6、写硬件相关的操作:
1、使能时钟:
clk = clk_get(NULL, "adc"); //使能时钟
clk_enable(clk); /* PCLK_GATE[12]设为1 */
2、对各种寄存器进行设置
1、在init外定义一个结构体struct adc_regs {};把各种寄存器放进去,再定义一个指针
static struct adc_regs *adc_regs;
2、在init中对地址映射:adc_regs = ioremap(0x7E00B000, sizeof(struct adc_regs));
7、先只对三个寄存器进行设置:adc_regs->adccon = (1<<16) | (1<<14) | (65<<6);
adc_regs->adcdly = 0xffff; //给它最大的delay延迟时间。
adc_regs->adcclrintpndnup = 0;
8、定义第一个中断:
1、request_irq(IRQ_TC, ts_pen_down_up_isr, IRQF_SHARED, "pen_down_up", 1);
2、然后进入该函数,在该中断下写入:enter_wait_for_pen_down();
3、在init外定义这个函数,在里面定义寄存器adc_regs->adctsc = 0xd3;进入等待中断方式,检测按下;再定义一个函数static void enter_wait_for_pen_up(void),检测松开
4、写上面那个中断的中断函数:static irqreturn_t ts_pen_down_up_isr(int irq, void *dev_id);
5、在中断中判断是按下还是松开,然后对其分别进行操作:
1如果是按下的(down),进入enter_measure_xy_mode();(自动的XY坐标转化模式),再之后启动start_adc()这个函数,这就需要添加两个函数:static void enter_measure_xy_mode(void),static void start_adc(void)
6、start_adc转换不能瞬间完成,我们也不能死等,它完成后会产生一个中断,so在init中再添加一个中断:request_irq(IRQ_ADC, adc_isr, IRQF_SHARED, "adc", 1);写这个中断的中断函数
***************************************************************************************
优化措施
***************************************************************************************
9、在自动的xy坐标模式,在adc中断后,需要几毫秒时间去判断下是up还是down;
10、如果ADC完成之后,如果触摸笔已经松开,则丢弃此次结果;
11、多册测量,求平均值;
12、软件过滤;
***************************************************************************************
13、按下的时候如果是长按,滑动,那就需要启动定时器来定时。
1、static struct timer_list ts_timer;
2、在init中写入:
init_timer(&ts_timer);
ts_timer.expires = 0;
ts_timer.function = ts_timer_function;
add_timer(&ts_timer);
3、在init外设置函数:static void ts_timer_function(unsigned long data)
4、在adc_irq函数中启动定时器:mod_timer(&ts_timer, jiffies + HZ/100); HZ为1s,所以定时10ms;
14、对ts_timer_function()这个函数进行设置;
15、上报事件:
input_event(ts_dev, EV_ABS, ABS_X, x);
input_event(ts_dev, EV_ABS, ABS_Y, y);
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1);
input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);
input_sync(ts_dev);
多放也不会有什么影响。
**********************************************************************************************************************
二、驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>
static struct timer_list ts_timer;
static struct input_dev *ts_dev;
struct adc_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
unsigned long adcclrint;
unsigned long reserved;
unsigned long adcclrintpndnup;
};
static struct adc_regs *adc_regs;
static void enter_wait_for_pen_down(void)
{
adc_regs->adctsc = 0xd3; //进入等待中断模式,检测按下
}
static void enter_wait_for_pen_up(void)
{
adc_regs->adctsc = 0x1d3;//进入等待中断模式,检测松开
}
static void enter_measure_xy_mode(void)
{
adc_regs->adctsc = ((1<<7) | (1<<6) | (1<<4) | (1<<3) | (1<<2)); //自动的 xy坐标模式
}
static void start_adc(void)
{
adc_regs->adccon |= (1<<0); //开始adc转换
}
static irqreturn_t ts_pen_down_up_isr(int irq, void *dev_id)
{
unsigned long data0, data1;
int down;
data0 = adc_regs->adcdat0; //adcdat0存放X坐标
data1 = adc_regs->adcdat1; //adcdat0存放y坐标
down = (!(data0 & (1<<15))) && (!(data1 & (1<<15)));//第十五位判断是按下还是松开,0是down,1是up
if (!down) //if not down
{
//printk("pen up\n");
enter_wait_for_pen_down(); //wait for down;
/* 调用evdev_event: 保存,唤醒 */
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0); //0 is 松开
input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
input_sync(ts_dev);
}
else //if down
{
//printk("pen down\n");
//enter_wait_for_pen_up();
/* 进入"自动测量x/y座标模式" */
enter_measure_xy_mode();
/* 启动ADC */
start_adc(); //start ADC change
}
adc_regs->adcupdn = 0; //触摸笔向上或向下中断寄存器,0表示无触摸笔向下或向上
adc_regs->adcclrint = 0; //清楚ADC中断,在这寄存器上写入任何值都将清楚相关有效的中断
adc_regs->adcclrintpndnup = 0; //查无此寄存器澹?
return IRQ_HANDLED;
}
static irqreturn_t adc_isr(int irq, void *dev_id)
{
/* 为何不在这里立刻处理ADC到结果?
* 因为6410的ADC有个缺点:
* 当ADC刚完成时, 必须等待若干ms,
* 才能读取adcdat0,adcdata1来判断
* 当前的触摸屏是被按下还是松开
*/
/* 启动定时器, 是为了不在中断处理函数里等待 */
mod_timer(&ts_timer, jiffies + HZ/100); //HZ是1秒,so HZ/100 =10ms,10ms后再去判断按下还是松开
enter_wait_for_pen_up();
adc_regs->adcupdn = 0;
adc_regs->adcclrint = 0;
adc_regs->adcclrintpndnup = 0;
return IRQ_HANDLED;
}
***************************************************************************************
static irqreturn_t adc_isr_ok_old(int irq, void *dev_id)//没用到
{
int x,y;
int adcdat0,adcdat1;
int down;
#if 1 /* in auto xy mode, after adc interrupt, have to wait several ms to test up/down */
udelay(1000);
udelay(1000);
udelay(1000);
udelay(1000);
#endif
adcdat0 = adc_regs->adcdat0;
adcdat1 = adc_regs->adcdat1;
x = adcdat0 & 0xfff;
y = adcdat1 & 0xfff;
down = (!(adcdat0 & (1<<15))) && (!(adcdat1 & (1<<15)));
//printk("adcdat0 = 0x%x, adc_dat1 = 0x%x\n", adcdat0, adcdat1);
if (down)
{
//printk("enter_wait_for_pen_up\n");
enter_wait_for_pen_up();
//printk("x = %d, y = %d\n", x, y);
input_event(ts_dev, EV_ABS, ABS_X, x);
input_event(ts_dev, EV_ABS, ABS_Y, y);
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1); //1表示按下,0表示松开
input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);
input_sync(ts_dev);
/* 启动定时器 */
mod_timer(&ts_timer, jiffies + HZ/100);
}
else
{
//printk("enter_wait_for_pen_down\n");
enter_wait_for_pen_down();
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);
input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
input_sync(ts_dev);
}
adc_regs->adcupdn = 0;
adc_regs->adcclrint = 0;
adc_regs->adcclrintpndnup = 0;
return IRQ_HANDLED;
}
static void ts_timer_function_ok_old(unsigned long data) //没用到
{
/* 如果触摸笔已经松开, 就没必要再次启动ADC */
/* 否则, 启动ADC */
unsigned long data0, data1;
int down;
data0 = adc_regs->adcdat0;
data1 = adc_regs->adcdat1;
down = (!(data0 & (1<<15))) && (!(data1 & (1<<15)));
if (down)
{
enter_measure_xy_mode();
start_adc();
}
else
{
enter_wait_for_pen_down();
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);
input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
input_sync(ts_dev);
}
}
**********************************************************************
static void ts_timer_function(unsigned long data)
{
/* 如果触摸笔已经松开, 就没必要再次启动ADC */
/* 否则, 启动ADC */
unsigned long data0, data1;
int down;
int x, y;
data0 = adc_regs->adcdat0;
data1 = adc_regs->adcdat1;
down = (!(data0 & (1<<15))) && (!(data1 & (1<<15)));
if (down)
{
x = data0 & 0xfff;
y = data1 & 0xfff;
input_event(ts_dev, EV_ABS, ABS_X, x);
input_event(ts_dev, EV_ABS, ABS_Y, y);
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1);
input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);
input_sync(ts_dev);
enter_measure_xy_mode();
start_adc();
}
else
{
enter_wait_for_pen_down();
input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);
input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);
input_sync(ts_dev);
}
}
static int ts_init(void)
{
struct clk *clk;
/* 1. 分配input_dev */
ts_dev = input_allocate_device();
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, ts_dev->evbit);//按键事件
set_bit(EV_ABS, ts_dev->evbit);//触摸屏事件
/* 2.2 能产生这类事件里的哪些事件 */
set_bit(BTN_TOUCH, ts_dev->keybit);//按键事件中的触摸屏事件
input_set_abs_params(ts_dev, ABS_X, 0, 0xfff, 0, 0);//这些内容可直接复制粘贴
input_set_abs_params(ts_dev, ABS_Y, 0, 0xfff, 0, 0);
input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);//压力。只有0和1,要么按下,要么松开。
/* 3. 注册 */
input_register_device(ts_dev);
/* 4. 硬件相关 */
adc_regs = ioremap(0x7E00B000, sizeof(struct adc_regs));
clk = clk_get(NULL, "adc"); //使能时钟
clk_enable(clk); /* PCLK_GATE[12]设为1 */
/* bit[16] : 1 = 12-bit A/D conversion
* bit[14] : 1 - enable A/D converter prescaler enable
* bit[13:6] : A/D converter prescaler value,
* PCLK=66500000, adcclk=pclk/(n+1)
* 取值13, adclk=66.5MHz/14=4.75
*
*/
adc_regs->adccon = (1<<16) | (1<<14) | (65<<6);/*貌似没有16位,个人觉得那个不用设置。*/
adc_regs->adcdly = 0xffff; //给它最大的delay延迟时间。
adc_regs->adcclrintpndnup = 0;
request_irq(IRQ_TC, ts_pen_down_up_isr, IRQF_SHARED, "pen_down_up", 1);
request_irq(IRQ_ADC, adc_isr, IRQF_SHARED, "adc", 1);
init_timer(&ts_timer);
ts_timer.expires = 0;
ts_timer.function = ts_timer_function;
add_timer(&ts_timer);
/* 进入"wait for interrupt mode", 等待触摸笔按下或松开的模式 */
enter_wait_for_pen_down();
return 0;
}
static void ts_exit(void)
{
del_timer(&ts_timer);
free_irq(IRQ_TC, 1);
free_irq(IRQ_ADC, 1);
iounmap(adc_regs);
input_unregister_device(ts_dev);
input_free_device(ts_dev);
}
module_init(ts_init);
module_exit(ts_exit);
MODULE_LICENSE("GPL");