9. 输入子系统–电阻触摸驱动实验¶
有关电阻触摸的基础知识内容可以参考野火STM32相关教程,这里只介绍电阻触摸驱动的相关内容。与一般的微处理器 不同,本节使用的imx6ull内自带触摸屏控制器,只需要把电阻触摸屏的信号线接到对应的IO即可,通过配置imx6ull 触摸屏控制器获取触点坐标值。
9.1. TSC 触摸屏控制器介绍¶
触摸屏系统由三个部分组成,它们分别是TSC控制器、TSC_ANA、ADC。其中,TSC控制器是整个系统的核心, 负责为 TSC_ANA 和 ADC 提供控制信号,实现电阻屏的触摸检测和触点坐标测量;TSC_ANA根据TSC控制器 提供的信号负责向电阻屏的 X+、X-、Y+、Y- 四根线提供正负电压;ADC则根据TSC控制器的坐标转换信号负责 采集触点x轴和y轴的电压,并进行坐标值转换。TSC控制器、TSC_ANA、ADC三个模块协同工作,形成一个触摸屏系统。
上图触摸屏系统有以下几种工作状态:
空闲状态: 当完成坐标测量后,TSC会停留在空闲状态。只有当TSC_FLOW_CONTROL寄存器start_sen位被设置为1时, TCS才会开始进行触摸检测或者坐标测量;当TSC_FLOW_CONTROL寄存器的disable位被设置为1时,完成当前的任务后返回空闲状态。
预充电状态: 这是触摸检测前的工作状态,在该状态下,电阻屏的其中一层被设置为正电压。
触摸检测状态: 如果TSC控制器设置为自动测量,当检测到触摸后,TSC自动控制ADC测量坐标,否则,需要通过软件设置TSC_FLOW_CONTROL寄存器 start_measure位开始测量坐标。
坐标测量状态: 在坐标测量状态下,TSC通过硬件自动控制TSC_ANA和ADC进行坐标测量,无需软件干预。如果设置了ADC硬件平均功能, ADC对采集到的数据进行求均值。
数据有效性检测状态: 在测量到坐标值之后,TSC会再一次检测是否有触摸,如果没有检测到触摸,则判断先前测量到的坐标值是无效的;否则,测量到的坐标是有效的。
中断状态: 每个中断都可以通过TSC_INT_EN、TSC_INT_SIG_EN设置。
复位状态: TSC提供硬件复位ipg_reset_b和软件复位sw_rst两种复位方式。
调试状态: 一旦该状态被使能,所有的TSC输出信号将会被软件控制。软件还好可以通过调试接口获取所有TSC的输入信号。此外,还可以通过 获取debug寄存器对应位的值,获取TSC当前所处在的工作状态。
触摸屏控制器相关寄存器介绍:
这里只做简单介绍,详细内容请查阅《IMX6ULLRM(6ULL用户手册).pdf》的ADC和TSC章节内容
1、TSC_BASIC_SETTING 寄存器
该寄存器我们需用配置三个地方:
MEASURE_DELAY_TIME: 由于电阻屏检测的是触点的电压信号,TSC检测到触点按下后,ADC需要等待触点的电压稳定后再进行坐标 测量,这个等待的时间就是测量延迟时间。
4_5_WIRE:imx6ull触摸屏控制器支持4线模式和5线模式,我们使用的是4线的电阻屏,需要把它设置为4线模式;
AUTO_MEASURE:设置TSC检测到触点按下之后,是否自动启动坐标测量,这里我们选择自动测量。
2、TSC_PS_INPUT_BUFFER_ADDR 寄存器
该寄存器用于设置预充电时间,即在TSC控制器进入检测状态之前,会对电阻屏其中一层进行预充电(即设置为正电压),只有达到预充电时间要求之后, 才可以进行到下一个检测状态。
3、TSC_INT_EN 寄存器
这个是中断使能寄存器,通过该寄存器可以设置空闲中断、触摸检测中断、坐标测量完成中断。在本实验中,只需使能坐标测量完成中断,坐标测量完成后 产生中断,我们可以在中断处理函数中读取测量到的坐标值。
4、TSC_INT_SIG_EN 寄存器
中断信号使能寄存器,前面我们使能了坐标测量中断,那么对应的这里需要设置MEASURE_SIG_EN使能测量信号。 此外,还需要设置VALID_SIG_EN使能使能数据有效性判断。判断坐标有效性的机制:在坐标测量完成之后, 再次检测是否有触点按下,如果有,则测量到的坐标是有效的;否则,测量到的坐标无效。
5、TSC_FLOW_CONTROL 寄存器
设置好前面的寄存器之后,我们需要把TSC_FLOW_CONTROL的DISABLE位清零退出空闲状态,并设置START_SENSE位 开启触摸检测。
5、TSC_MEASEURE_VALUE 寄存器
当坐标测量完整后,坐标值最终存储TSC_MEASEURE_VALUE寄存器,其中bit[27~16]存储的是x轴作坐标值, bit[11~0]存储的是y轴坐标值。在中断处理函数里读取该寄存器即可以获取触点的xy轴坐标。
为了节省文章篇幅,寄存器介绍就到此为止,除了配置TSC部分,ADC也需要对其进行配置。其中涉及的配置 内容包括:ADC的时钟源选择、分辨率设置、采样模式设置、ADC输入通道选择、是否使用硬件平均、启动ADC校准等, 涉及到的寄存器有:ADCx_CFG、ADCx_HC0~ADCx_HC5、ADCx_GC、ADCx_GS。详细内容请阅读《IMX6ULLRM(6ULL用户手册).pdf》 的“Chapter 13 Analog-to-Digital Converter (ADC)”章节。
9.2. 电阻触摸屏实验¶
本章配套源码以及设备树位于“~/linux_driver/touch_screen_resisitive”目录下。
9.2.1. 硬件介绍¶
imx6ull的触摸屏控制器分4线模式和5线模式,我们使用的电阻触摸屏是4线的,在4线模式下, 触摸屏功能接口与GPIO对应的关系如下:
TSC function ports |
GPIO ports |
---|---|
ynlr |
GPIO1_IO01 |
ypll |
GPIO1_IO02 |
xnur |
GPIO1_IO03 |
xpul |
GPIO1_IO04 |
在本实验中,电阻屏接口Y-、Y+、X-、X+分别接到开发板的GPIO1_IO01、GPIO1_IO02、GPIO1_IO03、GPIO1_IO04。
9.2.2. 设备树插件实现¶
根据电阻触摸屏功能接口所用到的IO,对应的设备树插件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include "../imx6ul-pinfunc.h"
#include "../imx6ull-pinfunc.h"
#include "../imx6ull-pinfunc-snvs.h"
#include "dt-bindings/interrupt-controller/irq.h"
#include "dt-bindings/gpio/gpio.h"
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&iomuxc>;
__overlay__ {
pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xB0
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xB0
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xB0
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xB0
>;
};
};
};
fragment@1 {
target=<&tsc>;
__overlay__ {
compatible = "fire,res_tsc";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
measure-delay-time = <0xfffff>;
pre-charge-time = <0xffff>;
touchscreen-average-samples = <32>;
status = "okay";
};
};
};
|
第10~22行:向pinctrl子系统节点追加电阻触摸屏使用到的引脚。
第30行:向gpio子系统添加GPIO1_IO03引脚,该引脚可以用于辅助判断触摸屏是否有触点按下。在imx6ull触摸屏处于检测状态时, 当有触点按下时,该引脚的电平为低电平;当触点离开触摸屏后,该引脚恢复为高电平。
第31行:设置测量延迟时间。这个时间就是ADC在测量坐标之前需要等待触点电压的稳定的时间。
第32行:设置电阻屏的预充电时间。
第33行:设置平均采样点。
注: 若需使用内核自带的电阻触摸屏驱动,向自行编译linux_driver/touch_screen_resisitive目录下的 imx-fire-touch-resisitive-4wires-overlay.dts设备树插件,具体使用方法请参考: input子系统:电阻触摸屏 章节。
9.2.3. 驱动程序实现¶
9.2.3.1. 驱动入口和出口函数实现¶
本实验的驱动程序代码是基于平台设备驱动编写的,驱动入口和出口函数仅用于平台驱动的注册和注销,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | static struct of_device_id res_ts_of_match[] = {
{.compatible = "fire,res_tsc",},
{},
};
static struct platform_driver res_ts_drv = {
.probe = res_ts_driver_probe,
.remove = res_ts_driver_remove,
.driver = {
.name = "res_ts_drv",
.of_match_table = res_ts_of_match,
},
};
static int __init res_ts_driver_init(void)
{
return platform_driver_register(&res_ts_drv);
}
static void __exit res_ts_driver_exit(void)
{
platform_driver_unregister(&res_ts_drv);
}
module_init(res_ts_driver_init);
module_exit(res_ts_driver_exit);
MODULE_LICENSE("GPL");
|
第1~4行:定义电阻触摸屏的设备树匹配表。
第6~13行:定义电阻触摸屏的平台驱动结构体。
第7~8行:在驱动加载注册平台驱动时会与设备树进行匹配,若匹配成功则会执行.probe函数; 在驱动卸载注销平台驱动时.remove函数会被执行; 我们可以在.probe函数实现一些初始化的工作, 在.remove函数实现一些清理工作。
第11行:.of_match_table 用于和设备树节点匹配。
第16~19行:在驱动程序的入口函数注册平台驱动。
第21~24行:在驱动程序的出口函数注销平台驱动。
9.2.3.2. .prob函数实现¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | static int res_ts_driver_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct imx6ull_rests *ts;
struct input_dev *input_dev;
int err;
int tsc_irq;
ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL);
if (!ts)
return -ENOMEM;
input_dev = devm_input_allocate_device(&pdev->dev);
if (!input_dev)
return -ENOMEM;
input_dev->name = "Fire Touchscreen Driver";
input_dev->open = imx6ull_tsc_open;
input_dev->close = imx6ull_tsc_close;
input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, 0xFFF, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 0xFFF, 0, 0);
input_set_drvdata(input_dev, ts);
ts->dev = &pdev->dev;
ts->input_dev = input_dev;
ts->tsc_regs = devm_of_iomap(&pdev->dev, np, 0, NULL);
if (IS_ERR(ts->tsc_regs)) {
err = PTR_ERR(ts->tsc_regs);
dev_err(&pdev->dev, "failed to remap tsc memory: %d\n", err);
return err;
}
ts->adc_regs = devm_of_iomap(&pdev->dev, np, 1, NULL);
if (IS_ERR(ts->adc_regs)) {
err = PTR_ERR(ts->adc_regs);
dev_err(&pdev->dev, "failed to remap adc memory: %d\n", err);
return err;
}
ts->tsc_clk = devm_clk_get(&pdev->dev, "tsc");
if (IS_ERR(ts->tsc_clk)) {
err = PTR_ERR(ts->tsc_clk);
dev_err(&pdev->dev, "failed getting tsc clock: %d\n", err);
return err;
}
ts->adc_clk = devm_clk_get(&pdev->dev, "adc");
if (IS_ERR(ts->adc_clk)) {
err = PTR_ERR(ts->adc_clk);
dev_err(&pdev->dev, "failed getting adc clock: %d\n", err);
return err;
}
ts->xnur_gpio = devm_gpiod_get(&pdev->dev, "xnur", GPIOD_IN);
if (IS_ERR(ts->xnur_gpio)) {
err = PTR_ERR(ts->xnur_gpio);
dev_err(&pdev->dev,
"failed to request GPIO tsc_X- (xnur): %d\n", err);
return err;
}
tsc_irq = platform_get_irq(pdev, 0);
if (tsc_irq < 0) {
dev_err(&pdev->dev, "no tsc irq resource?\n");
return tsc_irq;
}
err = devm_request_threaded_irq(ts->dev, tsc_irq,NULL, tsc_irq_fn, IRQF_ONESHOT, dev_name(&pdev->dev), ts);
if (err) {
dev_err(&pdev->dev,
"failed requesting tsc irq %d: %d\n",
tsc_irq, err);
return err;
}
get_tsc_para_from_dt(&pdev->dev, ts);
err = input_register_device(ts->input_dev);
if (err) {
dev_err(&pdev->dev,
"failed to register input device: %d\n", err);
return err;
}
printk(KERN_EMERG"match success!\n");
return 0;
}
|
第9行:分配一个imx6ull_rests结构体,该结构体存放的是电阻屏驱动的一些私有数据,该结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
struct imx6ull_rests { struct device *dev; /* 触摸屏驱动对应的设备 */ struct input_dev *input_dev; /* 输入设备 */ struct imx6ull_tsc *tsc_regs; /* imx6ull tsc控制器的寄存器 */ struct imx6ull_adc *adc_regs; /* imx6ull ADC的寄存器 */ struct clk *tsc_clk; /* tsc控制器时钟 */ struct clk *adc_clk; /* ADC时钟 */ struct gpio_desc *xnur_gpio; /* 辅助检测是否有触摸的gpio */ unsigned int measure_delay_time; /* 测量延迟时间 */ unsigned int pre_charge_time; /* 电阻屏预充电时间 */ bool average_enable; /* 是否使能ADC硬件平均功能 */ unsigned int average_select; /* ADC的平均采样点 */ };
第14行:分配一个input_dev结构体。
第17行:设置输入设备的名称。
第19~20行:分别设置input_dev结构体的open、close函数,在open函数中实现触摸屏控制器和ADC的时钟使能、寄存器初始化; 在close函数关闭时钟、关闭触摸屏控制器、关闭ADC。
第22行:设置支持触摸事件
第23~24行:设置x轴、y轴的绝对位移事件,以及位移的范围(位移范围:0x00~0xFFF)。
第26行:把ts设置为输入设备设备的私有数据,以便在open/close时获取输入设备的私有数据。
第31行:通过设备节点的reg属性,进行tsc触摸屏控制器寄存器地址的映射,得到该寄存对应的虚拟地址。
第38行:通过设备节点的reg属性,进行ADC寄存器地址的映射,得到该寄存对应的虚拟地址。
第45行:获取设备节点tsc触摸屏控制器的时钟。
第52行:获取设备节点adc的时钟。
第59行:获取设备节点名为“xnur”的gpio。
第67行:从平台资源中获取tsc的中断号。
第73行:申请tsc中断,注册tsc_irq_fn中断处理函数。
第81行:从设备树节点获取电阻屏相关的设置参数,并填充ts结构体。
第83行:注册输入设备。
9.2.3.3. .open函数实现¶
在.probe函数中,我们并没有对ADC、TSC进行相应的初始化,只填充了imx6ull_rests结构体、申请注册中断处理函数、 注册一个输入设备。ADC、TSC硬件硬件相关的初始化操作放在.open函数,只用当我们使用到触摸屏时才对其进行初始化。 .open函数的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | static int imx6ull_tsc_open(struct input_dev *input_dev)
{
struct imx6ull_rests *tsc = input_get_drvdata(input_dev);
int err;
err = clk_prepare_enable(tsc->adc_clk);
if (err) {
dev_err(tsc->dev,
"Could not prepare or enable the adc clock: %d\n",
err);
return err;
}
err = clk_prepare_enable(tsc->tsc_clk);
if (err) {
dev_err(tsc->dev,
"Could not prepare or enable the tsc clock: %d\n",
err);
goto disable_adc_clk;
}
err = imx6ull_tsc_init(tsc);
if (err)
goto disable_tsc_clk;
return 0;
disable_tsc_clk:
clk_disable_unprepare(tsc->tsc_clk);
disable_adc_clk:
clk_disable_unprepare(tsc->adc_clk);
return err;
}
|
第3行:从输入设备中获取私有数据imx6ull_rests结构体。
第6、14行:分别使能adc时钟、tsc时钟。
第22行:对ADC、TSC进行硬件初始化工作。
9.2.3.4. .close函数实现¶
在我们不使用触摸屏时,我们应该关闭它,以设备降低功耗。.close函数主要关闭ADC、TSC及其对应的时钟,代码如下:
1 2 3 4 5 6 7 8 9 | static void imx6ull_tsc_close(struct input_dev *input_dev)
{
struct imx6ull_rests *tsc = input_get_drvdata(input_dev);
imx6ull_tsc_disable(tsc);
clk_disable_unprepare(tsc->tsc_clk);
clk_disable_unprepare(tsc->adc_clk);
}
|
9.2.3.5. 触摸屏控制器相关的硬件操作¶
触摸屏控制器控制器的初始化主要包含两个部分:ADC初始化、TSC初始化。
9.2.3.5.1. ADC 初始化¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | static int imx6ull_adc_init(struct imx6ull_rests *ts)
{
struct imx6ull_adc *adc_regs = ts->adc_regs;
adc_regs->CFG &= ~(0xf << 0);
adc_regs->CFG |= (0x2 << 2); /* 设置ADC的分辨率为12bit */
adc_regs->CFG |= (0 << 0); /* 选择ADC的时钟源,这里选择IPG clock */
adc_regs->CFG |= (0x3 << 5); /* 对ADC的时钟源进行8分频 */
adc_regs->CFG &= ~(0x1 << 4); /* 设置为短采样模式 */
if (ts->average_enable)
{
adc_regs->CFG &= ~(0x3 << 14);
adc_regs->CFG |= (ts->average_select << 14); /* 设置平均采样点个数 */
}
if (ts->average_enable)
{
adc_regs->GC |= (0x1 << 5); /* 使能ADC硬件平均功能 */
}
adc_regs->HC[1] &= ~(0x1 <<7); /* 禁止channel3转换完成中断 */
adc_regs->HC[1] &= ~0x1F;
adc_regs->HC[1] |= 0x03; /* 配置channel3为xnur */
adc_regs->HC[3] &= ~(0x1 <<7); /* 禁止channel1转换完成中断 */
adc_regs->HC[3] &= ~0x1F;
adc_regs->HC[3] |= 0x01; /* 配置channel1为ynlr */
if(!imx6ull_adc_auto_calibration(ts))
{
dev_err(ts->dev, "ADC calibration failed\n");
return -1;
}
return 0;
}
|
第5~9行:配置ADC的分辨率、ADC时钟源、时钟分频系数,以及采样模式。
第11~15行: 如果使用ADC的硬件平均功能,则设置ADC平均采样点个数。
第19行: 打开ADC硬件平均功能。
第23~29行:配置ADC采集触摸屏坐标的输入通道,在4线模式中,测量X坐标时,ADC的channel1连接到ynlr测量 x轴坐标电压;测量y坐标时,ADC的channel3连接到xnur测量y轴坐标电压。
第31行: 启动ADC自动校准,imx6ull_adc_auto_calibration()函数的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
static bool imx6ull_adc_auto_calibration(struct imx6ull_rests *ts) { struct imx6ull_adc *adc_regs = ts->adc_regs; unsigned int value; adc_regs->CFG &= ~(0x1 << 13); /* 选择软件触发 */ adc_regs->GS |= (0x1 << 1); /* 清除校准完成标记 */ adc_regs->GC |= (0x1 << 7); /* 启动ADC校准 */ mdelay(100); /* 等待校准完成 */ if (adc_regs->GC & (0x1 << 7)) /* 判断校准是否完成, 校准完成该寄存器的bit[7] 会自动清0*/ return false; if (adc_regs->GS & (0x1 << 1)) /* 判断,校准失败该寄存器的bit[1] 被置1 */ return false; if ((adc_regs->HS & 0x01) == 0) /* 判断是否转换完成 */ return false; value = adc_regs->R[0]; /* 通过读adc_regs->R[0]清除adc_regs->HS转换完成标记 */ adc_regs->CFG |= (0x1 << 13); /* 选择硬件触发 */ return true; }
上面的代码,需要注意的是在启动ADC校准之前,ADC的触发方式必须设置为软件触发,否则无法启动ADC校准。校准完成之后,我们 需要把ADC的触发方式设置为硬件触发,这样TSC控制器可以通过给ADC的触发器发信号控制ADC采集坐标值。
9.2.3.5.2. TSC 初始化¶
TSC初始化配置主要包含以下几个方面:
测量延迟时间设置、根据使用的电阻屏配置为4线或者5线检测模式、使能自动测量;
设置电阻屏的预充电时间;
中断和中断信号使能;
启动触摸检测。
具体的初始化代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | static int imx6ull_tsc_config(struct imx6ull_rests *ts)
{
struct imx6ull_tsc *tsc_regs = ts->tsc_regs;
tsc_regs->BASIC_SETTING |= ts->measure_delay_time << 8; /* 设置测量延迟时间 */
tsc_regs->BASIC_SETTING &= ~(0x1 << 4); /* 设置4线检测模式 */
tsc_regs->BASIC_SETTING |= (0x1 << 0); /* 使能自动测量 */
tsc_regs->PS_INPUT_BUFFER_ADDR = ts->pre_charge_time; /* 设置预充电时间 */
tsc_regs->INT_EN = (0x1 << 0); /* 测量中断使能 */
tsc_regs->INT_SIG_EN |= (0x1 << 0); /* 测量信号使能 */
tsc_regs->INT_SIG_EN |= (0x1 << 8); /* 有效信号使能 */
tsc_regs->FLOW_CONTROL |= (0x1 << 12); /* 启动触摸检测, 当检测到触摸时,开始测量 */
tsc_regs->FLOW_CONTROL &= ~(0x1 << 16); /* 使能TSC */
return 0;
}
|
9.2.3.6. 中断处理函数¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | static irqreturn_t tsc_irq_fn(int irq, void *dev_id)
{
struct imx6ull_rests *ts = dev_id;
unsigned int status;
unsigned int value;
unsigned int x;
unsigned int y;
/* 读取状态寄存器 */
status = ts->tsc_regs->INT_STATUS;
/* 清除坐标测量中断信号标记 */
ts->tsc_regs->INT_STATUS = 0x01;
/* 启动触摸检测 */
ts->tsc_regs->FLOW_CONTROL |= (1 << 12);
if (status & 0x1)
{
value = ts->tsc_regs->MEASEURE_VALUE;
x = (value >> 16) & 0x0fff;
y = value & 0x0fff;
if (!tsc_wait_detect_mode(ts) || gpiod_get_value_cansleep(ts->xnur_gpio))
{
input_report_key(ts->input_dev, BTN_TOUCH, 1); /* 按下 */
input_report_abs(ts->input_dev, ABS_X, x);
input_report_abs(ts->input_dev, ABS_Y, y);
}
else
{
input_report_key(ts->input_dev, BTN_TOUCH, 0); /* 松开 */
}
input_sync(ts->input_dev);
}
return IRQ_HANDLED;
}
|
第10、13行:当坐标测量完成后再次检测到有触摸,此时会产生一个坐标测量完中断,并把tsc_regs->INT_STATUS寄存器的bit[0] 坐标测量中断状态标记置1,在中断处理函数中需要向该位写1清0。
第16行:重新启动触摸检测。
第18行:判断该中断是否是坐标测量中断,如果是,接下来就读取测量的坐标值。
第24行:在触摸检测状态下,读取“xnur”对应gpio的逻辑值,当读取到的逻辑值为1时,说明有触摸。 (注: 在设备树中,gpio的有效值为GPIO_ACTIVE_LOW时,读到的逻辑值与实际的物理电平相反)
第26~28行:向输入子系统上报触摸按下、x轴坐绝对位移、y轴坐标绝对位移事件。
第32行:向输入子系统上报触摸松开事件。
第35行:向输入子系统上报同步事件。
9.2.4. 实验准备¶
在板卡上的部分GPIO可能会被系统占用,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可注释掉某些设备树插件的加载,重启系统,释放相应的GPIO引脚。
如本节实验中,可能在鲁班猫系统中默认使能了 ADC1
LED
电容屏
的设备功能,GPIO1_IO03、GPIO1_IO03引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。
方法参考如下:
取消 ADC1
LED
电容屏
设备树插件,以释放系统对应系统资源,操作如下:
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-ts-res-4wires.dtbo
如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象, 请按上述情况检查并按上述步骤操作。
如出现 Permission denied
或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
9.2.4.1. 编译设备树插件¶
将 linux_driver/touch_screen_resisitive/imx-fire-ts-res-4wires-overlay.dts
拷贝到 内核源码/arch/arm/boot/dts/overlays
目录下,
并修改同级目录下的Makefile,追加 imx-fire-ts-res-4wires.dtbo
编译选项。然后执行如下命令编译设备树插件:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfig
make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
编译成功后生成同名的设备树插件文件(imx-fire-ts-res-4wires.dtbo)位于 内核源码/arch/arm/boot/dts/overlays
目录下。
9.2.4.2. 编译驱动程序¶
将 /linux_driver/touch_screen_resisitive 拷贝到内核源码同级目录,执行里面的MakeFile,生成resisitive_touchscreen.ko。
9.2.5. 驱动测试¶
9.2.5.1. 加载设备树插件和驱动文件¶
将设备树插件拷贝到开发板 /usr/lib/linux-image-4.19.35-imx6/overlays/
目录下,并且在/boot/uEnv.txt中添加
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-ts-res-4wires.dtbo
,然后 sudo reboot
重启开发板。
加载驱动程序 insmod resisitive_touchscreen.ko
,驱动程序加载成功打印match successed。
9.2.5.2. 测试¶
9.2.5.2.1. 使用evtest测试屏幕¶
通过以下命令下载evtest工具
1 | sudo apt install evtest
|
在终端上执行evtest命令并选择触摸屏进行测试,请根据具体输入设备选择正确设备。测试结果如下:
使用evtest获取得到的是原始的数值,需要用户自行进行坐标转换。
9.2.5.2.2. 使用libts校准电阻触摸屏¶
与电容屏不同,电阻触摸屏获取得到的是原始数据并不是坐标值,想要获取相关的坐标点需要对原始数据进行换算, libts是一种实用的触摸工具,能够用于触摸屏的校准,将触摸位置与屏幕显示位置统一起来。
本小节实验也需要开启显示屏相关设备树插件,在 /boot/uEnv.txt
中,打开lcd的设备树插件,如下图所示:
通过以下命令下载libts-bin工具
1 | sudo apt install libts-bin
|
使用以下命令进行屏幕校准,执行以下两条命令之后,显示屏将会显示需要点击的位置,依次点击完成后完成屏幕。
设置环境变量,并将/dev/input/event2替换为自己的触摸屏输入设备:
1 | export TSLIB_TSDEVICE=/dev/input/event2
|
进行屏幕校准
1 | ts_calibrate
|
终端的打印信息如下:
执行ts_print命令后触摸电阻屏将会打印出相对应的坐标,如下所示:
关于libts工具使用的详细说明可参考以下链接: https://github.com/libts/tslib