一、前言

        在 ESP32 项目中,同时使用 SPI TFT 显示屏 和 SPI 触摸芯片 时,常见做法是共用一条 SPI 总线。但在显示刷新或 DMA 传输较频繁的情况下,容易出现触摸不稳定、数据冲突等问题。

本文以 ESP32-2432S028R 开发板为示例,介绍一种 显示与触摸不共享 SPI 总线 的实现方式:

  • 开发环境:Arduino IDE
  • TFT 显示:使用 TFT_eSPI
  • 触摸控制:使用 XPT2046_Touchscreen

通过将 TFT 与触摸分配到不同的 SPI 控制器,提升系统整体稳定性。

二、SPI 总线配置教程

        首先安装库TFT_eSPI、XPT2046_Touchscreen,然后查看开发板ESP32-2432S028R的引脚资料: 2.8英尺 ESP32-2432S028R - esp3d.io

然后打开库文件夹C:Users......DocumentsArduinolibrariesTFT_eSPI文件夹,打开文件User_Setup.h,添加或修改下面的内容:

#define ILI9341_DRIVER      // 指定屏幕使用 ILI9341 控制芯片(非常常见)
#define TFT_MISO 12         // SPI 主输入从输出引脚(MISO),读取屏幕数据--TFT_SDO
#define TFT_MOSI 13         // SPI 主输出从输入引脚(MOSI),向屏幕写数据--TFT_SDI
#define TFT_SCLK 14         // SPI 时钟线
#define TFT_CS   15         // 屏幕片选引脚(Chip Select)
#define TFT_DC   2          // 数据/命令选择引脚(Data/Command)
#define TFT_RST  -1          // 屏幕复位引脚(Reset) EN/RESET 
#define TOUCH_CS 33         // 触摸屏片选引脚(Chip Select),XPT2046 控制芯片使用 33
#define TOUCH_CLK 25
#define TOUCH_DIN 32
#define TOUCH_OUT 39
#define TOUCH_IRQ 36 //中断引脚
// #define TOUCH_IRQ (gpio_num_t)GPIO_NUM_NC //不启用中断引脚,即采用轮询的方式查询触摸信号
#define TFT_BL   21            // LED back-light control pin
#define TFT_BACKLIGHT_ON HIGH  // Level to turn ON back-light (HIGH or LOW)
#define TFT_BACKLIGHT_OFF LOW  // Level to turn ON back-light (HIGH or LOW)
#define LOAD_GLCD           // 加载基础 8x8 ASCII 字体
#define LOAD_FONT2          // 加载中号字体(常用)
#define LOAD_FONT4          // 加载较大字体
#define LOAD_FONT6          // 加载更大字体(适合标题)
#define SMOOTH_FONT         // 启用平滑字体(抗锯齿效果更好)
#define SPI_FREQUENCY       40000000    // 主屏 SPI 通信频率 40MHz(ILI9341 推荐值)
#define SPI_READ_FREQUENCY  20000000    // 屏幕读取频率(读取像素或ID时)
#define SPI_TOUCH_FREQUENCY 2500000     // 触摸芯片 SPI 频率(一般 2.5MHz)

三、使用示例

        下面我将做一个四个功能按键和翻页的示例,以注释的形式解释代码,代码如下(注释借鉴了AI):

/*******************************************************
 * ESP32 不共享 SPI 总线的 TFT + 触摸 示例
 * 硬件平台:ESP32-2432S028R
 * 显示驱动:TFT_eSPI(VSPI)
 * 触摸驱动:XPT2046_Touchscreen(HSPI)
 * 文件类型:.ino
 *******************************************************/

#include <TFT_eSPI.h>              // TFT 显示库(内部使用 VSPI)
#include <SPI.h>                   // SPI 库
#include <XPT2046_Touchscreen.h>   // XPT2046 触摸库

/* ===================== 触摸屏引脚定义 =====================
 * 这里的 TOUCH_xxx 宏通自于前面的User_Setup.h中定义的
 * XPT2046 本身是 SPI 接口,因此需要 MOSI/MISO/CLK/CS
 */
#define XPT2046_IRQ  TOUCH_IRQ   // 触摸中断引脚(T_IRQ)
#define XPT2046_MOSI TOUCH_DIN   // SPI MOSI(T_DIN)
#define XPT2046_MISO TOUCH_OUT   // SPI MISO(T_OUT)
#define XPT2046_CLK  TOUCH_CLK   // SPI 时钟(T_CLK)
#define XPT2046_CS   TOUCH_CS    // SPI 片选(T_CS)

/* ===================== 屏幕参数 ===================== */
#define SCREEN_WIDTH  320
#define SCREEN_HEIGHT 240
#define FONT_SIZE     2

/* ===================== TFT 对象 =====================
 * TFT_eSPI 默认使用 VSPI
 * SPI 引脚在 TFT_eSPI 的 User_Setup 中配置
 */
TFT_eSPI tft = TFT_eSPI();  // VSPI 在库中会自动调用User_Setup.h中的引脚定义

/* TFT_eSPI 的按钮对象(当前代码未使用,预留) */
TFT_eSPI_Button btnAdd, btnDel, btnRef, btnPrev, btnNext;

/* ===================== 触摸 SPI 配置 =====================
 * 创建一个独立的 SPIClass 对象
 * 使用 HSPI,避免与 TFT_eSPI 的 VSPI 冲突
 */
SPIClass touchscreenSPI = SPIClass(HSPI);   // HSPI
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);//需要主动传入读/写引脚

/* ===================== 页面状态 =====================
 * currentPage 用于记录当前显示的页面
 * 0 = 主页面
 * 1 = 设置
 * 2 = 登录
 * 3 = 注册
 * 4 = 查询
 */
int currentPage = 0;

/* ===================== 按钮结构体 =====================
 * 用于描述一个简单的矩形按钮
 */
struct Button {
  int x, y;       // 左上角坐标
  int w, h;       // 宽和高
  const char *label; // 按钮文字
};

/* ===================== 主页面的四个按钮 ===================== */
Button homeButtons[4] = {
  {20,  40, 130, 60, "Settings"},
  {170, 40, 130, 60, "Login"},
  {20,  140,130, 60, "Register"},
  {170,140,130, 60, "Query"}
};

/* =====================================================
 * 绘制单个按钮
 * ===================================================== */
void drawButton(Button &btn) {
  // 填充圆角矩形作为按钮背景
  tft.fillRoundRect(btn.x, btn.y, btn.w, btn.h, 8, TFT_BLUE);

  // 绘制按钮边框
  tft.drawRoundRect(btn.x, btn.y, btn.w, btn.h, 8, TFT_WHITE);

  // 设置文字颜色
  tft.setTextColor(TFT_WHITE);

  // 设置文本基准点为居中
  tft.setTextDatum(MC_DATUM);

  // 在按钮中心绘制文字
  tft.drawString(btn.label,
                 btn.x + btn.w / 2,
                 btn.y + btn.h / 2,
                 FONT_SIZE);
}

/* ===================== 页面绘制函数 ===================== */

/* 主页面 */
void drawHomePage() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("主页面", 120, 10, FONT_SIZE);

  // 绘制四个功能按钮
  for (int i = 0; i < 4; i++) {
    drawButton(homeButtons[i]);
  }
}

/* 设置页面 */
void drawSettingsPage() {
  tft.fillScreen(TFT_NAVY);
  tft.drawString("drawSettingsPage", 120, 120, FONT_SIZE);
}

/* 登录页面 */
void drawLoginPage() {
  tft.fillScreen(TFT_DARKGREEN);
  tft.drawString("drawLoginPage", 120, 120, FONT_SIZE);
}

/* 注册页面 */
void drawRegisterPage() {
  tft.fillScreen(TFT_DARKCYAN);
  tft.drawString("drawRegisterPage", 120, 120, FONT_SIZE);
}

/* 查询页面 */
void drawQueryPage() {
  tft.fillScreen(TFT_MAROON);
  tft.drawString("drawQueryPage", 120, 120, FONT_SIZE);
}

/* =====================================================
 * 页面切换函数
 * 根据 page 参数绘制不同页面
 * ===================================================== */
void changePage(int page) {
  currentPage = page;

  switch (page) {
    case 0: drawHomePage();      break;
    case 1: drawSettingsPage(); break;
    case 2: drawLoginPage();    break;
    case 3: drawRegisterPage(); break;
    case 4: drawQueryPage();    break;
  }
}

/* =====================================================
 * 触摸检测函数
 * ===================================================== */
void checkTouch() {
  // 同时检测 IRQ 和触摸状态,提高稳定性
  if (!(touchscreen.tirqTouched() && touchscreen.touched())) {
    return;
  }

  // 读取原始触摸数据
  TS_Point p = touchscreen.getPoint();

  /* ===================== 坐标映射 =====================
   * XPT2046 输出的是原始 ADC 值(非像素坐标)
   * 需要通过 map() 映射到屏幕分辨率
   * 具体数值需要根据实际校准结果调整
   */
  int x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
  int y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
  int z = p.z;  // 压力值

  // 打印触摸信息到串口,方便调试
  printTouchToSerial(x, y, z);

  /* ===================== 页面交互逻辑 ===================== */
  if (currentPage == 0) {
    // 主页面:检测是否点击了按钮
    for (int i = 0; i < 4; i++) {
      Button &b = homeButtons[i];

      if (x > b.x && x < b.x + b.w &&
          y > b.y && y < b.y + b.h) {
        // 切换到对应页面
        changePage(i + 1);
        delay(300);  // 防止重复触发
      }
    }
  } else {
    // 非主页面:点击任意位置返回主页面
    changePage(0);
    delay(300);
  }

  delay(100); // 简单消抖
}

/* =====================================================
 * 串口打印触摸信息
 * ===================================================== */
void printTouchToSerial(int touchX, int touchY, int touchZ) {
  Serial.print("X = ");
  Serial.print(touchX);
  Serial.print(" | Y = ");
  Serial.print(touchY);
  Serial.print(" | Pressure = ");
  Serial.println(touchZ);
}

/* =====================================================
 * 初始化函数
 * ===================================================== */
void setup() {
  Serial.begin(115200);

  /* ---------- 初始化触摸 SPI(HSPI) ---------- */
  touchscreenSPI.begin(
    XPT2046_CLK,
    XPT2046_MISO,
    XPT2046_MOSI,
    XPT2046_CS
  );

  touchscreen.begin(touchscreenSPI);

  // 设置触摸旋转方向(需与 TFT 保持一致)
  touchscreen.setRotation(1);

  /* ---------- 初始化 TFT 显示(VSPI) ---------- */
  tft.init();
  tft.setRotation(1);

  // 显示主页面
  changePage(0);
}

/* =====================================================
 * 主循环
 * ===================================================== */
void loop() {
  // 持续检测触摸事件
  checkTouch();
}

四、可能遇到的问题

1、需要进行触摸校准,原因是SPI 时钟过高可能导致采样不稳定。

2、User_Setup.h宏定义的的名称需要和库/官方定义一致,否则后续库自动调用时会检测不到。

3、EN/RESET 一般定义为-1,否则触摸采样可能异常。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com