1. 项目缘起:为什么选择1.69英寸触摸屏?
最近在做一个便携式智能穿戴设备的原型,核心需求是既要有一块足够清晰、能显示丰富信息的屏幕,又要兼顾极致的体积和功耗控制。在市面上琳琅满目的小尺寸显示屏里,我最终锁定了一块1.69英寸的电容触摸LCD模块。这个尺寸很有意思,它不像0.96英寸那样捉襟见肘,也不像2.4英寸那样略显笨重,刚好卡在一个“信息密度”和“便携性”的甜蜜点上。对于智能手表、微型工控HMI、手持检测仪或者一些创意交互小装置来说,它都是一个非常平衡的选择。
这块屏通常指的是分辨率在240x280或240x240像素的IPS液晶屏,搭配电容式触摸面板(CTP),通过SPI或I2C接口与主控MCU通信。选择它,不仅仅是看中了它的物理尺寸,更是看中了它背后一整套开发生态和性能潜力。市面上很多模块都基于ST7789V或GC9A01这类主流驱动芯片,资料丰富,社区支持好,这意味着从点亮到驱动触摸,你踩的每一个坑大概率都有人填过。接下来,我就结合自己从选型、点亮到深度优化的一手经验,把这个小模块里里外外讲透,让你不仅能快速上手,还能避开那些数据手册里不会写的“暗礁”。
2. 模块深度拆解:硬件接口与驱动芯片选型
拿到一个1.69寸触摸屏模块,第一步不是急着写代码,而是把它翻过来,看清楚它的“身份证”。这决定了你后续所有软件工作的起点。
2.1 核心驱动IC:ST7789V与GC9A01的细微差别
目前市面上主流的1.69寸模块,驱动IC无外乎ST7789V和GC9A01这两大阵营。它们都支持262K色,SPI接口,性能相近,但细节上各有千秋。
ST7789V是老牌选手,资料极其丰富,几乎所有主流嵌入式框架(Arduino, ESP-IDF, MicroPython, LVGL等)都有现成的驱动库。它的初始化序列比较标准化,稳定性经过长时间考验。如果你追求快速验证和稳定的社区支持,ST7789V是首选。
GC9A01则是后起之秀,在一些参数上略有优势,例如其帧率可能稍高一点。但更关键的是,部分厂商为了降低成本,会选用GC9A01。它的初始化序列可能与ST7789V有细微差别,如果你拿到的模块是GC9A01但用了ST7789V的驱动代码,很可能屏幕一片漆黑或者颜色异常。
实操心得:如何快速鉴别驱动IC?最可靠的方法是查看模块背面的丝印,或者用万用表测量。一个更软性的判断方法是:观察配套的示例代码。如果商家提供的Arduino库里有
GC9A01A.h之类的头文件,那基本就是GC9A01了。如果没有明确说明,可以尝试用逻辑分析仪抓取上电初始化阶段的SPI数据,对比两种IC的典型初始化命令序列来区分。
2.2 触摸芯片:GT911与FT6236的取舍
电容触摸功能通常由一颗独立的触摸芯片实现,通过I2C接口输出坐标数据。常见的有GT911和FT6236。
GT911支持多点触控(通常是2点或5点),精度高,抗干扰能力强,但价格也相对较高,初始化配置稍复杂,需要从主机加载配置参数。如果你的应用需要复杂的手势识别(如缩放、旋转),GT911是更专业的选择。
FT6236更为常见和经济,通常只支持单点触控(市面上有些标称2点的FT6236G,但实际稳定性一般)。它的优点是接口简单,上电即用,无需复杂配置。对于绝大多数只需要点击、滑动的应用,FT6236完全够用,且能节省成本和开发时间。
我的选择是FT6236,因为当前项目只需要单点操作。这里有一个关键细节:FT6236的I2C设备地址通常是0x38(7位地址),但有些模块可以通过引脚配置为0x48。务必在原理图或购买页面上确认。
2.3 接口定义与硬件连接
一个典型的1.69寸触摸屏模块,其引脚大致如下表所示:
| 引脚标号 | 引脚名称 | 类型 | 说明 |
|---|---|---|---|
| VCC | 电源正极 | 输入 | 3.3V供电,绝对禁止接5V,会永久损坏屏和触摸芯片! |
| GND | 电源地 | 输入 | 系统共地 |
| SCL | SPI时钟 | 输入 | 显示驱动SPI时钟线 |
| SDA | SPI数据 | 双向 | 显示驱动SPI数据线(MOSI) |
| RES | 复位 | 输入 | 显示驱动复位,低电平有效 |
| DC | 数据/命令 | 输入 | 显示驱动数据/命令选择(高=数据,低=命令) |
| CS | 片选 | 输入 | 显示驱动SPI片选,低电平有效 |
| BLK | 背光控制 | 输入 | 背光LED阳极控制(接限流电阻后接MCU IO) |
| SCL | I2C时钟 | 输入 | 触摸芯片I2C时钟线 |
| SDA | I2C数据 | 双向 | 触摸芯片I2C数据线 |
| INT | 中断输出 | 输出 | 触摸芯片中断信号,触摸发生时产生下降沿 |
硬件连接核心注意事项:
- 电平匹配:确保你的MCU IO口电平是3.3V。如果是5V MCU(如Arduino Uno),必须使用电平转换模块,否则有损坏风险。
- 电源去耦:在模块的VCC和GND之间,尽可能靠近引脚处并联一个10uF的钽电容和一个0.1uF的陶瓷电容,这是稳定工作的基石,能有效消除电源噪声导致的显示花屏或触摸失灵。
- 背光电路:BLK引脚内部通常已串联背光LED。你需要外接一个限流电阻(阻值根据屏规格书,常见100-200欧姆)到MCU的一个IO口,通过PWM控制该IO即可实现亮度调节。直接接VCC会导致背光常亮且无法调光。
- SPI速率:初期调试建议先将SPI时钟设置得低一些,如10MHz以下,确保通信稳定。待显示正常后,再逐步提高速率测试上限。
3. 从零点亮:软件驱动与初始化陷阱
硬件连接妥当后,就进入了软件环节。这里以在ESP32平台上使用Arduino框架驱动一个ST7789V+FT6236的模块为例,讲解关键步骤和坑点。
3.1 显示驱动的初始化序列
不要盲目复制粘贴库中的初始化代码,理解每一行命令在做什么,才能在出问题时快速定位。
// 示例:关键初始化命令解析 void initST7789V() { // 1. 硬件复位 digitalWrite(RES_PIN, LOW); delay(20); digitalWrite(RES_PIN, HIGH); delay(120); // 等待内部复位完成,这个120ms延时至关重要,短了可能导致初始化失败 // 2. 退出睡眠模式 (Command 0x11) sendCommand(0x11); delay(120); // 睡眠退出时间,手册要求120ms,必须给足 // 3. 颜色格式设置 (Command 0x3A) sendCommand(0x3A); sendData(0x55); // 0x55 代表16位RGB565格式,这是最常用的格式 // 4. 内存访问控制 (Command 0x36) sendCommand(0x36); // 这个字节的每一位都控制着屏幕的旋转、镜像、颜色顺序 // 例如:0x00 为默认(竖屏,RGB顺序)。如果显示颜色反了或方向不对,优先调整这个参数。 sendData(0x00); // 5. 设置列地址和行地址范围 (Commands 0x2A, 0x2B) // 这告诉驱动IC有效的显示区域。对于240x280的屏: sendCommand(0x2A); sendData(0); sendData(0); // 起始列高8位、低8位 sendData(0); sendData(240 - 1); // 结束列高8位、低8位 sendCommand(0x2B); sendData(0); sendData(0); // 起始行高8位、低8位 sendData(0); sendData(280 - 1); // 结束行高8位、低8位 // 6. 打开显示 (Command 0x29) sendCommand(0x29); delay(120); // 再次等待稳定 // 7. 设置背光(通过你的BLK控制IO) analogWrite(BLK_PIN, 128); // 50%亮度 }关键陷阱解析:
- 延时不足:
0x11(睡眠退出)和复位后的延时是硬性要求,并非可有可无。如果缩短,屏幕可能能亮但显示异常,或完全无反应。 - 颜色错乱:如果显示红色变成了蓝色,绿色不对,问题大概率出在颜色格式(
0x3A)或内存访问控制(0x36)的设置上。RGB565格式下,一个像素点用2字节表示,字节顺序(高位在前还是低位在前)也需要匹配MCU的SPI设置。 - 显示区域错误:如果只有部分屏幕能显示,或者内容被偏移,请检查
0x2A和0x2B命令设置的行列地址范围是否与你的物理屏幕分辨率完全一致。
3.2 触摸驱动的集成与校准
FT6236的驱动相对简单,核心是周期性地读取其寄存器数据。
#define FT6236_ADDR 0x38 #define FT6236_REG_NUMTOUCHES 0x02 // 触摸点数量寄存器 #define FT6236_REG_DATA 0x03 // 触摸数据起始寄存器 bool readTouch(int16_t *x, int16_t *y) { Wire.beginTransmission(FT6236_ADDR); Wire.write(FT6236_REG_NUMTOUCHES); Wire.endTransmission(false); // 发送重复开始条件 Wire.requestFrom(FT6236_ADDR, 1); if (Wire.available()) { uint8_t numTouches = Wire.read() & 0x0F; // 低4位有效 if (numTouches != 1) { // 我们只处理单点 *x = *y = -1; return false; } } // 读取触摸坐标数据(7字节) Wire.beginTransmission(FT6236_ADDR); Wire.write(FT6236_REG_DATA); Wire.endTransmission(false); Wire.requestFrom(FT6236_ADDR, 7); // 实际只读4个坐标字节,但多读无害 if (Wire.available() >= 4) { uint8_t data[4]; for (int i=0; i<4; i++) data[i] = Wire.read(); // FT6236数据格式:X高8位,X低8位,Y高8位,Y低8位 // 注意:坐标值是12位的(0-4095),存储在两个字节中。 uint16_t touchX = ((data[0] & 0x0F) << 8) | data[1]; uint16_t touchY = ((data[2] & 0x0F) << 8) | data[3]; // 关键步骤:坐标映射与校准 *x = map(touchX, 0, 4095, 0, 239); // 映射到240像素宽度 *y = map(touchY, 0, 4095, 0, 279); // 映射到280像素高度 return true; } return false; }触摸不准?你必须校准!上面的map函数是最简单的线性映射,但实际中,触摸屏的坐标轴可能与显示屏不完全对齐,存在旋转、偏移甚至非线性失真。因此,一套简单的两点校准算法是必须的。
- 在屏幕上依次显示两个点(如左上角和右下角),提示用户点击。
- 记录下用户点击时FT6236读出的原始坐标
(rawX1, rawY1),(rawX2, rawY2)。 - 已知这两个点的理论屏幕坐标应为
(dispX1, dispY1),(dispX2, dispY2)。 - 使用以下公式进行转换,这比简单的
map更准确,能补偿偏移和缩放:dispX = (rawX - rawX1) * (dispX2 - dispX1) / (rawX2 - rawX1) + dispX1;dispY = (rawY - rawY1) * (dispY2 - dispY1) / (rawY2 - rawY1) + dispY1;
将计算出的系数(缩放比例和偏移量)保存到非易失性存储器(如EEPROM),每次上电后加载使用。
4. 性能优化与功耗控制实战
点亮和触摸只是第一步,要让它在产品中稳定、省电地工作,还需要深入优化。
4.1 提升显示刷新率:SPI与DMA的魔法
默认的Arduino SPI库配合单字节发送,刷新一屏数据(2402802字节 ≈ 131KB)会非常慢,导致动画卡顿。优化手段如下:
- 使用硬件SPI并提升时钟频率:ESP32的硬件SPI (HSPI, VSPI) 可以轻松跑到40MHz甚至80MHz(取决于布线质量)。将
SPI.beginTransaction中的时钟设置到最高稳定值。 - 采用DMA传输:这是终极解决方案。ESP32的SPI主机驱动支持DMA,可以配置一个大的显示缓冲区,在向屏幕发送上一帧数据的同时,CPU已经开始准备下一帧数据,实现近乎零CPU占用的刷屏。以ESP-IDF框架为例,可以使用
spi_bus_add_device和spi_device_transmit等API,并配置SPI_TRANS_USE_TXDATA标志。在Arduino环境下,可以寻找像TFT_eSPI这样的高级库,它通常已经集成了对ESP32 DMA的支持。 - 局部刷新:如果UI只有小部分区域变化(如一个数字、一个图标),只刷新该矩形区域,而不是全屏。这需要驱动库支持设置窗口(通过
0x2A,0x2B命令)。
4.2 触摸扫描策略:中断与轮询的平衡
不断轮询I2C会浪费CPU时间和功耗。FT6236的INT引脚就是为此而生。
- 中断模式:将INT引脚连接到MCU的一个外部中断引脚,并配置为下降沿触发。当无触摸时,INT为高电平;有触摸发生时,INT变为低电平,触发MCU中断。在中断服务程序(ISR)中设置一个标志位,主循环检测到这个标志位后再去读取I2C数据。这是最省电的方式。
- 注意防抖:触摸中断可能伴有抖动,在ISR中不要进行复杂的I2C操作,仅设置标志。在主循环中读取数据前,可以加一个短暂的延时(如5ms)再判断INT引脚是否仍为低电平,以确认是有效触摸。
4.3 背光管理与系统级省电
屏幕背光是功耗大户。1.69寸屏的背光电流可能在几十到一百毫安。
- PWM调光:用MCU的PWM引脚控制BLK,不仅能无极调光,在低亮度下还能显著省电。人眼对低亮度感知是非线性的,将亮度从100%降到50%,省电近半,但视觉亮度感觉只下降了一点点。
- 超时熄屏:在用户无操作一段时间后,逐步降低背光PWM值直至关闭(
analogWrite(BLK_PIN, 0))。同时,可以向屏幕发送睡眠命令(0x10)。进入睡眠模式后,屏幕功耗可以降至微安级别。唤醒时,先发送唤醒命令(0x11),并等待足够延时,再恢复背光。 - 动态帧率:在显示静态画面时,可以降低SPI总线频率,甚至暂停刷新。当需要更新时再恢复。一些高级驱动IC支持部分刷新或自刷新模式,可以进一步降低功耗。
5. 高级应用:与LVGL图形库的集成
当需要实现滑动列表、动画、复杂控件时,自己手绘底层图形是不现实的。LVGL是一个开源、强大的嵌入式图形库,与这类小屏模块是绝配。
5.1 移植显示与触摸驱动
LVGL需要一个“刷屏”回调函数和一个“读触摸”回调函数。
// 1. 显示驱动回调函数 void my_disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { // 设置显示窗口为 area 指定的区域 setWindow(area->x1, area->y1, area->x2, area->y2); // 将 color_p 缓冲区中的像素数据(lv_color_t格式)通过SPI发送到屏幕 // 注意颜色格式转换(LVGL通常用RGB565,与屏一致) sendPixels((uint8_t*)color_p, (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2); // 告诉LVGL刷新完成 lv_disp_flush_ready(disp_drv); } // 2. 触摸驱动回调函数 void my_touch_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) { int16_t touchX, touchY; bool touched = readTouch(&touchX, &touchY); // 调用你自己的触摸读取函数 if(touched) { >