Linux驱动开发(17):输入子系统–电阻触摸驱动实验

有关电阻触摸的基础知识内容可以参考野火STM32相关教程,这里只介绍电阻触摸驱动的相关内容。与一般的微处理器 不同,本节使用的imx6ull内自带触摸屏控制器,只需要把电阻触摸屏的信号线接到对应的IO即可,通过配置imx6ull 触摸屏控制器获取触点坐标值。

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)"章节。

2. 电阻触摸屏实验

本章配套源码以及设备树位于"~/linux_driver/touch_screen_resisitive"目录下。

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。

2.2. 设备树插件实现

根据电阻触摸屏功能接口所用到的IO,对应的设备树插件如下:

设备树插件 (位于 linux_driver/touch_screen_resisitive/imx-fire-ts-res-4wires-overlay.dts):

cpp 复制代码
#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子系统:电阻触摸屏 章节。

2.3. 驱动程序实现

2.3.1. 驱动入口和出口函数实现

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

驱动入口和出口函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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行:在驱动程序的出口函数注销平台驱动。

2.3.2. .prob函数实现

.prob函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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结构体,该结构体存放的是电阻屏驱动的一些私有数据,该结构体定义如下:

cpp 复制代码
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的平均采样点 */
};
  • 第13行:分配一个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行:注册输入设备。

2.3.3. .open函数实现

在.probe函数中,我们并没有对ADC、TSC进行相应的初始化,只填充了imx6ull_rests结构体、申请注册中断处理函数、 注册一个输入设备。ADC、TSC硬件硬件相关的初始化操作放在.open函数,只用当我们使用到触摸屏时才对其进行初始化。 .open函数的代码如下:

.open函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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进行硬件初始化工作。

2.3.4. .close函数实现

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

.close函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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);
}
2.3.5. 触摸屏控制器相关的硬件操作

触摸屏控制器控制器的初始化主要包含两个部分:ADC初始化、TSC初始化。

2.3.5.1. ADC 初始化

ADC 初始化函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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()函数的代码如下:

cpp 复制代码
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采集坐标值。

2.3.5.2. TSC 初始化

TSC初始化配置主要包含以下几个方面:

  • 测量延迟时间设置、根据使用的电阻屏配置为4线或者5线检测模式、使能自动测量;

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

  • 中断和中断信号使能;

  • 启动触摸检测。

具体的初始化代码如下:

TSC 初始化函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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;
}
2.3.6. 中断处理函数

中断处理函数 (位于 linux_driver/touch_screen_resisitive/resisitive_touchscreen.c):

cpp 复制代码
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行:向输入子系统上报同步事件。

2.4. 实验准备

在板卡上的部分GPIO可能会被系统占用,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可注释掉某些设备树插件的加载,重启系统,释放相应的GPIO引脚。

如本节实验中,可能在鲁班猫系统中默认使能了 ADC1 LED 电容屏 的设备功能,GPIO1_IO03、GPIO1_IO03引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。

方法参考如下:

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

bash 复制代码
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用户运行程序。

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 编译选项。然后执行如下命令编译设备树插件:

bash 复制代码
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 目录下。

2.4.2. 编译驱动程序

/linux_driver/touch_screen_resisitive 拷贝到内核源码同级目录,执行里面的MakeFile,生成resisitive_touchscreen.ko。

2.5. 驱动测试

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。

2.5.2. 测试
2.5.2.1. 使用evtest测试屏幕

通过以下命令下载evtest工具

bash 复制代码
sudo apt install evtest

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

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

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

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

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

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

cpp 复制代码
sudo apt install libts-bin

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

  • 设置环境变量,并将/dev/input/event2替换为自己的触摸屏输入设备:
cpp 复制代码
export TSLIB_TSDEVICE=/dev/input/event2
  • 进行屏幕校准
cpp 复制代码
ts_calibrate
  • 终端的打印信息如下:

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

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

相关推荐
岁岁种桃花儿4 分钟前
Nginx高并发网站技术的实战篇
运维·nginx
AndyHeee4 分钟前
【瑞芯微rk3576刷ubuntu根文件系统容量不足问题解决】
linux·数据库·ubuntu
liulilittle4 分钟前
Ubuntu挂在新云盘(Disk磁盘)
运维·服务器·ubuntu
李昊哲小课6 分钟前
Ubuntu 24.04 在线安装 Redis 8.x 完整教程
linux·redis·ubuntu
sao.hk6 分钟前
ubuntu2404,vbox,全屏显示
linux·运维·服务器
危笑ioi6 分钟前
linux配置nfs在ubuntu22.04
linux·运维·服务器
社会零时工15 分钟前
【ROS2】海康相机ROS2设备服务节点开发
linux·c++·相机·ros2
东城绝神20 分钟前
《Linux运维总结:Ubuntu 22.04配置chrony时间同步服务》
linux·运维·ubuntu·chrony
刘程佳22 分钟前
Ubuntu 系统没有识别 Pixel 6 的 USB 设备权限
linux·运维·ubuntu
wa的一声哭了30 分钟前
矩阵分析 单元函数矩阵微积分和多元向量值的导数
linux·c语言·c++·线性代数·算法·矩阵·云计算