STM32电阻触摸屏五点校准:原理、C语言实现与调试技巧
1. 电阻触摸屏校准到底在解决什么问题很多人第一次把电阻触摸屏接到STM32上跑通了ADC读取和LCD显示看着屏幕能画出线条就觉得大功告成了。结果实际用起来才发现手指点在按钮正中间光标却跑到按钮外面去了画线的时候笔尖和线条之间总差那么一截。这不是代码写错了而是触摸屏的坐标系和LCD的像素坐标系之间没有建立正确的映射关系。电阻触摸屏的本质是两层透明导电膜中间用微小的绝缘点隔开。当你用手指或触笔按压时上下两层膜在受力点接触形成一个分压电路。STM32通过ADC读取X轴和Y轴的电压值换算成坐标。但问题在于每块触摸屏的电阻值、线性度、边缘效应都不一样而且LCD的像素坐标和触摸屏的物理坐标之间还存在偏移、缩放甚至旋转。校准的目的就是找到一组参数把触摸屏读到的原始ADC值准确映射到LCD的像素坐标上。五点校准是目前嵌入式项目里最常用的方案比三点校准精度更高比七点校准操作更简单。它的核心思路是在屏幕上取五个已知坐标的点分别记录触摸屏读到的ADC值然后通过最小二乘法或线性回归解出映射矩阵的各个系数。听起来有点数学但实际写代码的时候你会发现核心逻辑并不复杂真正麻烦的是采样稳定性、去抖动和边界处理。这篇文章面向的是已经能把STM32的ADC和LCD跑起来、但触摸坐标总是不准的开发者。我会从触摸屏的物理原理讲起拆解五点校准的数学推导给出完整的C语言实现代码并分享我在实际项目中踩过的坑和调试技巧。代码基于STM32 HAL库但思路适用于任何MCU平台。2. 触摸屏坐标与LCD像素坐标的映射原理2.1 电阻屏的物理采样过程电阻触摸屏有四个电极X、X-、Y、Y-。测量X坐标时STM32给X和X-分别加电压Y和Y-设为高阻态然后读取Y或Y-上的电压。这个电压和触摸点的X位置成正比。测量Y坐标时反过来给Y和Y-加电压读取X或X-上的电压。以常见的四线电阻屏为例假设屏幕宽度方向对应X轴ADC是12位参考电压3.3V。当你点在屏幕最左边时X轴的ADC读数接近0点在屏幕最右边时ADC读数接近4095。但实际因为电极电阻、接触电阻和ADC噪声这个范围往往不是满量程而且边缘区域非线性比较严重。我实测过几款不同尺寸的电阻屏4.3寸屏的X轴有效范围大概在200到3900之间Y轴在300到3800之间。不同批次、不同厂家的屏这个范围差异很大。所以校准的第一步就是通过采样确定每块屏的实际有效范围。2.2 为什么需要五点而不是三点三点校准通常取左上、右上、左下三个角假设屏幕是完美的矩形X和Y方向完全独立。但实际屏幕存在旋转和剪切变形尤其是屏幕和触摸膜贴合时有微小角度偏差三点校准就无能为力了。五点校准在四个角的基础上增加了中心点。中心点的作用是检测非线性。如果屏幕中间区域的线性度不好中心点的误差会明显偏大这时候就需要引入二次项或者分段映射。另外中心点还能帮助判断触摸屏是否装反了方向或者X/Y轴是否需要对调。从数学上看五点校准可以解出六个参数X轴和Y轴各自的缩放系数、偏移量以及两个轴之间的耦合系数。这六个参数构成了一个仿射变换矩阵能够处理平移、缩放、旋转和剪切。对于绝大多数电阻屏应用这个精度已经足够了。2.3 仿射变换的数学表达假设触摸屏读到的原始坐标为raw_x, raw_yLCD像素坐标为lcd_x, lcd_y仿射变换可以写成lcd_x a * raw_x b * raw_y c lcd_y d * raw_x e * raw_y f其中a、b、c、d、e、f就是我们需要求解的六个系数。五点校准提供了五组对应关系也就是十个方程求解六个未知数属于超定方程组用最小二乘法可以得到最优解。如果屏幕没有旋转和剪切b和d接近0公式退化成简单的缩放加偏移lcd_x a * raw_x c lcd_y e * raw_y f这时候其实三点校准就够了。但为了兼容性五点校准的代码可以同时处理这两种情况通过判断b和d的大小来决定是否启用旋转补偿。3. 五点校准的采样策略与去抖动处理3.1 五个校准点的位置选择五个点的位置不能随便选。如果都挤在屏幕中间边缘的误差会很大如果太靠近边缘又容易因为边框按压导致采样不稳定。我的经验是取屏幕有效区域的10%到90%之间均匀分布。具体来说假设LCD分辨率是480x320五个点的像素坐标可以这样取点位LCD XLCD Y说明点14832左上角留10%边距点243232右上角点348288左下角点4432288右下角点5240160正中心这样取点的好处是覆盖了整个屏幕的有效区域同时避开了边缘非线性最严重的部分。每个点显示一个十字光标提示用户点击。3.2 多次采样取中值电阻屏的ADC读数跳动很大尤其是手指按压的力度和面积变化时。单次采样根本不能用。我的做法是每个点连续采样20次然后去掉最大和最小的5个值剩下的取平均。这样既能抑制突发噪声又能保留真实的触摸信号。采样间隔也很关键。连续采样之间要加1到2毫秒的延时让ADC的采样保持电容充分稳定。如果采样太快相邻两次读数会互相干扰。我试过不加延时20次采样的标准差能达到50个ADC单位加了2毫秒延时后降到10以内。#define SAMPLE_COUNT 20 #define DISCARD_COUNT 5 uint16_t sample_touch_x(void) { uint16_t samples[SAMPLE_COUNT]; uint32_t sum 0; for (int i 0; i SAMPLE_COUNT; i) { samples[i] read_adc(ADC_CHANNEL_X); HAL_Delay(2); } // 冒泡排序 for (int i 0; i SAMPLE_COUNT - 1; i) { for (int j 0; j SAMPLE_COUNT - 1 - i; j) { if (samples[j] samples[j 1]) { uint16_t temp samples[j]; samples[j] samples[j 1]; samples[j 1] temp; } } } // 去掉最大和最小的各5个取中间10个的平均 for (int i DISCARD_COUNT; i SAMPLE_COUNT - DISCARD_COUNT; i) { sum samples[i]; } return (uint16_t)(sum / (SAMPLE_COUNT - 2 * DISCARD_COUNT)); }3.3 触摸按下的判定逻辑采样之前必须先判断有没有触摸。电阻屏没有触摸时X和X-之间是断开的ADC读到的值要么是0要么是悬空的高电平。我的做法是给Y加一个上拉电阻然后读Y-的电压。如果有触摸Y-会被拉低没有触摸Y-保持高电平。更可靠的方法是测量X和X-之间的电阻。没有触摸时电阻无穷大有触摸时电阻在几百欧姆到几kΩ之间。但这个方法需要额外的电路对于大多数应用简单的电平判断就够了。uint8_t is_touched(void) { // 配置Y为推挽输出高电平Y-为浮空输入 set_pin_output(Y_PLUS); set_pin_input(Y_MINUS); write_pin(Y_PLUS, 1); HAL_Delay(1); // 如果Y-读到低电平说明有触摸 return (read_pin(Y_MINUS) 0); }注意判断触摸和读取坐标之间要加一个小延时让引脚电平稳定。我一般加1到2毫秒太短了容易误判太长了影响响应速度。4. 从采样值到校准系数的完整计算过程4.1 建立方程组假设五个校准点的LCD坐标分别是x1, y1到x5, y5对应的触摸屏原始ADC值是u1, v1到u5, v5。根据仿射变换公式对于X轴有x1 a * u1 b * v1 c x2 a * u2 b * v2 c ... x5 a * u5 b * v5 c这是一个超定方程组写成矩阵形式是| u1 v1 1 | | a | | x1 | | u2 v2 1 | * | b | | x2 | | u3 v3 1 | | c | | x3 | | u4 v4 1 | | x4 | | u5 v5 1 | | x5 |记作A * P X其中A是5x3的矩阵P是3x1的未知数向量X是5x1的观测值向量。最小二乘解是P (A^T * A)^(-1) * A^T * X同理Y轴的系数d、e、f用同样的方法求解只是观测值向量换成y1到y5。4.2 用C语言实现矩阵运算在STM32上做矩阵求逆最直接的方法是手动展开。3x3矩阵的逆有解析公式不需要迭代。下面是完整的计算代码typedef struct { float a, b, c; // X轴系数 float d, e, f; // Y轴系数 } CalibParams; void compute_calibration(uint16_t *raw_x, uint16_t *raw_y, uint16_t *lcd_x, uint16_t *lcd_y, CalibParams *params) { float A[5][3]; float AT[3][5]; float ATA[3][3]; float ATX[3]; float ATY[3]; // 构建矩阵A for (int i 0; i 5; i) { A[i][0] (float)raw_x[i]; A[i][1] (float)raw_y[i]; A[i][2] 1.0f; } // 计算A的转置 for (int i 0; i 5; i) { for (int j 0; j 3; j) { AT[j][i] A[i][j]; } } // 计算ATA A^T * A for (int i 0; i 3; i) { for (int j 0; j 3; j) { ATA[i][j] 0; for (int k 0; k 5; k) { ATA[i][j] AT[i][k] * A[k][j]; } } } // 计算ATX和ATY for (int i 0; i 3; i) { ATX[i] 0; ATY[i] 0; for (int k 0; k 5; k) { ATX[i] AT[i][k] * (float)lcd_x[k]; ATY[i] AT[i][k] * (float)lcd_y[k]; } } // 求ATA的逆矩阵 float det ATA[0][0] * (ATA[1][1] * ATA[2][2] - ATA[1][2] * ATA[2][1]) - ATA[0][1] * (ATA[1][0] * ATA[2][2] - ATA[1][2] * ATA[2][0]) ATA[0][2] * (ATA[1][0] * ATA[2][1] - ATA[1][1] * ATA[2][0]); if (fabsf(det) 1e-6f) { // 矩阵奇异校准失败 return; } float inv_det 1.0f / det; float invATA[3][3]; invATA[0][0] (ATA[1][1] * ATA[2][2] - ATA[1][2] * ATA[2][1]) * inv_det; invATA[0][1] (ATA[0][2] * ATA[2][1] - ATA[0][1] * ATA[2][2]) * inv_det; invATA[0][2] (ATA[0][1] * ATA[1][2] - ATA[0][2] * ATA[1][1]) * inv_det; invATA[1][0] (ATA[1][2] * ATA[2][0] - ATA[1][0] * ATA[2][2]) * inv_det; invATA[1][1] (ATA[0][0] * ATA[2][2] - ATA[0][2] * ATA[2][0]) * inv_det; invATA[1][2] (ATA[0][2] * ATA[1][0] - ATA[0][0] * ATA[1][2]) * inv_det; invATA[2][0] (ATA[1][0] * ATA[2][1] - ATA[1][1] * ATA[2][0]) * inv_det; invATA[2][1] (ATA[0][1] * ATA[2][0] - ATA[0][0] * ATA[2][1]) * inv_det; invATA[2][2] (ATA[0][0] * ATA[1][1] - ATA[0][1] * ATA[1][0]) * inv_det; // 计算P invATA * ATX params-a invATA[0][0] * ATX[0] invATA[0][1] * ATX[1] invATA[0][2] * ATX[2]; params-b invATA[1][0] * ATX[0] invATA[1][1] * ATX[1] invATA[1][2] * ATX[2]; params-c invATA[2][0] * ATX[0] invATA[2][1] * ATX[1] invATA[2][2] * ATX[2]; params-d invATA[0][0] * ATY[0] invATA[0][1] * ATY[1] invATA[0][2] * ATY[2]; params-e invATA[1][0] * ATY[0] invATA[1][1] * ATY[1] invATA[1][2] * ATY[2]; params-f invATA[2][0] * ATY[0] invATA[2][1] * ATY[1] invATA[2][2] * ATY[2]; }这段代码在STM32F103上跑一次大概需要几百微秒完全不影响实时性。如果你用的是F4或H7系列带FPU的话会更快。4.3 校准结果的验证方法算完系数不能直接用得先验证。我的做法是让用户点击屏幕上的几个测试点然后比较实际触摸坐标和期望坐标的偏差。如果偏差超过5个像素就提示重新校准。验证点的选择也有讲究。除了五个校准点本身最好再取几个非校准点比如屏幕的四分之一位置。如果这些点的误差也在可接受范围内说明校准参数是可靠的。void verify_calibration(CalibParams *params) { uint16_t test_points[][2] { {120, 80}, {360, 80}, {120, 240}, {360, 240}, {240, 160} }; for (int i 0; i 5; i) { // 在LCD上显示测试点 draw_cross(test_points[i][0], test_points[i][1]); // 等待用户点击 while (!is_touched()); uint16_t raw_x sample_touch_x(); uint16_t raw_y sample_touch_y(); while (is_touched()); // 计算映射后的坐标 int16_t lcd_x (int16_t)(params-a * raw_x params-b * raw_y params-c); int16_t lcd_y (int16_t)(params-d * raw_x params-e * raw_y params-f); // 计算偏差 int16_t dx lcd_x - test_points[i][0]; int16_t dy lcd_y - test_points[i][1]; if (abs(dx) 5 || abs(dy) 5) { // 偏差过大提示重新校准 show_message(Calibration failed, please retry); return; } } show_message(Calibration OK); }5. 实际调试中遇到的典型问题与解决思路5.1 采样值跳动导致校准失败这是最常见的问题。表现是每次校准算出来的系数都不一样有时候偏差还很大。根本原因是ADC采样不稳定。除了前面说的多次采样取中值还有几个细节容易被忽略。第一ADC的参考电压要稳定。如果你直接用STM32的VDDA做参考而VDDA又和LCD背光共用电源背光亮度变化时ADC读数会跟着漂。我的做法是给VDDA加一个单独的LC滤波或者在软件里做参考电压补偿。第二触摸屏的四个引脚要加上拉或下拉电阻。悬空的时候ADC读到的值完全是随机的。我一般加10kΩ的下拉电阻确保没有触摸时读到确定的低电平。第三采样时的GPIO配置要正确。测量X轴时Y和Y-必须设为模拟输入或高阻态不能有内部上拉。我见过有人忘了关内部上拉结果X轴的读数始终偏大。5.2 边缘区域坐标跳变屏幕边缘的触摸坐标经常跳来跳去尤其是靠近边框的地方。这是因为边缘区域的电场分布不均匀ADC读到的电压和实际位置不是线性关系。五点校准假设整个屏幕是线性的边缘的误差自然就大。解决思路有两个。一是把校准点往内缩避开边缘区域然后在映射的时候做钳位处理。比如LCD宽度是480但触摸有效区域只映射到20到460超出范围的坐标直接钳到边界值。二是引入分段校准。把屏幕分成九宫格每个格子单独算一组系数。这样精度更高但代码复杂度和校准时间都会增加。对于大多数应用第一种方法就够了。int16_t map_x(CalibParams *params, uint16_t raw_x, uint16_t raw_y) { int16_t lcd_x (int16_t)(params-a * raw_x params-b * raw_y params-c); // 钳位处理 if (lcd_x 0) lcd_x 0; if (lcd_x LCD_WIDTH - 1) lcd_x LCD_WIDTH - 1; return lcd_x; }5.3 触摸屏和LCD方向不一致有时候触摸屏的X轴对应LCD的Y轴或者方向完全相反。这在五点校准里其实不是问题因为仿射变换会自动处理旋转和轴对调。但前提是你的五个校准点顺序和LCD显示的顺序一致。我遇到过一种情况用户把触摸屏装反了LCD显示左上角的十字用户实际点的是右下角。这时候算出来的系数会完全错误。解决办法是在校准前先做一个简单的方向测试让用户点击屏幕中心如果读到的原始坐标和预期偏差太大就提示检查硬件连接。5.4 校准参数的存储与加载校准一次不容易参数必须存起来。STM32内部Flash、外部EEPROM、甚至备份寄存器都可以用。我一般存在内部Flash的最后一页加一个简单的校验和。#define CALIB_FLASH_ADDR 0x0801FC00 #define CALIB_MAGIC 0x5A5A typedef struct { uint16_t magic; CalibParams params; uint16_t checksum; } CalibStorage; void save_calibration(CalibParams *params) { CalibStorage storage; storage.magic CALIB_MAGIC; storage.params *params; // 计算校验和 uint16_t *p (uint16_t *)storage; uint16_t sum 0; for (int i 0; i (sizeof(CalibStorage) - 2) / 2; i) { sum p[i]; } storage.checksum sum; // 解锁Flash并写入 HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef erase; erase.TypeErase FLASH_TYPEERASE_PAGES; erase.PageAddress CALIB_FLASH_ADDR; erase.NbPages 1; uint32_t page_error; HAL_FLASHEx_Erase(erase, page_error); uint16_t *src (uint16_t *)storage; for (int i 0; i sizeof(CalibStorage) / 2; i) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, CALIB_FLASH_ADDR i * 2, src[i]); } HAL_FLASH_Lock(); }加载的时候先检查magic和校验和都对了才使用。如果Flash是空的或者数据损坏就自动进入校准流程。提示Flash写入之前一定要先擦除而且擦除操作会阻塞CPU。如果你的系统对实时性要求高建议在系统启动阶段完成校准参数的加载不要在运行中频繁读写。6. 完整代码结构与集成步骤6.1 文件组织与接口设计我把触摸屏校准相关的代码分成三个文件touch_calib.c负责采样和计算touch_calib.h放结构体定义和函数声明touch_driver.c负责底层的ADC读取和GPIO控制。这样分层的好处是换MCU平台时只需要改touch_driver.c校准算法不用动。核心接口就三个函数// 初始化触摸屏硬件 void touch_init(void); // 执行五点校准返回0表示成功 int touch_calibrate(void); // 读取当前触摸坐标返回0表示无触摸 int touch_read(int16_t *x, int16_t *y);touch_read内部会自动应用校准参数调用者不需要关心原始ADC值。6.2 校准流程的状态机设计校准过程是一个交互流程用状态机来实现最清晰。我定义了五个状态等待点1、等待点2、等待点3、等待点4、等待点5、计算参数、验证。每个状态在LCD上显示对应的十字光标等待用户点击。typedef enum { CALIB_IDLE, CALIB_POINT1, CALIB_POINT2, CALIB_POINT3, CALIB_POINT4, CALIB_POINT5, CALIB_COMPUTE, CALIB_VERIFY, CALIB_DONE, CALIB_FAIL } CalibState; void touch_calibrate_task(void) { static CalibState state CALIB_IDLE; static uint16_t raw_x[5], raw_y[5]; static uint8_t point_index 0; switch (state) { case CALIB_IDLE: lcd_clear(); show_message(Touch the cross); draw_cross(calib_points[0][0], calib_points[0][1]); state CALIB_POINT1; break; case CALIB_POINT1: case CALIB_POINT2: case CALIB_POINT3: case CALIB_POINT4: case CALIB_POINT5: if (is_touched()) { HAL_Delay(20); // 去抖动 if (is_touched()) { raw_x[point_index] sample_touch_x(); raw_y[point_index] sample_touch_y(); while (is_touched()); // 等待松开 point_index; if (point_index 5) { draw_cross(calib_points[point_index][0], calib_points[point_index][1]); state (CalibState)(state 1); } else { state CALIB_COMPUTE; } } } break; case CALIB_COMPUTE: compute_calibration(raw_x, raw_y, calib_lcd_x, calib_lcd_y, calib_params); state CALIB_VERIFY; break; case CALIB_VERIFY: if (verify_calibration(calib_params) 0) { save_calibration(calib_params); state CALIB_DONE; } else { state CALIB_FAIL; } break; case CALIB_DONE: show_message(Calibration OK); break; case CALIB_FAIL: show_message(Failed, retry); state CALIB_IDLE; point_index 0; break; } }这个状态机放在主循环里轮询不占用中断资源。如果你用的是RTOS也可以把它做成一个独立的任务。6.3 与LCD驱动的对接要点校准界面需要LCD驱动配合主要是画十字和显示文字。画十字很简单就是两条线段void draw_cross(uint16_t x, uint16_t y) { LCD_DrawLine(x - 10, y, x 10, y, RED); LCD_DrawLine(x, y - 10, x, y 10, RED); }但要注意画十字之前要先清屏或者恢复背景否则多次校准后屏幕上全是十字。我的做法是每次画新十字之前先用背景色把上一个十字覆盖掉。另外LCD的坐标系原点在左上角X向右递增Y向下递增。触摸屏的原始坐标原点可能在左下角或右上角这没关系仿射变换会自动处理。但校准点的LCD坐标必须和LCD驱动的坐标系一致否则算出来的参数会反。6.4 在Keil和STM32CubeIDE中的编译配置代码本身不依赖特定的IDE但有几个编译选项需要注意。如果你用的是Keil MDK要在Options for Target的C/C选项卡里勾选“Use MicroLIB”否则fabsf和abs可能会链接失败。STM32CubeIDE默认用newlib没这个问题。浮点运算方面如果你的STM32带FPU比如F4、F7、H7记得在编译选项里加上-mfpufpv4-sp-d16 -mfloat-abihard这样浮点运算会快很多。F1系列没有FPU浮点运算是软件模拟的但校准计算只在初始化时跑一次影响不大。Flash写入部分需要包含stm32f1xx_hal_flash.h或对应系列的HAL头文件。不同系列的Flash编程函数名可能略有不同比如F4系列用的是FLASH_TYPEPROGRAM_WORD而不是FLASH_TYPEPROGRAM_HALFWORD移植时要注意。7. 提升校准精度的几个进阶技巧7.1 温度补偿的必要性电阻屏的电阻值会随温度变化尤其是户外设备或者工业环境。我做过一个测试同一块屏在零下10度和零上40度时同样的触摸点ADC读数能差100多个单位。如果设备工作温度范围大校准参数最好能根据温度做补偿。最简单的补偿方法是存储两组校准参数一组在常温下校准一组在高温下校准然后根据温度传感器读数做线性插值。如果嫌麻烦至少要在设备启动时检测温度如果和校准时的温度差太多提示用户重新校准。7.2 压力信息的利用电阻屏的ADC读数其实包含了压力信息。按得重接触电阻小ADC读数更接近理论值按得轻接触电阻大读数会偏。如果你发现同一个点每次按的读数不一样可以尝试同时测量接触电阻然后根据压力大小对坐标做微调。具体做法是测量X和X-之间的电压差这个差值反映了接触电阻。压力大时差值小压力小时差值大。然后建立一个压力-偏差查找表对坐标进行补偿。这个方法比较繁琐但对于需要高精度的场景是值得的。7.3 多次校准取平均如果对精度要求极高可以让用户连续校准三次然后取三组参数的平均值。这样能抵消单次校准中的随机误差。但用户体验会变差所以一般只在工厂生产环节使用。我在一个医疗设备项目里用过这个方法三次校准后取平均坐标重复性从±8像素提升到±3像素。代价是校准时间从10秒增加到30秒但医疗设备对精度的要求远高于对时间的敏感度。7.4 校准参数的边界检查算出来的参数不能直接用必须做合理性检查。比如缩放系数a应该是正数如果算出来是负数说明触摸屏的X轴和LCD的X轴方向相反这可能是硬件接反了。偏移量c应该在合理范围内如果c是几万说明矩阵求解出了问题。int check_calib_params(CalibParams *params) { // 缩放系数应该在合理范围内 if (params-a 0.01f || params-a 1.0f) return -1; if (params-e 0.01f || params-e 1.0f) return -1; // 耦合系数不应该太大 if (fabsf(params-b) 0.5f) return -1; if (fabsf(params-d) 0.5f) return -1; // 偏移量应该在屏幕尺寸范围内 if (params-c -1000 || params-c 1000) return -1; if (params-f -1000 || params-f 1000) return -1; return 0; }这个检查能拦住大部分因为采样异常导致的错误参数避免把错误的校准结果存进Flash。8. 从校准数据看触摸屏的一致性批量生产的时候你会发现不同批次的触摸屏校准参数差异很大。我统计过一批100块的4.3寸屏X轴缩放系数a的分布范围是0.11到0.15偏移量c从-50到-80不等。这意味着如果固件里写死一组参数换一批屏就得重新校准。更麻烦的是同一批屏里偶尔会混进几块线性度特别差的。表现是五个校准点算出来的参数在验证时中心点的偏差特别大。这种屏要么退货要么在固件里做分段校准。我的经验是如果中心点偏差超过10像素这块屏就不适合用五点校准了。从成本角度考虑五点校准的代码量不大Flash占用大概2KB左右RAM占用不到100字节。对于STM32F103C8T6这种资源紧张的芯片也完全跑得起来。如果你在做基于STM32的毕业设计或者小批量产品这套方案可以直接用。最后分享一个调试时的小技巧在校准过程中把每次采样的原始ADC值通过串口打印出来。这样当校准失败时你能一眼看出是哪个点的采样出了问题。我一般会在sample_touch_x和sample_touch_y里加一句printf调试完再注释掉。串口波特率用115200打印20次采样值大概需要20毫秒不会影响校准流程。