拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STC单片机驱动ST7735 TFT屏英文显示实战:从SPI初始化到字模输出

简介面向嵌入式初学者与单片机开发者这份基于STC单片机驱动ST7735控制器的1.8英寸TFT屏英文显示示例工程解决了从SPI接口配置、初始化序列到文本显示与色彩转换的完整实现问题。包内含72个文件以C源码、头文件、Keil工程文件为主另有编译中间文件、烧录hex文件及一段实测演示视频压缩包整体约1.1MB。目前已有297人学习下载适合需要快速验证屏幕驱动或参照移植的开发者。读者可从中获得可直接编译运行的工程模板、分层清晰的驱动代码如延迟、定时器、串口、GUI模块以及ST7735初始化流程、ASCII字符字模映射等关键实现细节可显著缩短屏幕驱动的调试验证周期是学习STC单片机与TFT屏配合使用的实用参考。1. 一块 1.8 寸 TFT 屏挂到 STC 单片机上显示英文是第一个真正要过的关拿到 STC单片机ST7735 1.8寸TFT屏的英文显示 这个需求很多人第一步是找现成 GUI 库或者直接搬 Arduino 驱动结果在 STC89C52 上根本跑不起来它只有 256 字节内部 RAM连 128×160×2 字节的一张整屏图像都装不下更不用说带缓冲的中文点阵库。ST7735 这颗控制器本身不像字符型 LCD 那样内置字库它只会按你提供的颜色值往显存里填点。所以英文显示拆开看其实是三件事用 SPI 把 RGB565 数据填进正确位置用一行初始化命令让屏退出睡眠并把扫描方向调对再用一张 6×8 的 ASCII 字模表把字母变成点阵序列。三件事单看都不难串在一起时白屏、花屏、方向颠倒这三个问题会消耗掉大半调试时间。下面按接口→初始化→字模→排错的顺序把可以直接抄进 Keil C51 工程的代码和参数写出来。新手能一步步复现老手可以直接跳到参数表和坑位清单。2. 先理清 ST7735 的 SPI 接口与 STC 单片机的引脚分配2.1 模块引脚与信号时序DC/CS/RST 各干什么1.8 寸 TFT 模块引脚一般是 8 个VCC、GND、CS、RESET、DC也叫 A0、SDIMOSI、SCKSCLK、LED 背光。有些模块把 SDI 标成 SDA、SCK 标成 SCL本质还是一样的 SPI 写操作。VCC 和 LED 直接接 3.3V如果模块板载了稳压LED 也可以接 5V 但建议串一个 10~47Ω 电阻限流。这里有个必须提醒的点STC89C52 的 IO 输出是 5V 电平而 ST7735 是 3.3V 器件正规做法是加一级电平转换不过市面上大多数 1.8 寸模块在 SPI 引脚上做了电平兼容处理5V 直连也能工作稳妥起见在 MOSI、SCK 线上各串一个 1kΩ 电阻不会影响 10MHz 以内的时钟边沿。这些引脚里最容易被忽略的是 DC。SPI 本身只有时钟和数据两根线但 ST7735 的命令和数据都走 MOSI区分靠 DC 电平DC0 时写入的是命令DC1 时写入的是命令参数或显存数据。CS 是片选低有效整段发送过程保持拉低即可实际操作中可以把它当使能信号理解。RESET 需要一次完整的低到高跳变由单片机 GPIO 控制比直接接 RC 上电复位更可靠因为 ST7735 对复位脉冲宽度和复位后的延时都有明确要求。模块引脚STC89C52说明VCC3.3V模块供电必须与单片机共地GNDGND共地CSP1.3片选低有效RESETP1.1复位低脉冲后拉高DC / A0P1.20命令1数据SDI / MOSIP1.5数据输入写方向SCK / SCLKP1.4时钟空闲低电平LED / BLK3.3V背光可串限流电阻STC 单片机的复位电路在这个项目里要顺手核对一遍STC89 系列沿用经典的 10μF 电容 10kΩ 电阻复位电路RST 高电平有效STC15、STC8 系列多数型号内部已经集成复位电路外部 RST 引脚可以直接悬空。但注意单片机的复位和 ST7735 的 RESET 是两回事模块复位必须由 GPIO 拉低再拉高并等待 120ms 以上时序细节放在下一章。2.2 STC89C52 软件模拟 SPI谁快谁慢时钟怎么兜底STC89C52 没有硬件 SPI 外设最稳妥的做法是 GPIO 模拟。模拟 SPI 只有一个核心要求确保每个 bit 在 SCLK 上升沿到来之前已经在 MOSI 上稳定。所以标准顺序是先把 SCLK 拉低按位设置 MOSI再把 SCLK 拉高由上升沿让 ST7735 锁存数据。// 模拟SPI写一个字节高位在前 #define LCD_MOSI P1_5 #define LCD_SCLK P1_4 #define LCD_CS P1_3 #define LCD_DC P1_2 #define LCD_RST P1_1 void SPI_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { LCD_SCLK 0; // 时钟拉低准备切换数据线 if (dat 0x80) LCD_MOSI 1; else LCD_MOSI 0; dat 1; LCD_SCLK 1; // 上升沿ST7735锁存当前bit } }这段代码逻辑上等价于 SPI 模式 0CPOL0CPHA0空闲时 SCLK 为低数据在上升沿被采样。多数 1.8 寸模块在模式 0 和模式 3 下都能工作但我建议固定按模式 0 写因为后面如果换 STC15/STC8 的硬件 SPI配置也是 CPOL0、CPHA0两边行为一致排查时少一个变量。模拟 SPI 的时钟上限取决于主频和循环指令条数STC89C52 跑 12MHz 时一个字节大约 80~100μs也就是 80~100kbps 级别。这个速度做静态英文菜单完全够用做全屏动画则不够后面第五章会给出具体耗时估算。2.3 硬件 SPI 与 STC15/STC8 的取舍如果选型还没定优先考虑 STC15 或 STC8 系列。它们自带硬件 SPI主频可以跑到 24~36MHz同样显示一屏文本体感差距相当于闪烁和流畅的区别。硬件 SPI 初始化只需要配置 SPI 控制寄存器为主机模式、CPOL0、CPHA0再把 MOSI、SCK 通过引脚切换寄存器选到目标 IO 组。注意 STC15/STC8 的 SPI 复用引脚不固定具体是 P1.2~P1.5 还是其他引脚要以数据手册的I/O 口复用功能表为准不同型号差异很大。// STC15/STC8 硬件SPI发送字节 SPEN 1; // 使能SPISPEN是SPCTL寄存器的bit6 SPSTAT 0xC0; // 写1清WCOL和SPIF标志 SPDAT dat; // 写入数据即启动发送 while (!(SPSTAT 0x80)); // 等待SPIF置位表示发送完成 SPSTAT 0xC0; // 软件写1清标志必须清这里最容易踩的坑是 SPSTAT 的标志位需要软件写 1 清除。如果忘记清下一次发送永远等不到 SPIF 置位程序会卡死在等待循环里。另一个坑是 STC 的 SPI 时钟默认 4 分频对 ST7735 这种 15MHz 等级的屏幕来说把 SPI 时钟降到 10MHz 以内最稳没必要追高。3. ST7735 初始化序列上电时序、命令表和扫描方向3.1 上电与复位时序初始化失败九成出在这一步ST7735 上电后必须先等电源稳定然后 RESET 拉低至少 10μs拉高后延时 120ms接着才能发命令。缺少这段延时的典型症状是上电白屏按一下复位按钮就正常本质是芯片还没完成内部复位就收到了命令命令被丢弃。还有一类问题是 STC 单片机复位电路引起的STC89 上电复位本身要几十 ms如果程序一启动就立刻操作屏幕屏幕可能还没准备好。保险做法是在 main 开头先把背光、RESET 引脚初始化好再做一次 200ms 级别的等待再进初始化函数。移植到 STC8 等 1T 单片机时所有延时都要按主频重新折算12MHz 的 89C52 和 24MHz 的 STC8 在同一个 delay 函数下实际延时时长差一倍这个细节很容易被忽略。3.2 常用初始化命令表SLPOUT/COLMOD/MADCTL/CASET 逐条讲各家驱动库的初始化序列写法略有差异但核心命令是一致的按顺序做完就能点亮。Gamma 校正命令0xE0、0xE1参数因面板批次而异多数模块省略后能正常点亮只是颜色偏淡这里先不放。命令代码参数作用SWRESET0x01无软件复位之后延时150msSLPOUT0x11无退出睡眠模式之后延时120msFRMCTR10xB10x05 0x3C正常模式帧率控制DISCTRL0xB40x03反相/扫描配置保持默认PWCTR10xC00x28 0x08电源控制默认即可COLMOD0x3A0x05像素格式16位/像素 RGB565MADCTL0x360xC8扫描方向与 RGB/BGR 选择CASET0x2A0x00 0x02 0x00 0x81列地址范围第2~129列RASET0x2B0x00 0x01 0x00 0xA0行地址范围第1~160行NORON0x13无正常显示模式DISPON0x29无打开显示先说最关键的一行COLMOD0x05 表示每像素 16 位 RGB565颜色用两个字节表示。如果设成 0x0618 位每个像素写 3 字节数据量直接多 50%而且 8 位单片机做字节对齐很别扭统一用 0x05。MADCTL0xC8 是竖屏 128×160 下比较通用的取值其中 bit6/bit7 是 MY/MX控制行列扫描方向相当于旋转屏幕bit3 是 BGR 位置 1 表示按 BGR 顺序解析颜色很多国产 1.8 寸面板的 BGR 位必须置 1否则红色和蓝色整体对调。CASET 和 RASET 里的偏移 0x02、0x01 是给面板边框留的不同厂家模块不一样画面整体偏左或偏上时改这两个偏移最快。提示同一批模块往往共用一个偏移值。如果换了供货商先不要动其他参数只调 CASET/RASET 里的高字节或低字节增量。3.3 用一张命令表驱动初始化的代码写法把上面命令整理成命令 参数个数 参数的查表结构初始化就只是一个循环。参数区长度按最长的命令留CASET/RASET 需要 4 字节所以数组给到 4。typedef struct { unsigned char cmd; // 命令 unsigned char len; // 参数个数 unsigned char param[4]; // 参数 } INIT_CMD; code INIT_CMD init_cmds[] { {0x01, 0, {0}}, // SWRESET {0x11, 0, {0}}, // SLPOUT {0xB1, 2, {0x05, 0x3C}}, {0xB4, 1, {0x03}}, {0xC0, 2, {0x28, 0x08}}, {0x3A, 1, {0x05}}, // 16bit RGB565 {0x36, 1, {0xC8}}, // 竖屏 BGR {0x2A, 4, {0x00, 0x02, 0x00, 0x81}}, {0x2B, 4, {0x00, 0x01, 0x00, 0xA0}}, {0x13, 0, {0}}, {0x29, 0, {0}}, }; void LCD_WrCmd(unsigned char cmd) { LCD_DC 0; // 命令模式 LCD_CS 0; SPI_WriteByte(cmd); LCD_CS 1; } void LCD_WrData(unsigned char dat) { LCD_DC 1; // 数据模式 LCD_CS 0; SPI_WriteByte(dat); LCD_CS 1; } void LCD_Init(void) { unsigned char i, j; LCD_RST 0; delay_ms(50); // 拉低至少10us这里放宽 LCD_RST 1; delay_ms(120); // 等内部复位完成 for (i 0; i sizeof(init_cmds) / sizeof(init_cmds[0]); i) { LCD_WrCmd(init_cmds[i].cmd); for (j 0; j init_cmds[i].len; j) LCD_WrData(init_cmds[i].param[j]); } LCD_Clear(0x0000); // 清成黑色验证初始化通路 }RST 拉低延时写成 50ms 而不是数据手册的最小 10μs是为了让 STC 复位电路参数比较松散的板子先稳定下来。初始化之后第一件事不是显示字符而是整屏清一种颜色。整屏颜色正确说明 SPI 和初始化方向都对红蓝互换就改 MADCTL 的 BGR 位画面整体偏移就调 CASET/RASET出现色块花屏几乎可以断定是少了 Gamma 或者 COLMOD 位数不对。4. 英文显示实现6×8 ASCII 字模与字符/字符串输出4.1 为什么选 6×8 字模而不是 8×16ST7735 不内置字库英文显示的本质就是把 ASCII 码翻译成点阵数据。常用规格有两种6×8 和 8×16。8×16 显示效果好每个字符 16 字节95 个可见字符全表约 1.5KB对 STC89C52 的 8KB 程序空间来说偏奢侈。6×8 每个字符 8 字节高 6 位有效全表约 760 字节6 列宽在 128 像素屏上刚好排 21 个字符、16 行做状态栏和菜单非常合适和 1602 字符 LCD 的排版习惯接近估算坐标很方便。还要注意存储位置STC89C52 的变量区只有 256 字节内部 RAM字模表必须用 code 关键字放进程序 ROM这也是英文显示比中文省事的原因——16×16 中文点阵一张表就是几千字节很容易撞 ROM 上限。4.2 字模表定义与行列扫描方向约定字模表按 ASCII 码从空格0x20开始连续存放每字符 8 个字节每个字节表示一行中的 6 个点bit5~bit0 对应从左到右的第 1~6 列bit6 和 bit7 恒为 0。以字母 A 为例行数据6位显示效果第1行011110.XXXX.第2行100001X....X第3行100001X....X第4行011110.XXXX.第5行100001X....X第6行100001X....X第7行100001X....X第8行000000......写进代码时每一位按行存放0x1E 对应 0111100x21 对应 100001。取模软件生成时要选逐行式、从左到右、高位在前和这里的约定一致如果选成列扫描方式出来的数据是 6 字节一个字符显示结果会转置或镜像这是英文显示里最隐蔽的错位原因。约定一旦确定后面画字符的代码就按这个方向写不要混用两套。4.3 画字符/画字符串窗口 RGB565 的最小实现显示一个字符的流程是设置 6×8 的显示窗口然后逐行取字模字节逐列判断该点是否属于前景色写入两字节 RGB565 颜色。RGB565 的布局是高字节红、中间绿、低字节蓝白色 0xFFFF、黑色 0x0000、红色 0xF800发送时高字节在前。窗口设置命令是 0x2A列范围和 0x2B行范围写完后连续发 0x2C 启动 RAMWR之后写入的所有数据都按窗口内的扫描顺序填充。code unsigned char ASCII_6x8[95][8] { /* 0x20 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0x21 ! */ {0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00}, /* 0x41 A */ {0x1E,0x21,0x21,0x1E,0x21,0x21,0x21,0x00}, /* 0x22~0x7E 按同一规则补全 */ }; void LCD_SetWindow(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1) { LCD_WrCmd(0x2A); // CASET 列地址 LCD_WrData(x0 8); LCD_WrData(x0); LCD_WrData(x1 8); LCD_WrData(x1); LCD_WrCmd(0x2B); // RASET 行地址 LCD_WrData(y0 8); LCD_WrData(y0); LCD_WrData(y1 8); LCD_WrData(y1); LCD_WrCmd(0x2C); // RAMWR 开始写显存 } void LCD_DrawChar(unsigned char x, unsigned char y, char c, unsigned int color, unsigned int bg) { unsigned char i, j, dat; unsigned char idx; if (c 0x20) // 越界字符按空格处理 c 0x20; idx (unsigned char)(c - 0x20); LCD_SetWindow(x, y, x 5, y 7); for (i 0; i 8; i) // 8行 { dat ASCII_6x8[idx][i]; // 取第i行点阵 for (j 0; j 6; j) // 6列 { if (dat (0x20 j)) // bit5对应最左列 { LCD_WrData(color 8); LCD_WrData(color 0xFF); } else { LCD_WrData(bg 8); LCD_WrData(bg 0xFF); } } } }这里的0x20 j是整套方案的关键因为约定 bit5 是最左列第一列判断 0x20第二列判断 0x10依次右移。如果把字模定义成 bit0 为最左列判断条件就要改成dat (0x01 j)。两套方案本质等价但必须和字模生成方向完全一致混用的后果就是字母左右镜像。4.4 字符串输出与 code 区指针的常见坑有了 DrawChar字符串输出就是把光标移动和循环打印组合起来。光标用全局变量维护每打印一个字符 x 加 6超过行宽就换行、y 加 8unsigned char cursor_x 0, cursor_y 0; void LCD_PrintString(unsigned char x, unsigned char y, unsigned char code *str, unsigned int color, unsigned int bg) { cursor_x x; cursor_y y; while (*str) { if (*str \n) // 手动换行 { cursor_x 0; cursor_y 8; } else { LCD_DrawChar(cursor_x, cursor_y, *str, color, bg); cursor_x 6; if (cursor_x 120) // 128像素宽度21个字符后换行 { cursor_x 0; cursor_y 8; } } str; } }这里有个 C51 专项坑函数形参必须写成unsigned char code *str明确告诉编译器这是指向程序存储区的指针。如果只写unsigned char *strKeil C51 默认当成 data 或 xdata 指针处理而字符串常量实际放在 code 区轻则内容错乱重则指针跑飞。调用时直接写LCD_PrintString(0, 0, TFT OK, 0xFFFF, 0x0000)字符串常量自动落在 code 区与形参类型正好匹配。如果字符串需要放在运行时可修改的 RAM 中形参改成unsigned char *str两种指针类型不要混用。提示换行逻辑里 y 只加 8 不判断底部越界。160 行高度共 20 行字如果内容超过 20 行超出的字符会写到窗口外ST7735 对越界写入的处理是丢弃不会破坏其他区域所以菜单设计时自己控制行数即可。5. 验证与进阶白屏、花屏、方向错的排查顺序5.1 白屏先查背光与 RESET花屏先查 RGB565 对齐白屏和花屏的排查方向完全相反。白屏说明屏根本没被驱动起来优先量 VCC 和 LED 引脚电压确认 3.3V 到位然后手动拉低 RESET 再拉高观察画面是否闪动如果闪一下就黑了说明初始化时序里的延时不够。花屏则说明 SPI 通信已经建立问题集中在数据格式RGB565 高低字节写反会让画面整体偏绿因为绿色分量正好跨在两个字节的交界处COLMOD 误设成 0x06 会把每像素当 3 字节解析画面出现规律的彩色条纹。这两种错误在只显示英文字符时表现为字符周围有横向杂色因为每个像素的 16 位被拆分错位了。方向问题的判断用纯色不直观最快的方法是显示一个非对称字符串比如 Hello TFT。左右镜像改 MADCTL 的 MX 位bit6上下颠倒改 MY 位bit7红蓝对调改 BGR 位bit3。每次只改一个 bit改完重新编译烧录比对着资料猜快得多。CASET/RASET 偏移可以用一条 1 像素宽的亮线确认把窗口设成 1×160写白色看亮线是否贴在最左边缘偏差值就是列偏移行偏移同理。5.2 性能参数估算与硬件 SPI 的验证方法最后给一组可以直接套用的性能参考值。模拟 SPI 按每字节 100μs 算一个 6×8 字符是 48 个像素、每个像素 2 字节共 96 字节约 10ms 显示一个字符一屏 128×160×240960 字节全屏刷新约 4 秒。这个数字决定了 STC89C52 方案只适合静态刷新的英文界面滚动字幕和全屏动画不要尝试。换成 STC8H 的硬件 SPI 按 10MHz 时钟算一屏数据约 33ms就能支撑简单的翻页和移动效果这也是为什么建议新项目直接上 STC8 系列。验证硬件 SPI 是否真正工作不要只看显示效果最好用逻辑分析仪抓 CS、SCLK、MOSI 三根线确认 MOSI 数据在 SCLK 上升沿前稳定了半个周期。没有逻辑分析仪时有个土办法让 SPI 连续发送 0x55二进制 01010101用万用表测 MOSI 引脚平均电压应该在电源电压的一半左右如果测到接近全高或全低说明字节序反了或者 SPI 模式配置错误。最后提醒一个容易复发的细节换单片机型号后delay_ms 的基准变了ST7735 复位和 SLPOUT 后的延时必须重新实测不要在 24MHz 的 1T 单片机上沿用 12MHz 89C52 的延时参数。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门