ST1633I触摸芯片与CH554单片机I2C驱动移植及坐标解析实践

📅 发布时间:2026/10/14 1:25:56
ST1633I触摸芯片与CH554单片机I2C驱动移植及坐标解析实践
简介基于沁恒微 CH554 的 ST1633I 触摸屏源码包定位嵌入式人机交互开发面向需要驱动电容屏、完成坐标采集与 USB HID 上报的软硬件工程师中高级开发者可直接借鉴其驱动框架。压缩包共 56 个文件、约 409KB主体由 10 个 C 源文件与 12 个头文件构成附带 Keil 工程文件、hex 固件及编译中间文件既有便于查看配置的工程入口也有可单独阅读的底层驱动模块。代码整合了多种主流电容触控芯片支持覆盖 GT911、ILI2511、FT6236 等常见型号从底层初始化到上层数据上报均有完整链路对 I2C 读写、中断上报、Flash 参数存取等关键流程作了模块化封装移植到其他单片机平台时可减少重复开发。已有 302 人学习/下载适合正在选型或调试电容触摸方案的开发者作实测对照、排错参考与移植蓝本。1. ST1633I与沁恒CH554一颗电容触摸芯片在一颗51核单片机上的落地如果你同时拿到了一个ST1633I触摸屏模组和一块沁恒微CH554开发板想搞清楚它们能不能跑起来、代码怎么组织、调通要踩哪些坑那么这篇笔记就是按这个场景写的。ST1633I是一颗常见的电容触摸控制IC通过I2C接口上报触点坐标CH554则是一颗内置USB控制器、基于增强型51内核的MCU。把ST1633I的驱动代码移植到CH554上是典型的“现成外设通用MCU”组合不需要太多玄学但有几个容易翻车的细节。本文不依赖某个具体项目代码包而是把这个方案从头到尾拆开硬件连接、I2C时序、寄存器初始化、坐标上报、手势解析以及我实际调过的坑。适合正在做触摸按键替代、小尺寸人机交互面板或者想用一颗低价MCU驱动触摸屏的开发者新手照着做能跑通熟手可以重点看第三、四章的参数边界。2. 先把硬件关系理顺ST1633I的I2C从机时序与CH554的引脚复用2.1 ST1633I到底是一个什么样的器件ST1633I是一颗电容式触摸屏控制IC常见于7寸以下的小尺寸屏幕模组上。它内部完成电容检测、信号放大、坐标计算然后通过I2C接口把触摸点的原始坐标、触点数量、手势事件上报给主控。由主控负责的是给它上电、配置工作模式、周期性读取触摸数据、把坐标换算成屏幕像素坐标。这颗IC的I2C从机地址通常是0x557位地址或0xAA8位地址写方向具体要看模组厂商标定。它的I2C通信速率一般支持标准模式100kHz和快速模式400kHzCH554的硬件I2C最高能跑多少需要查对应型号数据手册但实际用400kHz是常见做法。CH554是一颗增强型51内核MCU主频最高48MHz内置了硬件I2C主机、UART、SPI、PWM以及USB控制器。对于驱动ST1633I来说最有价值的是它的硬件I2C模块它不需要你用GPIO去模拟I2C时序省掉了很多位操作和延时调整。需要留意的是CH554的I2C引脚和UART、SPI存在部分引脚复用选型时先确定触摸屏的I2C接在哪一组引脚上避免和调试串口冲突。实际项目中我一般会把ST1633I的I2C放在P1口或P3口的I2C功能复用引脚上然后单独留出一路UART做日志打印。2.2 最小硬件连接与上电时序ST1633I模组通常引出VCC、GND、SCL、SDA、INT、RESET这六根线。RESET不是必须每次都拉但第一次上电后建议给一个低电平脉冲确保IC内部状态机复位到已知状态。INT是触摸中断输出有触摸事件时由高拉低CH554可以把INT接到一个外部中断引脚这样可以避免轮询浪费CPU。硬件连接请按下表接线ST1633I引脚CH554引脚说明VCC3.3VST1633I一般支持2.8V-3.3V不要接5VGNDGND共地是必须的SCLP1.0I2C时钟确认复用配置SDAP1.1I2C数据确认复用配置INTP3.2外部中断0可选轮询模式可以不接RESET任意GPIO上电后拉低10ms再拉高上电顺序在触摸屏这里真的能出问题。常见做法是先让CH554完成自身时钟配置和GPIO初始化再给ST1633I的RESET引脚一个复位脉冲最后初始化I2C模块。如果反过来先初始化I2C再复位触摸IC可能出现触摸IC复位期间SCL被拉死的情况后续通信全部失败。CH554的I2C模块需要在复位触摸IC之后再配置让总线处于空闲状态时再使能主机模式。2.3 CH554的I2C配置代码点开硬件模块的三种模式CH554的I2C模块使用起来非常直接核心是把I2C控制寄存器配置成主机模式然后操作发送、接收状态位。下面是一段初始化I2C并发送起始位的代码用C语言写// CH554 I2C主机模式初始化 #include CH554.H void I2C_Init(void) { // 将P1.0和P1.1配置为I2C功能引脚 P1_MOD_OC ~0x03; // P1.0/P1.1 设置为推挽输出 P1_DIR_PU | 0x03; // P1.0/P1.1 设置为输出方向 // 开启I2C硬件模块主机模式速率设置 I2C_CTRL bI2C_IF_EN | bI2C_IF_ADDR | bI2C_MODE; I2C_CTRL ~bI2C_IF_BUSY; // 清除忙标志 } void I2C_Start(void) { I2C_CTRL | bI2C_IF_BUSY; // 触发起始位 while (I2C_CTRL bI2C_IF_BUSY); // 等待发送完成 }这段代码做的事情很明确先把P1.0和P1.1设为推挽输出这是I2C开漏信号在CH554上能否被外部上拉正确驱动的关键然后使能I2C硬件模块并设置为主机模式最后每次发送起始位时置位忙标志硬件完成后自动清掉。注意bI2C_IF_ADDR其实是用在从机模式的地址匹配使能主机模式下可以不用置位。上面的写法里为了演示就把常用寄存器位都打开了实际项目里最好像下面这样精简void I2C_Init(void) { P1_MOD_OC ~0x03; P1_DIR_PU | 0x03; I2C_CTRL bI2C_IF_EN | bI2C_MODE; }2.4 ST1633I的寄存器雏形先知道要读什么ST1633I的寄存器空间不大核心就三类设备信息寄存器、触控状态寄存器、坐标数据寄存器。设备ID一般在偏移地址0xA0的位置用于确认I2C通信正常。触控状态寄存器用来判断当前有几个触点按下坐标数据寄存器按触点ID连续存放X和Y的原始值。实际代码里第一步要做的是读设备ID这一步能通过基本就说明I2C物理链路和从机地址配置是对的。CH554读ST1633I寄存器时需要先发送要读的寄存器地址然后重新发送起始位并带上读方向位再从总线上读取数据。这里有个值得注意的细节ST1633I支持的寄存器自动递增读模式可以一次读完所有坐标数据避免每读一个坐标就重复发送寄存器地址能明显降低I2C总线占用但这取决于具体型号是否支持ST1633I有部分型号支持建议以模组厂家的规格书为准。我实际遇到过声称兼容但读出来全0的模组后来确认是不支持自增读改成逐字节读就正常了。3. 把CH554的代码框架搭出来I2C驱动、延迟和五步初始化流程3.1 I2C读写函数的完整实现与参数说明驱动ST1633I最少需要三个基础函数写寄存器、读寄存器、读多个连续寄存器。CH554的硬件I2C在字节传输完成后会置位完成标志所以不需要靠延时去等。下面给出我常用的实现带详细注释// ST1633I I2C基础读写函数 // 写单字节寄存器 bit I2C_WriteReg(UINT8 addr, UINT8 data) { I2C_Start(); I2C_WriteByte(0xAA); // 设备写地址0x55左移一位 if (I2C_GetAck() 0) return 0; // 无ACK说明设备不在总线上 I2C_WriteByte(addr); I2C_GetAck(); I2C_WriteByte(data); I2C_GetAck(); I2C_Stop(); return 1; } // 从指定寄存器读单字节 UINT8 I2C_ReadReg(UINT8 addr) { UINT8 dat; I2C_Start(); I2C_WriteByte(0xAA); I2C_GetAck(); I2C_WriteByte(addr); I2C_GetAck(); I2C_Start(); // 重复起始位 I2C_WriteByte(0xAB); // 设备读地址0x55左移一位加1 I2C_GetAck(); dat I2C_ReadByte(0); // 最后一个字节发NACK I2C_Stop(); return dat; } // 连续读取n个字节 void I2C_ReadMulti(UINT8 addr, UINT8 *buf, UINT8 n) { UINT8 i; I2C_Start(); I2C_WriteByte(0xAA); I2C_GetAck(); I2C_WriteByte(addr); I2C_GetAck(); I2C_Start(); I2C_WriteByte(0xAB); I2C_GetAck(); for (i 0; i n; i) { if (i n - 1) buf[i] I2C_ReadByte(0); // 最后一字节NACK else buf[i] I2C_ReadByte(1); // 前面的字节发送ACK } I2C_Stop(); }这段代码里有三个关键参数值得说清楚。第一个是设备地址0xAA和0xAB这是8位地址写法分别代表写方向和读方向如果你用的是7位地址写法就是0x55两种写法实质相同但容易在混用代码时出错。第二个是I2C_ReadByte的参数0表示读完之后回NACK1表示回ACK连续读多字节时最后一个必须回NACK否则设备会继续输出数据导致总线被锁死。第三个是重复起始位的使用从读寄存器切换到读数据时必须经过重复起始位这是I2C协议的规定不能省略。3.2 触摸IC的五步初始化流程ST1633I的初始化代码其实不复杂但顺序错了后面就会读不到坐标。我整理的初始化顺序是复位引脚操作、等待内部启动、检查设备ID、读取固件版本、设置扫描频率。每次调用这个初始化函数都应该让IC回到一个确定的状态。给出一段完整初始化代码#define ST1633I_ADDR_W 0xAA #define ST1633I_ADDR_R 0xAB #define ST1633I_REG_ID 0xA0 #define ST1633I_REG_FW 0xA2 #define ST1633I_REG_RATE 0x80 bit ST1633I_Init(void) { UINT16 retry; // 第一步复位引脚操作 RESET_PIN 0; Delay_ms(20); RESET_PIN 1; Delay_ms(50); // 让内部LDO稳定 // 第二步检查设备ID for (retry 0; retry 5; retry) { if (I2C_ReadReg(ST1633I_REG_ID) 0x33) break; Delay_ms(10); } if (retry 5) return 0; // ID读不到初始化失败 // 第三步读取固件版本一般0x01-0x0A fw_ver I2C_ReadReg(ST1633I_REG_FW); // 第四步设置触摸扫描频率默认值即可 I2C_WriteReg(ST1633I_REG_RATE, 0x10); // 第五步如果支持设置触摸中断使能 I2C_WriteReg(0xA4, 0x01); return 1; }初始化后有一个容易忽略的点第一笔触摸事件可能出现在初始化后几十毫秒内但此时IC还处于内部校准阶段读到的事件大概率是无效的。我一般会在初始化后加一个200ms左右的稳定延时再开始读取坐标否则系统刚上电时可能会误触发一次点击事件。这个现象不是IC坏了而是上电自校准期间的正常行为。3.3 CH554的系统时钟配置与I2C速率关系CH554的I2C时钟源来自系统时钟分频系统时钟越高I2C可配置的速率范围越大。默认情况下CH554内部RC振荡器提供12MHz时钟如果要跑400kHz的I2C需要把时钟配置到24MHz或更高。这里给出一段系统时钟配置代码// 配置CH554系统时钟为24MHz void CLK_Init(void) { SAFE_MOD 0xA2; // 进入安全模式 CLOCK_CFG 0x03; // 24MHz SAFE_MOD 0x00; // 退出安全模式 }注意CLOCK_CFG这个寄存器的取值不是固定的它会因芯片型号而不同我的经验是查阅对应头文件中CLK_CFG相关的宏定义不要照抄别的板子。I2C速率寄存器需要和系统时钟配合计算CH554的头文件里给了几个典型配置值直接选用最接近400kHz的配置即可。调试时可以从100kHz跑起等通信稳定后再往上提速这样遇到波形幅值不足的问题时更容易定位不会一上来就是时序问题。4. 坐标数据解析与点击手势识别从原始寄存器读到应用层可用的事件4.1 坐标数据的存储格式与多触点排列ST1633I上报的数据帧结构大体上是状态字节、点数、X坐标高位、X坐标低位、Y坐标高位、Y坐标低位这样一个触点占4字节。状态字节的低四位表示当前有效触点数量最高位有时用于表示是否有大动作手势。多个触点按ID顺序排列第一个触点的坐标在最前面。读取时最好一次读8-16个字节然后统一解析减少I2C总线上的消耗。下面给出一个读取与解析单点和双点数据的代码// 触摸数据结构体 typedef struct { UINT8 point_num; // 有效触点个数 UINT16 x[2]; // X坐标 UINT16 y[2]; // Y坐标 } TouchData; // 读取并解析触摸数据 void ST1633I_ReadTouch(TouchData *td) { UINT8 buf[9]; // 从0x02开始读状态寄存器在0x02坐标从0x03开始 I2C_ReadMulti(0x02, buf, 9); td-point_num buf[0] 0x07; if (td-point_num 0) { td-x[0] ((UINT16)(buf[1] 0x0F) 8) | buf[2]; td-y[0] ((UINT16)(buf[3] 0x0F) 8) | buf[4]; } if (td-point_num 1) { td-x[1] ((UINT16)(buf[5] 0x0F) 8) | buf[6]; td-y[1] ((UINT16)(buf[7] 0x0F) 8) | buf[8]; } }这段代码特别要注意的是掩码操作。ST1633I的坐标分辨率通常超过8位但高4位和低8位在帧里不是连续排列的高4位只在buf[1]的低4位有效所以必须做0x0F掩码后才左移8位。很多新手在这里直接做buf[1] 8结果坐标在滑动时会周期性地跳变其实就是没有掩码把高4位里的干扰位也带了进来。4.2 坐标滤波与抖动消除触摸IC输出的坐标虽然经过内部滤波但在手指轻触或环境干扰时仍然会有几个像素的抖动。处理方式有两种一是软件平均值滤波取最近4次采样求平均适合低速移动的场景二是变化阈值判定只有当坐标变化量大于一个预设值时才认为手指真的移动了。我一般用后一种因为STM32上常见的滑动滤波在CH554这种51核上计算量偏大会占掉主循环不少时间。给出一个基于阈值的滤波实现// 基于变化阈值的坐标滤波 #define MOVE_THRESHOLD 8 // 8个像素以内视为抖动 UINT16 FilterX(UINT16 new_x, UINT16 last_x) { INT16 diff (INT16)new_x - (INT16)last_x; if (diff MOVE_THRESHOLD || diff -MOVE_THRESHOLD) return new_x; // 真实移动 return last_x; // 抖动保持上一个值 }参数MOVE_THRESHOLD要根据屏幕分辨率和触摸IC的原始精度调整。分辨率是480x272的屏幕阈值设在4-8之间效果不错如果分辨率到1024x600阈值可以放宽到12-16。阈值设得太小过滤不掉噪声设得太大手指滑动时会有明显的延迟感这是取舍问题没有绝对正确的值。我在模拟项目X里用480x272的屏幕调试时最后定在6像素既过滤掉了轻触时的抖动拖动时也不觉得黏滞。4.3 单击、长按、滑动手势的状态机实现拿到稳定的坐标后应用层还需要区分单击、长按和滑动。最简单的方法是做一个基于时间窗口的状态机。每次检测到第一个触点按下时记录按下时间和起始坐标然后在主循环里周期性检查当前时间并更新状态。下面是一个精简的状态机实现typedef enum { TOUCH_IDLE 0, TOUCH_DOWN, TOUCH_MOVE, TOUCH_UP } TouchState; TouchState t_state; UINT16 down_x, down_y; UINT32 down_time; void Touch_Task(TouchData *td) { switch (t_state) { case TOUCH_IDLE: if (td-point_num 0) { down_x td-x[0]; down_y td-y[0]; down_time GetTickCount(); t_state TOUCH_DOWN; } break; case TOUCH_DOWN: if (td-point_num 0) { if (abs((int)td-x[0] - down_x) 15 || abs((int)td-y[0] - down_y) 15) { t_state TOUCH_MOVE; // 按下后移动超过阈值 } else if (GetTickCount() - down_time 800) { // 长按时间到 HandleLongPress(); t_state TOUCH_IDLE; } } else { HandleClick(); // 快速抬起判定为单击 t_state TOUCH_IDLE; } break; case TOUCH_MOVE: if (td-point_num 0) { HandleMove(td-x[0], td-y[0]); } else { HandleRelease(); t_state TOUCH_IDLE; } break; default: t_state TOUCH_IDLE; break; } }在这个状态机里TOUCH_DOWN和TOUCH_MOVE之间的切换条件是15像素的移动阈值这个值可以和前面滤波阈值不同因为这里要识别的不是抖动而是用户有意识的滑动意图。如果用户按下后轻微移动但不超过15像素系统仍然判定为点击返回的坐标精度对点击事件影响不大。长按判定时间是800ms主流触摸方案里500ms-1000ms都有人用关键看你的产品操作习惯是偏快还是偏重。4.4 屏幕坐标换算为什么触摸IC的坐标和屏幕像素不是一回事ST1633I输出的坐标范围由电容感应节点的数量和扫描精度决定一般来说它是一个比例值不是像素值。在CH554驱动LCD显示的方案里需要把触摸IC的原始坐标映射到屏幕分辨率上。换算公式是屏幕坐标 触摸原始值 × LCD分辨率 / 触摸原始最大值。这个最大值可以从空载和满载扫描中获取也可以直接查模组规格书。举一个具体例子假设ST1633I在X方向上的原始输出范围是0-65535屏幕分辨率为480那么x_pixel raw_x * 480 / 65535。这个除法在51核上只靠整数运算会损失精度可以先乘后除注意中间值需要一个32位变量来承接否则直接溢出。实战中很多人的坐标在右半边跳变就是中间变量用了UINT16导致乘法结果截断了。推荐用long类型做中间计算CH554的编译器支持16位和32位混合运算性能损失可以在可接受范围内。5. 踩坑记录ST1633I和CH554组合最容易翻车的五个问题5.1 现象上电后I2C总线被锁死SCL一直为低原因CH554复位时如果I2C引脚处于不确定状态而此时ST1633I已经开始输出时钟信号或数据两者冲突后会把SCL拉死或者触摸IC在上电复位期间主控提前给它发了I2C起始位。解决方法是确保先让CH554完全启动等待触摸IC的RESET引脚拉高并延时50ms以上再初始化I2C总线同时对I2C总线增加软件恢复代码检测到SCL为低时连续翻转SCL九次来释放总线。这个坑最容易出现在多路供电的板子上触摸IC的上电速度比MCU慢两者没有严格等上电完成就开始通信总线就锁死。5.2 现象设备ID读取到了但偶尔坐标全部为0xFF或0x00原因I2C读取时序中重复起始位后发送读地址那一步如果设备没有足够时间准备数据可能会输出无效字节。表现为10次读取里偶尔有一两次数据全错。解决办法是在读地址发送后增加一个小延时或者把I2C速率从400kHz降到100kHz测试。如果降速后问题消失说明是时钟太快导致设备数据准备时间不足选用更低速率即可。我遇到过一种特殊情况某款模组的ST1633I不响应重复起始位后跟读地址的操作而是需要先停止总线再重新启动这个现象是模组厂家自己改动了时序只能按厂家的要求定制驱动。5.3 现象单点触摸正常双指触摸时第二个点坐标一直在跳动原因两个靠得很近的触点在被电容扫描时会互相干扰ST1633I区分两个触点的能力有限会出现两个触点坐标被互相拉近的情形。这是单芯片电容触摸方案的固有限制不是代码能完全解决的。经验做法是把第二个触点的坐标在靠近第一个触点时做合并处理或者加载一个最小间距约束间距小于某个像素时强制把第二个触点坐标置为一个固定偏移。如果产品必须稳定支持双指捏合操作建议换更高端的触摸ICST1633I更适合单指点击和简单滑动。5.4 现象CH554进入低功耗模式后再次唤醒触摸无响应原因进入低功耗前后CH554的I2C模块和GPIO方向寄存器可能被系统复位或关闭了外设时钟唤醒后不会自动恢复成初始化时的状态。解决方法是把整个ST1633I的初始化和I2C模块重配代码写成可重复调用函数在唤醒流程中重新执行一遍。另外要注意触摸IC的INT引脚在低功耗模式下如果是悬空的可能在唤醒瞬间产生毛刺事件导致外部中断误触发最好给它配置一个内部上拉电阻。5.5 现象坐标X和Y方向反了或者某个方向完全反向原因触摸IC在模组上的安装方向和LCD面板的显示方向不一致一般发生在采用FPC软板连接的模组上软板翻转后X方向自然反向。解决方法是硬件上不要改走线在软件里做方向配置。ST1633I如果支持寄存器级的方向设置可以直接改方向寄存器如果不支持只能在上层坐标换算时对X或Y做翻转运算。这个坑不值得硬改硬件画一小段映射函数就解决但需要把翻转逻辑和滤波逻辑放在一起处理否则先翻转再滤波会改变阈值方向。6. 升级调试效率的小技巧给触摸系统加一个可视化的坐标回环驱动代码调通后接下去最耗时间的是校准坐标映射和验证手势识别效果。一个高效做法是让CH554通过UART把触摸坐标、点数和当前状态机状态打印出来并在上位机里画成散点图。这样做的好处是能直观看到坐标是否有跳变、吞吐数据是否阻塞、状态机是否在按预期流转。不需要复杂的上位机任何带串口绘图功能的调试工具都行或者用串口打印原始值自己写个小脚本画点。数据格式可以设计成t:1 x:123 y:45 st:2每一行代表一次采样。这样跑一轮手动画圈的动作就能很快看出滤波阈值是否合适。另外有一个值得投入的习惯把整个STA1633I驱动代码固定封装成一个独立模块只暴露触摸初始化和获取触摸数据两个接口主程序里不要直接调I2C寄存器操作。这样后续换触摸IC型号时只需要替换模块内部的寄存器映射和数据解析部分主控程序一行都不用改。我在某跨平台系统中就用这种分层方式把ST1633I替换成另一颗触摸IC整个过程只花了半天。触摸驱动不难难的是把代码结构设计得让人愿意维护。希望帮到你。本文还有配套的精品资源点击获取