
简介这是一份基于STM32G030F6P6的0.96英寸TFT LCD彩色液晶屏驱动例程工程平台为STM32CubeIDE使用SPI接口与ST7735驱动芯片通信实现图像显示与控制。资源共193个文件以C语言源码、头文件以及编译中间文件为主同时包含.ioc图形化配置、链接脚本、makefile等工程构建文件整体压缩包仅4.31MB便于直接导入开发环境学习。目前已有1694人学习下载适用于嵌入式入门者或需要为项目增加彩屏显示的开发者。借助例程可掌握STM32与ST7735的底层通信流程、SPI时序配置、像素点阵刷新逻辑等关键知识点配合作者CSDN博文中的讲解能快速理解代码结构并进行移植修改缩短开发周期。1. 项目整体思路与选型考量搞嵌入式这些年陆陆续续点亮过不少屏幕从最早的1602字符屏到现在的各种IPS全视角屏踩过的坑连起来能绕开发板好几圈。但说实话用STM32G030F6P6这颗料去驱动一块0.96寸的ST7735 TFTLCD这个组合确实有意思既能发挥小封装MCU的极致性价比又能让设备获得一块彩色显示窗口。今天就把这个方案的完整细节整理出来送给手里有G030芯片、想做低成本显示终端的各位。1.1 为什么要用STM32G030F6P6先聊聊这颗芯片。STM32G030F6P6属于意法半导体的G0系列超值型产品线Cortex-M0内核主频最高64MHzFlash 32KBRAM 8KB。光看参数可能觉得平平无奇但关键在于它的封装是TSSOP20也就是只有20个引脚。这意味着什么意味着你可以在一个非常紧凑的板子上塞进一颗完整的MCU系统外围只需要两颗退耦电容一个复位电路一套完整的嵌入式系统就搭起来了。这个系列的定位就是“用最少的外围和成本去解决实际需求”。在成本敏感的消费电子、智能家居小配件、便携式仪器仪表这些领域它简直是小身材大能量的代表。特别是在当前缺芯涨价的行情背景下G030这颗料在性价比上的优势被进一步放大。STM32G030F6P6的电源范围是1.7V到3.6V内置RC振荡器精度虽然比不上外部晶振但驱动屏幕这种对时序要求并不苛刻的场景直接用内部HSI就够了这又省下了两颗晶振电容和一颗晶振的位置。那为什么是驱动TFTLCD而不是OLED或者普通的段码屏原因其实很实际在5块钱以内价格区间想同时获得全彩色显示能力、丰富的灰阶过渡以及较高的刷新率TFT方案几乎是唯一解。OLED固然显示效果通透但大尺寸的OLED成本让人望而却步段码屏虽然更便宜但只能显示固定的数字和图标用在产品上会显得档次不足。0.96寸的TFTLCD配合ST7735驱动IC价格通常控制在3到5元之间显示效果完全能承担起一个产品的人机交互界面。1.2 0.96寸ST7735屏幕的规格理解很多人一开始会对“0.96寸”和“ST7735”这两个词产生误解以为它们是一回事。实际上0.96寸指的是屏幕的物理尺寸ST7735指的是屏幕内部集成的驱动控制芯片型号。同样大小的一块屏幕驱动IC可能是ST7735也可能是ST7735S、ILI9163C等等。不过ST7735系列是当前市场上0.96寸和1.44寸小尺寸TFT屏幕中最主流的驱动方案兼容性好资料多网上能找到的现成代码也多对于新手来说学习成本低。0.96寸的ST7735屏幕分为两个版本一种是分辨率80x160像素的另一种是128x160像素的。这两个版本在市面上都在卖但驱动初始化代码略有差异如果混用会出现显示错位、画面偏移的问题。我在第一个项目中就吃过这个亏买了两块屏商家发的标注都是0.96寸结果一块是80x160的另一块是128x160的初始化代码需要专门配置行地址和列地址偏移量才能正常显示。所以你在买屏幕的时候一定要问清楚分辨率或者直接让商家提供对应的驱动示例代码。ST7735屏幕的接口一般是7针或8针的SPI接口VCC、GND、CS片选、RESET复位、DC数据命令选择、SDA串行数据、SCL串行时钟部分模块还有BLK背光控制引脚。这种屏在硬件上最大的优势是只需要使用SPI这种高速串行接口占用的MCU引脚资源极少非常适合STM32G030F6P6这种引脚紧张的小封装芯片。1.3 硬件接线方案设计选好了MCU和屏幕接下来最关键的就是接线了。20个引脚的TSSOP封装能分配给屏幕使用的IO口并不多因为还要预留调试口、按键输入和外部通信接口。我的做法是利用STM32G030F6P6的SPI1外设来驱动屏幕硬件上走硬件SPI这样可以显著提升刷新速度同时让出CPU给其他任务。实测下来比较稳定的接线方案如下屏幕引脚连接MCU引脚说明VCC3.3V屏幕供电注意不要接5VGNDGND共地CSPB0软件控制片选RESETPB1低电平复位DCPA9数据/命令切换SDAPA7SPI1_MOSISCLPA5SPI1_SCKBLK3.3V背光常亮也可接PWM引脚调节亮度这里有一个细节值得说明一下。STM32G030F6P6的复用功能映射里SPI1的SCK可以映射到PA5或PB3MOSI可以映射到PA7或PB4。考虑到PB3和PB4同时也是SWD调试口的引脚如果把它们作为SPI使用会占用调试端口给程序烧录和调试带来麻烦。所以我选择了PA5和PA7这个组合把片上调试功能完整保留下来这在开发阶段能大幅提升效率。如果你使用的是软件模拟SPI那引脚选择就更随意了任意两个GPIO都可以只是刷新率远不如硬件SPI。2. ST7735驱动原理与SPI通信配置2.1 ST7735的初始化流程详解ST7735驱动屏幕的过程并不复杂本质上就像一个Agent接收来自主控MCU的指令按照指令去控制屏幕上的每一个像素点。主控只需要做一件事通过SPI接口向ST7735内部的寄存器写入命令和数据。ST7735有一个经典的初始化序列这个序列是芯片出厂时厂商给出的标准参考不同厂家的屏幕模块可能会在这个基础上做一些微调。初始化流程可以归纳为以下几个阶段第一个阶段是软件复位和退出睡眠模式。芯片上电后默认处于Sleep Out状态前的模式需要发送SWRESET命令0x01让芯片复位紧接着发送SLPOUT命令0x11让芯片从睡眠模式中唤醒然后再发送DISPON命令0x29打开显示。这几个命令之间需要适当的延时特别是在SLPOUT之后至少要等120ms给芯片内部电路稳定时间。第二个阶段是设置帧率和显示模式。通过FRMCTR1到FRMCTR3这几条命令设置正常模式和空闲模式下的帧率通常设置为60Hz。然后是PWCTR1到PWCTR3命令设置电源控制相关的VIP、VCI等电压值。这里面涉及到的电压调节命令比如0xC0、0xC1、0xC2、0xC3这些各个厂家模块差异较大必须参照你手上的模块资料来设置不能一概而论。第三个阶段是伽马校正值配置。GMCTRP10xE0和GMCTRN10xE1这两条命令需要写入一串固定的伽马曲线参数。说白了就是定制每个灰度级别上液晶的偏转电压让颜色显示更自然。这个参数同样跟屏幕面板有关不同厂家的面板用同一组伽马值显示出来的色彩饱和度会有差异。最后一个阶段是设置像素格式和扫描方向。通过COLMOD0x3A命令设置RGB565格式这是TFT显示中最常用的16位色格式。然后通过MADCTL0x36命令设置扫描方向控制RGB红绿蓝的排列顺序。这个命令很关键如果你发现显示出来的颜色偏红或者偏蓝问题通常就出在这里。2.2 SPI时序与速率选择ST7735的SPI接口支持Mode 0和Mode 3两种模式也就是CPOL0/CPHA0或CPOL1/CPHA1。在STM32的HAL库配置中需要把SPI初始化结构体中的ClockPolarity和ClockPhase设置成一致的值。我用的是模式0也就是空闲时时钟为低电平数据在上升沿采样。SPI的速率方面ST7735的数据手册给出的最大串行时钟频率是15.4MHz典型值但实际使用中还要考虑PCB走线长度和杜邦线的质量。如果用面包板加杜邦线连接线的寄生电容会对高速信号造成影响太高的SPI时钟会导致数据出错屏幕上出现雪花点或花屏。我实测下来5MHz到10MHz是一个比较稳定的区间。STM32G030的SPI1最高时钟可以做到系统时钟的一半也就是32MHz这个上限远超ST7735的能力所以瓶颈反而在屏幕这边。如果你只需要显示静态画面SPI速率设置2MHz就够了如果要做动画或者实时波形显示就必须让CPU尽量不参与数据传输——直接用DMA把显存内容搬给SPI外设。这一点在后面做中文显示和图形刷新时能明显感受到区别。2.3 STM32G030的SPI初始化配置在STM32G030F6P6上配置SPI1使用STM32CubeMX会比较直观。在Pinout页面里把PA5设为SPI1_SCKPA7设为SPI1_MOSI然后切换到Connectivity页面找到SPI1进行参数配置。参数配置的核心要点如下ModeTransmit Only Master只发送主机模式Hardware NSS SignalDisable片选信号由软件控制Data Size8 BitsFirst BitMSB First高位先行Prescaler4分频或8分频对应8MHz或4MHzClock PolarityLowClock Phase1 Edge第一个沿采样仔细看我特意把模式设置成Transmit Only Master。因为ST7735这个屏幕模块在实际使用中绝大多数场景都是单向写数据不涉及从屏幕读回数据。如果你在CubeMX里选了Full-Duplex Master模式MISO引脚会被强制占用这在引脚紧张的G030上是一种浪费。当然如果你需要读回屏幕的ID或状态寄存器就需要使用全双工模式或软件模拟SPI这就看具体需求了。片选信号CS之所以要软件控制是因为多个SPI从设备共用一个SPI总线时每个设备需要独立的片选。虽然这个项目只挂了一块屏幕但软件控制片选增加了灵活性也方便后续扩展其他SPI设备比如Flash芯片或者SD卡。3. 显示驱动实现与中文显示实战3.1 底层驱动函数设计思路驱动ST7735屏幕的程序结构我习惯分成三层来组织这样代码清晰以后换MCU平台或者换屏幕型号时只需要改动底层函数上层逻辑完全不用动。底层是硬件抽象层向SPI总线发送一个字节数据。这一层隔离了HAL库的差异让上层驱动不关心具体是用的硬件SPI还是软件模拟SPI。中间层是屏幕控制层包括发送命令、发送数据、设置窗口和画点这几个核心函数。最上层是应用层负责绘制字符、显示中文字符串、画线段和矩形等。中间层的核心是两个基础函数static void LCD_WR_REG(uint8_t reg) { LCD_DC_LOW(); // DC引脚拉低表示发送的是命令 LCD_CS_LOW(); // 片选使能 HAL_SPI_Transmit(hspi1, reg, 1, 100); // SPI发送命令字节 LCD_CS_HIGH(); // 片选释放 } static void LCD_WR_DATA8(uint8_t dat) { LCD_DC_HIGH(); // DC引脚拉高表示发送的是数据 LCD_CS_LOW(); HAL_SPI_Transmit(hspi1, dat, 1, 100); LCD_CS_HIGH(); }这里有个细节发送命令之前DC引脚拉低发送数据之前DC引脚拉高时序绝对不能搞反否则屏幕根本不知道你发来的是让它执行动作的命令还是该显示的内容。很多新手第一次驱动失败就是在这个DC引脚的时序上翻车。在画点函数中需要先设置显示窗口Set Address Window也就是告诉控制器哪一个矩形区域内的像素要被更新。ST7735通过CASET0x2A命令设置列地址通过RASET0x2B命令设置行地址然后再执行RAMWR0x2C命令开始写像素数据。void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WR_REG(0x2A); LCD_WR_DATA8(x0 8); LCD_WR_DATA8(x0 0xFF); LCD_WR_DATA8(x1 8); LCD_WR_DATA8(x1 0xFF); LCD_WR_REG(0x2B); LCD_WR_DATA8(y0 8); LCD_WR_DATA8(y0 0xFF); LCD_WR_DATA8(y1 8); LCD_WR_DATA8(y1 0xFF); LCD_WR_REG(0x2C); }设置窗口这个操作是TFT屏幕提高刷新效率的关键。因为在更新局部画面时只需要告诉控制器更新范围控制器会自动把后续收到的像素数据按顺序填入窗口内不需要逐行发送完整帧。ST7735的内部有一个指针每收到一个像素数据就自动跳到下一个地址所以只要窗口设置正确连续发送数据就能连续填充画面。3.2 使用G030驱动屏幕的填色与刷新优化填色函数算是整个驱动里最高频使用的基础函数了。你可以用一个最简单的清屏函数来验证屏幕驱动是否正常void LCD_Fill(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t size (x1 - x0 1) * (y1 - y0 1); LCD_SetWindow(x0, y0, x1, y1); for (uint32_t i 0; i size; i) { LCD_WR_DATA16(color); } LCD_SetWindow(0, 0, Width - 1, Height - 1); }写成这样的直接调用LCD_WR_DATA16对于一颗64MHz主频Cortex-M0内核来说清屏一次80x160也就是12800个像素大约需要几十毫秒。每次发送一个16位像素都是通过HAL_SPI_Transmit这层函数而这些函数内部有超时检查和中断标志位的操作开销比较可观整体下来的耗时比想象中要大得多。如果你对刷新速度有要求比如要显示动态波形或者做简单动画那么DMA几乎是绕不开的优化方案。思路是把屏幕窗口设置好后直接用DMA把显存中的一段数据搬给SPI_TX寄存器发送过程中CPU不参与任何操作。uint16_t framebuffer[128 * 160]; void LCD_Fill_DMA(uint16_t color) { // 先填充一次framebuffer for (int i 0; i 128 * 160; i) { framebuffer[i] color; } LCD_SetWindow(0, 0, 127, 159); LCD_DC_HIGH(); LCD_CS_LOW(); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)framebuffer, 128 * 160 * 2); LCD_CS_HIGH(); }有一点很关键启用SPI DMA前必须在CubeMX或代码中配置好DMA通道并且注意DMA传输完成中断里要释放信号量或者做标记确保下一次DMA传输不会和上一次冲突。实际测试中用DMA清屏的速度可以比阻塞式发送快好几倍这个差距在动画显示时是肉眼可见的顺畅。但这里必须提醒如果SPI的MOSI引脚发送数据时是用DMA方式连续不断的发SPI外设的TXE标志位和DMA请求信号的配合时序一定要理清楚。用G030的HAL库时容易出现一种情况DMA传输完成后SPI内部FIFO还没刷完这时如果把CS拉高最后的几个像素数据可能还没发出去导致屏幕底部出现一条彩色残影。解决办法是查询等待SPI内部空闲再释放CS引脚。while (hspi1.State HAL_SPI_STATE_BUSY_TX) {}3.3 中文显示原理与字库方案英文和数字字符在TFT屏幕上显示非常简单因为ASCII码只要查一张很小的字模表就行。5x7或者8x16点阵的字模每字节代表一行像素点阵程序扫描每个字符的每一行把对应的像素画到屏幕上速度很快。但中文不行中文是方块字常用汉字有3000多个GB2312标准定义的字符集里汉字就有6763个再算上数字、字母和符号一套完整的16x16点阵字库体积大概在256KB左右。问题来了STM32G030F6P6的内置Flash只有32KB。别说是完整的16x16点阵字库了就算你只放常用汉字前1000个字库文件也要占掉32KB的一半以上再加上程序本身的代码Flash空间根本不够用。更不用说24x24点阵的大字库了。所以中文显示这块必须做取舍和裁剪。我的做法是使用取模软件预先提取需要的字模生成一个精简字库数组嵌到程序里。具体步骤如下在电脑端用PCtoLCD2002或者字模提取工具选择汉字。设置取模方式我通常选“阴码、逐行式、顺向”也就是二进制0表示显示点1表示空白每一行从左到右排列。生成一个C语言数组每行字节数就是宽度除以8。16x16点阵的汉字一行有2个字节总共16行一整个字就是32字节。使用UTF-8编码的字符串在程序里把中文字符串的每个字符转成GB2312编码用来索引字模数组。不过在G030这种小芯片上最简洁的显示方式是使用字模数组配合Unicode转GB2312映射的函数。每个中文字符用2个字节表示字符的第一个字节可以当作区码第二个字节当作位码这两个字节经过偏移计算就得到了字库中的起始位置。简化的代码如下void LCD_ShowChinese(uint16_t x, uint16_t y, uint8_t *str, uint16_t color, uint16_t bgcolor) { while (*str ! 0) { uint8_t high *str; // 区码 str; uint8_t low *str; // 位码 str; // 计算在字库数组中的偏移 uint16_t offset ((high - 0xA1) * 94 (low - 0xA1)) * 32; // 绘制点阵 LCD_DrawChar16x16(x, y, Font_Chinese[offset], color, bgcolor); x 16; } }以项目标题中显示的“0.96INCH”为例如果你做的是个带产品名的显示界面可以把产品名、版本号等固定文本先提出来形成一张字模表。一个界面上显示的汉字通常不超过几十个取一次模生成数组就能满足绝大多数等于“固定显示内容”的应用场景。如果是显示任意输入的中文那就必须外接Flash芯片存放完整字库或者从SD卡中动态读取这在引脚只有20个的G030上会比较吃力需要仔细权衡。3.4 显示驱动的完整初始化序列参考我把自己整理的一套能直接适配0.96寸80x160分辨率屏幕的初始化序列贴出来这个序列在多个厂家的ST7735S模块上验证过如果你的屏是128x160分辨率的后面的离散度和偏移量需要微调。void LCD_Init(void) { LCD_RES_LOW(); HAL_Delay(20); LCD_RES_HIGH(); HAL_Delay(150); LCD_WR_REG(0x01); // SWRESET 软件复位 HAL_Delay(150); LCD_WR_REG(0x11); // SLPOUT 退出睡眠 HAL_Delay(120); LCD_WR_REG(0xB1); // FRMCTR1 帧率设置 LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x3C); LCD_WR_REG(0xB2); // FRMCTR2 LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x3C); LCD_WR_REG(0xB3); // FRMCTR3 LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x3C); LCD_WR_DATA8(0x3C); LCD_WR_REG(0xB4); // INVCTR 反色控制 LCD_WR_DATA8(0x03); LCD_WR_REG(0xC0); // PWCTR1 电源控制1 LCD_WR_DATA8(0x62); LCD_WR_DATA8(0x02); LCD_WR_DATA8(0x04); LCD_WR_REG(0xC1); // PWCTR2 LCD_WR_DATA8(0xC0); LCD_WR_REG(0xC2); // PWCTR3 LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x00); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x8D); LCD_WR_DATA8(0x6A); LCD_WR_REG(0xC4); LCD_WR_DATA8(0x8D); LCD_WR_DATA8(0xEE); LCD_WR_REG(0xC5); // VMCTR1 VCOM电压 LCD_WR_DATA8(0x0E); LCD_WR_REG(0x20); // INVOFF 关闭反色 LCD_WR_REG(0x36); // MADCTL 扫描方向 LCD_WR_DATA8(0xC8); LCD_WR_REG(0x3A); // COLMOD 像素格式 LCD_WR_DATA8(0x05); // 16位RGB565 LCD_WR_REG(0xE0); // 伽马正曲线 LCD_WR_DATA8(0x10); LCD_WR_DATA8(0x0E); LCD_WR_DATA8(0x02); LCD_WR_DATA8(0x03); LCD_WR_DATA8(0x0E); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x02); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x0A); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x27); LCD_WR_DATA8(0x37); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x0E); LCD_WR_DATA8(0x10); LCD_WR_REG(0xE1); // 伽马负曲线 LCD_WR_DATA8(0x10); LCD_WR_DATA8(0x0E); LCD_WR_DATA8(0x03); LCD_WR_DATA8(0x03); LCD_WR_DATA8(0x0F); LCD_WR_DATA8(0x06); LCD_WR_DATA8(0x02); LCD_WR_DATA8(0x08); LCD_WR_DATA8(0x0A); LCD_WR_DATA8(0x13); LCD_WR_DATA8(0x26); LCD_WR_DATA8(0x36); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x0E); LCD_WR_DATA8(0x10); LCD_WR_REG(0x29); // DISPON 打开显示 }如果初始化完屏幕还是白屏不显示优先检查两点一是复位时序RST引脚必须在程序运行后经历一次低电平脉冲并延时足够时间二是SLPOUT命令后延时必须充足120ms是最低要求有些模块需要200ms以上才能稳定。在正经的嵌入式项目里延时强烈不建议用空循环做死等最好用系统时基比如Systick来做精确延时。4. 实际项目中的坑与排错经验4.1 白屏和花屏问题排查白屏问题是我在这个项目里遇到最多的情况也是整个驱动流程中最容易出现故障的环节。新手拿到一块屏初始化代码一跑屏幕上没有任何反应这里面的原因可以分成四类第一类是供电不足。0.96寸屏幕的背光LED正常工作电流在20mA到40mA之间而ST7735芯片本身工作时也需要一定的电流。如果使用3.3V稳压器给MCU和屏幕同时供电而且稳压器输出能力不够强比如某些HT7333芯片的最大输出只有100mA在打开背光的瞬间会导致电压跌落MCU就可能不断重启表现就是屏幕闪一下白屏然后熄灭。解决的方法很简单给屏幕背光单独用一个限流电阻连接电流控制在20mA左右同时确保稳压器的余量足够。第二类是复位时序问题。ST7735正常上电后RST引脚需要由MCU拉低至少10微秒然后再拉高并延时足够的时间芯片才会稳定进入工作状态。如果你把RST引脚直接接到了VCC没有由MCU控制有可能上电瞬间电源建立的过程不够理想芯片内部的复位电路没有正确触发最终导致芯片处于异常状态。所以即便电路设计上允许直接接高电平我的建议还是用GPIO控制复位这会给你多留一个排错的空间。第三类是初始化命令不匹配。不同厂家的0.96寸屏虽然都宣称用的是ST7735但实际使用的可能是ST7735S这种兼容型号命令上存在细微差别。比如ST7735S的部分寄存器默认值与ST7735R不同如果直接用老代码会出现颜色不对、显示偏移、甚至白屏的情况。对应策略是让淘宝卖家提供他们模块的测试代码按照卖家的初始化序列走一遍确认没问题之后再裁剪优化。第四类是SPI通信故障。这种故障比较隐蔽因为MCU和屏幕各自都认为自己的工作正常但数据就是传不过去。常见原因包括MOSI和SCL接线接反、DC引脚时序不对、SPI模式设置错误。检查方法是使用逻辑分析仪或者示波器抓一下SPI的三个信号线看看是否有正常的时钟和数据信号。在没有示波器的情况下可以用一个简单粗暴的办法在初始化函数里连续发送几个颜色填充命令用万用表量一下屏幕的电源电流变化如果电流随着填充命令在跳动说明数据至少到了芯片内部问题可能出在初始化参数而不是接线。4.2 显示颜色异常的分析与处理很多人在屏幕上能正常显示内容之后下一个遇到的坑就是颜色不对。明明代码里写的是红色屏幕上显示的却是蓝色或者显示的“红绿蓝”跟预期的完全错位。这种问题也分成两种可能性。第一种可能是RGB排列方式设置错误。ST7735的像素格式是RGB565其中5位红色、6位绿色、5位蓝色。但是液晶面板上子像素在物理排列上可能是RGB顺序也可能反过来是BGR顺序这取决于面板的驱动电路设计。MADCTL命令0x36的第3位和第7位控制着RGB顺序和扫描方向。如果你的屏是BGR排列而初始化配置里没有设置对应的位那么红色和蓝色就会完全对调。这个问题可以这样快速验证在屏幕上分别显示纯红、纯绿、纯蓝三个色块如果红色显示成了蓝色那就是RGB顺序配置反了。解决办法是在MADCTL的值上额外加上0x08这个位。第二种可能是背光偏色或面板偏色这类问题多半是硬件层面的。有些低成本屏幕为了省料背光的白光LED色温偏冷或者偏暖导致显示白色时看起来发黄或者发蓝。如果差异不大可以通过调节伽马参数来补偿。如果差异很大比如明显看到屏幕发红或者发绿那多半是屏幕品质问题换一块屏实测一下最直接。另外值得注意的是颜色偏移量的问题。很多低价模块在面板贴装时可视区域相对于驱动芯片的GRAM会存在几个像素的偏移常见的就是列地址偏移1到2个像素。这个时候你在MADCTL下面设置的偏移量就起作用了。通过在初始化命令中写入OFSX和OFSY参数ST7735的0xC6命令可以调整显示区域的起始位置。LCD_WR_REG(0xC6); // 偏置调整 LCD_WR_DATA8(0x01); // 列偏移 LCD_WR_DATA8(0x00); // 行偏移不过0xC6命令只在部分ST7735S版本里有效。更通用的办法是在软件层的SetWindow函数中直接加上坐标偏移不依赖硬件命令。这个办法虽然会增加一点点计算开销但是兼容性最好任何基于ST7735的屏幕都能用。我在做产品适配不同厂家屏幕时会把偏移量定义成宏#define LCD_X_OFFSET 1 #define LCD_Y_OFFSET 0到了新模块上只需要调整这两个宏的值就能解决显示内容整体偏移的问题。4.3 Flash空间不足的优化经验这个可能是整个系列项目里最让人头疼的一个问题。前面说了STM32G030F6P6只有32KB Flash你以为把字库裁剪了就一切太平了但实际上还有其他消耗空间的地方。HAL库本身要占用不少FlashSTM32CubeMX默认生成的项目如果什么都不加编译出来就有10KB到14KB左右。如果再加一个简洁版printf重定向、几个GPIO控制函数、SPI驱动、延时函数Flash已经用掉接近一半。此时再放入30个汉字的字模数组30 x 32字节 960字节看似不多但加上其他全局变量和中断向量表整个项目到了后期空间会非常吃紧。我的解决思路有几条。第一是使用LL库替代HAL库。LL库是STM32的底层库代码量远小于HAL库执行效率也更高。如果你只是点亮一块屏幕并不需要用到复杂的时序中断配合那么LL库完全可以胜任。同样的功能用LL库写代码体积可以缩小大概30%到40%。第二是在编译选项里打开优化等级官方IDE里把优化等级设为“-Os”优化体积这个选项能显著减少代码大小但要注意调试时可能会碰到变量无法实时查看的情况。第三是精简单片内部的初始化代码不用CubeMX自动生成的一大堆外设初始化只手动初始化需要的GPIO和SPI外设这样能省掉不少不必要的外设配置代码。翻译一下就是用G030做项目Flash空间管理从第一天就要开始如果你一开始规划一个功能就把代码写得放荡不羁到最后一定会面临删功能的悲剧。4.4 常见问题速查与踩坑记录把我在多个ST7735项目中遇到的高频问题整理成一个速查表供大家直接对照检查现象可能原因解决路径白屏复位引脚时序不对确保RST先低后高延时至少100ms白屏供电不足检查3.3V在亮屏瞬间是否有跌落花屏SPI速率过高降低PSc改到2MHz测试花屏接线接触不良杜邦线换短线检查CS/SCL/MOSI颜色错乱RGB顺序配置错误MADCTL增加0x08位颜色偏色伽马参数不匹配使用模块厂家提供参数画面偏移屏幕分辨率不一致确认是80x160还是128x160底部残影DMA发送完成太快等待SPI内部空闲再拉高CS闪烁刷新率低或未锁存帧率寄存器设置60Hz无法下载程序PB3/PB4被复用恢复SWD引脚功能或者改用PA5/PA7方案5. 显示性能实测与后续扩展建议5.1 实测数据分享为了让大家直观感受到这套方案的性能上限我用一个简单的计时函数对几个关键操作进行了实测。测试环境主频64MHzSPI速率8MHz屏为0.96寸80x160分辨率代码按上述示例实现优化等级-O2。清屏整屏填充单一颜色不使用DMA时实测约22.5ms使用DMA后约8.3ms。显示一个8x16 ASCII字符约0.12ms。显示一个16x16点阵汉字约0.28ms。填充一块40x40像素的色块约1.9ms。这个数据反映出在G030这类M0核心上用硬件SPIDMA驱动小尺寸TFT屏刷新率完全够用。就算你要做简单的动画只要保证每帧更新区域不大动画流畅度是可以接受的。但如果你试图在整屏80x160区域上做全屏动态刷新那么8.3ms一帧的时间约合120fps早就超过屏幕本身的极限了。真正的瓶颈反而是你更新内容时生成像素数据的CPU时间。5.2 常用功能扩展思路基于这套驱动后续可以做的扩展很多。第一个是加入触摸功能给屏幕配一块电阻触摸膜或者换成带触摸的电容屏再加一个ADS7846触摸控制器就能实现简单的GUI交互。第二个是接入传感器数据展示把STM32G030的另一个SPI或者I2C外设连上温湿度传感器在屏幕上实时绘制曲线这需要用到基础的图形绘制函数。第三个是加入系统菜单功能通过GPIO外接按键或者编码器实现类似仪器仪表的多级菜单切换这需要编写一个简单的状态机来管理菜单层级。再往深了做你可以把显示刷新和主流程解耦用定时器中断定期刷新屏幕主循环专注于业务逻辑这是比较稳健的架构设计。也可以把常用字符和数字字模转成常量数组放到Flash里把动态变化的数据通过一个sprintf格式化后再输出到屏幕。总之这套软硬件的组合在小型化低成本产品中能玩出的花样很多多折腾几轮你对MCU软件架构的理解和对显示驱动原理的掌握都会有很大的提升。5.3 项目文件管理细节最后提一下压缩包文件名的细节。很多人下载了以“.rar”结尾的项目文件直接双击解压却发现提示文件损坏或者解压出来代码文件名的中文变成了乱码。这个问题的根源多半是文件名编码的问题尤其是在Windows和Linux之间传输的文件。如果你用7-Zip工具在解压时注意选择正确代码页或者直接用英文重命名压缩包基本都能解决。还有一类情况是递归破解rar密码的问题这个属于另说了实际意义不大真要是自己压缩的时候设置了密码建议密码设为空或者纯数字省得到时候自己都解不出来。项目文件备份是一个良好的工程习惯按日期命名版本压缩带上日期和功能描述时间长了以后你会感谢当初这个习惯。本文还有配套的精品资源点击获取