CH554驱动ST1633i触摸屏:I2C通信与坐标解析实战

发布时间:2026/9/9 5:41:16
CH554驱动ST1633i触摸屏:I2C通信与坐标解析实战 简介面向使用沁恒微CH554单片机开发电容触摸屏应用的嵌入式工程师这套代码提供了ST1633I触摸IC的完整驱动实现同时兼容GT911、ILI2511、FT6236等多款常用触摸控制器可直接用于CH554单片机的电容触摸屏方案开发。工程基于Keil搭建压缩包内共56个文件包含.c源码、.h头文件、.uvproj/uvopt工程配置、hex固件以及obj/lst等编译中间文件整体大小仅409KB。其中头文件负责寄存器与接口定义C源文件实现I2C通信、触摸坐标解析与USB HID上报hex固件则可用于直接烧录验证。当前已有300人学习/下载适合有一定单片机基础、正在做USB HID或触控方案的工程师参考。工程目录结构清晰源码层次分明既方便快速评估触摸屏整体工作流程也能作为基于CH554的触控产品二次开发模板帮助缩短原型验证周期。 做嵌入式这么多年触摸屏这块算是比较常见但坑也比较多的外设之一。前阵子帮朋友捣鼓一个项目需要用一个国产8位单片机去驱动一块拆机触摸屏屏的触摸控制IC型号是ST1633i主控选了沁恒微电子的CH554。折腾了几天把整个流程跑通了代码也整理干净了这里把完整思路和实操过程做个记录方便以后自己复用也给打算这么玩的朋友一个参考。先交代一下背景。ST1633i是深圳升迈Silead出品的一颗电容触摸屏控制IC常见于手机、平板、工控面板这些设备上支持I2C接口输出触摸坐标单点、多点都能做。CH554则是沁恒微电子的一款增强型E8051内核单片机主频最高24MHz带USB控制器、内置ADC、PWM、UART、SPI、I2C等外设最重要的是价格便宜、开发简单Keil C51就能写特别适合做这种嵌入式控制类的小项目。这块IC的代码难点不在于复杂度而在于几个地方的细节I2C时序的把握、寄存器地址的确认、坐标数据的解析格式以及多点触摸时的数据包处理。这些信息散落在芯片手册里网上能搜到的现成代码不多很多还是适配ARM平台的直接搬到51上不一定能跑。我这篇文章就把基于CH554的代码拆开揉碎讲清楚适合手里正好有这个IC、或者想学51驱动触摸屏的朋友参考。1. 整体设计思路与方案选型1.1 为什么选中CH554来做这个驱动选CH554而不是STM32核心原因是成本和开发效率。CH554在国产8位机里属于资源比较全的1KB XRAM、16KB Flash、最多14个I/O口关键是自带硬件I2C和USB一片的价格比STM32F103便宜不少而且Keil C51的开发环境大家基本都熟上手门槛很低。对一个小型触控面板或者DIY项目来说性能完全够用。另一个重要原因是CH554的供电和电平适配。ST1633i的工作电压一般在2.8V到3.3V之间CH554支持3.3V供电两者可以直接对接不需要额外做电平转换这省了不少事。如果换成5V的51单片机还要考虑I2C上拉电阻和电平匹配的问题麻烦不少。1.2 ST1633i 这块触摸IC的定位ST1633i本质上是把电容触摸的模拟前端和数字处理逻辑封装在一起的专用IC。它负责扫描触摸屏上的电容变化经过内部算法处理后把触摸点的坐标信息通过I2C接口输出给主控。主控不需要关心电容检测这些底层的事情只需要正确读取坐标数据就行。这里有个容易误解的点ST1633i不是屏幕驱动IC它只管触摸不管显示。屏幕的显示部分要另外接RGB或者MIPI接口的驱动芯片两者是独立的。我这次项目里用的是一块已经集成好的屏幕模组显示和触摸分开接触摸部分就是通过I2C接到CH554上的。1.3 方案整体框图整个系统的连接关系其实很简单CH554的P1.0引脚配置为SCLP1.1引脚配置为SDA与ST1633i的I2C接口相连。ST1633i的中断输出引脚INT连接到CH554的一个外部中断输入引脚我用的是P3.2/INT0有触摸事件发生时IC会拉低这个引脚通知主控来读数据。CH554通过UART把解析好的坐标数据发送出去方便调试和后续对接其他设备。这个方案的好处是主控不需要轮询触摸状态全靠中断触发省CPU资源响应也快。后面代码部分我会详细展开。2. 硬件连接与底层驱动的关键细节2.1 引脚连接与注意事项CH554和ST1633i的接线我列个表这个表是我实际验证过的照着接基本没问题CH554引脚功能连接目标说明P1.0SCLST1633i SCL接2.2kΩ上拉电阻到3.3VP1.1SDAST1633i SDA接2.2kΩ上拉电阻到3.3VP3.2INT0ST1633i INT下降沿触发外部中断GND地ST1633i GND共地3.3V电源ST1633i VDD供电三项注意上拉电阻必须加I2C总线是开漏结构不加会上拉电阻的话通信不稳定严重的直接读不到数据。我用的2.2kΩ实测没问题如果用10kΩ也行就是上升沿慢一点速率不高的话无所谓。中断引脚不要悬空ST1633i的INT是开漏输出需要外部上拉电阻我用10kΩ到3.3V不然中断信号拉不低主控收不到通知。TVS管和地线处理如果触摸屏的排线比较长或者应用现场有静电风险建议在SCL、SDA线上各加一个TVS管到地TVS管的接地端要单独走线到主控IC的地不要跟电源地混在一起否则ESD电流会通过地线干扰I2C信号。这个是我踩过的坑之前偷懒直接共地结果触摸一按就死机。2.2 ST1633i的I2C通信协议要点ST1633i的I2C从机地址是0x157位地址写操作时地址字节是0x2A读操作时是0x2B。这个地址是芯片固有的不同型号可能不同拿到新IC先查数据手册确认。I2C的通信速率方面ST1633i支持标准模式100kHz和快速模式400kHz。我用的是400kHzCH554的硬件I2C配置成快速模式没问题。但要注意CH554的硬件I2C模块在连续读取多字节时需要在最后一个字节前发送NACK信号这个细节如果没处理好读出来的数据会错位。寄存器层面ST1633i的寄存器空间不大常用的几个地址我整理如下寄存器地址功能说明0x00设备ID/版本读出来通常是0x15或类似值0x01状态寄存器高位表示是否有触摸0x02-0x05第一个触摸点坐标X低8位、X高4位、Y低8位、Y高4位0x06-0x09第二个触摸点坐标多点触摸时才有效0x0A按键状态有些屏带触摸按键功能实际坐标系需要根据屏幕分辨率做换算。比如我用的屏是480x800触摸IC输出的原始坐标是一个12位的数值0-4095需要按比例映射到实际分辨率上。这个换算逻辑我会在代码里给出。2.3 CH554的硬件I2C初始化CH554自带硬件I2C控制器初始化代码相对简单。我用的是Keil C51开发环境工程配置里需要选择CH554芯片型号然后在代码里调用对应的头文件。初始化流程是关总中断、配置I2C引脚为开漏模式、设置I2C时钟分频、使能I2C模块、开总中断。时钟分频这块要注意CH554的系统时钟是24MHzI2C模块的时钟源是系统时钟的12分频也就是2MHz再通过分频寄存器配置成400kHz。具体分频值我后面代码里会有直接抄就行。3. 驱动代码逐段解析3.1 I2C底层读写函数先看最底层的I2C字节读写。CH554的硬件I2C使用起来跟普通51的软件模拟I2C不太一样它有一个状态机需要根据状态寄存器的值来判断当前处于什么阶段。我封装了两个基础函数写一个字节和读一个字节上层所有操作都基于这两个函数。// I2C写一个字节返回0表示成功非0表示失败 UINT8 I2C_WriteByte(UINT8 addr, UINT8 dat) { UINT8 status; // 发送起始条件 I2C_START(); // 发送设备地址写标志 I2C_WriteByteToBuf(addr 1); // 等待传输完成 status I2C_WaitACK(); if(status ! 0) return 1; // 发送寄存器地址 I2C_WriteByteToBuf(dat); status I2C_WaitACK(); if(status ! 0) return 1; // 发送停止条件 I2C_STOP(); return 0; }这里有个细节CH554的数据手册里要求写入数据到I2C数据寄存器后要等待中断标志位置位再读取状态寄存器确认应答信号。我一开始没按这个顺序来直接读状态导致第一次通信就一直卡死。正确做法是先等中断标志再判断状态顺序不能反。3.2 读取触摸坐标的核心函数读取触摸坐标是整个驱动最核心的部分。我的实现思路是外部中断触发后主控先读状态寄存器判断是否有有效触摸如果有再依次读坐标寄存器。这里用到了连续读的功能也就是I2C的重复起始条件Restart一次把多个寄存器的值读出来。// 读取触摸坐标返回触摸点数量坐标通过指针参数返回 UINT8 ST1633i_ReadTouch(UINT16 *x, UINT16 *y) { UINT8 buf[5]; UINT8 touch_cnt 0; // 先读设备状态寄存器 I2C_Start(); I2C_WriteByteToBuf(0x2A); // 写地址 I2C_WaitACK(); I2C_WriteByteToBuf(0x01); // 状态寄存器地址 I2C_WaitACK(); // 重复起始转为读模式 I2C_Restart(); I2C_WriteByteToBuf(0x2B); // 读地址 I2C_WaitACK(); // 连续读取5个字节状态 点1的X/Y坐标 buf[0] I2C_ReadByte(1); // ACK buf[1] I2C_ReadByte(1); // ACK buf[2] I2C_ReadByte(1); // ACK buf[3] I2C_ReadByte(1); // ACK buf[4] I2C_ReadByte(0); // NACK最后一个字节 I2C_Stop(); // 解析坐标 if(buf[0] 0x80) // 最高位表示是否有触摸 { touch_cnt buf[0] 0x0F; if(touch_cnt 0) { // 12位坐标X buf[1] ((buf[2] 0x0F) 8) // Y buf[3] ((buf[4] 0x0F) 8) *x buf[1] | ((buf[2] 0x0F) 8); *y buf[3] | ((buf[4] 0x0F) 8); } } return touch_cnt; }读坐标这里有个常见的坑坐标数据的高4位在状态字节后面的一个半字节里不是独立的高字节。很多第一次接触这颗IC的人会直接把buf[2]当成高8位来用结果坐标完全对不上。正确做法是像上面代码里写的那样只取低4位跟前面8位拼接成12位的数据。3.3 外部中断与主循环的配合触摸屏的读取千万不要在主循环里轮询那样既浪费CPU又可能漏掉快速滑动的手指。我这边用的是CH554的外部中断0配置成下降沿触发。ST1633i有触摸事件时INT引脚会从高电平拉低一直保持到主控读完数据为止。这样设计的好处是没有触摸的时候主控完全不用管I2C省电省心。// 外部中断0服务函数 void INT0_ISR(void) interrupt 0 { UINT16 x 0, y 0; UINT8 cnt; // 关闭中断避免重入 EA 0; // 读取触摸坐标 cnt ST1633i_ReadTouch(x, y); if(cnt 0) { // 坐标映射到实际屏幕分辨率 UINT16 real_x (UINT32)x * 480 / 4096; UINT16 real_y (UINT32)y * 800 / 4096; // 通过UART发送坐标格式AA xx yyyy BB UART_SendByte(0xAA); UART_SendByte(real_x 8); UART_SendByte(real_x 0xFF); UART_SendByte(real_y 8); UART_SendByte(real_y 0xFF); UART_SendByte(0xBB); } // 清中断标志 INT0 0; EA 1; }中断服务函数里有个细节要注意I2C操作不能放在中断里执行太长时间否则会影响其他功能的实时性。我实测了一下读一次完整坐标大概需要几十微秒在24MHz主频下完全能接受但如果你的系统里还有别的更紧迫的中断建议把I2C读取放在主循环里做中断里只置标志位。3.4 坐标映射与多点触控单点触摸的实现相对简单上面的代码已经搞定了。多点触摸才是真正磨人的地方。ST1633i最多支持5点触摸每个点占4个字节数据量比单点大不少。我这次项目只需要单点所以上面给的代码也只处理单点场景但把多点逻辑说清楚方便以后扩展。多点数据格式是状态字节低4位表示触摸点数后面依次跟各个点的坐标每个点4字节X低8位、X高4位、Y低8位、Y高4位。读完一个点的坐标后如果触摸点数大于1还要继续读后面的数据。因为连续读模式下寄存器地址会自动递增所以流程是先读状态寄存器然后根据触摸点数决定连续读多少个字节。我做多点扩展的时候直接把读触摸函数改成了循环读每4个字节解析出一个点的坐标存到数组里。但要注意最多支持几个点要以数据手册为准有些低配版IC即使标称5点实际只有前2个点是有效数据。这个只能拿实物测试来确认。4. 调试过程中的典型问题与排查技巧4.1 I2C总线卡死问题这是我在调试过程中遇到最多的问题现象是程序跑起来后第一次读触摸就卡在I2C等待ACK的循环里出不来。排查思路是这样第一步先确认上拉电阻。I2C总线必须接上拉电阻到电源阻值范围2.2kΩ到10kΩ都可以但如果没有上拉SDA和SCL线会一直处于低电平通信必然失败。第二步排查地址是否正确。ST1633i的7位地址是0x15转换到8位就是0x2A写和0x2B读。有的资料上写的是0x15直接左移一位得到0x2A这个没错但如果用的屏幕模组有多个I2C设备比如触控和显示共用总线地址可能被硬件跳线改掉最好先用I2C扫描程序把所有挂在总线上的设备地址扫一遍。第三步是检查时序。CH554的硬件I2C模块如果通信出错会进入异常状态此时必须调用一次I2C复位将I2C模块的使能位关掉再打开才能恢复。我在驱动里加了一个超时保护如果等待ACK超过一定时间就强制复位I2C模块并重新初始化。// 带超时的I2C等待ACK函数 UINT8 I2C_WaitACK_Timeout(void) { UINT16 timeout 0; while(timeout 5000) { if(I2C_GetStatus() I2C_ACK) return 0; // 成功 if(I2C_GetStatus() I2C_NACK) return 1; // 收到了NACK timeout; } // 超时复位I2C模块 I2C_Reset(); return 2; // 失败 }加了超时保护之后即使硬件出现异常程序也不会死在那里而是会自动恢复。这个经验值得所有写I2C驱动的人借鉴。4.2 触摸坐标跳动或翻转坐标读出来了但数值一直在跳或者跟手指的实际位置对不上这种问题通常是以下几个原因导致的坐标换算比例不对上面提到过原始坐标是12位的0-4095实际屏幕可能只有480x800需要做等比映射。如果比例系数搞错坐标要么偏小要么超出范围。数据字节序弄反了ST1633i输出的是先低8位后高4位如果你先读了高字节再读低字节拼接出来的数值就不对。屏幕安装方向不同导致坐标轴翻转触摸屏的坐标原点和LCD的坐标原点可能不一致需要在代码里做镜像或者旋转处理。这个没有标准答案得根据实际安装方向调试确认。我的做法是在调试阶段把解析前后的原始数据都通过串口打印出来然后手动点击屏幕的几个固定位置根据原始值和实际位置的对应关系反推换算公式。找到规律后再把固定换算做到代码里基本上一次就能调好。4.3 中断频繁触发导致系统卡顿ST1633i的INT引脚在触摸期间会一直拉低如果你在中断服务函数里做完整的I2C读取和串口发送触摸比较快速滑动的时候中断频率会很高可能影响其他任务的执行。解决方案有两种一种是上面提到的中断里只标志一个触摸事件主循环里检测到标志后再执行读取和发送另一种是引入防抖逻辑比如连续多次读取到的坐标变化量都很小就认为手指停住了不再上报。实际项目里我一般两种方案结合用效果最好。4.4 典型问题速查表现象可能原因处理方法I2C通信超时上拉电阻缺失、地址错误、时序异常检查硬件连接、用I2C扫描确认地址、加超时复位坐标完全不对字节序错误、坐标换算错误打印原始数据对比修正拼接逻辑坐标偶尔跳变触摸抖动、数据未读完被覆盖加滤波算法、降低读取频率触摸无响应中断引脚未配置、电源问题检查INT引脚配置、确认供电稳定死机或复位地线干扰、静电TVS管单独接地、电源加电容滤波5. 代码扩展与工程化的一点体会驱动跑通之后我在这个基础上做了几个功能扩展这里一并分享。一个是在坐标上报的串口协议里加入了校验位和帧尾方便上位机做数据校验。原始协议是AA 坐标 BB帧头帧尾很容易被干扰数据打断改成AA 长度 数据 校验和 BB之后稳定性好很多而且后续要加多点触摸的数据也方便扩展。另一个是把坐标转换做成了查表方式因为在某些固定分辨率的屏幕上用查表比每次做乘除法运算快得多。虽然51单片机的乘除法在几微秒内就能算完但在触摸响应频率很高的情况下省一点是一点。源码我最后整理成了三个文件i2c_drv.cI2C底层驱动、st1633i.c触摸IC驱动、main.c主程序和外设初始化。这样分层的好处是后续换主控平台的话只需要改底层I2C驱动触摸IC的逻辑基本不用动复用的成本很低。最后再分享一个调试工具上的小技巧我用逻辑分析仪抓I2C波形来排查通信问题比靠肉眼看代码高效得多。CH554的I2C时序其实比较标准用逻辑分析仪可以直观看到起始条件、地址、ACK/NACK、数据字节每一个环节是否有异常。如果你手头有逻辑分析仪强烈建议在调试I2C设备时用起来能少走很多弯路。本文还有配套的精品资源点击获取