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三个模块协同工作,形成一个触摸屏系统。

1

上图触摸屏系统有以下几种工作状态:

  • 空闲状态: 当完成坐标测量后,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 寄存器

1

该寄存器我们需用配置三个地方:

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

  • 4_5_WIRE:imx6ull触摸屏控制器支持4线模式和5线模式,我们使用的是4线的电阻屏,需要把它设置为4线模式;

  • AUTO_MEASURE:设置TSC检测到触点按下之后,是否自动启动坐标测量,这里我们选择自动测量。

2、TSC_PS_INPUT_BUFFER_ADDR 寄存器

1

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

3、TSC_INT_EN 寄存器

1

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

4、TSC_INT_SIG_EN 寄存器

1

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

5、TSC_FLOW_CONTROL 寄存器

1

设置好前面的寄存器之后,我们需要把TSC_FLOW_CONTROL的DISABLE位清零退出空闲状态,并设置START_SENSE位 开启触摸检测。

5、TSC_MEASEURE_VALUE 寄存器

1

当坐标测量完整后,坐标值最终存储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,对应的设备树插件如下:

设备树插件 (位于 linux_driver/touch_screen_resisitive/imx-fire-ts-res-4wires-overlay.dts)
 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. 驱动入口和出口函数实现

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

驱动入口和出口函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
 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函数实现

.prob函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
 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函数的代码如下:

.open函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
 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及其对应的时钟,代码如下:

.close函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
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 初始化
ADC 初始化函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
 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线检测模式、使能自动测量;

  • 设置电阻屏的预充电时间;

  • 中断和中断信号使能;

  • 启动触摸检测。

具体的初始化代码如下:

TSC 初始化函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
 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. 中断处理函数

中断处理函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c)
 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引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。

方法参考如下:

broken

取消 ADC1 LED 电容屏 设备树插件,以释放系统对应系统资源,操作如下:

broken
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。

1

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 重启开发板。

1

加载驱动程序 insmod resisitive_touchscreen.ko ,驱动程序加载成功打印match successed。

加载驱动程序

9.2.5.2. 测试

9.2.5.2.1. 使用evtest测试屏幕

通过以下命令下载evtest工具

1
sudo apt install evtest

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

1

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

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

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

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

1

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

1
sudo apt install libts-bin

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

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

1
export TSLIB_TSDEVICE=/dev/input/event2
  • 进行屏幕校准

1
ts_calibrate
  • 终端的打印信息如下:

1

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

1

关于libts工具使用的详细说明可参考以下链接: https://github.com/libts/tslib