3、坐标映射基础:从ADC原始值到屏幕像素坐标的线性变换模型(比例因子与偏移量)

触摸屏按下时,ADC 给我们的是什么?是一堆数字。比如 1850、3720 这样的原始值。但屏幕要的是像素坐标,比如 (x=120, y=240)。

从原始值到像素坐标,中间需要一个桥梁。这个桥梁就是线性变换模型。说白了,就是一条直线方程。

我个人习惯把这个问题拆成两步:先搞清楚 X 轴怎么变,再搞清楚 Y 轴怎么变。因为触摸屏的 X 和 Y 通道是独立的,互不干扰。

3.1 为什么是线性模型?

你想想看,电阻触摸屏的工作原理是分压。电压和触摸位置之间,理论上就是线性关系。ADC 采样到的电压值,和触摸点的物理位置成正比。

所以,我们只需要两个参数就能描述这个关系:

  • 比例因子(Scale Factor):也叫增益。它决定了 ADC 值变化多少,像素坐标变化 1 个单位。
  • 偏移量(Offset):也叫截距。它解决了 ADC 的零点对应屏幕哪个像素的问题。

公式很简单:

像素坐标 = ADC原始值 × 比例因子 + 偏移量

嗯,这里要注意。这个公式是理想情况。实际项目中,ADC 的参考电压、触摸屏的物理尺寸、屏幕的分辨率,都会影响这两个参数的值。

3.2 比例因子:决定灵敏度

比例因子怎么算?举个例子。

假设我的屏幕分辨率是 320×240。触摸屏 X 方向的最大 ADC 值是 4000,最小是 200。那么 X 方向的比例因子就是:

ScaleX = (320 - 0) / (4000 - 200) = 320 / 3800 ≈ 0.0842

这个值意味着:ADC 每变化 1 个单位,像素坐标变化约 0.0842 个像素。反过来,如果你想移动 1 个像素,ADC 需要变化大约 11.88 个单位。

我在项目中遇到过一个问题:比例因子算出来很小,比如 0.05 以下。这时候触摸会变得很“迟钝”,手指划了很长一段距离,光标才动一点点。后来发现是 ADC 的参考电压设置太高了,导致 ADC 值范围过大。

经验之谈: 比例因子最好在 0.1 ~ 1.0 之间。太小了触摸不灵敏,太大了又太灵敏,轻轻一碰光标就飞出去。

3.3 偏移量:解决零点漂移

偏移量解决的是“原点对齐”问题。

理论上,触摸屏左上角对应的 ADC 值应该是 (0, 0)。但实际不是。因为触摸屏的物理原点、ADC 的零点、屏幕的像素原点,这三者很难天然对齐。

偏移量的计算公式:

OffsetX = 0 - (ADC最小值 × ScaleX)

或者更直观的写法:

OffsetX = 目标像素值 - (ADC值 × ScaleX)

我曾经踩过一个坑。有一批触摸屏,ADC 最小值在 50 左右,但另一批在 150 左右。我一开始写死了偏移量,结果第二批屏触摸位置整体偏了十几个像素。后来改成动态校准,才彻底解决。

注意: 偏移量可以是负数。不要觉得奇怪。如果 ADC 最小值对应的像素坐标是负数,说明触摸屏的物理区域比屏幕显示区域偏右或偏下,需要向左或向上补偿。

3.4 完整的线性变换公式

把比例因子和偏移量合在一起,就是完整的变换公式:

// X 轴变换
PixelX = (ADC_X - ADC_X_Min) * ScaleX

// Y 轴变换
PixelY = (ADC_Y - ADC_Y_Min) * ScaleY

或者用我前面提到的带偏移量的形式:

PixelX = ADC_X * ScaleX + OffsetX
PixelY = ADC_Y * ScaleY + OffsetY

两种写法本质一样。我个人习惯用第二种,因为代码里只需要存 Scale 和 Offset 两个参数,计算起来更直观。

3.5 参数获取:两点校准法

比例因子和偏移量怎么得到?最简单的方法:两点校准。

步骤很简单:

  1. 在屏幕左上角显示一个校准点,用户点击。记录 ADC 值 (adc_x1, adc_y1),对应的像素坐标是 (0, 0)。
  2. 在屏幕右下角显示另一个校准点,用户点击。记录 ADC 值 (adc_x2, adc_y2),对应的像素坐标是 (ScreenWidth, ScreenHeight)。
  3. 代入公式求解。

X 轴参数计算:

ScaleX = (ScreenWidth - 0) / (adc_x2 - adc_x1)
OffsetX = 0 - adc_x1 * ScaleX

Y 轴同理。

两点校准法虽然简单,但精度有限。如果触摸屏本身有非线性误差,或者安装有倾斜,两点校准就不够用了。这时候需要三点、五点甚至九点校准。

核心要点: 两点校准假设触摸屏是完美的线性器件。如果你的项目对触摸精度要求高,或者触摸屏质量一般,建议用三点校准。三点校准可以补偿旋转和缩放误差。

3.6 坐标系方向问题

还有一个容易忽略的点:坐标系方向。

ADC 值通常是“左上角最小,右下角最大”。但有些触摸屏的 X 轴方向是反的,或者 Y 轴方向是反的。这时候比例因子会变成负数。

比如:

ScaleX = (320 - 0) / (200 - 4000) = 320 / (-3800) ≈ -0.0842

比例因子为负数,意味着 ADC 越大,像素坐标越小。这是正常的,只要你的公式里正确处理了符号,触摸方向就是对的。

我曾经接手过一个项目,触摸方向左右是反的。前任工程师把 ADC 值的正负搞反了,结果比例因子算出来是正数,但触摸方向就是不对。我花了半天才找到问题——不是比例因子错了,是 ADC 值的读取顺序反了。

3.7 知识体系图

下面这张图总结了从 ADC 原始值到像素坐标的完整映射关系:

ADC原始值 → 像素坐标 线性映射模型 ADC原始值 (X_raw, Y_raw) 线性变换参数 比例因子 (Scale) 偏移量 (Offset) 像素坐标 (PixelX, PixelY) 核心公式 PixelX = ADC_X × ScaleX + OffsetX PixelY = ADC_Y × ScaleY + OffsetY 两点校准法 左上角 + 右下角 三点校准法 可补偿旋转误差 多点校准法 五/九点,精度最高 ⚠ 注意事项 比例因子为负数时表示坐标系方向相反 | 偏移量可为负值 | 参数需根据实际硬件动态校准

3.8 代码实现示例

最后,给一个 LVGL 中坐标映射的代码片段。这是我在实际项目中用过的:

// 触摸校准参数结构体
typedef struct {
    float scale_x;
    float scale_y;
    int16_t offset_x;
    int16_t offset_y;
} touch_calib_t;

// 全局校准参数
static touch_calib_t g_calib = {
    .scale_x = 0.0842f,
    .scale_y = 0.1125f,
    .offset_x = -16,
    .offset_y = -22
};

// ADC原始值转像素坐标
void touch_adc_to_pixel(int16_t adc_x, int16_t adc_y,
                        int16_t *pixel_x, int16_t *pixel_y)
{
    *pixel_x = (int16_t)(adc_x * g_calib.scale_x + g_calib.offset_x);
    *pixel_y = (int16_t)(adc_y * g_calib.scale_y + g_calib.offset_y);

    // 边界裁剪,防止越界
    if (*pixel_x < 0) *pixel_x = 0;
    if (*pixel_x >= LV_HOR_RES) *pixel_x = LV_HOR_RES - 1;
    if (*pixel_y < 0) *pixel_y = 0;
    if (*pixel_y >= LV_VER_RES) *pixel_y = LV_VER_RES - 1;
}

这段代码看起来简单,但边界裁剪那一步很重要。ADC 值偶尔会超出校准范围,如果不做裁剪,触摸点可能会跑到屏幕外面去,导致 UI 组件响应异常。

嗯,坐标映射的基础就这些。比例因子和偏移量,两个参数,一条直线方程。理解了这个模型,后面再讲多点校准、非线性补偿,你就知道根在哪里了。