首页 >> 知识 >> 9. 输入子系统–电阻触摸驱动实验

9. 输入子系统–电阻触摸驱动实验

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 寄存器

该寄存器草莓视频在线观看APP需用配置三个地方:

MEASURE_DELAY_TIME: 由于电阻屏检测的是触点的电压信号,TSC检测到触点按下后,ADC需要等待触点的电压稳定后再进行坐标测量,这个等待的时间就是测量延迟时间。

4_5_WIRE:imx6ull触摸屏控制器支持4线模式和5线模式,草莓视频在线观看APP使用的是4线的电阻屏,需要把它设置为4线模式;

AUTO_MEASURE:设置TSC检测到触点按下之后,是否自动启动坐标测量,这里草莓视频在线观看APP选择自动测量。

2、TSC_PS_INPUT_BUFFER_ADDR 寄存器

该寄存器用于设置预充电时间,即在TSC控制器进入检测状态之前,会对电阻屏其中一层进行预充电(即设置为正电压),只有达到预充电时间要求之后,才可以进行到下一个检测状态。

3、TSC_INT_EN 寄存器

这个是中断使能寄存器,通过该寄存器可以设置空闲中断、触摸检测中断、坐标测量完成中断。在本实验中,只需使能坐标测量完成中断,坐标测量完成后产生中断,草莓视频在线观看APP可以在中断处理函数中读取测量到的坐标值。

4、TSC_INT_SIG_EN 寄存器

中断信号使能寄存器,前面草莓视频在线观看APP使能了坐标测量中断,那么对应的这里需要设置MEASURE_SIG_EN使能测量信号。此外,还需要设置VALID_SIG_EN使能使能数据有效性判断。判断坐标有效性的机制:在坐标测量完成之后,再次检测是否有触点按下,如果有,则测量到的坐标是有效的;否则,测量到的坐标无效。

5、TSC_FLOW_CONTROL 寄存器

设置好前面的寄存器之后,草莓视频在线观看APP需要把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线模式,草莓视频在线观看APP使用的电阻触摸屏是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,对应的设备树插件如下:

设备树插件 (位于 linux_driver/touch_screen_resisitive/imx-fire-ts-res-4wires-overlay.dts)¶ 1 2 3 4 5 6 7 8 91011121314151617181920232223242526272829303132333435363738#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 = ; __overlay__ { pinctrl_tsc: tscgrp { fsl,pins = ; }; }; }; fragment@1 { target=; __overlay__ { compatible = "fire,res_tsc"; pinctrl-names = "default"; pinctrl-0 = ; xnur-gpio = ; measure-delay-time = ; pre-charge-time = ; touchscreen-average-samples = ; 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. 驱动入口和出口函数实现¶

本实验的驱动程序代码是基于平台设备驱动编写的,驱动入口和出口函数仅用于平台驱动的注册和注销,代码如下:

驱动入口和出口函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)¶ 1 2 3 4 5 6 7 8 910111213141516171819202322232425262728static 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函数会被执行; 草莓视频在线观看APP可以在.probe函数实现一些初始化的工作,在.remove函数实现一些清理工作。

第11行:.of_match_table 用于和设备树节点匹配。

第16~19行:在驱动程序的入口函数注册平台驱动。

第21~24行:在驱动程序的出口函数注销平台驱动。

9.2.3.2. .prob函数实现¶.prob函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)¶ 1 2 3 4 5 6 7 8 91011121314151617181920232223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192static 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 ", 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 ", 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 ", 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 ", 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 ", err); return err; } tsc_irq = platform_get_irq(pdev, 0); if (tsc_irq dev, "no tsc irq resource? "); 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 ", 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 ", err); return err; } printk(KERN_EMERG"match success! "); return 0;}

第9行:分配一个imx6ull_rests结构体,该结构体存放的是电阻屏驱动的一些私有数据,该结构体定义如下:

1 2 3 4 5 6 7 8 91011121314struct 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函数中,草莓视频在线观看APP并没有对ADC、TSC进行相应的初始化,只填充了imx6ull_rests结构体、申请注册中断处理函数、注册一个输入设备。ADC、TSC硬件硬件相关的初始化操作放在.open函数,只用当草莓视频在线观看APP使用到触摸屏时才对其进行初始化。.open函数的代码如下:

.open函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)¶ 1 2 3 4 5 6 7 8 9101112131415161718192023222324252627282930313233static 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 ", 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 ", 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函数实现¶

在草莓视频在线观看APP不使用触摸屏时,草莓视频在线观看APP应该关闭它,以设备降低功耗。.close函数主要关闭ADC、TSC及其对应的时钟,代码如下:

.close函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)¶123456789static 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 初始化¶ADC 初始化函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)¶ 1 2 3 4 5 6 7 8 91011121314151617181920232223242526272829303132333435363738static int imx6ull_adc_init(struct imx6ull_rests *ts){ struct imx6ull_adc *adc_regs = ts->adc_regs; adc_regs->CFG &= ~(0xf adc_regs; unsigned int value; adc_regs->CFG &= ~(0x1 measure_delay_time > 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_defconfigmake 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工具

1sudo apt install evtest

在终端上执行evtest命令并选择触摸屏进行测试,请根据具体输入设备选择正确设备。测试结果如下:

使用evtest获取得到的是原始的数值,需要用户自行进行坐标转换。

9.2.5.2.2. 使用libts校准电阻触摸屏¶

与电容屏不同,电阻触摸屏获取得到的是原始数据并不是坐标值,想要获取相关的坐标点需要对原始数据进行换算,libts是一种实用的触摸工具,能够用于触摸屏的校准,将触摸位置与屏幕显示位置统一起来。

本小节实验也需要开启显示屏相关设备树插件,在 /boot/uEnv.txt 中,打开lcd的设备树插件,如下图所示:

通过以下命令下载libts-bin工具

1sudo apt install libts-bin

使用以下命令进行屏幕校准,执行以下两条命令之后,显示屏将会显示需要点击的位置,依次点击完成后完成屏幕。

设置环境变量,并将/dev/input/event2替换为自己的触摸屏输入设备:

1export TSLIB_TSDEVICE=/dev/input/event2

进行屏幕校准

1ts_calibrate

终端的打印信息如下:

执行ts_print命令后触摸电阻屏将会打印出相对应的坐标,如下所示:

关于libts工具使用的详细说明可参考以下

网站地图