简介:这套基于STM32F407的触摸屏测试工程,面向嵌入式开发者和学生,重点解决触摸屏通信驱动、坐标转换与连续画线显示等常见问题。压缩包共171个文件、4.86MB,内含47个C源码、48个H头文件、hex/axf固件以及Keil工程文件,工程结构完整,可直接编译烧录,也方便对照源码理解外设配置;map/lst等调试辅助文件还可辅助追溯编译细节。围绕happyus2触摸屏完成触控采集,演示STM32F407的GPIO与I2C初始化,并把触摸坐标映射到LCD像素坐标;画线部分引入线性插值/贝塞尔平滑算法,同时通过双缓冲刷新减少画面闪烁,对打造流畅触控交互很有帮助。已有161人学习,适合作为STM32F4系列触摸屏项目的参考例程,也可用于课程设计。
1. 把 STM32F407 触摸屏测试做成可复现的工程,先盯住上报链路
在嵌入式触摸屏项目里,最耗时的往往不是驱动程序本身,而是“屏幕点了没反应”或“坐标飘得厉害”这类问题背后的链路排查。触摸面板、控制器、MCU 的 I2C/SPI 读取时序、USB 上报格式,任何一环有隐患,最终表现都只是触摸无效。本文以 STM32F407 为测试主控,结合电阻屏、电容 I2C 屏和 USB HID 屏三种常见形态,给出从初始化到上位机联调的完整测试路径,重点讲如何用 happyus2 这类轻量上位机工具抓取触摸坐标上报帧,从而快速定位是面板问题还是代码问题。适合正在做 7 寸以下屏幕的嵌入式工程师,也适合想用 F407 验证触摸屏方案选型的人。
2. 先分清触摸屏接口类型,再决定 STM32F407 的外设分配
2.1 电阻屏、电容 I2C 屏、USB HID 屏,测试场景和接口完全不同
触摸屏测试的第一件事不是写代码,而是确认触摸控制器长什么样。电阻屏常见的控制器是 XPT2046、ADS7846,输出的是 12 位 ADC 原始值,MCU 通过 SPI 或 GPIO 模拟时序读取,核心测试点是压力阈值和坐标换算。电容屏常见的控制器是 GT911、FT5x06,通过 I2C 读取以手指编号区分的坐标数据,并且有中断脚通知 MCU 有触摸,测试重点是寄存器配置和防抖。还有一类触摸屏模组自带 USB HID 接口,内部已经完成了坐标计算,MCU 只需要作为 USB 主机接收 HID report,或者作为 USB 从机把数据转发给电脑,测试重点变成了 USB 枚举和报告描述符解析。
三种方案在 STM32F407 上的引脚开销和调试难度差异很大。建议在开始焊线前就列一张对比表,避免测试中途换接口。
| 触摸屏类型 | 典型器件 | 接口 | STM32F407 外设 | 主要测试点 |
|---|---|---|---|---|
| 电阻触摸 | XPT2046 | SPI / 模拟时序 | SPI1、2 个 GPIO | ADC 值跳动、压力阈值 |
| 电容触摸 | GT911 | I2C + 中断 | I2C1、EXTI | 坐标毛刺、多指跟踪 |
| USB HID 触摸 | 屏模组自带控制器 | USB | OTG_FS / HS | HID 枚举、多触点报告 |
从测试平台的角度看,电阻屏和电容屏都需要 MCU 自己做一轮坐标换算,而 USB HID 触摸屏把换算环节移到了模组内部。所以用 STM32F407 做测试时,建议把电阻屏和电容屏作为“需要调试完整信号链”的对象,把 USB HID 触摸屏作为“需要调试 USB 协议栈”的对象。
2.2 STM32F407 的 USB 资源分配和 PA8 VBUS 的关系
STM32F407 有两个 USB OTG 控制器:OTG_FS 内置全速 PHY,引脚固定为 PA11(DM)和 PA12(DP);OTG_HS 需要外部 ULPI PHY,常用于高速 USB。触摸 HID 屏基本是低速或全速设备,所以 OTG_FS 已经够用。官方库和 HAL 库的 USB Host 例程也默认跑 OTG_FS,测试时优先用这一组。
PA8 在这个场景里的作用经常被忽略。它用于外部 VBUS 检测或供电器件控制,具体取决于开发板原理图。当通过 Type-C 接口给屏幕供电时,PA8 需要配置为模拟输入或外部中断模式,用来监控 VBUS 是否存在。很多触摸屏测试一开始就枚举失败,不是因为 USB 协议不对,而是 VBUS 检测脚没有初始化,导致连接状态机一直认为未插线。
F407 的 OTG_FS 还支持嵌入式 PHY 的内部 VBUS 检测,但大多数开发板为了电气隔离,仍然选择用 GPIO 检测。测试的时候可以先用示波器确认 PA8 上有 5V 电平变化,再考虑软件问题。USB 触摸屏插上后如果 D+ 上拉正常,PA8 的捕获中断会先触发,然后 OTG 核心才开始枚举流程。
2.3 三段式测试架构:面板、MCU、上位机各管一段
做触摸屏测试不是只写一个读寄存器函数,而是要建立“面板 + MCU + 上位机”三层结构。面板负责把物理触摸变成电信号,MCU 负责读取并转换坐标,上位机负责可视化验证。用 STM32F407 作为中间层时,最好把采集和上报拆成两个模块:采集模块输出统一的触摸点数据结构,上报模块负责通过串口或 USB 发给 PC。这样换屏时只需要改采集模块,上报逻辑和上位机 happus2 的配置可以复用。
在调试 XP T2046 这类电阻屏时,中间层还能帮你区分“坐标源是否稳定”。如果上位机收到的点是连续的跳变,那是采样时序问题;如果点是单调漂移,那是校准参数问题。这个区分就是三段式测试架构的价值,也是后续用 happyus2 抓包的依据。
3. 在 STM32F407 上初始化触摸屏并读取有效坐标
3.1 XPT2046 电阻触摸屏的 SPI 读取代码与参数说明
先看电阻屏。XPT2046 的控制命令是 8 位,通过 DIN 写入,然后用 24 个时钟读取 12 位 ADC 结果。它支持多次采样,通常读 3 次取中值能有效降低触摸笔抖动。下面的代码基于 STM32 HAL 库,使用 SPI1 主模式。初始化时要注意把 SPI 时钟设为低频,XPT2046 最高支持 25MHz,但在长排线上跑 1MHz 更稳定。
// XPT2046 数据格式:命令字节 0x90 = 读 X 坐标,0xD0 = 读 Y 坐标 uint16_t xpt2046_read_adc(uint8_t cmd) { uint8_t buf[3] = {0}; uint16_t value = 0; GPIO_InitTypeDef gpio = {0}; // 片选拉低,开始一次转换 HAL_GPIO_WritePin(XPT_CS_GPIO_Port, XPT_CS_Pin, GPIO_PIN_RESET); buf[0] = cmd; HAL_SPI_TransmitReceive(&hspi1, buf, buf, 3, 10); // 第1个字节的高12位为 ADC 转换结果,第2个字节低4位忽略 value = ((buf[1] << 8) | buf[2]) >> 3; HAL_GPIO_WritePin(XPT_CS_GPIO_Port, XPT_CS_Pin, GPIO_PIN_SET); return value; }这段代码发送一个命令字节后连续读取 3 个字节,最后把 16 位结果右移 3 位得到 12 位数据。片选信号必须保证低电平期间完成整套时序,否则 XPT2046 会提前结束转换。实际操作中还会加上压力判断,触摸屏在未按下时 ADC 会返回接近满幅的值,按下时等效电阻变化导致值下降,所以要设定一个经验阈值。
3.2 GT911 电容屏的 I2C 读取与中断处理
电容屏测试的常见做法是使用 I2C 主模式轮询或者配合中断。GT911 复位后通过 INT 脚电平选择 I2C 地址,INT 高电平时地址为 0x14,低电平时地址为 0x5D。初始化时先给复位脚一个低脉冲,然后拉高 INT 并等待 50ms 再开始通信。读取坐标之前需要检查 0x8040 寄存器的高位,该位为 1 表示当前有触摸数据可以读取。
void gt911_read_points(I2C_HandleTypeDef *hi2c, gt911_point_t *points) { uint8_t status = 0; uint8_t buf[6] = {0}; // 读取状态寄存器,判断 buffer 是否 ready HAL_I2C_Mem_Read(hi2c, GT911_ADDR, 0x8040, 2, &status, 1, 20); if ((status & 0x80) == 0) { // bit7 = buffer status return; } // 读取第1个触摸点的原始坐标数据,34字节包含最多5个点 HAL_I2C_Mem_Read(hi2c, GT911_ADDR, 0x8150, 2, buf, 34, 20); points[0].x = (buf[1] << 8) | buf[0]; points[0].y = (buf[3] << 8) | buf[2]; points[0].id = buf[4] & 0x0F; points[0].pressed = status & 0x0F; // 读完之后清 buffer HAL_I2C_Mem_Write(hi2c, GT911_ADDR, 0x8040, 2, &status, 1, 20); }GT911 的寄存器地址是 16 位,I2C Mem_Read/Write 的地址长度要设置成 2 字节。状态寄存器的低 4 位表示当前触摸点数,配合每个点的 ID 可以追踪同一手指的滑动路径。读取后必须写回状态寄存器清 buffer,否则下一轮数据不会更新。很多触摸不灵的问题就是漏了这一步,导致上报坐标永远停留在第一次触摸的值。
3.3 把原始坐标变成屏幕坐标时的三组关键参数
无论是电阻屏还是电容屏,MCU 拿到的坐标都是控制器视角的原始值。面板贴装时可能旋转了 90 度,或者触摸屏的有效区域比显示区域小,这时需要做坐标变换。常见的做法是维护三组参数:方向变换矩阵、偏移量和缩放比例。
方向变换矩阵处理 0 度、90 度、180 度、270 度旋转。偏移量用于去掉触摸屏边缘的无效像素,缩放比例把原始 ADC 值映射到显示屏分辨率。比如一块 800x480 的屏幕,GT911 输出的 X 范围是 0 到 1023,缩放系数就是 800/1024。测试时不要写死屏幕分辨率,建议把这几个参数做成结构体,在初始化时从配置表读取。
typedef struct { uint8_t rotation; // 0/90/180/270 int16_t offset_x; int16_t offset_y; uint16_t scale_x; uint16_t scale_y; uint16_t screen_w; uint16_t screen_h; } touch_calib_t;值得强调的是,触摸测试和显示驱动强相关。先让屏幕显示十字线网格,用触摸笔点击每个交叉点,把读回的坐标记录成表,再反推校准参数。这个流程虽然原始,但比所谓的“自动校准”更可靠。STM32F407 的 FPU 对浮点运算有加速,坐标换算可以直接用 float,不需要特意转成定点数。
4. 通过 happyus2 上位机联调 STM32F407 触摸上报
4.1 把 STM32F407 配置成 USB CDC 虚拟串口发送触摸数据
要让 happyus2 能处理触摸数据,首先得让 STM32F407 把坐标发到 PC。最稳妥的方式是复用 F407 的 OTG_FS 外设,将其配置为 USB CDC 设备,虚拟出一个串口。这样 happyus2 只需要打开串口就能接收数据,不用处理复杂驱动。CDC 工程的 USB 描述符里要把端点配置为双缓冲,例如使用端点 3 IN 和端点 1 OUT,保证高速发送触摸点时不会丢包。
在发送端定义一个固定帧结构,每 5 毫秒上报一次坐标。帧格式建议包含帧头、触摸点数、坐标值和校验字节。happyus2 解析数据时会依赖帧头的同步,如果只发裸坐标,很容易因为错位导致显示一片乱点。
typedef struct __attribute__((packed)) { uint8_t head[2]; // 0xAA 0x55 uint8_t id; uint8_t count; uint16_t x[5]; uint16_t y[5]; uint8_t crc; } touch_report_t; void send_touch_report(touch_report_t *report) { report->head[0] = 0xAA; report->head[1] = 0x55; report->crc = calculate_crc((uint8_t *)report, sizeof(*report) - 1); CDC_Transmit_FS((uint8_t *)report, sizeof(*report)); }帧头 0xAA 0x55 方便上位机做滑窗同步,ID 段用来标识不同的 F407 设备,count 表示有效触摸点个数。如果你的屏幕只支持单点,可以直接把 x 和 y 数组缩成两字节,减少每帧长度。数据发送间隔不要低于 USB 全速帧的 1ms 限制,实际测试中 5ms 一帧已经能覆盖大多数人的点击习惯。
4.2 happyus2 的界面参数配置和触摸轨迹查看
happyus2 常见的使用方式是把它当作一个纯协议观察器。它本身不关心触摸屏是 I2C 还是 SPI,只认串口或 USB HID 的输入字节流。在配置时需要注意三件事:选择正确的虚拟 COM 口号,波特率无所谓因为 CDC 不关注;数据位、停止位按默认的 8-N-1 设置;接收缓冲大小建议设成 2048 字节,避免高频触摸时缓存溢出。
连接成功后,happyus2 通常提供点阵图窗口和原始字节窗口两个视图。点阵图窗口适合观察触摸轨迹是否平滑,原始字节窗口适合核对帧内容。实际测试时如果发现点阵图出现断线,先别怀疑触摸屏,很可能是 STM32F407 的程序中 touch_report_t 结构体没有按 1 字节对齐,导致上位机接收到的坐标偏移了一位。把结构体加上 packed 属性后再看数据就正常了。
happyus2 还可以导出日志。导出的 CSV 文件中每行包含时间戳、触摸点 ID 和坐标值。这组数据可以直接丢到 Python 或 MATLAB 里做抖动分析,比盯着屏幕看更客观。很多“触摸屏幕乱跳”的案例,最终都是通过导出的数据看出某个点的 Y 坐标偶尔跳到最大值,然后顺藤摸瓜找到初始化时 Y 方向电压设错。
4.3 三个必须调的参数:上报率、去抖时间、坐标滤波窗口
| 参数 | 推荐范围 | 作用 | 调大后果 |
|---|---|---|---|
| 上报率 | 100~200 Hz | 决定触摸响应流畅度 | CPU 占用增加 |
| 去抖时间 | 物理触摸 3~5ms | 滤除按下瞬间的毛刺 | 响应变迟钝 |
| 坐标滤波窗口 | 3~5 点 | 拉平 ADC 抖动 | 滑动轨迹失控 |
这几个参数直接决定了触摸体验。上报率降到 50Hz 以下时会明显感觉到点按延迟,高于 200Hz 对全速 USB 来说虽然能传,但 STM32F407 的中断负担会升高。去抖时间用定时器计,第一次检测到触摸后不立刻上报,而是等几个毫秒再确认。坐标滤波窗口只对静止触摸有效,滑动时窗口大了会造成轨迹跟不上手指,所以很多方案会根据手指移动速度动态调整窗口大小。
上面这些参数的组合效果,一定是在 happyus2 的实时窗口里看出来的。调完上报率后立刻用手指画个圆,如果圆变成多边形,就说明帧率不够或滤波过度。
5. 触摸屏测试里的高频踩坑和验证技巧
5.1 PA8 的 Type-C VBUS 检测导致枚举时有时无
STM32F407 开发板上 PA8 常常和 OTG_FS 的 VBUS 关联。调试 USB HID 触摸屏时,如果插上屏幕后设备在电脑端反复“叮咚”断开重连,先查 PA8 是否被复用成了别的功能。很多基于正点原子或野火的例程把 PA8 设置为 GPIO 输出拉高,结果它输出的 3.3V 和 VBUS 的 5V 形成一个不稳定的分压,USB 连接检测就会误判。正确做法是把 PA8 配置为模拟输入,由外部电路决定电平。
5.2 用逻辑分析仪确认 I2C 地址和时隙
GT911 这类电容屏初始化失败时,第一反应不要改代码,直接把逻辑分析仪接在 SCL 和 SDA 上。抓一遍上电后的通信波形,确认 MCU 发出的地址对不对。GPIO 模拟 I2C 时很容易把起始条件搞错,导致设备不应答。波形里如果只有 SCL 翻转而 SDA 没有响应,就检查地址移位方向是否正确,GT911 的 7 位地址要左移一位才能填进 I2C 设备的 8 位地址段。
5.3 用 GPIO 翻转测量触摸响应延迟
验证触摸系统响应时间不能只靠手感和上位机。在 STM32F407 里把一个空闲 GPIO 在触摸断判定时拉高,在坐标数据处理完时拉低,用示波器看这个引脚的脉宽,就能算出整个触摸链路延迟。测量时把触摸屏放在桌面上用机械装置模拟点击,或者用固定频率的信号发生器驱动触摸屏的电容变化,这样测出来的数据才是稳定的。配合 happyus2 导出的时间戳,可以进一步拆解出 USB 传输耗时,最终确认瓶颈是在触摸控制器的积分时间,还是在 MCU 的 I2C 轮询周期。这三步验证做完,一块触摸屏的可行性判断基本就闭环了。
本文还有配套的精品资源,点击获取