简介:本资源是一套基于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_SCK | PA5 | 必须,不可重映射 |
| SPI1_MOSI | PA7 | 必须,不可重映射 |
| CS(片选) | PB0 | 软件控制,高电平有效(ST7735 默认低电平有效,故反相) |
| DC(数据/命令) | PB1 | 软件控制,高=数据,低=命令 |
| RES(复位) | PB2 | 开漏输出,需 10kΩ 上拉 |
| LED(背光) | PA6 | PWM 控制亮度,非必需但强烈建议 |
提示: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;// 关闭 SPISPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1 | SPI_CR1_SSM | SPI_CR1_SSI;// BR_1 = PCLK/8 = 64MHz/8 = 8MHzSPI1->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 '~' };渲染单字符流程:
- 计算字符在 Tile 中的起始位置(X_offset = (char_idx % 16) × 5)
- 对每行(0–7),取
font5x8[ch][row],按位展开为 RGB565 像素(1=前景色,0=背景色) - 写入
tile_buf[active_tile]对应位置 - 当
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 速率 | 背光 CCR1 | Tile 高度 | 刷新帧率 | 备注 |
|---|---|---|---|---|---|
| 静态状态页 | 8MHz | 20 | 12 | 1.2 fps | 文字更新少,省电优先 |
| 动态曲线图 | 12MHz | 40 | 8 | 8 fps | 每秒刷新 1 行,需更高带宽 |
| 低功耗待机 | 4MHz | 5 | 12 | 0.3 fps | 背光微亮,SPI 降速减噪 |
| 调试模式 | 8MHz | 63 | 16 | 3 fps | 全屏刷新,便于查错 |
我的习惯是:硬件定型后,把
spi_set_baudrate()和TIM1->CCR1封装成宏,在main.c顶部用#define DEBUG_MODE 1控制;量产时DEBUG_MODE=0,删掉 UART 指令解析,腾出 1.2KB Flash。这个习惯救过我三次——两次是客户现场改需求,一次是产线测试发现背光一致性差,靠指令快速校准。希望帮到你。
本文还有配套的精品资源,点击获取