Linux学习笔记--触摸屏驱动
关键数据结构
1. 触摸控制器寄存器结构
struct qemu_ts_con {volatile unsigned int pressure; // 压力/触摸状态volatile unsigned int x; // X 坐标volatile unsigned int y; // Y 坐标 volatile unsigned int clean; // 清理标志
};
volatile
确保编译器不会优化对这些寄存器的访问这个结构体映射到硬件的内存区域
2. 全局变量
static struct input_dev *g_input_dev; // 输入设备
static int g_irq; // 中断号
static struct qemu_ts_con *ts_con; // 映射的硬件寄存器
struct timer_list ts_timer; // 定时器
核心机制:中断 + 定时器
1. 定时器回调函数
static void ts_irq_timer(unsigned long _data)
{if (ts_con->pressure) // 如果仍然处于按压状态{// 持续上报坐标input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x);input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y);input_sync(g_input_dev);// 重新设置定时器,实现轮询mod_timer(&ts_timer, jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));}
}
在触摸持续期间,定时器每 10ms 触发一次
持续上报触摸坐标,实现触摸移动的跟踪
为什么需要定时器轮询?
触摸移动的连续性:
中断只在触摸开始和结束时触发
但在触摸过程中,手指可能持续移动
定时器轮询可以捕获这些连续的坐标变化
时间间隔选择:10ms
TOUCHSCREEN_POLL_TIME_MS 10
的含义:
100Hz 采样率:每秒采样 100 次坐标
平衡性能与流畅度:
太频繁:CPU 占用率高
太稀疏:触摸轨迹不连贯
典型触摸屏的采样率在 60-120Hz 之间
内核定时器机制
定时器初始化:
setup_timer(&ts_timer, ts_irq_timer, (unsigned long)NULL);
时间计算:
jiffies + msecs_to_jiffies(10)
jiffies
: 当前时间(从系统启动开始的节拍数)msecs_to_jiffies(10)
: 将 10ms 转换为对应的节拍数结果:定时器在"当前时间 + 10ms"后再次触发
与中断处理函数的协作
中断函数中的定时器启动:
// 在 input_dev_demo_isr 中:
if (ts_con->pressure) // 触摸按下
{// ... 上报初始坐标 .../* start timer */mod_timer(&ts_timer, jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
}
2. 中断服务程序
static irqreturn_t input_dev_demo_isr(int irq, void *dev_id)
{if (ts_con->pressure) // 触摸按下{// 上报初始触摸坐标input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x);input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y);input_event(g_input_dev, EV_KEY, BTN_TOUCH, 1); // 触摸开始input_sync(g_input_dev);// 启动定时器进行持续轮询mod_timer(&ts_timer, jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));}else // 触摸释放{input_event(g_input_dev, EV_KEY, BTN_TOUCH, 0); // 触摸结束input_sync(g_input_dev);// 注意:这里应该停止定时器,但代码中缺失了 del_timer}return IRQ_HANDLED;
}
驱动probe函数详解
1. 设备信息获取
// 从设备树获取 GPIO
gpio = of_get_gpio(pdev->dev.of_node, 0);// 获取内存映射资源
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ts_con = ioremap(io->start, io->end - io->start + 1);
2. 输入设备设置
/* set 1: which type event ? */
__set_bit(EV_KEY, g_input_dev->evbit); // 按键事件
__set_bit(EV_ABS, g_input_dev->evbit); // 绝对坐标事件
__set_bit(INPUT_PROP_DIRECT, g_input_dev->propbit); // 直接输入设备/* set 2: which event ? */
__set_bit(BTN_TOUCH, g_input_dev->keybit); // 触摸按键
__set_bit(ABS_X, g_input_dev->absbit); // X 坐标
__set_bit(ABS_Y, g_input_dev->absbit); // Y 坐标/* set 3: event params ? */
input_set_abs_params(g_input_dev, ABS_X, 0, 0xffff, 0, 0);
input_set_abs_params(g_input_dev, ABS_Y, 0, 0xffff, 0, 0);
__set_bit(INPUT_PROP_DIRECT, g_input_dev->propbit);
用于设置输入设备的属性,表明这是一个直接输入设备。
__set_bit
宏:
// 内核中的位操作宏
#define __set_bit(nr, addr) set_bit(nr, addr)
作用:将位图中的特定位设置为 1
nr
:要设置的位号(这里是INPUT_PROP_DIRECT
)addr
:位图地址(这里是g_input_dev->propbit
)
参数说明:
INPUT_PROP_DIRECT
:输入设备属性常量,值为 0g_input_dev->propbit
:输入设备的属性位图
INPUT_PROP_DIRECT 的含义
直接输入设备(Direct Input Device):
定义:用户直接在设备表面上操作,操作位置与光标位置直接对应
典型例子:
触摸屏(Touchscreen)
数字化仪(Graphics Tablet)
触摸板(Touchpad)
3. 硬件初始化
// 内存映射
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ts_con = ioremap(io->start, io->end - io->start + 1);// GPIO 转中断号,并注册中断
g_irq = gpio_to_irq(gpio);
error = request_irq(g_irq, input_dev_demo_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL);// 初始化定时器
setup_timer(&ts_timer, ts_irq_timer, (unsigned long)NULL);
工作流程
1. 触摸按下流程
GPIO 中断(上升沿) → 中断处理函数 → 上报触摸开始 → 启动定时器 → 定时器轮询坐标
2. 触摸移动流程
定时器每10ms触发 → 读取坐标 → 上报新位置 → 重置定时器
3. 触摸释放流程
GPIO 中断(下降沿) → 中断处理函数 → 上报触摸结束 → 停止定时器(代码中缺失)
设备树配置
这个驱动期望的设备树节点:
input_dev_demo: input_dev_demo {compatible = "100ask,input_dev_demo";reg = <0x12345000 0x1000>; // 内存映射区域gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>; // GPIO 引脚interrupt-parent = <&gpio1>;interrupts = <1 IRQ_TYPE_EDGE_BOTH>; // 双边沿触发
};