4、两点校准法:原理推导、数学公式、实现步骤与代码示例
触摸校准,说白了就是解决一个「你说东,它指西」的问题。你点屏幕左上角,系统却以为你点了右下角——这种体验,谁用谁崩溃。
两点校准法,是实际项目中最常用的方案之一。它不像三点校准那么复杂,也不像单点校准那么粗糙。我个人习惯在量产项目中优先用两点法,因为它刚好能解决大部分触摸偏差问题。
4.1 为什么需要两点?
触摸屏和显示屏是两个独立的物理设备。它们之间天然存在三个偏差:
- 平移:触摸层贴歪了,整体偏移
- 缩放:触摸分辨率与显示分辨率不匹配
- 旋转:触摸层安装时转了个小角度
单点校准只能解决平移。两点校准,可以同时解决平移和缩放。至于旋转?嗯,两点法其实也能处理一部分,但严格来说需要三点。不过在实际项目中,触摸层安装旋转通常很小,两点法已经够用。
核心结论:两点校准法假设触摸屏与显示屏之间是线性仿射变换,且忽略旋转。这个假设在绝大多数嵌入式产品中成立。
4.2 数学推导:从物理坐标到屏幕坐标
我们定义两组坐标:
- (Xd, Yd) — 显示屏坐标,单位像素
- (Xt, Yt) — 触摸屏坐标,单位是ADC采样值
两点校准的核心,就是找到一组线性映射参数,把触摸坐标转换成显示坐标。
公式长这样:
Xd = A * Xt + B * Yt + C
Yd = D * Xt + E * Yt + F
等一下,这看起来有6个未知数?别急。两点校准法做了简化:我们假设触摸屏的X轴和Y轴是正交的,且没有旋转。于是公式简化为:
Xd = A * Xt + C
Yd = E * Yt + F
现在只有4个未知数:A、C、E、F。两个校准点,每个点提供X和Y两个方程,刚好4个方程解4个未知数。
我的经验:实际项目中,我见过有人把公式写成 Xd = (Xt - Xt_offset) * Xscale 这种形式。本质上是一样的,只是参数含义更直观。我个人习惯用上面那种通用形式,方便后续扩展到三点校准。
4.3 参数求解:手算一遍
假设我们选了屏幕上的两个校准点:
- 点1:显示屏坐标 (Xd1, Yd1),触摸采样值 (Xt1, Yt1)
- 点2:显示屏坐标 (Xd2, Yd2),触摸采样值 (Xt2, Yt2)
代入公式:
Xd1 = A * Xt1 + C
Xd2 = A * Xt2 + C
Yd1 = E * Yt1 + F
Yd2 = E * Yt2 + F
解出来:
A = (Xd2 - Xd1) / (Xt2 - Xt1)
C = Xd1 - A * Xt1
E = (Yd2 - Yd1) / (Yt2 - Yt1)
F = Yd1 - E * Yt1
就这么简单。四个参数,一次除法,一次减法。
注意:分母不能为零。也就是说,两个校准点不能在同一个X坐标或同一个Y坐标上。我曾经见过有人把两个点选在屏幕正中间,结果除零崩溃——嗯,这种坑踩过一次就不会再犯了。
4.4 校准点的选择策略
点选在哪里,直接影响校准精度。我建议:
| 策略 | 点1位置 | 点2位置 | 适用场景 |
|---|---|---|---|
| 对角线法 | 左上角 (10%, 10%) | 右下角 (90%, 90%) | 通用推荐 |
| 水平法 | 左侧中间 (10%, 50%) | 右侧中间 (90%, 50%) | 仅需校准X轴 |
| 垂直法 | 上侧中间 (50%, 10%) | 下侧中间 (50%, 90%) | 仅需校准Y轴 |
我个人推荐对角线法。两个点离得越远,计算出的缩放系数越稳定。你想想看,如果两个点只隔了10个像素,触摸稍微抖一下,算出来的A值可能差好几倍。
4.5 实现步骤
整个流程分三步走:
- 采集阶段:在屏幕上显示两个校准点,等待用户点击,记录触摸采样值
- 计算阶段:用上面的公式算出A、C、E、F四个参数
- 应用阶段:每次触摸事件,用参数把触摸坐标转换成显示坐标
这里有个细节:采集阶段需要做多次采样取平均。我一般每个点采5次,去掉最大最小值,再取平均。为什么?因为触摸屏的ADC采样本身有噪声,单次采样可能偏差好几个像素。
4.6 代码示例:LVGL中的两点校准
下面是我在LVGL项目中实际用过的校准代码。注意,LVGL本身没有内置两点校准算法,我们需要自己实现。
// 校准参数结构体
typedef struct {
float A;
float C;
float E;
float F;
} calib_params_t;
// 采集校准点
void touch_calib_collect_point(lv_point_t *display_pt, lv_point_t *touch_pt) {
// 在显示屏上画一个十字
lv_obj_t *cross = lv_obj_create(lv_scr_act());
lv_obj_set_pos(cross, display_pt->x - 15, display_pt->y - 15);
lv_obj_set_size(cross, 30, 30);
// 等待用户触摸
while(1) {
lv_indev_data_t data;
lv_indev_read(lv_indev_get_act(), &data);
if(data.state == LV_INDEV_STATE_PRESSED) {
touch_pt->x = data.point.x;
touch_pt->y = data.point.y;
break;
}
lv_task_handler();
lv_tick_inc(5);
}
lv_obj_del(cross);
}
// 计算校准参数
void touch_calib_compute(calib_params_t *params,
lv_point_t *pt1_disp, lv_point_t *pt1_touch,
lv_point_t *pt2_disp, lv_point_t *pt2_touch) {
// 计算X轴参数
float dx_disp = pt2_disp->x - pt1_disp->x;
float dx_touch = pt2_touch->x - pt1_touch->x;
if(dx_touch == 0) {
// 防除零,实际项目中应该做更完善的错误处理
params->A = 1.0f;
params->C = 0;
} else {
params->A = dx_disp / dx_touch;
params->C = pt1_disp->x - params->A * pt1_touch->x;
}
// 计算Y轴参数
float dy_disp = pt2_disp->y - pt1_disp->y;
float dy_touch = pt2_touch->y - pt1_touch->y;
if(dy_touch == 0) {
params->E = 1.0f;
params->F = 0;
} else {
params->E = dy_disp / dy_touch;
params->F = pt1_disp->y - params->E * pt1_touch->y;
}
}
// 应用校准:将触摸坐标转换为显示坐标
void touch_calib_apply(calib_params_t *params,
lv_point_t *touch_pt,
lv_point_t *disp_pt) {
disp_pt->x = (int)(params->A * touch_pt->x + params->C);
disp_pt->y = (int)(params->E * touch_pt->y + params->F);
}
使用建议:计算出的参数建议保存到Flash或EEPROM中。每次开机直接读取,不需要重新校准。除非用户换了触摸屏或者屏幕贴膜,否则参数基本不变。
4.7 两点校准的局限性
说实话,两点校准不是万能的。它假设触摸屏的X轴和Y轴完全独立,且没有旋转。如果触摸屏安装时歪了超过5度,两点校准的效果就会变差。
我在一个7寸屏项目上遇到过这种情况:触摸层贴歪了大概3度,两点校准后,屏幕边缘的触摸误差达到了15个像素。用户点关闭按钮,总是点偏。后来我换成了三点校准,误差降到了3个像素以内。
所以,两点校准适合:
- 触摸屏安装精度较高的产品
- 屏幕尺寸较小(3.5寸以下)
- 对触摸精度要求不高的场景(比如菜单选择)
不适合:
- 大尺寸屏幕(7寸以上)
- 触摸层安装有明显旋转
- 需要精确绘图或手写输入
4.8 核心流程一览
下面这张图,把两点校准的完整流程画出来了。从用户点击到坐标转换,每一步都清楚。
嗯,到这里两点校准法就讲完了。代码可以直接拿去用,但记得根据你的触摸芯片型号调整采样逻辑。不同的触摸控制器,读取坐标的方式可能不太一样。