汉中市网站建设_网站建设公司_悬停效果_seo优化
2026/1/11 1:40:06 网站建设 项目流程

从零开始:在STM32上驱动ST7735实现高效图形绘制

你有没有遇到过这样的情况?项目已经跑通了传感器数据采集,逻辑控制也写得差不多了,但用户却一脸茫然:“这东西到底在干什么?”——因为没有直观的反馈界面。

这时候,一块小巧便宜、能显示彩色图形的小屏幕就成了救星。而ST7735 + STM32的组合,正是解决这类问题的经典答案。

它不像段码屏那样只能显示数字,也不像搭载LVGL的大屏方案那样吃内存、烧Flash。它是轻量级可视化系统的“甜点”选择:成本低至几块钱,分辨率够用(128×160),颜色丰富(65K色),且完全可以用Cortex-M3/M4级别的MCU原生驱动。

本文将带你一步步打通从硬件连接到像素绘制的完整链路,不依赖GUI框架,手把手写出高效的底层驱动代码,让你真正掌握“如何让第一个像素亮起来”的全过程。


为什么是ST7735?一个小而强的TFT控制器

市面上的TFT驱动芯片不少,比如更常见的ILI9341(常用于2.4寸屏),那为什么我们选ST7735?

简单说:小尺寸、低功耗、接线少、启动快、价格便宜

ST7735专为1.44~1.8英寸的小型TFT模块设计,典型分辨率为128×160。虽然物理驱动区域是132×162,但有效可视区通常裁剪为128×160。它的最大优势在于高度集成:

  • 内置升压电路,可直接生成LCD所需的偏压;
  • 支持SPI四线通信(SCK、MOSI、CS、DC)+ RST共5根控制线即可工作;
  • 提供标准命令集,兼容主流MCU;
  • 可配置RGB565色彩模式,每像素仅需2字节,非常适合资源受限系统。

更重要的是,国产厂商大量生产基于ST7735的廉价TFT模块,BOM成本极低,适合批量应用或DIY项目。


硬件怎么连?五线制SPI就够了

要让STM32和ST7735对话,首先要接好物理线路。以下是推荐的引脚映射:

STM32引脚连接到ST7735功能说明
PA5SCKSPI时钟线
PA7MOSI主机发送,从机接收
PA4CS片选信号,低电平有效
PA8DCData/Command选择
PA9RST复位引脚,低电平复位

注意:VCC接3.3V,部分模块支持5V耐压,但务必查看规格书确认!GND必须共地。

其中最关键的是DC 引脚—— 它决定了当前传输的是“命令”还是“数据”。
- 当DC = 0:表示接下来发送的是命令(如0x2A设置列地址);
- 当DC = 1:表示接下来发送的是参数或像素数据。

这种机制使得主机可以通过简单的GPIO切换来区分两类操作,无需额外协议开销。


SPI通信怎么配?HAL库轻松搞定

STM32的SPI外设非常成熟,使用HAL库可以快速初始化。我们需要配置为Mode 0(CPOL=0, CPHA=0),即空闲时SCK为低电平,在上升沿采样数据。

建议SPI波特率预分频设为fpclk / 16或更低(例如72MHz主频下约为4.5MHz),以保证信号稳定性,尤其在面包板或长导线上更容易出错。

// SPI1 初始化示例(使用CubeMX生成后调整) hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_1LINE; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // ~4.5MHz hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;

由于ST7735只接收不回传,我们可以安全地禁用MISO,节省一个IO口。


最关键一步:初始化序列不能错

很多初学者遇到“黑屏”、“花屏”、“闪一下就灭”的问题,根源往往出在初始化流程不完整或延时不准确

ST7735上电后处于睡眠状态,必须经过一系列精确的命令和等待才能唤醒。不同厂商的模块略有差异(比如Adafruit和WaveShare的偏移不同),但基本流程一致:

标准初始化步骤如下:

  1. 拉低RST至少10ms → 实现硬件复位;
  2. 拉高RST,延时120ms等待内部电源稳定;
  3. 发送软复位命令0x01(可选);
  4. 发送0x11退出睡眠模式,必须延时≥120ms
  5. 配置像素格式为16位(0x3A, 参数0x05);
  6. 设置内存访问方向(0x36,决定坐标系原点);
  7. 设置列地址(0x2A)和页地址(0x2B)范围;
  8. 开启显示(0x29)。

下面是优化后的初始化函数:

void ST7735_Init(void) { // 硬件复位 HAL_GPIO_WritePin(ST7735_RST_GPIO_Port, ST7735_RST_Pin, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(ST7735_RST_GPIO_Port, ST7735_RST_Pin, GPIO_PIN_SET); HAL_Delay(120); ST7735_WriteCmd(0x11); // Sleep out HAL_Delay(120); ST7735_WriteCmd(0x36); // MADCTL: 控制屏幕扫描方向 ST7735_WriteData(0xA0); // RGB顺序,从左到右,从上到下 ST7735_WriteCmd(0x3A); // COLMOD: 设置接口像素格式 ST7735_WriteData(0x05); // 16-bit/px, RGB565 ST7735_WriteCmd(0x21); // INVON: 开启显示反转(可选,增强对比) // 设置列地址范围 (0~127) ST7735_WriteCmd(0x2A); ST7735_WriteData(0x00); ST7735_WriteData(0x00); ST7735_WriteData(0x00); ST7735_WriteData(0x7F); // 设置页地址范围 (0~159) ST7735_WriteCmd(0x2B); ST7735_WriteData(0x00); ST7735_WriteData(0x00); ST7735_WriteData(0x00); ST7735_WriteData(0x9F); ST7735_WriteCmd(0x29); // Display ON }

⚠️ 特别提醒:0x11之后的120ms延时不可省略!否则内部稳压未建立,可能导致显示异常。


坐标与颜色:理解GRAM是如何被更新的

ST7735内部有一块叫GRAM(Graphic RAM)的显存区域,大小为132 × 162 × 18bit,但我们通常只使用其中的128×160区域。

每次绘图前,必须先通过CASET (0x2A)PASET (0x2B)命令划定一个“窗口”,然后发送RAMWR (0x2C)命令,后续所有数据都会按行优先顺序自动填入该窗口内的GRAM中。

关于坐标偏移

有些模块的实际显示区域并不是从(0,0)开始的,存在物理偏移。常见的是x_offset = 2, y_offset = 1,因此我们在结构体中定义设备参数:

static st7735_dev_t dev = { .width=128, .height=160, .x_offset=2, .y_offset=1 };

这个偏移必须在绘图时补偿,否则会出现边缘缺失或错位。

颜色格式:RGB565怎么算?

每个像素占2字节(16位),格式如下:

Bit: 15-------------------------0 RRRRR GGGGGG BBBBB

例如红色(255,0,0)转换为:

color = ((255 >> 3) << 11) | ((0 >> 2) << 5) | (0 >> 3); // 即: 0b11111 << 11 = 0xF800

你可以封装一个宏来简化转换:

#define RGB565(r,g,b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))

画点是基础,批量写才是性能关键

一切图形绘制都始于“画点”。下面是最基础的DrawPixel函数:

void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if (x >= dev.width || y >= dev.height) return; x += dev.x_offset; y += dev.y_offset; ST7735_SetAddressWindow(x, y, 1, 1); // 设置单像素窗口 ST7735_WriteCmd(0x2C); // 写GRAM ST7735_WriteData(color >> 8); ST7735_WriteData(color & 0xFF); }

但如果你用这个函数去画一个实心矩形,效率会非常低——每画一个点都要重新设置地址窗口,导致SPI事务频繁,刷新慢得肉眼可见。

正确做法:批量写入

我们应该一次性设置一个矩形区域,然后把所有像素数据打包发送:

void ST7735_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { uint32_t total = w * h; uint8_t *buf = malloc(total * 2); for (uint32_t i = 0; i < total; i++) { buf[2*i] = color >> 8; buf[2*i + 1] = color & 0xFF; } ST7735_SetAddressWindow(x, y, w, h); ST7735_WriteDataBuffer(buf, total * 2); free(buf); }

💡 提示:若RAM紧张,也可采用分块发送方式,每次发256字节,避免动态分配大缓冲区。


更进一步:线条、矩形、文本都能自己写

有了FillRect,就可以轻松实现各种图形:

绘制空心矩形

void ST7735_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { ST7735_FillRect(x, y, w, 1, color); // 上边 ST7735_FillRect(x, y + h - 1, w, 1, color); // 下边 ST7735_FillRect(x, y, 1, h, color); // 左边 ST7735_FillRect(x + w - 1, y, 1, h, color); // 右边 }

绘制水平/垂直线(比逐点快)

void ST7735_DrawHLine(uint16_t x, uint16_t y, uint16_t w, uint16_t color) { ST7735_FillRect(x, y, w, 1, color); } void ST7735_DrawVLine(uint16_t x, uint16_t y, uint16_t h, uint16_t color) { ST7735_FillRect(x, y, 1, h, color); }

显示字符(基于点阵字体)

假设你有一个8×16的ASCII字体数组:

extern const uint8_t font8x16[95][16]; // 存储' ' to '~' void ST7735_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color) { uint8_t idx = c - ' '; for (int row = 0; row < 16; row++) { uint8_t bits = font8x16[idx][row]; for (int col = 0; col < 8; col++) { if (bits & (0x80 >> col)) { ST7735_DrawPixel(x + col, y + row, color); } } } }

虽然逐点绘制较慢,但对于少量文字仍可用。如需高性能文本渲染,建议启用DMA传输整行数据。


性能瓶颈在哪?SPI带宽说了算

ST7735的最大理论刷新率受限于SPI速率。以4.5MHz为例:

  • 每秒传输位数:4.5M bit/s
  • 每像素2字节 = 16 bit
  • 全屏像素数:128 × 160 = 20,480 px
  • 全屏数据量:20,480 × 2 = 40,960 字节 ≈ 327,680 bit
  • 理论刷新率:4.5M / 327.68k ≈13.7 fps

也就是说,即使全速运行,也只能做到约14帧/秒的全屏刷新。

如何提升体验?

  • 局部刷新:只更新变化区域,减少数据量;
  • 使用DMA:释放CPU,实现后台传输;
  • 压缩静态内容:图标、背景图可存储在Flash中按需加载;
  • 背光PWM调光:通过PA6输出PWM控制BLK引脚,调节亮度节能。

实战建议:这些坑你一定要避开

  1. 电源不够稳?屏幕一闪就灭
    ST7735启动瞬间电流可达50mA以上,LDO带载能力不足会导致电压跌落。建议使用独立LDO或增加10μF陶瓷电容。

  2. SPI走线太长?出现乱码
    尽量缩短飞线长度,必要时在SCK线上串联100Ω电阻抑制反射。

  3. 不同模块偏移不同?画面偏移或裁剪错误
    记住:有的模块x_offset=0,有的是2,务必根据实际型号调整。

  4. 用了DMA却卡住?记得开启DMA中断并正确处理完成回调
    否则可能在传输未完成时就开始下一次操作。

  5. 想省Flash?别把整个帧缓冲放RAM里
    128×160×2 = 40KB,多数STM32没这么多SRAM。应采用“直接写屏”模式,边计算边发送。


结语:这是通往嵌入式GUI的第一步

看到这里,你应该已经掌握了如何在STM32上点亮一块ST7735屏幕,并亲手实现了基础图形绘制。

这套方案不需要任何第三方GUI库,代码体积小,执行效率高,特别适合以下场景:

  • 智能仪表盘(温度、湿度、PM2.5)
  • 手持测试工具(LCR表、频率计)
  • 教学实验平台
  • DIY游戏机、电子相册

下一步,你可以尝试:

  • 添加触摸功能(配合XPT2046实现点击交互);
  • 移植小型GUI引擎(如GUIslice或LittlevGL Lite);
  • 使用外部SPI Flash存放图片资源;
  • 在FreeRTOS中创建独立显示任务,实现非阻塞刷新。

当你能在指尖掌控每一个像素的时候,真正的嵌入式视觉世界才刚刚打开大门。

如果你正在做一个需要本地显示的小项目,不妨试试这块几块钱的TFT模块,也许它就是你产品体验升级的关键一环。

欢迎在评论区分享你的ST7735实战经验,或者提出你在驱动过程中遇到的问题,我们一起探讨解决方案。

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

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

立即咨询