65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 TFT 显示屏进行滑动姿势

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2020 年 12 月 4 日

MIT

5分钟阅读

viewsIcon

8556

downloadIcon

158

使用TFT触摸屏和兼容Arduino的设备实现向左/右/上/下滑动

引言

与简单的触摸输入相比,手势可以提供更复杂和更具表现力的输入。通过手势,用户可以通过以特定方式“绘制”在屏幕上来指示各种操作。在许多小型物联网设备上,触摸显示屏可能是与之直接通信的唯一方式,此时手势变得极为有用。

不幸的是,关于为这些小型设备实现手势的信息实在太少了。

在本文中,我旨在向您展示如何使用连接到兼容Arduino设备的TFT触摸显示屏来实现简单的滑动手势。

必备组件

  • 一个兼容Arduino的设备。我在本次演示中使用的是ESP32,但通过一些接线修改,也可以使用其他设备。
  • 一个TFT显示设备。代码是为RA8875编写的,但通过最小的代码修改,也可以用于ILI9341等其他显示屏。
  • Arduino IDE和相应的开发板管理器。
  • 通常的跳线和原型板。

概念化这个混乱的局面

在编码方面,我们需要解决许多问题。读取触摸事件对于这些设备来说可能有点棘手——哦,没有包罗万象的框架来处理细节的编码是多么令人欣喜!我们还需要具备各种功能的计时能力。此外,我们还需要实际实现手势计算本身。

代码计时

让我们从一个简单的模式开始——无delay()的定时器。delay()loop()中使用可能会有问题,因为它会阻塞,导致在等待时其他活动会停止。有时,我们需要以固定的间隔运行某些内容,同时允许loop()的其余部分继续运行。我们可以通过一个全局变量来存储时间戳,并结合millis()函数来实现这一点。

#define INTERVAL 100 // <sup>1</sup>/<sub>10</sub> of a second
// set to 0 if you want it to fire 
// immediately the first time
uint32_t _timeoutTS = millis(); 
...
void loop() {
    if(millis()-_timeoutTS>INTERVAL) {
        _timeoutTS = millis();
        // do work
        ...
    }
    ...
}

我们的做法是使“执行工作”部分的代码每秒只运行10次,而不是在每次调用loop()时都运行。这比使用delay(100);要好,因为后者会减慢整个loop()例程的速度,而不仅仅是代码的这一部分。我们稍后将使用这种技术,所以请记住它,因为这就是我们解决计时问题的方法。

读取触摸事件

请注意,这可能因设备而异。请查阅您库的示例代码,了解如何读取触摸事件。一个或多或少普遍存在的问题是,设备无法像loop()调用那样快速地处理触摸事件。SPI总线速度不如我们的CPU。为了应对这种情况,我们只在固定的时间间隔内处理触摸事件,方法如前所述。不过,我们将提供一个隐藏细节的例程。它被称为tryGetTouchEvent(),它将填充一个tsPoint_t结构体,并在发生触摸事件时返回true。您的设备如何做到这一点是您自己的事情,但我将为您展示RA8875的代码。

请注意,大多数此类屏幕都需要校准。它们从触摸事件返回的点与屏幕上的点不匹配,必须进行偏移。幸运的是,我们不需要精确的物理坐标。我们只需要知道坐标之间的相对关系,而不是设备的物理坐标。我们在这里不讨论校准。

计算手势

对于任何手势计算,我们需要知道设备何时被触摸以及何时释放触摸。为此,我们在设备可接受的间隔(这里是每秒1/10)轮询触摸事件。我们跟踪设备是否被触摸以及当前是否被触摸。这一点很重要。我们需要这些边缘条件来确定何时有人将手指放在屏幕上,然后一直保持直到他们释放。基本上,我们这样做:

...
_touched = tryGetTouchEvent(&pt);
if(_touched!=_touchedOld) { 
    if(_touched) {
        // touched event handling code
    } else {
        // released event handling code
    }
}
_touchedOld=_touched;

这应该相当直接。我们只是检查触摸状态是否发生变化,如果发生变化,我们就确定它是触摸还是释放。

对于复杂的手势,我们需要一个数组来存储触摸显示屏时“绘制”的点,并在触摸释放时处理它们。但是,由于我们只做基本的滑动,我们可以大大简化代码。

我们只需要跟踪用户第一次触摸显示屏的点以及他们释放的点。然后,我们比较x和y值的差异。较大的差值决定了我们是垂直滑动(y差值最大)还是水平滑动(x差值最大)。

我们还需要确保他们实际滑动的距离足够长才能被识别,但这很简单。我们只需要确保x或y坐标的差值足够长。我们垂直和水平方向的阈值不同,因为屏幕不是方形的。

当我们进入代码部分时,我们将看到这一切是如何实现的。

构建这个大杂烩

连接这个“大杂烩”

请注意,这取决于硬件。思路是将显示屏连接到设备的**主SPI总线**,然后连接任何额外的引脚,如RST。在带有RA8875的ESP32上,接线如下:

RA8875 VIN ➜ ESP32 VIN (+5vdc)
RA8875 GND ➜ ESP32 GND
RA8875 3Vo ➜ N/C
RA8875 LITE ➜ N/C
RA8875 SCK ➜ ESP32 GPIO18
RA8875 MISO ➜ ESP32 GPIO19
RA8875 MOSI ➜ ESP32 GPIO23
RA8875 CS ➜ ESP32 GPIO5
RA8875 RST ➜ ESP32 GPIO15
RA8875 WAIT ➜ N/C
RA8875 INT ➜ N/C
RA8875 Y+ ➜ N/C
RA8875 Y- ➜ N/C
RA8875 X- ➜ N/C
RA8875 X+ ➜ N/C

编写这个混乱的程序

终于到了代码部分。由于我们已经探讨了这些技术,我们将通过查看下面的整个tft_swipe.ino文件来了解它们是如何在实践中应用的。

// touch configuration
#define TOUCH_INTERVAL 100
#define TOUCH_THRESHOLD_X 200
#define TOUCH_THRESHOLD_Y 120

// time before text indicator disappears
#define TEXT_TIMEOUT 2000

// hardware configuration
#define RA8875_CS 5
#define RA8875_RST 15

#include <math.h>
#include <Adafruit_RA8875.h>
#include <Adafruit_GFX.h>

void drawCentered(char* sz);
bool tryGetTouchEvent(tsPoint_t * point);

Adafruit_RA8875 tft = Adafruit_RA8875(RA8875_CS, RA8875_RST);

// for handling touched/release events
bool _touchedOld = false;
bool _touched = _touchedOld;
uint32_t _touchTS = 0;

// first and last points touched
tsPoint_t _touchFirst;
tsPoint_t _touchLast;

// the timestamp for making text go away
uint32_t _textTimeoutTS = 0;

void setup() {
  Serial.begin(115200);

  // init the display
  if (!tft.begin(RA8875_800x480)) {
    Serial.println(F("RA8875 Not Found!"));
    while (1);
  }
  tft.displayOn(true);
  tft.GPIOX(true);      // Enable TFT - display enable tied to GPIOX
  tft.PWM1config(true, RA8875_PWM_CLK_DIV1024); // PWM output for backlight
  tft.PWM1out(255);
  tft.touchEnable(true);
  tft.fillScreen(RA8875_WHITE);
}

void loop() {
  
  if (millis() - _touchTS > TOUCH_INTERVAL) {
    _touchTS = millis();
    // process our touch events
    tsPoint_t pt;
    _touched = tryGetTouchEvent(&pt);
    if(_touched)
      _touchLast = pt;
    if (_touched != _touchedOld) {
      if (_touched) {
        _touchFirst = pt;
      } else {
        pt = _touchLast;
        // compute differences
        int32_t dx = pt.x-_touchFirst.x;
        int32_t dy = pt.y-_touchFirst.y;
        uint32_t adx=abs(dx);
        uint32_t ady=abs(dy);
        // swipe horizontal
        if(adx>ady && adx> TOUCH_THRESHOLD_X) { 
          if(0>dx) { // swipe right to left
            drawCentered("Right to left");
            _textTimeoutTS=millis();
          } else { // swipe left to right
            drawCentered("Left to right");
            _textTimeoutTS=millis();
          }
        // swipe vertical
        } else if(ady>adx && ady>TOUCH_THRESHOLD_Y) { 
          if(0>dy) { // swipe bottom to top
            drawCentered("Bottom to top");
            _textTimeoutTS=millis();
          } else { // swipe top to bottom
            drawCentered("Top to bottom");
            _textTimeoutTS=millis();
          }
        }
      }
    }
    _touchedOld = _touched;
  }
  // make the text we displayed disappear, if necessary
  if (_textTimeoutTS && millis() - _textTimeoutTS > TEXT_TIMEOUT) {
    _textTimeoutTS = 0;
    tft.fillScreen(RA8875_WHITE);
  }
}
// read the TFT touch
bool tryGetTouchEvent(tsPoint_t * point) {
  uint16_t x, y;
  tft.touchRead(&x, &y);
  delay(1);

  if (tft.touched()) {
    tft.touchRead(&x, &y);
    point->x = x;
    point->y = y;
    return true;
  }
  return false;
}
// draws text centered on the display
// BUG: getTextBounds() doesn't seem
// to work right for computing width
void drawCentered(const char *sz) {
  int16_t x, y;
  uint16_t w, h;
  tft.textMode();
  tft.setTextWrap(false);
  tft.setTextSize(1);
  tft.textEnlarge(1);
  tft.getTextBounds(sz, 0, 0, &x, &y, &w, &h); 
  tft.textTransparent(RA8875_BLACK);
  tft.textSetCursor((tft.width() - w) / 2, (tft.height() - h) / 2);
  tft.textWrite(sz);
}

考虑到我们之前讨论的内容,其中大部分应该相当清楚。我们运行两个“定时器”,一个用于触摸事件,一个用于清除文本。我们在触摸释放时通过计算差值来处理触摸事件,如前所述。当我们滑动时,我们将滑动命令写入显示屏,然后启动“定时器”来清除文本。

Bug

文本未正确居中。我可能错误地使用了getTextBounds(),或者它在我的设置中实际上不起作用。

历史

  • 2020年12月4日 - 首次提交
© . All rights reserved.