S3C2440触摸屏校准原理详解:从ADC电压到LCD坐标映射
刚把第018课「ADC和触摸屏」这部分啃完特别是第009到012节关于触摸屏编程和校准原理的内容我前前后后看了两遍又自己上手把实验做了一遍才算是真正搞懂了。这一课的标题里写的是“较准原理”实际就是“校准原理”刚开始我还以为是字幕打错字看完才发现这一小节是整个触摸屏课程里最值得反复琢磨的部分。先说说这一课适合谁看你在学ARM裸机开发LCD已经能点亮想给板子加上触摸输入或者你手头有块电阻触摸屏想搞明白为什么读出来的坐标总是偏的又或者你只是好奇当年手机上那个“十字校准界面”到底在做什么——这一课都能给你答案。我按自己的学习笔记整理了一份完整的思路包括寄存器怎么配、流程怎么走、校准公式怎么推导以及我实操中踩过的几个坑直接往下看。1. 为什么触摸屏课程里反复强调“先读电压再谈坐标”很多初学者拿到触摸屏第一反应是这东西是不是和按键一样按下就返回一个坐标值实际上完全不是。电阻触摸屏本身只是一个“模拟器件”它唯一能告诉你的是某个引脚的电压值。这个电压经过ADC转换成数字量之后才勉强算是一个“原始坐标”。而这个原始坐标和LCD上看到的像素坐标之间还隔着一整个校准过程。第018课把ADC和触摸屏放在一起讲就是因为这个原因ADC是触摸屏的“测量器官”触摸屏是ADC的“典型应用场景”。S3C2440这颗芯片内部集成了一个10位ADC控制器同时把触摸屏的四个电极引脚也接到了ADC相关引脚上所以硬件上天然就是一套东西。你在学这一课之前如果已经把ADC的普通转换流程搞熟了那触摸屏的编程其实就是在那个基础上多加了几个控制模式而已。我自己学下来的体会是如果只盯着“怎么读坐标”去学很容易迷失在寄存器配置里但如果心里先立住一个模型——“触摸屏输出的是电压ADC把电压变成数字校准把数字变成像素”整条线路就清晰多了。后面所有看似复杂的寄存器操作都只是在为这条链路服务。2. 电阻屏背后的物理课两层薄膜怎么变成一组电压值2.1 四线电阻屏的结构两层导电膜夹着绝缘点电阻触摸屏的原理说穿了就是初中物理里的分压定理。典型的四线电阻屏内部结构可以理解成上下两层导电薄膜上层是柔软的PET薄膜内表面涂了一层透明的导电材料ITO下层是玻璃基板表面同样涂了ITO导电层两层之间散布着微小的绝缘隔离点防止没有按压时上下层短路四根电极分别引出上层左右两端是X和X-下层前后两端是Y和Y-。用手指或笔尖按压时上层薄膜发生形变在按压点与下层导电层接触。这个接触点附近上下两层就形成了一个通路。接下来要做的事情就是想办法把接触点的位置“算”出来。2.2 X坐标怎么测给X层加电压从Y层读电压测量X坐标的过程是这样的在X和X-之间加上一个已知电压例如3.3V于是上层薄膜从一端到另一端形成一个均匀的电压梯度按压点处上层和下层接触这个接触点的电压就被“传导”到了下层Y或Y-引脚接到ADC输入端读到的电压值就对应按压点在X方向上的位置。如果按压点靠近X这一端读到的电压就接近3.3V靠近X-这一端读到的电压就接近0V。ADC把电压量化成0到1023的数值这就是触摸屏的X原始坐标。2.3 Y坐标怎么测把角色互换一遍测量Y坐标的过程完全对称只是把电源和接地接到Y、Y-上再从上层的X引脚读取电压。也就是说测XX方向加压Y方向读电压测YY方向加压X方向读电压。这也是为什么触摸屏控制器要提供“自动X/Y转换模式”——一次按压需要连续完成两次加压和采样硬件如果能在内部自动切换软件就省了很多事。2.4 为什么一定要有ADC触摸屏本质是个电位器用生活化的类比触摸屏的按压点就相当于一个滑动变阻器的滑片位置。滑片位置不同输出的分压就不同。你没法直接读取一个滑动变阻器的“位置”只能通过ADC测量其输出电压来反推位置。所以“触摸屏编程”的第一步永远是配置ADC而不是配置什么触摸屏协议。S3C2440的ADC分辨率是10位也就是把0到3.3V的模拟电压量化成0~1023的数字量。对于一块240x320或480x640的LCD来说这个分辨率完全够用后面在校准时会处理精度问题。3. S3C2440触摸屏接口寄存器普通转换、自动转换、等待中断怎么选3.1 涉及到的寄存器一览S3C2440的ADC和触摸屏控制器共用一组寄存器这里先列个总表后面讲到模式切换时再逐个展开寄存器主要作用ADCCONADC转换控制预分频、通道选择、启动转换、转换结束标志ADCTSC触摸屏控制选择普通/自动/分离/等待中断模式ADCDLY转换延时设定也用于等待中断模式下的按下/松开延时ADCDAT0ADC转换结果 / 触摸屏X坐标原始值ADCDAT1ADC转换结果 / 触摸屏Y坐标原始值ADCUPDN按下/松开状态标志触摸屏等待中断模式时用注意一点ADCDAT0和ADCDAT1在普通ADC模式下就是两路独立的采集结果在触摸屏自动转换模式下它们分别存放X、Y方向的坐标原始值。这个双重身份刚接触时容易绕晕我一开始就老是搞混。3.2 普通转换模式先单独验证ADC链路普通转换模式就是纯粹的ADC采集和触摸屏无关。测试时可以把某个引脚接到电位器上调节电位器观察ADCDAT0变化。这一模式主要用来验证ADC时钟配置正确、中断能触发属于触摸屏调试的“前置准备”。配置要点ADCCON里的预分频值决定ADC时钟转换后的数字放在ADCDAT0低10位软件启动转换的方式是置位ENABLE_START位转换结束后ECFLG位自动置1读结果前要等待转换完成否则可能读到上一次的旧数据。3.3 分离的X/Y转换模式可以工作但太麻烦分离模式就是手动控制先配置成X方向测量启动转换读一次再配置成Y方向测量再启动转换读一次。两次之间还需要切换引脚方向时序控制全在软件里做。这种模式不是不能用而是容易出错。切换引脚方向时需要非常小心地设置ADCTSC的XY_PST字段如果间隙时间太短测量电压还没稳定就启动转换读出来的数值会明显抖动。课程里之所以要讲它是为了让你理解底层时序实际项目里很少直接这么用。3.4 自动X/Y转换模式读取坐标的首选自动模式把X和Y测量打包成一次连续操作启动一次转换后硬件自动依次完成X方向分压测量和Y方向分压测量结果分别写入ADCDAT0和ADCDAT1。这个模式下软件不需要干预中间时序只需要把ADCTSC设置为自动转换模式启动转换等待转换完成读取两个数据寄存器。这也意味着在裸机程序里一次触摸坐标的读取可以做到“原子化”大大降低出错概率。课程代码里进入自动模式后几乎都是这个套路。3.5 等待中断模式按下、松开都要能让CPU感知等待中断模式是触摸屏编程里最精妙的一部分。平时屏幕没事时CPU不可能一直轮询触摸屏引脚那样太浪费资源。触摸屏控制器提供了一种“等待笔按下”的状态在这个状态下一旦检测到按压硬件就触发中断松开时同样会触发中断。所以在整个触摸屏裸机程序里状态是这样流转的空闲时ADCTSC设为等待中断模式CPU可以干别的事按下时进入中断切换为自动转换模式读取X/Y原始坐标读完坐标并处理完业务逻辑后再次切回等待中断模式等下一次按下。这个“用完就切回去”的动作非常容易漏漏掉之后的表现就是只能读到一次坐标后面怎么按都没反应。我在实验板上第一次跑通触摸屏中断后就踩了这个坑。3.6 ADC时钟的计算别让ADC跑得太快ADC时钟不能超过芯片手册规定的上限一般推荐在5MHz以下。ADC时钟来源是PCLK经过预分频得到ADC_CLK PCLK / (PRSCVL 1)如果PCLK是50MHz希望得到大约1MHz的ADC时钟那么PRSCVL可以取49。这个时钟决定了一次转换需要的时间太快会导致采样不稳定太慢则影响响应速度。学习阶段通常取一个保守值先把链路跑通后续再根据实际调试效果微调。4. 裸机触摸屏编程流程从按下到读到一组原始坐标4.1 初始化和进入等待中断模式下面的代码是典型的裸机初始化流程我做了详细注释。重点不是让你背寄存器值而是看清每一步在干什么void touch_screen_init(void) { // 1. 配置ADC时钟 // PCLK 50MHzPRSCVL 49ADC_CLK 1MHz ADCCON (1 14) // 预分频使能 | (49 6) // 预分频值 | (0 3); // 选择ADC通道0触摸屏走的是内部通道 // 2. 设置转换延时用来避开按下瞬间的毛刺 ADCDLY 50000; // 3. 进入等待中断模式 // XP上拉使能等待笔按下/松开 ADCTSC 0xd3; // 4. 中断控制器里使能ADC/触摸屏中断 // 具体写法取决于你的中断控制器关键是把对应中断打开 enable_irq(ADC_IRQ); }4.2 中断服务函数区分按下和松开中断触发后第一件事是看ADCUPDN寄存器分清是按下还是松开void adc_irq_handler(void) { unsigned int updn ADCUPDN; if (updn (1 15)) { // 笔抬起回到等待中断模式等待下一次按下 ADCTSC 0xd3; return; } if (updn (1 0)) { // 笔按下切换到自动X/Y转换模式 ADCTSC (1 5) | (1 3); // 自动转换模式 // 发起一次转换 ADCCON | (1 0); // 等待转换结束标志 while (!(ADCCON (1 15))); // 读取原始坐标 unsigned int x_raw ADCDAT0 0x3ff; unsigned int y_raw ADCDAT1 0x3ff; // 对原始坐标做后续处理滤波、校准等 // 处理完成后回到等待中断模式 ADCTSC 0xd3; // 清除中断挂起标志 clear_irq_pending(ADC_IRQ); } }这套流程是这一课的骨架按下中断 - 切自动转换 - 启动转换 - 等待完成 - 读数据 - 回等待模式。理解了这个状态机后面加什么滤波、校准都是往这个骨架里塞东西。4.3 按下瞬间的抖动需要滤波不能拿一次读数就当真电阻触摸屏在按压接触的一瞬间上下两层薄膜会发生微小的机械振动反映到ADC数据上就是连续几次读数不稳定。如果只读一次就拿来用你会发现触摸点有时候会莫名跳出去几个像素。课程里比较早期的做法是延时后再读但更好的方式是多次采样取平均或取中值。例如在中断里连续读5组坐标去掉最大值和最小值再对剩下的取平均。这个处理非常朴素但在裸机环境下已经足够有效。4.4 注意中断服务函数里别做太重的活这个坑很多初学者都会踩。触摸屏中断只负责“读坐标”千万不要在里面做大量浮点运算、字符串格式化、LCD绘图这类耗时操作。因为S3C2440的中断处理如果长时间占用会影响按下/松开的连续检测。我自己的习惯是中断里只读原始坐标写入一个全局变量置一个标志位主循环里检测到标志位置位后再去完成校准换算和界面响应。这个“中断存数据、主循环做业务”的模型在整个嵌入式开发里都非常受用。5. 校准原理一段线性变换公式解决“读数”到“像素”的偏差5.1 为什么不能拿原始坐标直接对应LCD像素第一次看到触摸屏读出来的原始坐标时我天真地想直接把x_raw按比例映射到LCD宽度不就行了吗结果发现点出来的位置偏差很大尤其是屏幕边缘。原因有三个触摸屏的导电层并非绝对均匀同一物理位置在不同区域的分压比例并不完全一致触摸屏和LCD贴合时存在安装偏差两个坐标系的原点不重合甚至可能有轻微旋转LCD的像素坐标和触摸屏的电压坐标二者的“比例尺”本身就不一样。所以需要建立一种数学映射把触摸屏原始坐标空间变换到LCD像素坐标空间。最常用也最稳的方式是假设两者之间存在一个二维线性变换关系。5.2 校准的线性变换公式为什么要有交叉项二维线性变换的完整形式是x_lcd A * x_raw B * y_raw C y_lcd D * x_raw E * y_raw F和大多数人直觉里的“x只影响x、y只影响y”不同这个公式里加了交叉项B和D。原因在于如果触摸屏贴合时存在角度偏转那么触摸屏X方向的变化不仅会造成LCD横向偏移也会对纵向产生一定贡献。交叉项就是用来吸收这种旋转影响的。用生活类比你在一张纸上画了一个坐标系然后把这张纸稍微旋转了一下再贴在屏幕上。你要把纸上点的坐标换算成屏幕像素就不能只做“各自缩放”还得考虑旋转带来的交叉耦合。5.3 两点校准的局限坐标轴必须在一条线上简化版的两点校准假设没有旋转公式变成x_lcd Kx * x_raw Cx y_lcd Ky * y_raw Cy这种方案只需要两个校准点就能解出四个参数但在屏幕偏转时误差会非常明显。如果只是学习验证、屏幕安装方正两点校准勉强能用一旦涉及真实产品这块屏幕在结构上稍有一点角度偏斜线性映射就会失真。这也是课程里强调三点校准的原因三个点提供6个独立方程正好解出6个参数既考虑了缩放和平移也考虑了轻微旋转。实际产品里常见校准界面画三个十字让你点底层就是这套数学。5.4 三点校准的数学过程从方程组到参数假设三个校准点在LCD上的坐标分别为P1: LCD坐标(0, 0)P2: LCD坐标(639, 0)P3: LCD坐标(0, 479)实际操作时用户会依次点击这三个十字程序分别记录对应的触摸屏原始坐标x1,y1、x2,y2、x3,y3。代入线性变换公式后得到6个方程0 A * x1 B * y1 C 639 A * x2 B * y2 C 0 A * x3 B * y3 C 0 D * x1 E * y1 F 0 D * x2 E * y2 F 479 D * x3 E * y3 F前面三个方程解出A、B、C后面三个方程解出D、E、F。用中学的代入消元法就能算出来不需要线性代数矩阵求逆。我拿自己实验的一组数据实际算过一遍三个点击点的原始坐标分别是P1原始(150, 120)对应LCD(0, 0)P2原始(870, 135)对应LCD(639, 0)P3原始(160, 890)对应LCD(0, 479)。解出来的近似参数是A≈0.888B≈-0.0115C≈-131.8D≈-0.013E≈0.622F≈-72.7。把这组参数代回三个校准点验证都能回到对应的LCD坐标。每个芯片的安装情况不同参数自然不同不需要死记。5.5 在裸机程序里做校准流程的步骤我自己在板子上实现校准界面时是这么设计的在LCD固定位置画出第一个十字等待用户点击记录该点的触摸屏原始坐标多采几次取平均画出第二个十字重复记录画出第三个十字重复记录解方程组得到A到F六个参数把参数保存到NAND Flash里下次开机直接读取写一个测试模式在随机位置点击把换算后的坐标画成十字和实际触摸位置比对。校准完成后触摸屏的坐标处理就从读取原始值变成了int lcd_x (int)(A * raw_x B * raw_y C); int lcd_y (int)(D * raw_x E * raw_y F);这个换算看似简单却是整个触摸屏可用的关键一步。我在做完这一步后第一次体验到在LCD上画线能跟着手指走那种感觉比单纯点亮屏幕有成就感多了。5.6 校准的小细节校准点不要选在屏幕正中心或太边缘校准点的选取也有讲究。太靠近屏幕边缘触点按压区域容易超出有效触摸范围太靠近中间三个点坐标太接近解方程时会放大数值误差。比较稳妥的做法是选在屏幕内侧大约5%到10%边距的位置让三个点形成一个比较大的三角形分散开。如果校准之后发现某些区域点击偏差大大概率不是公式错了而是校准点本身采集到的原始坐标含有抖动误差导致解算出的参数不够准确。这就需要回到采样滤波环节而不是反复调整校准界面。6. 实操踩坑记录转换时序、抖动滤波和参数验证6.1 没等转换结束就读数据得到一堆旧值这是我第一个踩的坑。在自动转换模式下启动转换之后立刻读ADCDAT0和ADCDAT1读到的还是上一次的残留数据。原因是ADC转换需要一定时间如果不等ECFLG置1读取动作就会领先于转换完成。解决办法就是前面代码里写的读数据之前加一个while循环等待转换结束标志。这个等待很关键尤其是后续要加滤波、多次采样时每一次都要确保转换真正完成后再读。6.2 松开中断和按下中断没区分程序反复进入自动转换中断里如果不判断ADCUPDN是按下还是松开就会出现一个现象手还按在屏幕上程序就已经切回了等待中断模式然后硬件检测到“依然是按下状态”马上又触发中断来回反复坐标数据一片混乱。正确的做法是进来先判断松开标志如果是松开直接恢复等待中断模式并返回只有确认是按下才去执行自动转换和读取坐标。这个分支顺序我一开始写反了调试了很久才反应过来。6.3 校准后画线有“毛刺”甚至断线先查滤波再查参数校准参数如果是对的但在屏幕上画线时偶尔会飞出几个孤立的点这通常是原始坐标抖动造成的。不要一上来就怀疑校准公式更不要反复重新校准——先做一下原始坐标的采样滤波很多时候问题立刻消失。我后来在中断里连续读取5次坐标排序后取中值再用来做校准和画线。实测下来触摸画线的稳定度提升非常明显。注意取中值比取平均更能抵抗离群点个别极端抖动不会污染最终结果。6.4 触摸屏中断和普通ADC中断共用资源子中断标志要清干净S3C2440的触摸屏相关中断和ADC转换中断在中断控制器里有一定关联如果只清了主中断标志而忘了清触摸屏子中断相关标志中断有可能反复进入或者丢失。这个细节属于不看芯片手册很难发现的坑我当时的排查思路是在中断服务函数结尾把所有相关挂起位置都清理一遍然后重新使能中断。在写裸机代码时养成“随时查手册、随手记录寄存器含义”的习惯会省掉大量靠猜的调试时间。最后补充一点我在校准验证时的体会整个触摸屏课程学下来我的感受是触摸屏编程的代码量其实不大真正的门槛在于对状态切换的理解和对校准数学模型的信任。第一次算完校准参数我反复用测试点验证总担心算错了后来把三个校准点代回公式手算了一遍才彻底放心。你要是也做到这一步不妨把换算后的坐标直接打印到串口和LCD上显示的十字位置对照偏差在两三个像素以内就说明校准成功了。之后再做画板、菜单点击这类交互功能就能直接复用这套底层逻辑了。