ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

51单片机驱动ST7789 TFT屏:从接线到刷屏的完整指南

51单片机驱动ST7789 TFT屏:从接线到刷屏的完整指南 简介51单片机ST7789 320×240驱动包面向嵌入式开发者针对在51单片机上驱动ST7789控制器TFT屏幕的常见难题系统覆盖硬件SPI接口连接、初始化命令序列、数据读写协议、显示控制与代码优化等关键技能点。压缩包共23个文件以C源文件、头文件、Hex固件、Keil工程文件为核心并包含必要的备份、配置文件整体仅88KB轻量紧凑可直接导入51单片机工程使用。该驱动包已有1445人学习下载是ST7789与51单片机应用开发中值得参考的实例。资源中不仅提供可调用的驱动源码和工程模板还通过模拟SPI中文显示、时序控制、帧缓冲管理等实现细节帮助读者理解从底层寄存器配置到上层显示绘制的完整流程。同时涉及缓冲区管理、亮度调节与电源模式处理等实用内容对学习嵌入式人机交互界面和快速验证屏幕驱动方案有直接帮助。1. 51 单片机驱动 ST7789 320×240瓶颈在显存不在算力51 单片机加一块 320×240 的 ST7789 彩色屏很多人第一反应是“带得动吗”。真实情况是常见的 STC89C52 也能点亮它但刷新速度、内存占用、坐标方向这几件事能把第一次上手的人卡住一两天。问题并不是 51 的算力不够而是整幅画面 320×240×2 字节约 150KB 的显存需求51 内部 RAM 却只有 256 字节到 1KB 级别缓冲区根本放不下。ST7789 自带 20KB 级别 GRAM能存下一帧却没法和 51 共享这块数据。所以这篇驱动说明把整个流程拆成接线、命令初始化、窗口寻址、内存策略和排错五段一步一步落地适合刚学会 GPIO 输出想尝试 TFT 屏幕的初学者也能给想快速拿到 51 驱动 ST7789 方案的嵌入式工程师提供一份可以直接抄改的骨架。2. 51 最小驱动接线ST7789 屏引脚、软件 SPI 与硬件 SPI 选型2.1 320×240 屏的引脚定义与一组能直接用的接线表ST7789 这个芯片在 320×240 模组上最常见的接口是四线 SPI也就是 SCL、SDA、CS、DC 四条信号线加上背光、复位和电源整块屏一共露出八个引脚。买屏时先确认丝印很多模组把 DIN 写成 SDA、把 CLK 写成 SCL名称不一样含义一样。屏引脚含义接 51 芯片备注VCC屏供电3.3V模组带 LDO 时可接 5V没有 LDO 时只接 3.3VGND地系统共地不共地时 SPI 波形看起来正常但屏幕无响应DINSPI 数据输入P1.5四线 SPI 模式下只接 MOSIMISO 通常悬空CLKSPI 时钟P1.7时钟空闲电平和采样沿决定 CPOL/CPHACS片选P1.4 或任意 IO 口只有一块屏时可以直接接地但仍建议留 GPIODC数据/命令选择P1.3低电平写命令高电平写数据RESET复位P1.2上电拉低 20ms 以上再拉高BLK背光控制P1.1 或串电阻接 VCC高电平点亮低电平关背光硬件设计时注意几个细节VCC 和 GND 之间放一个 10μF 电解电容再加一个 0.1μF 陶瓷电容位置尽量靠近屏的电源排针背光电流通常在 20mA 到 40mA 之间如果把它和 51 的 IO 共用同一个稳压器刷新屏幕时电流波动可能会让单片机复位。最稳妥的做法是背光单独接一个 3.3V 电源轨IO 只负责切换高低电平。2.2 软件模拟 SPI一根普通 GPIO 就能点亮屏幕标准 8051 架构比如 STC89C52、AT89S52没有 SPI 外设只能用普通 GPIO 逐位翻转。软件模拟 SPI 的时钟频率取决于主频和循环语句条数在 12MHz 晶振下一个字节大约需要 7μs 到 10μs。这个速度对初始化命令完全够用但对全屏刷新来说偏慢640×480 的图片就别指望了320×240 一帧 153600 字节算下来要一秒多。// 软件模拟 SPI工作模式 0空闲低电平上升沿采样 void spi_write_byte_sw(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { SCLK 0; // 把时钟拉低准备移出数据 if (dat 0x80) { // MSB 先行先发 bit7 MOSI 1; } else { MOSI 0; } dat 1; SCLK 1; // 上升沿ST7789 在此时锁存数据 } }这段代码里的关键点是时钟先拉低再拉高采样发生在上升沿对应 SPI 模式 0。如果换成模式 3空闲电平变成高电平采样仍在上沿绝大多数 320×240 的 ST7789 模组也能工作但为了统一后续所有寄存器配置都以模式 0 为准。软件模拟 SPI 的好处是任何引脚都能用不需要查复用寄存器缺点是主循环被占满程序没法同时做别的事。2.3 硬件 SPISTC12/STC15 上更省心的寄存器写法STC12C5A60S2、STC15 系列和 STC8 系列都带硬件 SPI 模块。以 STC12C5A60S2 为例SPI 引脚默认复用在一组 P1 口上P1.4 是 SSP1.5 是 MOSIP1.6 是 MISOP1.7 是 SCLK。配置成主机模式后写一个字节只是往 SPDAT 寄存器填数据然后等 SPIF 标志位即可。// STC12C5A60S2 硬件 SPI 初始化 void spi_init_hw(void) { // SPCTL 各位含义 // SSIG1 忽略 SS 引脚SPEN1 使能 SPI // DORD0 高位先发MSTR1 主机模式 // CPOL0 CPHA0 对应模式 0 // SPR10 SPR00 分频系数 4 SPCTL 0xD0; } // 写一个字节并等待完成 uint8_t spi_rw_hw(uint8_t dat) { SPDAT dat; while (!(SPSTAT 0x80)); // 等待 SPIF 置位 SPSTAT 0x80; // 软件清标志写 1 才有效 return SPDAT; }分频系数 SPR1/SPR0 有四种组合00 对应 4 分频01 对应 16 分频10 对应 64 分频11 对应 128 分频。主频 12MHz 时4 分频得到 3MHz SPI 时钟ST7789 完全支持这个速度。如果主频是 24MHz建议先用 16 分频保守起见跑通后再把分频降到 4 分频。硬件 SPI 相比软件模拟大约快三到五倍全屏刷新能压到 0.3 秒左右这是 51 上做动态界面的基础。2.4 上电复位时序背光亮了不等于控制器就绪ST7789 的上电要求比普通 LCD 模块严格电源电压稳定后RESET 引脚至少要维持 10ms 低电平然后释放释放后还要再等 120ms 以上再发第一条命令。这里的延时不是敷衍用的芯片内部有模拟电路负责产生偏压电压没有稳定时收到命令会被直接丢弃表现就是屏幕有背光但永远黑屏。void lcd_reset(void) { LCD_RST 0; delay_ms(50); // 拉低 50ms确保完成硬件复位 LCD_RST 1; delay_ms(150); // 释放后等待内部电源稳定 }实际操作里很多人都栽在复位后的第一个延时上。初始化序列里看到 0x11 退出睡眠后再等 120ms 也不要省略这条延时和硬件复位延时负责的是同一个环节只是发生在软件层面。如果外接的 RC 复位电路和 51 的复位电路连在一起屏幕复位时序会受到单片机复位信号影响建议 RESET 引脚直接用 GPIO 控制不依赖外部电路。提示屏幕没有反应时先用万用表量 VCC、GND 和背光引脚电压再查逻辑分析仪上 SCLK 是否有下降沿。绝大多数“驱动代码死活不对”的情况根源是接线而不是寄存器配置。3. ST7789 初始化序列与窗口寻址从写命令到亮起第一个色块3.1 DC 引脚怎么区分命令和数据ST7789 通过 DC 引脚的电平区分当前 SPI 总线上传输的是命令还是数据。DC 为低下一个字节被解析为命令DC 为高下一个字节被解析为命令的参数或显存数据。写驱动的第一步就是封装两个最底层的函数。// 写命令DC 拉低发送命令字节 static void lcd_cmd(uint8_t cmd) { LCD_DC 0; LCD_CS 0; spi_write_byte(cmd); LCD_CS 1; } // 写数据DC 拉高发送参数或像素数据 static void lcd_dat(uint8_t dat) { LCD_DC 1; LCD_CS 0; spi_write_byte(dat); LCD_CS 1; }注意这里每次传输都把 CS 拉低再拉高。命令和数据分开传输时可以这么写连续写大量像素时为了性能要把 CS 一直拉低只发一次片选信号。每个命令的参数个数不同比如设置像素格式的命令 0x3A 带一个参数而设置扫描方向的命令 0x36 同样带一个参数具体格式以 ST7789 参考手册里的寄存器和时序说明为准。3.2 完整初始化序列带注释、带延时的可直接使用版本初始化序列在不同厂家模组之间有差异但核心步骤是固定的硬件复位、退出睡眠、设置像素格式、配置电源与伽马值、打开显示。下面这份序列是我在通用 320×240 模组上经常使用的版本参数偏向兼容性不是针对单一屏厂的官方代码。void st7789_init(void) { lcd_reset(); // 硬件复位拉低 50ms释放后延时 150ms lcd_cmd(0x01); // 软件复位 delay_ms(150); lcd_cmd(0x11); // Sleep Out退出睡眠模式 delay_ms(150); // 这 150ms 不能省等待内部 DC-DC 启动 lcd_cmd(0x36); lcd_dat(0x00); // MADCTL 扫描方向先写 0方向问题稍后调 lcd_cmd(0x3A); lcd_dat(0x05); // 像素格式RGB56516 位/像素 // 电源与时序设置 lcd_cmd(0xB2); lcd_dat(0x0C); lcd_dat(0x0C); lcd_dat(0x00); lcd_dat(0x33); lcd_dat(0x33); // Porch 设定 lcd_cmd(0xB7); lcd_dat(0x35); // Gate 控制 lcd_cmd(0xBB); lcd_dat(0x19); // VCOM 电压3.3V 供电常见值 lcd_cmd(0xC0); lcd_dat(0x2C); // LCM 电压控制 lcd_cmd(0xC2); lcd_dat(0x01); // VDV 使能 lcd_cmd(0xC3); lcd_dat(0x12); // VDV 值 lcd_cmd(0xC4); lcd_dat(0x20); // VDV 响应设置 lcd_cmd(0xC6); lcd_dat(0x0F); // 显示时钟频率 lcd_cmd(0xD0); lcd_dat(0xA4); lcd_dat(0xA1); // 调压设置 // 伽马校正影响灰阶通透度不影响能否点亮 lcd_cmd(0xE0); lcd_dat(0xD0); lcd_dat(0x04); lcd_dat(0x0D); lcd_dat(0x11); lcd_dat(0x13); lcd_dat(0x2B); lcd_dat(0x3F); lcd_dat(0x54); lcd_dat(0x4C); lcd_dat(0x18); lcd_dat(0x0D); lcd_dat(0x0B); lcd_dat(0x1F); lcd_dat(0x23); lcd_cmd(0xE1); lcd_dat(0xD0); lcd_dat(0x04); lcd_dat(0x0C); lcd_dat(0x11); lcd_dat(0x13); lcd_dat(0x2C); lcd_dat(0x3F); lcd_dat(0x44); lcd_dat(0x51); lcd_dat(0x2F); lcd_dat(0x1F); lcd_dat(0x1F); lcd_dat(0x20); lcd_dat(0x23); lcd_cmd(0x29); // Display On显示打开 delay_ms(50); }这段代码里有两个容易误解的参数。0x3A 命令收到 0x05内部会解析为 16 位的 RGB565 格式很多库写成 0x55是因为高字节位保留不校验写 0x55 和写 0x05 效果相同但按参考手册原始定义低三位 101 才是有效配置。0xB2 到 0xD0 这组参数决定显示面板的模拟电路工作点不同模组可能出现亮度差异调试时如果发现屏发暗或者底色偏灰优先检查 0xBB 的 VCOM 值而不是怀疑 SPI 接线。3.3 窗口寻址三件套CASET、RASET、RAMWR 与大端字节序ST7789 的显存操作不是从头到尾顺序写而是先指定一个矩形窗口然后向窗口内连续灌入像素数据。三个命令分别是 0x2A 列地址、0x2B 行地址、0x2C 开始写入显存。坐标参数为两个字节先发高字节再发低字节顺序颠倒会显示错乱。void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { lcd_cmd(0x2A); // 列地址对应 X 方向 lcd_dat(x0 8); lcd_dat(x0 0xFF); lcd_dat(x1 8); lcd_dat(x1 0xFF); lcd_cmd(0x2B); // 行地址对应 Y 方向 lcd_dat(y0 8); lcd_dat(y0 0xFF); lcd_dat(y1 8); lcd_dat(y1 0xFF); lcd_cmd(0x2C); // 开始写显存数据 }设置完窗口之后每写两个字节代表一个像素的 RGB565 颜色第一个字节是高 5 位红色和高 6 位绿色的组合第二个字节是低 5 位蓝色和绿色低位。窗口的 start 和 end 都是闭区间全屏写满时 x1、y1 分别取 319 和 239而不是 320 和 240。ST7789 的 GRAM 会自动跨行连续填充不需要手动改窗口边界这给后面做图片流式刷新埋下了基础。3.4 用小色块验证驱动是否成立再往下走初始化代码写完不要急着做全屏清屏或者画字库先用一个只有 16×16 像素的小色块验证。全屏清屏一次要写 153600 字节如果 SPI 或者窗口方向有问题满屏全是花点很难定位原因而 16×16 色块只有 512 字节颜色、位置、边界三件事一次就能验证清楚。uint16_t i; // 画一个 16x16 的红色块屏幕左上角 lcd_set_window(0, 0, 15, 15); for (i 0; i 16 * 16; i) { lcd_dat(0xF8); // RGB565 红色高字节 lcd_dat(0x00); // 低字节 }如果左上角出现一个红色方块说明 SPI、复位、DC、初始化序列全部正确。如果出现的是蓝色或者绿色代表 RGB565 字节序反了把两个 lcd_dat 参数交换即可。如果方块出现在其他角落说明 0x36 的扫描方向不对接下来进入第四章方向调试的逻辑。4. 51 显存不够怎么办条带缓冲、分块刷新与屏内 GRAM 复用4.1 150KB 全屏图像在 51 上注定不能做整幅缓冲一帧 320×240 的 RGB565 图像需要 320×240×2算下来 153600 字节。STC89C52 内部只有 128 字节或 256 字节的 RAMSTC12C5A60S2 内部多一块约 1KB 的扩展 RAM仍然装不下十分之一幅画面。ST7789 内部有 240×320×18 位的 GRAM约 20KB能完整存下 320×240 一帧但它的 GRAM 只能被屏幕控制器读取51 没法把它当临时画布来使用。所以 51 驱动 ST7789 的通用做法是放弃全屏重绘改成局部刷新。每个 UI 控件比如进度条、波形区、数字显示区只重绘自己负责的小窗口。这样每次传输量只有几百到几千字节16MHz 主频下毛刺完全可接受。4.2 裸点直写与条带缓冲两种模式的选择最直接的驱动是裸点直写任何绘图操作都立刻换算成窗口通过 SPI 写进屏幕显存。void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color) { lcd_set_window(x, y, x, y); lcd_dat(color 8); lcd_dat(color 0xFF); }这段代码用来画简单图形是够用的画一条直线就调用一百次每次调用都要发五条命令带六个参数效率很低。要显示字符、波形或滚动列表时更好的方案是维护一块条带缓冲把屏幕横向按 320 像素宽、纵向按 32 像素高切成条带先在内存里画好再整体提交给屏幕。#define STRIP_H 32 #define STRIP_SIZE (320 * STRIP_H * 2) // 320*32*2 20480 字节 // 将缓冲区内容一次性刷到屏幕指定区域 void flush_strip(uint16_t y0) { uint16_t i; lcd_set_window(0, y0, 319, y0 STRIP_H - 1); LCD_CS 0; // 片选只拉低一次提高连续传输效率 for (i 0; i STRIP_SIZE; i) spi_write_byte(strip_buf[i]); LCD_CS 1; }裸点直写适合界面元素少、每次重绘范围小的场景比如温度显示每隔一秒变一次。条带缓冲适合做波形滚动、中文字库混排、多行菜单这类频繁覆盖大区域的场景。20KB 的条带缓冲区如果片内放不下可以放到外部扩展 RAM或者在 STC15/STC8 上放宽到 64KB 片内存储的型号。4.3 从 SPI Flash 或串口流式刷全屏图片的代码骨架要显示整幅图片常见做法是外挂一片 SPI Flash 存放图片数据再按行分块读出来写入屏幕。这个过程不需要把整张图装进内存每读一块刷一块内存占用只有临时缓冲。#define CHUNK_ROWS 32 uint8_t chunk_buf[320 * CHUNK_ROWS * 2]; // 20480 字节 void show_image_from_flash(uint32_t img_offset) { uint16_t y; for (y 0; y 240; y CHUNK_ROWS) { // 从外部 Flash 读取 32 行图片数据到缓冲区 spi_flash_read(img_offset y * 320 * 2, chunk_buf, sizeof(chunk_buf)); // 设置窗口并写入屏幕 lcd_set_window(0, y, 319, y CHUNK_ROWS - 1); LCD_CS 0; for (uint16_t i 0; i sizeof(chunk_buf); i) spi_write_byte(chunk_buf[i]); LCD_CS 1; } }这个流程里每刷 32 行要用 20KB 缓冲区STM32 这类平台可以改大51 上 32 行是一个性价比很好的分块尺寸缓冲区能放进扩展 RAM刷新次数也只有八次每次窗口切换的开销可以忽略。需要显示动态波形或者动画时把整个显示刷新逻辑放在主循环里每帧只调一次 show_image_from_flash就能得到一个低速但可靠的幻灯效果。4.4 想刷动态界面STC8 的 SPIDMA 升级路线如果项目需要流畅刷动画STC12 系的软查标志位方式还是不够。STC8H 系列仍然属于 51 架构指令兼容但片内带有 SPI 和 DMA 控制器可以把一整块内存数据自动搬运到 SPI 外设发送完成后触发中断。这种模式下 51 主循环只需要修改条带缓冲区的数据然后启动一次 DMA 传输屏幕就会自动刷新。用这种方式时条带缓冲可以再一次扩大直接把 64KB 以内的图片缓存放在 STC8 的扩展 RAM 中DMA 每搬完一段就更新窗口地址。实际效果是分辨率 320×240 的静态刷新能降到 100ms 以内动画界面不再是奢求。这套方案仍然延续 51 的开发方式不需要切换到 ARM 平台是 51 系驱动 ST7789 最接近“流畅”的落点。5. 方向、花屏与刷新速度ST7789 参数调试的三个必查项5.1 画面镜像和旋转都由 0x36 控制不是改坐标ST7789 的扫描方向由 MADCTL 寄存器也就是命令 0x36 决定三个关键位是 MY、MX、MV。MY 控制从上到下还是从下到上扫MX 控制从左到右还是从右到左扫MV 控制行和列是否交换。注意旋转 90° 也许搭配设置 MV 位为 1同时交换宽度和高度而不是在代码里把所有 x、y 坐标对调。建议在驱动里固定一个函数集中管理方向后续所有绘图接口都基于逻辑坐标。void lcd_set_rotation(uint8_t rot) { switch (rot) { case 0: lcd_cmd(0x36); lcd_dat(0x00); break; // 纵屏左上角起点 case 1: lcd_cmd(0x36); lcd_dat(0x60); break; // 横屏左方旋转 case 2: lcd_cmd(0x36); lcd_dat(0xC0); break; // 纵屏倒置 case 3: lcd_cmd(0x36); lcd_dat(0xA0); break; // 横屏右方旋转 } }不同模组的排线方向不一样这四组值不保证每次装上就是正向。调试方法是先画一个带文字内容的色块观察镜像方向再调整对应位的值直到文字正向且不镜像。5.2 花屏的三个区间定位时序、电源、字节序花屏出问题时要分段定位不要上来就怀疑初始化序列。先从最原始的环节排查。现象优先检查项对应位置黑屏无任何反应RESET 延时、VCC 电压上电复位流程满屏噪点雪花SCLK 与 DIN 接反、模式 0/模式 3 设置错SPI 接线与 CPOL/CPHA显示颜色错乱RGB565 高字节低字节颠倒写入显存的数据格式底色偏灰、亮度忽闪0xBB 的 VCOM 值匹配屏厂推荐初始化序列电源参数画面残影、局部不刷新窗口边界算错x1/y1 写成尺寸值CASET/RASET 参数字节序的问题最隐蔽。RGB565 红色像素高字节是 0xF8低字节是 0x00如果把低字节先发红色会变成暗蓝色甚至紫黑色。确认方法是定义红色、绿色、蓝色三个纯色宏分别画三个色块比对输出。#define COLOR_RED 0xF800 #define COLOR_GREEN 0x07E0 #define COLOR_BLUE 0x001F5.3 刷新速度的最后一个减法省掉不必要的重建51 上最有效的优化不是改 SPI 分频而是减少写入量。全屏清一次颜色大约写 150KB即便硬件 SPI 也要几十毫秒许多界面刷新时并不需要全清只重绘变化区域是最高性价比的优化。其次是连续传输时保持 CS 低电平避免每个字节都做一次 CS 下降和上升这个动作对软件模拟 SPI 影响尤其明显。最后才是修改分频系数STC12 系把分频从 16 改成 4刷屏时间大约缩短四倍但要确认 SPI 时钟不超过 ST7789 手册给出的上限51 平台上一般跑 4MHz 以内足够稳定。本文还有配套的精品资源点击获取
返回列表