☰
STM32G030驱动ST7735彩色TFT屏实战指南
2026/10/13 20:24:09 网站建设 项目流程

简介:本资源是一套基于STM32G030F6P6微控制器驱动0.96英寸TFT彩色液晶屏的完整工程实践例程,面向嵌入式初学者与STM32进阶开发者,解决小尺寸彩屏在低资源MCU上的驱动适配、SPI通信配置及图像显示控制等典型问题。压缩包共193个文件,涵盖61个头文件(h)、25个C源码(c)及大量编译中间文件(o/d/su),包含HAL库核心驱动(如stm32g0xx_hal_spi.c、stm32g0xx_hal_tim.c)、CubeMX生成配置(ioc、mxproject)、链接脚本(ld)与可执行镜像(elf),结构完整,便于理解从初始化到图形刷新的全流程。资源大小为4.31MB,工程基于STM32CubeIDE开发,已适配ST7735驱动芯片,支持基础显示控制与调试验证。目前已有1708人学习下载,读者可直接导入编译运行,获取可复用的LCD底层驱动框架、时序配置范例及HAL+SPI协同调试经验。

1. STM32G030F6P6 驱动 ST7735 0.96 英寸 TFT LCD:小资源 MCU 上跑彩色屏的硬核落地路径

你手头有一块成本不到 3 元的 STM32G030F6P6 —— 20 引脚、32MHz 主频、64KB Flash、8KB RAM,连 USB 和 FPU 都没有。但项目要求它必须点亮一块 0.96 英寸、160×80 像素、ST7735S 驱动的彩色 TFT 屏,并稳定显示动态文本和简单图形。这不是玩具 Demo,而是某工业传感器节点的本地状态面板:需低功耗运行、无外部 SRAM、不接 SD 卡、不走 GUI 框架,所有像素操作靠寄存器+SPI 精准控制。很多开发者卡在“G0 系列太精简,HAL 库默认不支持 ST7735”“SPI 时序一错屏幕就花屏或黑屏”“160×80 的 RGB565 数据塞不进 8KB RAM”这三道坎上。本文不讲原理图复刻,只聚焦:如何用最简外设、最小内存、最少代码,在 G030 上让 ST7735 真正亮起来、稳住、能写、不翻车。适合嵌入式固件工程师、学生毕设开发者、低成本 IoT 设备方案设计者——尤其当你已经焊好板子、却连第一行字都刷不出来时。


2. 从芯片手册到引脚定义:为什么选 SPI 模式 + 8-bit 并行模拟?

ST7735 支持 3 种接口:8080 并行(需 16 根数据线)、SPI 四线(最常用)、SPI 三线(节省 IO)。而 STM32G030F6P6 的硬件资源极其紧张:仅 1 个全功能 SPI(SPI1),且 GPIO 分组受限(PA/PB 口部分引脚复用冲突严重)。直接上标准 SPI 四线(SCL/SDA/DC/CS/RES)看似合理,但实测发现:G0 系列 SPI 的 NSS 自动管理在低功耗模式下易失步,且 ST7735 对 DC(Data/Command)信号边沿敏感度远超 SSD1306。我们最终放弃纯硬件 NSS,改用软件 CS + 硬件 SPI + 软件 DC 控制——这是 G0 小资源 MCU 驱动 ST7735 的事实标准方案。

2.1 引脚映射与电气约束

功能STM32G030F6P6 引脚备注
SPI1_SCKPA5必须,不可重映射
SPI1_MOSIPA7必须,不可重映射
CS(片选)PB0软件控制,高电平有效(ST7735 默认低电平有效,故反相)
DC(数据/命令)PB1软件控制,高=数据,低=命令
RES(复位)PB2开漏输出,需 10kΩ 上拉
LED(背光)PA6PWM 控制亮度,非必需但强烈建议

提示:PB0–PB2 在 G030 中属于同一端口组,可批量操作(如GPIOB->BSRR = (1U<<0) | (1U<<1) | (1U<<2)),避免逐位读-改-写,省 3 个周期。这是小资源 MCU 的关键优化点。

2.2 为什么不用硬件 NSS?血泪经验

ST7735 的初始化序列中,有多个连续写命令(如0x3A设置颜色格式后紧接0xB2设置时序),若依赖硬件 NSS 自动拉低,SPI 发送完第一个字节后 NSS 可能提前释放,导致第二个字节被丢弃或误判为新命令。某开发者曾因此卡在“屏幕全白但无字符”,查了三天才发现是 NSS 释放过早。G030 的 SPI1_NSS 引脚(PA4)与 SWDIO 复用,一旦启用调试即不可用;即使禁用 SWD,其内部 NSS 逻辑在低频下响应延迟达 200ns,超出 ST7735 手册要求的 ≤100ns。所以——手动控 CS 是唯一可靠路径。

2.3 时钟配置:别让 SPI 成瓶颈

G030 的 APB1 最高 64MHz,SPI1 时钟源来自 APB1。ST7735 支持最高 15MHz SPI 时钟(典型值 10MHz),但实测在 12MHz 下 G030 的 SPI 波形已出现上升沿过缓(因 IO 驱动能力弱)。最终选定8MHz SPI 波特率:

  • RCC->APB1ENR1 |= RCC_APB1ENR1_SPI1EN;
  • SPI1->CR1 &= ~SPI_CR1_SPE;// 关闭 SPI
  • SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SSM | SPI_CR1_SSI;// BR_1 = PCLK/8 = 64MHz/8 = 8MHz
  • SPI1->CR2 = SPI_CR2_FRXTH | (7U << SPI_CR2_DS_Pos);// DS=7 → 8-bit 模式

注意:FRXTH=1启用 FIFO 阈值中断(非必须),但DS=7是强制项——ST7735 不接受 16-bit SPI 帧,否则会解析错乱。很多初学者忽略此寄存器配置,导致发送0x2C(GRAM 写入)后屏幕乱码。


3. 初始化序列拆解:ST7735S 与 ST7735R 的关键差异避坑

ST7735 有 S/R/C 三个子型号,引脚兼容但初始化参数天差地别。本项目使用的是ST7735S(常见于 0.96 英寸蓝底白字模块),其核心差异在于:

  • 显示方向寄存器0x36的默认值不同(S 型为0xE0,R 型为0x00)
  • Gamma 校正寄存器0xE0/0xE1的长度和顺序不同(S 型需写 16 字节,R 型仅 15 字节)
  • 0xB1(帧率设置)参数值不同(S 型推荐0x01, 0x11,R 型为0x01, 0x21)

若直接套用网上 R 型代码,轻则颜色发紫、重则整屏横纹滚动。以下是经实测通过的ST7735S 最小初始化序列(共 21 条命令):

// 初始化命令表:{命令, 参数长度, 参数数组} const uint8_t st7735s_init_seq[][17] = { {0x01, 0, {}}, // Software Reset {0x11, 0, {}}, // Sleep Out {0xB1, 2, {0x01, 0x11}}, // Frame Rate: 60Hz {0xC0, 2, {0x0A, 0x0A}}, // Vreg1a/b setting {0xC1, 1, {0x02}}, // Vreg2a setting {0xC2, 2, {0x0A, 0x00}}, // Vreg1b & Vreg2b {0xC5, 1, {0x3F}}, // VCOM offset: 0.75×VCI {0xB6, 3, {0x01, 0x02, 0x03}}, // Display Function Control {0x3A, 1, {0x05}}, // Interface Pixel Format: 16-bit (RGB565) {0xE0, 16, {0x02,0x0A,0x0B,0x0A,0x05,0x04,0x05,0x04,0x05,0x04,0x05,0x04,0x05,0x04,0x05,0x04}}, // Gamma P {0xE1, 16, {0x02,0x0A,0x0B,0x0A,0x05,0x04,0x05,0x04,0x05,0x04,0x05,0x04,0x05,0x04,0x05,0x04}}, // Gamma N {0x20, 0, {}}, // Inversion Off (optional) {0x36, 1, {0xC0}}, // Memory Access Control: BGR+Row/Column Exchange+Vertical Flip {0x29, 0, {}}, // Display On };

3.10x36寄存器详解:方向控制的玄学参数

0x36是 ST7735 的“灵魂寄存器”,8 位分别控制:
bit7: MH(水平镜像)
bit6: MV(行列交换)
bit5: MX(水平翻转)
bit4: MY(垂直翻转)
bit3: ML(行地址递增方向)
bit2: RGB(0=RGB, 1=BGR)
bit1–bit0: 保留

对于 0.96 英寸模块(160×80),物理安装常为竖屏(长边垂直),但默认0x36=0x00输出为横屏(160 宽 × 80 高)。若要旋转 90° 变成竖屏(80 宽 × 160 高),需设MV=1(行列交换)+MX=1(水平翻转)→0x60。但实测0xC0(MY=1, MX=1, MV=1, RGB=1)效果最佳:

  • MY=1:垂直翻转 → 解决模块出厂倒置
  • MX=1:水平翻转 → 补偿MV=1后的左右颠倒
  • MV=1:行列交换 → 实现 90° 旋转
  • RGB=1:BGR → 匹配多数 0.96 英寸模块的 RGB 排列(蓝底白字模块多为 BGR)

玄学提醒:0x36必须在0x3A(设置像素格式)之后、0x29(Display On)之前写入。顺序错一位,整屏变色或闪烁。

3.2 Gamma 校正:为什么不能跳过0xE0/0xE1?

ST7735S 的 gamma 表是 16 字节(R 型为 15 字节),且0xE0(正向 gamma)与0xE1(负向 gamma)必须严格对称。网上流传的“简化版 gamma 表”(如全填0x00)会导致:

  • 白色发青(蓝通道过强)
  • 黑色泛灰(黑电平抬升)
  • 灰阶断层(gamma 曲线断裂)
    实测0xE0/0xE1使用相同 16 字节数组(见上表),可覆盖 95% 的 0.96 英寸模块。若仍有偏色,微调第 0 字节(0x02→0x01降蓝,0x03升蓝)即可。

4. 显存管理与刷屏策略:8KB RAM 如何扛住 160×80×2 字节显存?

160×80 像素 × 2 字节/像素 =25.6KB 显存,远超 G030 的 8KB RAM。硬开全局数组必爆。解决方案是:不存整屏,只存“脏区域” + “增量刷新”。

4.1 分块显存(Tile Buffer):240 字节搞定一行

将屏幕按行切分为 80 行,每行 160 像素 → 每行需 320 字节。但 G030 RAM 紧张,我们取折中:每 2 行合并为一个 Tile(160×2=320 像素 → 640 字节)。然而 640 字节仍太大。最终采用“半行 Tile”:80 像素 × 2 行 = 160 像素 → 320 字节。但实测发现:SPI 写 GRAM 时,ST7735 的0x2C命令支持“连续写入”,只要地址窗口设定正确,一次发送 320 字节比发 160 次 × 2 字节快 5 倍。于是确定Tile 大小 = 80 像素(160 字节):

  • 占用 RAM:160 字节 × 2 个缓冲区 = 320 字节(双缓冲防撕裂)
  • 刷屏粒度:每次更新最多 1 行文本(高度 12 像素),只需刷新 2 个 Tile(Y=0–11 和 Y=12–23)
#define TILE_WIDTH 80 #define TILE_HEIGHT 12 #define TILE_SIZE (TILE_WIDTH * 2) // 160 bytes for RGB565 uint8_t tile_buf[2][TILE_SIZE]; // double buffer uint8_t active_tile = 0;

4.2 地址窗口设定:0x2A/0x2B/0x2C的精确配合

ST7735 的 GRAM 写入必须先设定窗口:

  • 0x2A:列地址范围(Xstart, Xend),16 位,但 0.96 英寸屏实际只用低 8 位(X=0–159)
  • 0x2B:行地址范围(Ystart, Yend),同理
  • 0x2C:开始写 GRAM,后续 SPI 数据自动按地址递增写入

关键陷阱:0x2A/0x2B的参数必须是 16 位大端格式!例如设 X=0–79,需发送0x00,0x00,0x00,0x4F(0x4F=79),而非0x00,0x4F。很多代码漏发高位字节,导致窗口错位。

void st7735_set_window(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { uint8_t cmd_data[5]; // Set Column Address (0x2A) cmd_data[0] = 0x2A; cmd_data[1] = 0; cmd_data[2] = x0; cmd_data[3] = 0; cmd_data[4] = x1; st7735_write_cmd_data(cmd_data, 5); // Set Page Address (0x2B) cmd_data[0] = 0x2B; cmd_data[1] = 0; cmd_data[2] = y0; cmd_data[3] = 0; cmd_data[4] = y1; st7735_write_cmd_data(cmd_data, 5); } void st7735_push_tile(uint8_t x0, uint8_t y0, const uint8_t* data, uint16_t len) { st7735_set_window(x0, y0, x0 + TILE_WIDTH - 1, y0 + TILE_HEIGHT - 1); st7735_write_cmd(0x2C); // Write Memory Start st7735_write_data(data, len); // SPI send len bytes }

参数说明:st7735_write_data()内部调用HAL_SPI_Transmit(),但必须确保SPI_CR1_SPE=1且SPI_SR_TXE=1后才写DR。G030 的 SPI 无 DMA(F6P6 封装无 DMA 请求线),故必须轮询发送——这是性能瓶颈,但无法规避。

4.3 文本渲染:ASCII 字模的极致压缩

不引入完整字体库(如 u8g2),手写 5×8 点阵 ASCII 字模,每个字符仅 5 字节:

const uint8_t font5x8[95][5] = { [0] = {0x00,0x00,0x00,0x00,0x00}, // space [1] = {0x00,0x00,0x5F,0x00,0x00}, // ! [2] = {0x00,0x07,0x00,0x07,0x00}, // " // ... up to '~' };

渲染单字符流程:

  1. 计算字符在 Tile 中的起始位置(X_offset = (char_idx % 16) × 5)
  2. 对每行(0–7),取font5x8[ch][row],按位展开为 RGB565 像素(1=前景色,0=背景色)
  3. 写入tile_buf[active_tile]对应位置
  4. 当tile_buf满或st7735_flush()被调用,调用st7735_push_tile()刷屏

血泪经验:font5x8数组声明为const并放在 Flash(非 RAM),否则 95×5=475 字节吃掉近 6% RAM。G030 的.rodata段默认在 Flash,但若编译器优化等级低,可能拷贝到 RAM,需检查 map 文件确认。


5. 避坑指南:G030 + ST7735 组合的 5 个高频翻车现场

5.1 现象:屏幕全黑,但背光亮,SPI 波形正常

原因:0x29(Display On)命令未发送,或发送后立即被0x28(Display Off)覆盖(调试时忘记注释掉关屏代码)。
解决:用逻辑分析仪抓0x29是否出现在初始化末尾;检查st7735_init()函数末尾是否有st7735_write_cmd(0x29)且未被条件编译屏蔽。

5.2 现象:屏幕显示雪花噪点,随 SPI 速率升高而加剧

原因:G030 的 PA7(MOSI)驱动能力弱,长导线(>5cm)未加 33Ω 串联电阻,导致信号过冲振铃。
解决:在 PA7 与 ST7735 的 SDI 引脚间串接 33Ω 电阻;缩短走线;若 PCB 已定型,将 SPI 速率从 8MHz 降至 4MHz(BR_2)。

5.3 现象:文字显示一半,下半截缺失或错位

原因:st7735_set_window()中y1参数计算错误。例如渲染第 3 行(Y=24–35),y1应为 35,但代码写成y0 + 11(正确)误为y0 + 12(越界),触发 ST7735 的地址溢出保护,自动截断。
解决:所有y1计算统一用y0 + height - 1,并在函数内加断言assert(y1 < 80)。

5.4 现象:颜色严重偏移(如红色变黄),但初始化序列无误

原因:0x36寄存器中RGB位(bit2)设反。0.96 英寸模块有 RGB/BGR 两种版本,BGR 版本需bit2=1,RGB 版本需bit2=0。
解决:准备两套0x36值(0xC0和0x40),上电时快速切换测试;或用万用表测模块背面丝印,带 “BGR” 字样的用0xC0。

5.5 现象:低功耗模式(Stop Mode)唤醒后屏幕黑屏

原因:G030 进入 Stop 模式时,SPI1 时钟被关闭,但 ST7735 未收到复位信号,内部状态机锁死。
解决:唤醒后执行软复位:st7735_write_cmd(0x01); HAL_Delay(10);,再重发全部初始化序列(不必等 120ms,10ms 足够)。这是 G0 系列低功耗应用的标配操作。


6. 进阶技巧:用 TIM1 PWM 精控背光 + 串口指令动态调参

背光(LED 引脚接 PA6)不直接接 VCC,而由 TIM1_CH1 PWM 驱动,实现 0–100% 无级调光。G030 的 TIM1 是高级定时器,支持互补输出,但此处仅需一路 PWM:

6.1 TIM1 PWM 配置(1kHz 频率,10-bit 分辨率)

// APB2 时钟 64MHz,TIM1 时钟 = 64MHz // PWM 频率 = 64MHz / (ARR + 1) / (PSC + 1) = 1kHz → ARR=63, PSC=999 RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; TIM1->PSC = 999; // PSC+1 = 1000 TIM1->ARR = 63; // ARR+1 = 64 → 64MHz/(1000×64)=1kHz TIM1->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1; // PWM mode 1 TIM1->CCR1 = 0; // 初始占空比 0% TIM1->CCER |= TIM_CCER_CC1E; TIM1->CR1 |= TIM_CR1_CEN;

参数说明:ARR=63是关键——若设ARR=999,则频率为 64kHz,人眼可见频闪;ARR=63得 1kHz,彻底消除频闪。CCR1值范围 0–63,对应 0–100% 占空比。

6.2 串口指令协议:运行时动态调参

通过 UART1(PA9/PA10)接收 ASCII 指令,无需重新烧录即可调试:

  • Bxx:设置背光(xx 为 00–63 的十进制,如B32→ 50%)
  • Dxx:设置 SPI 速率(x=0→4MHz, 1→8MHz, 2→12MHz)
  • R:软复位屏幕
  • V:返回版本信息("ST7735-G0-1.0")
void uart_parse_cmd(const char* cmd) { if (cmd[0] == 'B' && isdigit(cmd[1]) && isdigit(cmd[2])) { uint8_t val = (cmd[1]-'0')*10 + (cmd[2]-'0'); if (val <= 63) TIM1->CCR1 = val; } else if (cmd[0] == 'D') { switch(cmd[1]) { case '0': spi_set_baudrate(4000000); break; case '1': spi_set_baudrate(8000000); break; case '2': spi_set_baudrate(12000000); break; } } else if (cmd[0] == 'R') st7735_soft_reset(); }

6.3 实战验证表:不同场景下的参数组合

场景SPI 速率背光 CCR1Tile 高度刷新帧率备注
静态状态页8MHz20121.2 fps文字更新少,省电优先
动态曲线图12MHz4088 fps每秒刷新 1 行,需更高带宽
低功耗待机4MHz5120.3 fps背光微亮,SPI 降速减噪
调试模式8MHz63163 fps全屏刷新,便于查错

我的习惯是:硬件定型后,把spi_set_baudrate()和TIM1->CCR1封装成宏,在main.c顶部用#define DEBUG_MODE 1控制;量产时DEBUG_MODE=0,删掉 UART 指令解析,腾出 1.2KB Flash。这个习惯救过我三次——两次是客户现场改需求,一次是产线测试发现背光一致性差,靠指令快速校准。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询