做嵌入式的,绕不开屏。我见过太多人第一次点亮彩屏时那种又兴奋又懵的状态:屏幕亮了,但是白屏、花屏、偏移、触摸没反应,排个错排到半夜。这背后的芯片组合,十有八九就是ST7789系列加CST816D这种搭配。今天这篇把ST7789T3和CST816D的驱动从头到尾讲透,顺便用这个真实项目把嵌入式面试里最常考的10个场景问答串一遍。
这套组合太典型了。ST7789T3负责把图像画到屏幕上,CST816D负责感知你的手指按在哪个位置,一颗小尺寸彩屏产品的基本盘就这么搭起来了。不管你是正在调屏调触摸的嵌入式开发新手,还是准备嵌入式软件岗位面试、想找个实际项目当谈资的求职者,这篇文章都值得读完。我尽量不写教科书,全按实际干活时踩过的坑来讲。
1. 为什么是这个组合:两块芯片的搭配逻辑
1.1 ST7789T3是什么:小尺寸屏的“通用显示内核”
先看ST7789T3这块芯片。它是一颗小尺寸TFT-LCD驱动IC,常见的是240x320分辨率的内部GRAM,支持SPI、并行等接口,实际产品里以4线SPI为主。你手里那块1.28寸圆屏、1.69寸方屏、或者智能手表、手环、桌面小摆件的屏,拆开驱动IC看型号,很大概率就是ST7789V、ST7789T3这类血缘很近的芯片。
为什么这芯片在消费类市场占有率这么高,核心就是三个字:够用、便宜、资料多。240x320这个分辨率在几寸以下的小屏上观感已经不错,RGB565色彩格式在嵌入式设备里刷起来速度快、占用小。ST7789系列因为用得人太多,网上代码、踩坑贴、芯片手册到处都是,哪怕你换了一个具体型号后缀,初始化流程也能复用八九成。
这芯片和MCU之间的活儿不复杂,无非就是SPI发命令、发像素数据,但真正让新手崩溃的往往不是SPI传输本身,而是上电时序、初始化命令顺序、显示方向、GRAM偏移这几件事。这些细节恰恰是后面面试里最容易被追问的地方。
1.2 CST816D是什么:显示之外的触摸入口
CST816D是一颗电容触摸控制IC,通过I2C接口和主控通信。它和ST7789T3是两个独立的芯片,但物理上经常“绑”在一起:触摸感应层做在LCD玻璃上,触摸IC通过柔性排线连出来,最后统一做成一个显示+触摸模组。
触摸IC存在的意义很简单:屏幕只能给你看,不能告诉你用户按在哪。CST816D内部通过电容变化检测手指接触的位置,把触摸点的x、y坐标、手势类型等结果放到内部寄存器里,MCU通过I2C读取。这颗芯片还支持单击、双击、滑动等基本手势识别,也支持低功耗模式下的触摸唤醒,对做手环这类电池供电产品的开发者来说很实用。
它的数据读取本身不复杂,但实际项目里出问题最多的地方反而是中断使用不当、坐标映射错误、I2C地址搞错、电平极性配置反了这些看起来很小的点。
1.3 项目里选型时的实际考量
把显示和触摸拆成两颗独立芯片,而不是用自带触摸控制的整体方案,主要是成本和供应链的考虑。显示驱动IC和触摸IC分别采购,厂家可以自由搭配不同供应商的玻璃、FPC、背光,哪个缺货换哪个,这在消费电子产品里是常规操作。对开发者来说,代价就是需要分别跑通两套驱动,多写一点代码。
面试官特别喜欢拿这种项目来当话题,原因很简单:一个ST7789T3加CST816D的驱动项目,覆盖了SPI、I2C、中断、DMA、GPIO控制、低功耗、状态机处理,几乎一个嵌入式驱动开发的基础技能点全都点到了。后面那10个面试问答,全部是从这个屏幕项目里抽出来的高频考题,不是凭空编的。
2. ST7789T3驱动核心:从初始化序列到SPI细节
2.1 硬件连接与上电时序
先把引脚理清楚。ST7789T3在4线SPI方式下,典型接法如下:
| 功能 | 引脚 | 说明 |
|---|---|---|
| SPI时钟 | SCK | MCU的SPI时钟输出 |
| SPI数据 | MOSI/SDA | 命令和数据都从这根线发送 |
| 片选 | CS | 低电平有效 |
| 命令/数据选择 | DC | 低电平发命令,高电平发数据 |
| 复位 | RESET/RST | 低电平复位 |
| 背光控制 | BLK | 部分模组引出,用于开关背光 |
这里有个非常容易踩的坑:部分模组的BLK引脚默认是低电平,也就是说你如果没把它拉高,屏幕会一直是黑的,但MCU这边SPI刷得飞起,你以为代码有问题,查半天其实是背光没开。我在项目里习惯先把BLK单独拉出来控制,调试时先点亮背光,确认屏幕有反应再继续调显示。
上电时序也是第一次调屏最容易翻车的地方。常见要求是:电源VCC稳定之后,要等一段时间再给复位信号,复位脚拉低保持几毫秒再拉高,拉高后还要等一定延时,然后才能发第一条命令。如果上电后立刻就用SPI去写寄存器,芯片内部还在上电复位中,命令会被直接吞掉,表现就是屏幕没反应或者颜色不对。
实际上大多数手册会写一个最小延时,但不同批次模组表现有差异。我梳理了一套不会出错的流程:上电后延时50ms以上,RST拉低10ms,拉高后延时120ms,再开始发初始化命令。这个序列在很多屏上验证过,稳。
2.2 SPI模式选择与驱动骨架
ST7789T3用4线SPI,不是3线。意思是除了SCK和MOSI,还需要一根DC线来区分发过来的是命令还是数据。至于很多人纠结的SPI模式,典型配置是CPOL=0、CPHA=0,也就是模式0,时钟空闲为低、第一个边沿采样。我见过有些屏厂把模式写成模式1,所以最可靠的办法是查你手里模组的手册,或者用逻辑分析仪抓原厂驱动的波形。
驱动程序骨架其实非常简单。以STM32的HAL库为例,最基础的就是两个函数,一个发命令,一个发数据。
static void lcd_write_cmd(uint8_t cmd) { LCD_DC_LOW(); LCD_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); LCD_CS_HIGH(); } static void lcd_write_data(uint8_t *data, uint16_t len) { LCD_DC_HIGH(); LCD_CS_LOW(); HAL_SPI_Transmit(&hspi1, data, len, 100); LCD_CS_HIGH(); }注意DC和CS的先后顺序。很多人的代码先拉低CS再切DC,其实没大影响,但更规范的做法是先摆好DC方向,再拉低CS,这样可以避免在总线上出现意外的电平变化。另外,如果后续要用DMA刷屏,这两个函数就不能频繁拉CS了,需要在一次传输过程中把CS一直保持为低,这个后面刷屏部分细说。
2.3 初始化命令序列实战
初始化序列是ST7789系列驱动里信息量最大的一块。不同模组的手册会给不同偏置值,但大框架一致。我以一块240x240的常见圆屏为例,给一份实际验证过的初始化序列。
static void lcd_init(void) { lcd_write_cmd(0x01); // SWRESET,软件复位 HAL_Delay(120); lcd_write_cmd(0x11); // SLPOUT,退出睡眠 HAL_Delay(120); lcd_write_cmd(0x36); // MADCTL,显示方向控制 uint8_t madctl = 0x00; // 实际值看屏幕方向,也可以用0xC0 lcd_write_data(&madctl, 1); lcd_write_cmd(0x3A); // COLMOD,像素格式 uint8_t colmod = 0x55; // 0x55 = RGB565, 16bit lcd_write_data(&colmod, 1); // 电源控制、伽马、对比度等一整套寄存器 static const uint8_t pwr_cmds[] = { 0xB2, 0x0C, 0x0C, 0x00, 0x33, 0x33, // PORCTRL 0xB7, 0x35, // GCTRL 0xBB, 0x19, // VCOMS 0xC0, 0x2C, // LCMCTRL 0xC2, 0x01, // VDVVRHEN 0xC3, 0x12, // VRH 0xC4, 0x20, // VDVSET 0xC6, 0x0F, // FRCTRL2 0xD0, 0xA4, 0xA1, // PWCTRL1 0xE0, 0xD0, 0x08, 0x0B, 0x09, 0x09, 0x24, 0x35, 0x33, 0x51, 0x3A, 0x12, 0x14, 0x19, 0x1A, 0x15, // PVGAMCTRL 0xE1, 0xD0, 0x08, 0x0B, 0x09, 0x09, 0x23, 0x34, 0x32, 0x4F, 0x39, 0x12, 0x14, 0x18, 0x19, 0x15 // NVGAMCTRL }; // 实际需要按“命令、参数”逐个发送,这里拆成命令+数据方式调用 lcd_write_cmd(0x21); // INVON,反色显示,部分屏需要 lcd_write_cmd(0x13); // NORMALON,正常显示模式 lcd_write_cmd(0x29); // DISPON,开显示 HAL_Delay(50); }这份序列里值得展开讲两个点。第一个是0x3A这个寄存器,它直接决定发一个像素要几个字节。RGB565就是每像素2字节,RGB888就是每像素3字节。很多人刷屏发发现颜色不对、图像有条纹,先查这里。第二个是0x21反色命令,ST7789V默认是正常极性还是反色,不同批次居然有差异,如果你发现显示出来的图像颜色像“底片”,就是少了这条或多了这条。
还有一个新手常犯的错误:初始化过程中0x36方向寄存器太小看不出来,但如果你设置的扫描方向和屏幕实际引脚的接法不一致,会出现图像上下颠倒、左右翻转,甚至整个坐标轴转了90度。这些都不是芯片坏了,改MADCTL的值就能解决。
2.4 局部刷新与偏移设置
接下来是ST7789最折磨人的问题之一:偏移。芯片自带240x320的GRAM,但你的屏实际可能只有240x240,物理上只使用了GRAM的一部分。比如圆屏,显示区域通常不在GRAM正中心,而是在某个偏移位置。如果初始化后不去设置窗口,画面就会错位。
解决办法是用CASET和RASET两个命令设置显示窗口。
void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { uint8_t data[4]; lcd_write_cmd(0x2A); // CASET,列地址 data[0] = x0 >> 8; data[1] = x0 & 0xFF; data[2] = x1 >> 8; data[3] = x1 & 0xFF; lcd_write_data(data, 4); lcd_write_cmd(0x2B); // RASET,行地址 data[0] = y0 >> 8; data[1] = y0 & 0xFF; data[2] = y1 >> 8; data[3] = y1 & 0xFF; lcd_write_data(data, 4); lcd_write_cmd(0x2C); // RAMWR,开始写显存 }设置好窗口后,后续SPI发出来的数据会按顺序填充到这个窗口区域。比如一块240x240圆屏,实测后发现有效显示区域是x从0到239、y从40到279,那你在初始化和每次刷屏前把行地址设为40到279,画面就正常了。
问题是怎么确定这个偏移量?我不知道屏厂给的坐标映射时,先用最简单粗暴的办法:把整块GRAM都刷成红色,然后在屏幕上找亮区的位置。如果亮区明显偏向某一侧,就调整偏移值重新测试。这个办法比较原始,但比对着手册猜要快得多。常见圆屏的偏移一般集中在y方向40或80附近,也有x方向需要偏移的,一定要实测。
2.5 刷屏性能:DMA与像素格式
调通显示只是第一步,项目一旦跑起来,很快会碰到底层性能问题。一块240x240的屏,RGB565一帧数据量是240x240x2字节,约112.5KB。SPI时钟40MHz理论上每秒可以传输5MB,一帧理想耗时约22.5ms。但这是理想值,实际加上命令开销、CS切换、中断损耗,CPU逐字节刷屏能做到30ms一帧就算不错了。
解决办法是DMA刷屏。思路很简单:把要显示的数据缓冲区地址交给SPI外设,DMA自动把数据搬过去,CPU在等待期间可以去做别的事。用HAL库实现时,核心代码如下。
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)buf, len);DMA刷屏有几个容易翻车的细节。第一是CS不能每传输一个字节就拉一次,会在高速传输时出现毛刺,正确做法是在DMA传输前拉低CS、设置DC为高,传输完全结束后再拉高CS。第二是DMA传输期间缓冲区不能修改,否则画面会“撕裂”,常见做法是双缓冲,一帧在显示,另一帧在后台渲染,等DMA完成后再交换。第三是如果MCU同时还在跑其他DMA任务,要注意DMA通道优先级,不然刷屏会把其他传输挤掉。
如果对帧率还有更高要求,有些方案会把像素格式从RGB565降到RGB444或者RGB332,但颜色质量下降明显;或者用局部刷新,只更新变化的区域,像数字时钟这类场景就特别适合。
3. CST816D触摸驱动核心:I2C、中断与坐标上报
3.1 硬件连接与I2C地址确认
CST816D的硬件连接比LCD简单得多,就四根线:SDA、SCL、RST、INT。I2C作为总线协议只用两根线,RST用来复位触摸IC,INT用来通知主控“有人摸屏幕了”。
刚上手时第一件事就是确认I2C地址。CST816D比较常见的7位地址是0x15,换算成8位写地址是0x2A、读地址是0x2B。但同一个家族的芯片地址存在差异,CST816S、CST816T、CST816D不同后缀地址可能不一样。所以正规做法是,上电后先读芯片的版本号寄存器,确认I2C通信正常,再往下走。
读取版本寄存器的代码大概是这样的。
uint8_t cst816d_read_version(void) { uint8_t cmd = 0xAA; // 版本寄存器,以实际手册为准 uint8_t ver = 0; // 向地址0x15发寄存器地址,然后读回1字节 // 伪代码,使用具体HAL I2C接口 // HAL_I2C_Master_Transmit(&hi2c1, 0x2A, &cmd, 1, 100); // HAL_I2C_Master_Receive(&hi2c1, 0x2B, &ver, 1, 100); return ver; }如果读版本读到0xFF或者0x00,一般不是芯片坏了,而是I2C地址配错、上电时序不对,或者SDA/SCL接反了。先查这三个地方,比怀疑芯片要靠谱得多。
3.2 寄存器地图与数据读取流程
CST816D的寄存器布局在不同资料源里稍微有点区别,但常用的是下面这套:0xA5是手势寄存器,0xA6到0xA9是第一个触摸点的坐标数据,0xFE是中断和唤醒控制相关的寄存器。坐标数据里,高字节寄存器只保存高位,低字节寄存器保存完整低位,拼起来就是12位的坐标值。
读取一个触摸点的典型流程是这样:
typedef struct { uint8_t gesture; uint16_t x; uint16_t y; } touch_point_t; int cst816d_read_touch(touch_point_t *tp) { uint8_t reg = 0xA5; uint8_t buf[5] = {0}; // 连续读5字节:手势 + x高 + x低 + y高 + y低 // HAL_I2C_Mem_Read(&hi2c1, 0x2A, reg, I2C_MEMADD_SIZE_8BIT, buf, 5, 100); tp->gesture = buf[0]; tp->x = ((uint16_t)(buf[1] & 0x0F) << 8) | buf[2]; tp->y = ((uint16_t)(buf[3] & 0x0F) << 8) | buf[4]; return 0; }这里有个很容易被忽略的点:坐标寄存器读完后,触摸IC不会自动清除中断标志。有资料显示需要往0xFE寄存器写特定值来清除中断状态,否则INT引脚可能一直保持有效电平,导致主控反复触发中断。有些模组硬件上已经处理了这个问题,有些没有。所以调试时如果发现中断根本停不下来,一定要回去看数据手册里的中断清除部分。
另一个经验:读出来的x和y值是裸坐标,范围大致是0到240或略高,但方向未必和屏幕显示方向一致。如果发现触摸点水平方向反了,代码里做一次坐标镜像就能解决,不用改硬件。
3.3 中断处理与事件上报
触摸IC通常会接MCU的一个外部中断引脚。CST816D的INT引脚有效电平是低电平,也就是说没人触摸时是高,有人触摸时变低。配置时一般用下降沿触发,但为了稳妥,也可以配置成低电平触发,并在中断处理里加去抖动。
中断处理最核心的原则是:中断服务函数里绝对不要去做I2C读取。I2C本身是一个慢速协议,一帧数据要几毫秒甚至更长,放在中断里会长时间阻塞更高优先级的任务。正确做法是中断里只置一个标志位,由主循环或专用任务去处理触摸事件。
volatile uint8_t touch_int_flag = 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == TOUCH_INT_Pin) { touch_int_flag = 1; } } // 主循环中 while (1) { if (touch_int_flag) { touch_int_flag = 0; touch_point_t tp; if (cst816d_read_touch(&tp) == 0) { if (tp.x > 0 && tp.y > 0) { // 过滤掉无效坐标 // 在这里处理触摸消息,或者上报给上层应用 } } } }读取前加坐标有效性判断很有必要。触摸IC偶尔会报出全0坐标或者超大坐标,如果不去过滤,UI上会出现点击无效区域、误触等问题。这个看起来不起眼的判断,在真实项目里能省很多事。
3.4 手势检测与低功耗唤醒
CST816D的手势寄存器0xA5会返回手势类型。典型编码是:0x01上滑、0x02下滑、0x03左滑、0x04右滑、0x05单击、0x06双击、0x0B长按等。不过这类值在不同芯片版本上可能不同,拿来即用时最好用一片真实模组实测确认,不要直接照抄网上的表格。
低功耗场景是这颗芯片的强项。手环这类设备平时屏幕是灭的,用户用手指轻敲或滑动屏幕来唤醒设备。实现思路是:设备进入低功耗前,把触摸IC也切到低功耗模式,同时打开它的触摸唤醒功能,MCU本身进睡眠并保持外部中断使能。用户一碰屏幕,触摸IC检测到事件,拉低INT脚,MCU被唤醒,再反过来把屏幕点亮。
这块要注意的是触摸IC的低功耗唤醒阈值是可配置的,灵敏度调太高容易误唤醒,导致待机电流居高不下;调太低又会出现“敲不醒”的情况。这个参数没有绝对标准,只能在自己的壳料、玻璃厚度下多测试。
4. 嵌入式面试场景问答10连发
4.1 面试官视角:为什么拿这个项目当考题
这个屏幕项目几乎是嵌入式面试的“完美案例”。它覆盖面广:SPI协议、I2C协议、中断处理、DMA传输、状态机、低功耗、驱动分层,每一个都是嵌入式岗位的常规考点。面试官不需要你背芯片手册,而是通过这个项目看你怎么思考问题、怎么排错、怎么取舍。
所以下面这10个问答,我不按“八股文”的方式给你背答案,而是告诉你面试官问这个问题到底想要什么。你回答的时候,最好先用一两句话说出思路,再给具体做法,最后补一句“这里有个常见的坑”之类的话,效果会完全不一样。
4.2 驱动基础与协议:前3问
问题1:SPI有哪几种模式,你怎么确定你的LCD工作在哪一种模式?
SPI有4种模式,由时钟极性和时钟相位决定。CPOL决定时钟空闲时的电平,CPHA决定数据在哪个边沿采样。ST7789T3一般工作在模式0,也就是CPOL=0、CPHA=0,时钟空闲为低,第一个边沿采样。确定方式有两个:第一,查芯片手册的时序图,上面会画出SCK和MOSI的关系;第二,用逻辑分析仪抓原厂默认代码的波形,实测最稳。有些屏的默认配置可能是模式1或者模式3,初始化后画面完全不对,先怀疑SPI模式总没错。
问题2:I2C和SPI的区别是什么,为什么触摸芯片选I2C而不是SPI?
I2C和SPI都是最常见的板级通信协议。SPI速度快、全双工,但需要至少3根信号线,而且一般是一主一从或者通过片选扩展;I2C只用两根线就能挂多个设备,每个设备有独立地址,适合低速传感器类外设。触摸芯片本身要求的刷新率不高,坐标上报每秒几十次就够了,I2C的带宽绰绰有余,所以选用I2C可以节省GPIO,也方便和其他传感器共用总线。回答的时候如果能提到“速率匹配”这个概念,面试官会觉得你真的做过选型,而不是背答案。
问题3:读触摸坐标时,为什么你不在中断里读取,而是用一个标志位?
中断服务函数里应该尽量执行短小而确定性的操作。I2C读触摸坐标需要发送寄存器地址、等待从机响应、连续接收多个字节,总耗时在毫秒级,放在中断里会发生两件事:一是长时间阻塞其他中断,二是如果读操作被打断,数据会错乱。更严重的场景是MCU内置I2C外设在中断里被其他高优先级中断打断,总线状态机直接卡死。所以正确做法是中断里置一个标志位,返回主循环后再去读,这样即使读慢一点也不会影响系统其他功能。
4.3 初始化、时序与调试:4~6问
问题4:LCD初始化命令执行的先后顺序有讲究吗?
有,而且很讲究。比如退出睡眠命令0x11必须在设置显示方向0x36、像素格式0x3A之前执行吗?不一定,但完整的顺序逻辑是:先软件复位,让芯片回到已知状态;然后退出睡眠模式;接着设置显示方向、像素格式这些基本属性;再设置电源、伽马等模拟寄存器;最后开启显示。这中间最怕的是在芯片还没有退出睡眠时就发需要芯片内部模拟电路正常工作的命令,比如伽马调节、对比度调节,这些寄存器写入可能无效,表现出来就是屏幕能亮但颜色诡异,或者偏色严重。所以一个稳定的初始化序列,先复位、再切睡、再做配置、最后开显示,这个顺序不要乱改。
问题5:上电后屏幕白屏或者黑屏,你的排查思路是什么?
我会按四个层面来查。第一,电源,确认VCC电压正常、背光是否打开,背光没开最常见也最容易被忽略。第二,复位时序,确认RST引脚拉低、拉高、延时是否符合手册要求,有时候是MCU的GPIO没配置对,把复位脚当成了普通输出。第三,SPI信号,用逻辑分析仪看SCK、MOSI、DC、CS的波形,确认初始化命令真的发出去了。第四,初始化序列本身,比如命令参数是否写错、像素格式是否诡异、是否有漏掉的延迟。这个排查顺序从硬件到软件,基本能覆盖90%的白屏问题。
问题6:屏幕显示有偏移或者方向不对怎么处理?
先判断是方向问题还是偏移问题。如果画面整体颠倒、左右颠倒,改0x36 MADCTL寄存器里的扫描方向位。如果画面内容不完整、亮区偏一侧,多半是CASET/RASET窗口设置问题。比如240x240的圆屏用ST7789的240x320 GRAM,显示区域在GRAM中的位置需要手动指定,方法我已经在前面讲了,先全屏刷一个纯色,找亮区位置,再调整窗口偏移量。这里有一个容易混淆的点:偏移量不是每个人都能从屏厂规格书里直接看出来,很多规格书写得很含糊,实测反而是最快的方式。
4.4 性能、低功耗与系统整合:7~10问
问题7:刷一屏240x240 RGB565画面要多久,怎么估算和优化?
先算数据量,240x240x2字节等于112.5KB。假设SPI时钟40MHz,理论传输时间大概是22.5ms,但实际因为命令开销、CS切换、帧间隙,CPU发送一帧至少25到30ms,即帧率在每秒30到40帧左右。如果刷新率不够,优化手段有:第一,用DMA刷屏,把CPU从字节搬运中解放出来;第二,采用双缓冲,后台渲染和前台传输并行;第三,能局部刷就局部刷,像时钟显示这种应用,整个画面只有几个数字在变,全屏刷新纯属浪费。
问题8:DMA刷屏时怎么处理缓冲区冲突?
DMA传输是异步的,数据还在发送,CPU如果这时候去修改缓冲区里面的内容,画面会撕裂。我的做法是分配两个缓冲区,比如bufA和bufB。当前DMA正在发送bufA时,CPU在bufB渲染下一帧;等DMA完成中断来了,交换两个缓冲区角色,下次就用bufB发送、bufA渲染。如果内存紧张,也可以在DMA传输前先把数据拷贝到一块专用发送缓冲区,代价是多一次内存拷贝,但对小屏来说完全可接受。回答时如果能说到“缓冲区所有权”这个概念,比如同一时间只有发送端或渲染端拥有缓冲区,面试官会认为你理解并发。
问题9:产品要做低功耗,屏幕和触摸应该怎么处理?
低功耗处理分两步。第一步,关闭耗电外设。屏幕方面,先关背光、再发进入休眠模式的命令,ST7789有专门的睡眠命令,可以大幅降低功耗;触摸方面,把CST816D也切到低功耗状态。第二步,保留必要的唤醒源。比如触摸唤醒功能打开,用户轻碰屏幕时触摸IC通过INT脚把MCU从睡眠中唤醒。这里有一个典型坑:很多人只关了设备外设,忘了配置唤醒IO,导致系统睡死过去。还有就是对触摸唤醒灵敏度的校准,我建议在最终外壳和玻璃组装完成后做一轮测试,因为玻璃厚度直接改变电容检测的灵敏度。
问题10:如果让你把这两个驱动放到RTOS上,要注意什么?
这个题目考的是系统整合思维。核心有四点:第一,I2C和SPI外设要保证互斥访问,不能让两个任务同时对同一个外设操作,可以用互斥锁保护;第二,和触摸中断相关的变量要加volatile修饰,如果只是置标志位,系统可以接受,但如果要更严谨,可以用任务通知或信号量代替裸标志;第三,NVIC里SPI的DMA中断、外部中断优先级要合理配置,避免外部中断把刷屏DMA打断太频繁;第四,每个任务的栈空间要充足,刷屏任务如果用了局部大数组,栈给太小会直接HardFault。回答时如果能把“互斥”“优先级”“任务栈”这几个关键词说清楚,基本就能拿高分。
4.5 回答套路与加分小细节
这10个问答看下来,你会发现一个规律:面试官问的每一个问题,最后都会落到“你实际踩过什么坑”上。所以我特别建议你准备项目回答时,给每个问题准备一个具体的故障案例。比如“SPI模式不对导致花屏”“背光引脚没拉高导致黑屏”“触摸中断没清导致一直触发”,这种案例比任何空泛的理论都有说服力。
还有两个容易被忽略的加分点。第一个是画时序图的能力,你如果能当场画出一张“上电、复位、发命令、刷屏”的时序草图,面试官会直观感受到你的工程功底。第二个是对异常数据的处理意识,比如触摸坐标过滤、SPI传输返回错误检查,这些细节很能说明你的代码习惯,而代码习惯恰恰是面试官最看重的东西。
5. 常见问题与排查技巧实录
5.1 LCD问题速查表
我把实际调试里遇到最多的问题按现象整理了一个表,方便你现场对照。
| 现象 | 可能原因 | 优先排查 |
|---|---|---|
| 完全不亮 | 背光没开、电源异常 | BLK引脚电平、VCC电压 |
| 白屏 | SPI通信失败、初始化命令没发出去 | 逻辑分析仪看波形、确认CS和DC |
| 花屏 | SPI模式不对、像素格式不匹配、刷屏时序错 | 查CPOL/CPHA、0x3A配置、DMA传输 |
| 画面偏移 | CASET/RASET窗口设置错误、GRAM偏移未知 | 全屏刷纯色找亮区,调偏移 |
| 颜色像底片 | 反色命令0x21状态不对 | 确认是否发INVON,尝试去掉或加上 |
| 图像颠倒 | MADCTL扫描方向不对 | 改0x36值 |
| 局部闪烁 | 频繁全屏刷、背光滤波差 | 改局部刷新、优化电源设计 |
| 颜色偏色 | 伽马寄存器配置不当 | 用屏厂推荐伽马值 |
排查时记住一个原则:先确认通信正常,再怀疑配置参数。通信是否正常最直观的方式就是逻辑分析仪抓波形,这一步可以排除一大半“假问题”。
5.2 触摸问题速查表
| 现象 | 可能原因 | 优先排查 |
|---|---|---|
| 完全无响应 | I2C地址错、上电时序错、中断未触发 | 读版本号确认I2C、检查INT引脚 |
| 有中断但读不到坐标 | 寄存器地址不对、连续读时序错 | 对照手册确认坐标寄存器 |
| 坐标一直触发 | 中断标志未清除 | 查0xFE寄存器清除逻辑 |
| 坐标方向反了 | 触摸坐标映射和显示方向不一致 | 代码做镜像或旋转 |
| 坐标漂移 | 电源纹波大、触摸参考电容异常 | 示波器看电源、检查硬件 |
| 待机功耗高 | 触摸唤醒灵敏度太高、MCU没睡深 | 降低灵敏度、检查其他外设 |
触摸调试里最容易让人崩溃的是“时好时坏”,这种问题往往不是软件逻辑错,而是硬件上电时序紧张。比如触摸IC复位脚没做延时,MCU启动比触摸IC快,I2C发命令时触摸IC还在复位,于是偶尔成功、偶尔失败。遇到这种,第一步就是加长RST低电平和拉高后到首次I2C通信之间的延时。
5.3 从调试器到示波器:排查工具链
工欲善其事,必先利其器。调这类屏,我常备三样工具:逻辑分析仪、示波器、串口。
逻辑分析仪用来抓SPI和I2C协议帧,看初始化命令有没有发出去、触摸坐标数据长什么样。便宜的8通道逻辑分析仪就够用,采样率不用太高,抓SPI和I2C绰绰有余。示波器用来看关键波形,比如复位时序、电源纹波、背光PWM。串口则用来打印调试日志,把坐标值、状态标志、错误码打出来,确认软件走到了哪一步。
调试器这边顺便提一句,很多人用ST-Link或者J-Link连接时总出问题,大部分情况不是硬件坏了,而是驱动没装好或版本不匹配。装了驱动之后最好插上设备看一眼设备管理器,确认端口和调试器都被正确识别了再打开IDE,能省掉很多莫名其妙的连接报错。下载程序之后,先确认调试器的复位模式,不然某些芯片会有写入后不运行的情况。
5.4 两次真实故障复盘
最后分享两个我自己在调这个组合时遇到的真实故障,很有代表性。
第一次是屏幕显示亮度不均匀,左半边和右半边明显不一样。我一开始以为是代码问题,反复调背光PWM、调伽马都没用。后来用示波器测了一下VCOM引脚的波形,发现电源纹波比较大,显示电压不稳定。最终发现是电源设计上电容容量不够,加了一颗钽电容后,问题彻底消失。这个经历告诉我:显示问题不全是驱动代码问题,电源质量对显示效果的影响经常被低估。
第二次是触摸偶尔失灵,触发一次后要等很久才能再触发下一次。排查了很久,最后发现是中断标志没有正确清除,INT引脚一直被拉低,主循环里判断中断标志永远为真,但读出来的坐标却是无效值,于是画面一直停在“按下”的状态。补上中断清除逻辑后,触摸响应恢复灵敏。从那以后我在每颗新的触摸IC上板后,都先把中断清除机制搞清楚,再谈手势识别、低功耗这些高级功能。
如果你正在做类似的彩屏项目,大概率会遇到和上面类似的问题。希望这篇把ST7789T3和CST816D的驱动脉络讲清楚的同时,能帮你把“调屏调触摸”这件事从玄学变成有章可循的工程流程。以后不管是项目里再换一块屏,还是面试时被人问到驱动细节,这段“屏幕加触摸”的组合经历,都会是特别扎实的硬经验。