格格来闯关
113.36M · 2026-02-04
在 ESP32 项目中,同时使用 SPI TFT 显示屏 和 SPI 触摸芯片 时,常见做法是共用一条 SPI 总线。但在显示刷新或 DMA 传输较频繁的情况下,容易出现触摸不稳定、数据冲突等问题。
本文以 ESP32-2432S028R 开发板为示例,介绍一种 显示与触摸不共享 SPI 总线 的实现方式:
TFT_eSPI 库XPT2046_Touchscreen 库通过将 TFT 与触摸分配到不同的 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,否则触摸采样可能异常。