
简介面向嵌入式开发者这份资料聚焦STM32F407ZET驱动1.8寸TFT_LCD的完整实现涵盖Cortex-M4微控制器配置、TFT彩色显示屏驱动及SPI/I2C通信协议等关键知识点适合有基础单片机经验、希望学习显示驱动或进行小型人机界面开发的读者。压缩包共186个文件以C源码与头文件为主包含编译生成的.o、.d、.crf等中间文件以及工程配置文件、烧写文件整体5.72MB目录结构清晰便于定位与复用。目前已有815人学习。内容基于标准外设库覆盖定时器、ADC、Flash等底层模块并详细展示LCD初始化序列、图像数据发送和显示缓冲区管理流程。结合完整Keil工程读者可以边阅读边编译调试快速掌握TFT_LCD驱动编写方法也能为其他尺寸或接口的屏驱开发提供参考。1. 用 STM32F407ZET6 驱动 1.8 寸 TFT_LCD点亮不难点亮得对很难STM32F407ZET6 驱动一块 1.8 寸 TFT_LCD在别人手里是几小时跑通的入门项目在你手里很可能变成一块反复白屏的板砖。这块屏的驱动 IC 绝大多数是 ST7735 系列SPI 接口、128×160 分辨率、RGB565 色彩格式硬件上只需要 4 根信号线加背光和电源就能跑起来。真正卡人的不是接线而是初始化序列、SPI 时钟极性和相位、RGB565 字节序、坐标偏移寄存器这四件事——任何一个环节不对表现都是白屏、花屏或者偏色而且用示波器测波形都不一定能一眼看出问题。这篇笔记写给两类人第一次用 F407 点屏的新手以及想把这颗屏从“能亮”调到“稳定显示”的熟手。我会把引脚分配、SPI 配置、初始化序列、数据通路和常见坑按实际调试顺序讲清楚每一步都给出能直接抄进工程的代码和参数说明。2. 硬件接线与 SPI 配置引脚分配和时钟树上的第一个坑2.1 从芯片到屏幕F407 的 SPI 资源与 1.8 寸屏引脚STM32F407ZET6 上有 3 个 SPI 外设驱动 TFT 屏我习惯优先用 SPI1。原因很实际SPI1 挂在 APB2 总线上时钟 84MHz理论上可以跑到 42Mbps而 SPI2 和 SPI3 挂在 APB1 上时钟只有 42MHz上限低一半。对于 128×160 分辨率、每像素 2 字节的屏幕一帧数据是 40960 字节刷新率很大程度取决于 SPI 时钟上限。用 SPI1 能把同一块屏的帧率提上去后续做 DMA 刷新也更有余量。1.8 寸屏对外引出 8 个引脚常见丝印是 GND、VCC、SCL有的标 SCK、SDA有的标 MOSI、RES有的标 RST、DC有的标 RS、CS、BLK背光。VCC 接 3.3V背光 BLK 不要直接接 3.3V最好串一个三极管或 MOS 管由 MCU 引脚控制否则这块屏的背光电流在 20~40mA 左右直接灌 MCU 引脚问题不大但如果你想用 PWM 调光就必须走外部开关。引脚分配上SPI1 的 SCK 和 MOSI 用硬件默认复用功能PA5 做 SCK、PA7 做 MOSI。这里有一个新手最容易犯的错SPI 是全双工但 TFT 屏不需要回传数据MISOPA6可以不接。F407 的 SPI 库初始化如果要求配置 MISO 引脚你仍然要把它初始化成复用功能否则 HAL_SPI_Init 的 GPIO 检查可能会报错——这个细节后面避坑章再展开。CS、DC、RES、BLK 这四根控制线用普通 GPIO我一般选 PA4 做 CSPA3 做 DCPA2 做 RESPA1 做 BLK。这样 SPI1 的时钟、数据和两根控制线集中在 PA2~PA5 这一排焊接和后期用杜邦线调试都方便。完整接线如下表屏幕引脚方向接 F407 引脚说明GND-GND共地必须接VCC-3.3V屏幕逻辑电源不要接 5VSCL / SCK输入PA5SPI1_SCKSDA / MOSI输入PA7SPI1_MOSIRES / RST输入PA2复位控制低电平有效DC / RS输入PA3命令/数据选择CS输入PA4片选低电平有效BLK输入PA1背光使能高电平点亮2.2 用 HAL 库把 SPI 跑起来初始化代码与参数取舍我用 HAL 库做 GPIO 和 SPI 初始化先给出一份能直接用的代码。注意 GPIO 速度这里必须设成 Very High因为 F407 的 GPIO 输出速度会直接限制 SPI 时钟的边沿质量设成 Low 或 Medium 在 10MHz 以上的 SPI 时钟下会出现边沿变缓屏幕表现为雪花状干扰。#include main.h SPI_HandleTypeDef hspi1; /* 控制线引脚宏定义 */ #define LCD_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define LCD_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) #define LCD_DC_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET) #define LCD_DC_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET) #define LCD_RES_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET) #define LCD_RES_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET) #define LCD_BL_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET) #define LCD_BL_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET)static void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; /* 使能 GPIOA 时钟 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* PA5(SCK) PA7(MOSI) 复用为 SPI1 功能 */ GPIO_InitStruct.Pin GPIO_PIN_5 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); /* PA1(BLK) PA2(RES) PA3(DC) PA4(CS) 普通推挽输出 */ GPIO_InitStruct.Pin GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); /* 初始状态CS 高、RES 高、DC 高、背光开 */ LCD_CS_HIGH(); LCD_RES_HIGH(); LCD_DC_HIGH(); LCD_BL_HIGH(); }static void LCD_SPI_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; 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_8; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 10; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } __HAL_SPI_ENABLE(hspi1); }代码里的几个参数值得说明。CLKPolarity 设 LOW、CLKPhase 设 1EDGE也就是模式 0这是 ST7735 数据手册推荐的 SPI 时序空闲时 SCK 为低数据在 SCK 第一个上升沿采样。如果你在其他例程里看到模式 3极性高、相位 2EDGE也能点亮不要奇怪ST7735 对模式 0 和模式 3 都支持但模式 0 兼容性最好后续换屏批次时不容易翻车。BaudRatePrescaler 设 8SPI1 时钟 84MHz / 8 10.5MHz。ST7735 的数据手册标称 SCLK 最高约 15.4MHz不同生产批次有差异10.5MHz 留了 30% 余量稳定性最好。你可以在点亮后逐步改成 421MHz测极限但不要一开始就跑最高速——初始化阶段高速 SPI 容易遇到波形过冲导致寄存器写入错乱这是很多“白屏”的隐性原因。Direction 设 2LINES 是因为 HAL 库的 SPI 发送接口在单线模式下行为不同即使不用 MISO 回传数据也保持默认的双线全双工模式最简单。NSS 设 SOFT 软件管理片选由 GPIO 手动拉低 CS 选中屏幕。主要参数汇总成表方便不同时钟配置下对照调整参数配置值说明SPI Mode0CPOL0, CPHA1EDGEST7735 兼容换屏批次最稳BaudRatePrescaler810.5MHz点亮后可按需改 4DataSize8BitST7735 的写命令和数据都是 8 位FirstBitMSB FirstSPI 协议默认屏幕端不交换字节序NSSSoftCS 由 GPIO 手动控制3. ST7735 初始化序列白屏和花屏的分水岭3.1 初始化序列拆解每个命令在干什么1.8 寸屏上电后不会自动进入可显示状态必须由主机按固定顺序写入一串配置寄存器。ST7735 的初始化序列看起来很长本质上做五件事退出睡眠模式、设置帧率、设置电源电压、设置扫描方向和颜色格式、加载伽马曲线。这个顺序不能乱尤其是 SLPOUT0x11之后必须延时 120ms 左右因为屏幕内部的 DC-DC 升压电路需要时间稳定。很多做硬件的同学在这里有一个误区初始化序列是从某份开源代码里复制的能点亮就再也不管了。实际上不同厂家生产的 1.8 寸屏虽然驱动 IC 都是 ST7735但玻璃基板的扫描方向和 RGB 顺序可能不同。最典型的表现是一块屏显示正常换一批屏后画面上下颠倒或颜色偏蓝偏红。这个问题的根源在 MADCTL0x36寄存器的 MX、MY 位和 RGB 位以及 COLMOD0x3A的颜色格式设置不是 SPI 时序的问题。初始化序列的延时参数同样重要。SWRESET0x01后要等 150ms这是数据手册标注的最大值SLPOUT0x11后要等 120ms。有人为了“加快启动”把这两个延时砍到 10ms结果 30% 的概率白屏剩下 70% 的概率显示内容乱跳——这不是玄学是 ST7735 内部状态机在上电后需要足够时间完成校准和升压砍延时的收益远小于风险。伽马曲线0xE0 和 0xE1是初始化序列里最容易被人忽略的部分。这两条命令各写 16 个字节作用是校准屏幕的亮度响应曲线让灰阶过渡自然。不同批次的屏伽马值略有差异如果你发现显示图片暗部一团黑或亮部过曝优先怀疑伽马曲线和当前屏幕批次不匹配。我一般先用通用的 ST7735R 伽马值点亮后如果偏色严重再单独调这两段参数。3.2 完整初始化代码与参数说明下面是一份可复现的 ST7735 初始化序列命令顺序和延时参数来自 ST7735S 数据手册的标准初始化流程适配大多数 1.8 寸彩屏模块。#define LCD_WRITE_CMD(cmd) do { LCD_DC_LOW(); LCD_CS_LOW(); \ HAL_SPI_Transmit(hspi1, (uint8_t *)(cmd), 1, 100); \ LCD_CS_HIGH(); } while(0) #define LCD_WRITE_DATA(data) do { LCD_DC_HIGH(); LCD_CS_LOW(); \ HAL_SPI_Transmit(hspi1, (uint8_t *)(data), 1, 100); \ LCD_CS_HIGH(); } while(0) static void LCD_Reset(void) { LCD_RES_LOW(); HAL_Delay(20); LCD_RES_HIGH(); HAL_Delay(20); } static void LCD_Init(void) { LCD_Reset(); /* 硬件复位拉低 20ms 再拉高 */ LCD_WRITE_CMD(0x01); /* SWRESET 软件复位 */ HAL_Delay(150); LCD_WRITE_CMD(0x11); /* SLPOUT 退出睡眠模式 */ HAL_Delay(120); LCD_WRITE_CMD(0xB1); /* FRMCTR1 帧率控制正常模式 */ LCD_WRITE_DATA(0x05); LCD_WRITE_DATA(0x3A); LCD_WRITE_DATA(0x3A); LCD_WRITE_CMD(0xB2); /* FRMCTR2 帧率控制空闲模式 */ LCD_WRITE_DATA(0x05); LCD_WRITE_DATA(0x3A); LCD_WRITE_DATA(0x3D); LCD_WRITE_CMD(0xB3); /* FRMCTR3 帧率控制偏转模式 */ LCD_WRITE_DATA(0x05); LCD_WRITE_DATA(0x3A); LCD_WRITE_DATA(0x3D); LCD_WRITE_DATA(0x05); LCD_WRITE_DATA(0x3A); LCD_WRITE_DATA(0x3D); LCD_WRITE_CMD(0xC0); /* PWCTR1 电源控制AVDD 和 GVDD 电压 */ LCD_WRITE_DATA(0x2C); LCD_WRITE_DATA(0x2C); LCD_WRITE_CMD(0xC1); /* PWCTR2 电源控制VGH 和 VGL 电压 */ LCD_WRITE_DATA(0x02); LCD_WRITE_CMD(0xC5); /* VMCTR1 电源控制VCOM 电压 */ LCD_WRITE_DATA(0x05); LCD_WRITE_DATA(0x3A); LCD_WRITE_CMD(0x36); /* MADCTL 扫描方向MX1, MY1, RGB */ LCD_WRITE_DATA(0x48); LCD_WRITE_CMD(0x3A); /* COLMOD 像素格式16bit/像素 */ LCD_WRITE_DATA(0x05); LCD_WRITE_CMD(0xE0); /* GMCTRP1 伽马正校正 */ LCD_WRITE_DATA(0x02); LCD_WRITE_DATA(0x1C); LCD_WRITE_DATA(0x07); LCD_WRITE_DATA(0x12); LCD_WRITE_DATA(0x37); LCD_WRITE_DATA(0x32); LCD_WRITE_DATA(0x29); LCD_WRITE_DATA(0x2D); LCD_WRITE_DATA(0x29); LCD_WRITE_DATA(0x25); LCD_WRITE_DATA(0x2B); LCD_WRITE_DATA(0x39); LCD_WRITE_DATA(0x00); LCD_WRITE_DATA(0x01); LCD_WRITE_DATA(0x03); LCD_WRITE_DATA(0x10); LCD_WRITE_CMD(0xE1); /* GMCTRN1 伽马负校正 */ LCD_WRITE_DATA(0x03); LCD_WRITE_DATA(0x1D); LCD_WRITE_DATA(0x07); LCD_WRITE_DATA(0x06); LCD_WRITE_DATA(0x2E); LCD_WRITE_DATA(0x2C); LCD_WRITE_DATA(0x29); LCD_WRITE_DATA(0x2D); LCD_WRITE_DATA(0x2E); LCD_WRITE_DATA(0x2E); LCD_WRITE_DATA(0x37); LCD_WRITE_DATA(0x3F); LCD_WRITE_DATA(0x00); LCD_WRITE_DATA(0x00); LCD_WRITE_DATA(0x02); LCD_WRITE_DATA(0x10); LCD_WRITE_CMD(0x13); /* NORON 打开正常显示模式 */ HAL_Delay(10); LCD_WRITE_CMD(0x29); /* DISPON 打开显示 */ HAL_Delay(120); }代码里几个值得注意的地方。MADCTL 写 0x48 是横向 USB 型 1.8 寸屏最常见的默认方向bit6MX1 表示 X 方向刷新顺序从右到左bit3RGB1 表示按 RGB 顺序排列像素这样屏幕在图示正常朝上的情况下画面不倒置。如果你的屏装进外壳后是倒着的把这个值改成 0x08MX0, MY0或 0x88 就能旋转方向不需要改任何画点代码。COLMOD 设为 0x05 表示 16 位色深也就是每像素两个字节数据格式是 RGB565。如果你误写成 0x0618 位色深屏幕能亮但颜色会明显偏淡因为 F407 这边发送的还是 16 位数据屏幕却按 18 位解析低字节被吞掉一部分。遇到显示内容“能看清但颜色不对”先查这个寄存器。伽马曲线的 16 个字节看起来像“魔法数字”实际是 ST7735 的 VCOM 电压和灰阶响应校准值。不同屏幕模组厂家给出的默认伽马值可能有差异如果你的屏整体偏亮、对比度差可以只替换 E0/E1 的两组数组不需要动其他配置。我在实际调屏时遇到过一块屏用通用值显示正常、另一块同样型号却偏紫的情况最后把 E0 的第 6~10 字节微调了 2 个台阶才恢复——这说明伽马值和玻璃基板批次强相关不属于 SPI 时序问题。4. 显存与数据通路从画一个点到刷新整屏4.1 RGB565 字节序一个很容易翻车的细节F407 往 ST7735 写像素数据时每像素 2 字节按 RGB565 格式拆成高字节和低字节依次发送。RGB565 的含义是5 位红色、6 位绿色、5 位蓝色。红色占 bit15~bit11绿色占 bit10~bit5蓝色占 bit4~bit0。颜色 0xF800 是纯红红色通道满格0x07E0 是纯绿0x001F 是纯蓝。字节序的坑在于SPI 按 8 位一字节发送数据必须拼成“高字节在前、低字节在后”。以红色 0xF800 为例先发 0xF8再发 0x00。如果你用一个 uint16_t 数组直接做 HAL_SPI_Transmit在小端模式下内存里存的是 0x00、0xF8先发出去的就是低字节——屏幕会显示成 0x00F8这是一个偏蓝紫的颜色而不是红色。我见过的工程里至少有三种方式处理这个问题。第一种是在画点函数里手动拆字节先发送 (color 8) 0xFF再发送 color 0xFF简单直观推荐新手用。第二种是把帧缓存的类型声明成 uint8_t 数组填充时直接按高字节、低字节两个位置写入。第三种是索性把字节序转换做进 DMA 缓冲区填充环节用指针强制转换加字节交换宏。第一种方式代码最清晰调试时也容易对照数据手册排查我一般默认用它。坐标映射同样影响显示结果。ST7735 内部的 GRAM 地址是从 (0,0) 到 (127,159)但你的屏幕模块可能把第一个像素定义在左上角也可能在右下角。F407 这边画点前必须先设置窗口寄存器 CASET0x2A和 RASET0x2B告诉屏幕接下来要写哪一矩形区域内的像素。窗口设置里 x0、y0 是起始地址x1、y1 是结束地址各占 2 字节高字节在前。如果窗口设置和 MADCTL 的方向位不匹配显示内容会出现“画一条竖线却出现在横向上”的怪异现象。4.2 画点、填充与字符显示的最小实现有了窗口设置画点和填充的本质就是往 RAMWR0x2C连续写入像素数据。下面给出三个最常用的底层函数设置窗口、画一个点、整屏填充。这三个函数是所有后续 UI 的地基。static void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WRITE_CMD(0x2A); /* CASET 列地址 */ LCD_WRITE_DATA(x0 8); LCD_WRITE_DATA(x0 0xFF); LCD_WRITE_DATA(x1 8); LCD_WRITE_DATA(x1 0xFF); LCD_WRITE_CMD(0x2B); /* RASET 行地址 */ LCD_WRITE_DATA(y0 8); LCD_WRITE_DATA(y0 0xFF); LCD_WRITE_DATA(y1 8); LCD_WRITE_DATA(y1 0xFF); LCD_WRITE_CMD(0x2C); /* RAMWR 开始写显存 */ }void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { uint8_t hi (uint8_t)(color 8); /* 高字节RGB565 的高 8 位 */ uint8_t lo (uint8_t)(color 0xFF); /* 低字节RGB565 的低 8 位 */ LCD_SetWindow(x, y, x, y); /* 窗口只包含目标像素 */ LCD_DC_HIGH(); /* 数据模式 */ LCD_CS_LOW(); HAL_SPI_Transmit(hspi1, hi, 1, 100); HAL_SPI_Transmit(hspi1, lo, 1, 100); LCD_CS_HIGH(); }void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t pixels (uint32_t)(x1 - x0 1) * (y1 - y0 1); uint32_t i; uint8_t hi (uint8_t)(color 8); uint8_t lo (uint8_t)(color 0xFF); LCD_SetWindow(x0, y0, x1, y1); LCD_DC_HIGH(); LCD_CS_LOW(); for (i 0; i pixels; i) { HAL_SPI_Transmit(hspi1, hi, 1, 100); HAL_SPI_Transmit(hspi1, lo, 1, 100); } LCD_CS_HIGH(); }这三个函数的性能瓶颈在 LCD_FillRect 的循环发送。每像素两次 HAL_SPI_Transmit每帧 40960 像素就是 81920 次函数调用HAL 库的 SPI 发送函数有超时判断和状态检查单次调用开销在微秒级算下来整屏填充一帧要几百毫秒。如果只做静态画面没问题但做菜单切换、动画效果就会明显卡顿。改进方向不是优化这个循环而是把一整行或整块区域的颜色数据存到内存缓冲区里一次 HAL_SPI_Transmit 发送几十上百字节。后面第 6 章的 DMA 方案会彻底解决这个问题。字符显示本质上也是填充只不过每个像素的颜色由字模数据决定而不是统一颜色。常见做法是把字模按行展开成字节数组每字节的每一位对应一个像素为 1 的位用前景色为 0 的位用背景色。我用一个 8×16 像素的 ASCII 字符举例字模数组有 16 个字节第 n 个字节的第 7~0 位对应第 n 行第 0~7 列。取模软件生成的字模数组默认高位在左发送顺序就是从左到右、从上到下逐行扫描。显示中文需要 GB2312 或 GBK 字库字模更大16×16 需要 32 字节但原理一样只是把字模数组的索引方式换成区位码查表。5. 驱动 1.8 寸屏的 5 个踩坑记录现象、原因、解决5.1 白屏复位时序和初始化序列被跳过现象屏幕背光亮但整屏白色没有任何内容。原因最常见的有两个。一是硬件复位后延时不够LCD_RES 拉高后立即发命令屏幕内部状态机还没准备好收到的第一条命令被丢弃后续整个初始化序列错位。二是初始化代码里 SLPOUT 后面的延时被缩短到 10ms 以下屏幕的 DC-DC 升压电路没稳定就接收后续配置寄存器写入无效。解决硬件复位 RES 拉低至少 20ms拉高后至少等 20ms 再发第一条命令。软件复位 SWRESET 后延时 150msSLPOUT 后延时 120ms。这两个延时不要精简白屏时优先查这里。如果确认延时没问题用示波器抓 CS 和 SCLK 波形确认所有命令字节真的发出去了而不是卡在 HAL_SPI_Transmit 的超时错误上。5.2 花屏雪花SPI 极性与相位不匹配现象屏幕亮但显示的是随机噪点或者初始化后画面撕裂、有水平条纹。原因SPI 的模式 0 和模式 3 都能和 ST7735 通信但初始化序列是逐条命令写入的如果 CPOL/CPHA 配置和你的 GPIO 复用配置不匹配第一条命令写进去的时候还能侥幸正确后面的数据在时钟边沿采样时错位显示出雪花。另一种情况是 GPIO 速度配置太低SCK 信号边沿过缓导致采样不稳定。解决确认 SPI 配置为 CPOLLOW、CPHA1EDGE模式 0。把 SCK、MOSI 的 GPIO Speed 设为 VERY_HIGH。如果你一定要用模式 3必须同步修改初始化里所有命令和数据的时序约定但模式 0 兼容性最好遇到花屏先改回模式 0排除这个变量。检查 PA5 的复用功能配置成 GPIO_AF5_SPI1而不是默认的 AF0这个错误会让 SCK 变成普通 GPIO输出的不是时钟而是固定电平。5.3 颜色反色偏色RGB565 字节序问题现象显示红色区域变蓝色或者绿色偏紫、白底发黄。原因RGB565 的高字节和低字节发送顺序反了。0xF800红色发成 0x00F8 后屏幕解析为蓝色分量占满的低字节实际显示偏蓝。偏色不严重的情况多半是 COLMOD 被误设成 0x0618 位色深屏幕按 18 位解析 16 位数据低 2 位被截断导致灰阶偏移。解决画点函数里统一先发高字节再发低字节。如果是用 uint16_t 数组直接 DMA 发送在填充数组时做一次字节交换((color 0xFF) 8) | (color 8)。验证方法很简单显示纯红色 0xF800、纯绿色 0x07E0、纯蓝色 0x001F 三个色块看实际颜色是否对应。注意屏幕的 RGB 顺序由 MADCTL 的 bit3 控制如果纯红纯蓝对调但不是因为字节序而是屏的内部走线 RGB 顺序和默认值相反把 0x48 改成 0x08 重刷初始化序列。5.4 屏幕显示闪烁背光引脚悬空现象屏幕能显示但整体亮度闪烁或者显示静态画面时高频抖动。原因背光 BLK 引脚悬空或者直接接 3.3V 走线过长。BLK 引脚悬空时电平不确定屏幕内部背光驱动电路在置位和复位之间反复横跳。直接接 3.3V 且共用一根细走线时屏幕刷新电流波动会耦合进背光供电产生亮度抖动。解决BLK 必须由 MCU 引脚控制上电初始化为高电平点亮。做 PWM 调光时PWM 频率不要低于 1kHz推荐 2kHz~5kHz否则人眼能感知到亮度波动。背光供电的 3.3V 走线尽量短粗或者加 10uF 电容稳压。注意 PWM 调光的占空比和背光电流不是线性关系低占空比时屏幕可能直接黑掉这是 LED 驱动电路的导通阈值问题不是软件 bug。5.5 坐标镜像旋转MADCTL 设置错误现象显示内容上下颠倒、左右镜像或者画一条竖线变成横线。原因MADCTL0x36的 MXbit6、MYbit7控制行扫描方向MVbit5控制行列交换某些屏模组在出厂时对扫描方向做了改动和通用初始化序列不匹配。解决不要尝试在画点函数里做坐标变换来“纠正”显示方向直接在初始化里改 MADCTL 的值。0x48 是 MX1、MY1、RGB 顺序需要上下翻转时改成 0x88需要左右翻转时改成 0x28需要行列交换旋转 90 度时改成 0x68。修改后重新初始化屏幕不需要改动任何上层绘图代码。判断是否镜像的测试方法是显示一个不对称的字符 F看它是不是朝你预期的方向。6. DMA 刷新与验证技巧让 1.8 寸屏跑到 60 帧6.1 用 DMA 把画好的帧缓存一次性推给屏幕裸循环发送 40960 字节到 ST7735受限于 HAL_SPI_Transmit 的调用开销实测帧率只有 10~20fps。要让 1.8 寸屏跑满接口速度标准做法是在内存里维护一块完整的帧缓存更新内容时只改缓存数组然后启动一次 DMA 传输把整块缓存推给屏幕。SPI1 的 DMA 请求映射到 DMA2 的 Stream 3通道选择 Channel 3。用 CubeMX 配置最简单的做法是在 DMA 设置里添加 SPI1_TX传输方向 MemoryToPeripheral外设增量关闭内存增量开启数据宽度两边都设 Byte。数据宽度设 Byte 而不是 HalfWord原因还是字节序帧缓存数组按 uint8_t 存储颜色值在填充时已经拆成高字节和低字节DMA 只负责原样搬运。/* 帧缓存128*160 像素每像素 2 字节共 40960 字节 */ uint8_t framebuffer[128 * 160 * 2]; void LCD_CopyFramebuffer(void) { LCD_SetWindow(0, 0, 127, 159); /* 窗口覆盖整个屏幕 */ LCD_DC_HIGH(); LCD_CS_LOW(); HAL_SPI_Transmit_DMA(hspi1, framebuffer, 128 * 160 * 2); /* 注意这里不能立刻拉高 CS要等 DMA 传输完成回调 */ } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { LCD_CS_HIGH(); } }这段代码里的关键是 CS 的释放时机。DMA 启动后SPI 外设还在逐字节搬数据CS 必须保持拉低直到最后一字节发完。我见过有人在 LCD_CopyFramebuffer 末尾直接拉高 CS结果是屏幕只刷新了上半部分下半部分是旧内容——CS 提前释放切断了传输。正确做法是在 SPI 发送完成回调里拉高 CSHAL 库的 HAL_SPI_TxCpltCallback 会在最后一字节移位寄存器送出后触发。帧率能到多少SPI 时钟 10.5MHz40960 字节 × 8 位 327680 位理论最短传输时间 31ms加上命令字节和帧间隔实际帧率约 28fps。把预分频改成 421MHz后理论最短传输时间 15.6ms配合 DMA 可以逼近 55fps。注意 ST7735 的 SCLK 上限因批次而异21MHz 不是所有屏都能稳定跑建议用整屏填充红色再填充蓝色的方式连续刷新 10000 次观察有没有偶发花屏。6.2 用测试画面验证时序与颜色每做好一版驱动我都习惯烧录一套三屏测试画面而不是直接跑业务 UI。第一屏纯红、第二屏纯绿、第三屏纯蓝用肉眼确认 RGB 三通道顺序正确接着显示一个从黑到白的 16 级灰阶条确认伽马曲线没有把暗部压死或亮部过曝最后显示一个 8×8 棋盘格每个格子的边界应清晰锐利没有拖影或边缘错位。这套验证做完再确认 MADCTL 的扫描方向基本可以排除 90% 的显示异常原因。另一个验证技巧是看初始化序列是否被完整执行把 SLPOUT 的延时改成 1000ms屏幕从白屏到正常显示之间有一个明显的“变亮”过程这说明软件复位和退出睡眠命令生效。如果屏幕一直是白的、突然跳到正常画面说明 SWRESET 后的 150ms 延时里屏幕已经完成了状态切换也说明上电复位时序没问题。验证字节序同样可以用一个技巧显示 0x0001 这个颜色值它只有最低位为 1如果屏幕左上角出现一个极暗的蓝色点说明低字节发送正确如果出现的是红色点说明高字节先发了字节序反了。最后说一个我这几年点屏养成的习惯新拿一块屏先看模块背面丝印确认驱动 IC 型号和玻璃基板的排线方向再决定初始化序列里的 MADCTL 和伽马值。同一个型号的屏在不同卖家手里可能走线不同默认扫描方向差 180 度是常事。这块 1.8 寸屏的方案本身不复杂但它教会我一个道理嵌入式显示调试里能点亮只是起点把每个寄存器的作用和每个字节的时序都搞清楚才能在换屏、提速、加动画的时候不抓瞎。希望这篇笔记能帮你在 F407 和 1.8 寸屏之间少走一段弯路。本文还有配套的精品资源点击获取