☰
ILI9325 LCD驱动实战:从8080时序到中文显示的完整指南
2026/10/8 2:41:33 网站建设 项目流程

简介:基于STM32F103VET6的ILI9325液晶驱动完整工程,通过FSMC接口同时兼容ILI9328控制器,适合嵌入式入门及中级开发者快速落地LCD显示功能。压缩包仅36KB,共22个文件,包含13个c源码、8个头文件和1个html说明文档,代码结构清晰,可直接查阅修改。包内针对LCD的RS地址线连接提供了简易计算方法,并给出了NE1片选、A16地址线的配置要点,便于理解FSMC映射关系。同时实现多种画图函数、字符及字符串输出接口,覆盖几何图形绘制、文本显示等常见需求。此外提供RGB 24位转RGB 16位计算公式与常用颜色定义,帮助开发者高效处理色彩转换。工程内除ILI9325主驱动外,还整理了多种常见LCD控制器的独立驱动模块,便于横向对比和后续扩展。目前已有264人学习,对于正在调试FSMC液晶驱动或准备自制显示模块的工程师,是一份浓缩实用的参考工程。

1. 一块 240×320 的屏,为什么驱动的锅总是甩不干净

手头有块 TFT 屏模块,主控丝印是 ILI9325,240×320 分辨率,接线都按网上教程飞好了,背光一亮,屏幕上却什么都没有——白的白,黑的黑,或者滚动着一堆彩条。这是很多嵌入式工程师第一次碰 ILI9325 LCD 驱动时的真实遭遇。问题往往不是你的 MCU 代码写错了,而是 ILI9325 这个芯片对时序、寄存器写入顺序和接口模式非常敏感,任何一步不对,它都不会给你任何报错,只会安静地保持黑屏。这篇文章想把 ILI9325 驱动这件事讲透:从 8080 并行接口时序到初始化寄存器表,从画点函数到显示中文,最后落到实际调试中那些让人抓狂的坑。适合正在飞线点亮模块、想把屏接到自己板子上、或者已经点亮但显示错乱的开发者。

2. 8080 并行接口与初始化:先把时序和寄存器表理清楚

2.1 四种接口模式,为什么首选 8080 十六位并行

ILI9325 本身支持多种接口方式:MPU 并行接口(8080 和 6800 两种时序)、RGB 接口、以及串行 SPI。很多人在选接口时想省 IO 而走 SPI,这是第一个容易翻车的决定。ILI9325 的 SPI 模式只能用来配置寄存器,不能往 GRAM 里写像素数据,也就是说你没法用 SPI 把一张图刷到屏上。真正要显示内容,必须走并行总线。

实际项目里,绝大多数自带 ILI9325 控制器的模组都默认使用 8080 并行 16 位模式。一组典型的接线是这样的:

信号方向作用
DB15~DB0MCU → LCD16 位数据/寄存器值
RSMCU → LCD高电平写数据,低电平写寄存器地址
WRMCU → LCD写时序时钟,上升沿锁存数据
RDMCU → LCD读时序时钟
CSMCU → LCD片选,低有效
RESETMCU → LCD硬件复位,低有效
LEDA / LEDK背光背光正负极,常用 PWM 调亮度

6800 并行模式用的是 E 和 RW 两根控制线,时序跟 8080 略有不同。如果你的 MCU 本身没有现成的 8080 控制器,而是用 GPIO 模拟,那直接用 8080 模式就行,因为 GPIO 翻转控制 WR 和 RS 更直观。STM32 的 FSMC 接口就是 8080 时序的硬件实现,F103 系列接这种屏幕非常顺手。

2.2 先读 ID 再初始化,别一上来就盲写寄存器

网上流传的 ILI9325 初始化代码很多,但有个隐蔽问题:ILI9325 和它的同门 ILI9320 寄存器地址空间大部分兼容,但电源控制部分的寄存器定义有差异。如果你把 ILI9320 的初始化表盲写到 ILI9325 上,最典型的结果就是屏幕亮不起来,或者亮起来后颜色完全不对。

解决办法是:初始化第一步先读芯片 ID,再根据 ID 选择不同的寄存器表。ILI9325 在寄存器 0x0000 里暴露了型号信息,读出来高字节是 0x93。下面这段代码是读寄存器的基础原语,注意并行 16 位模式下,寄存器号本身也要走 16 位数据总线。

// ili9325_io.c —— 8080 并行的寄存器读写原语 static void ili9325_write_reg(uint16_t reg, uint16_t val) { // 第一步:RS 拉低,写寄存器地址 LCD_CS_LOW(); LCD_RS_LOW(); LCD_WR_LOW(); *LCD_DATA_PORT = reg; // 16 位并行,一次写入寄存器号 LCD_WR_HIGH(); // 第二步:RS 拉高,写寄存器值 LCD_RS_HIGH(); LCD_WR_LOW(); *LCD_DATA_PORT = val; // 写入 16 位数据 LCD_WR_HIGH(); LCD_CS_HIGH(); } static uint16_t ili9325_read_reg(uint16_t reg) { uint16_t val; LCD_CS_LOW(); LCD_RS_LOW(); LCD_WR_LOW(); *LCD_DATA_PORT = reg; // 先送寄存器号 LCD_WR_HIGH(); LCD_RS_HIGH(); LCD_RD_LOW(); val = *LCD_DATA_PORT; // 读回 16 位数据 LCD_RD_HIGH(); LCD_CS_HIGH(); return val; } uint16_t ili9325_read_id(void) { return ili9325_read_reg(0x0000); }

这段代码的逻辑很简单:写寄存器时拆成两步,先发寄存器号,再发数据;读寄存器时发完寄存器号后把 RD 拉低,直接从数据总线取值。唯一要注意的是,*LCD_DATA_PORT 这个宏定义在 16 位总线时没问题,如果你用的 MCU 数据引脚分散在 GPIO 不同端口上,就得改成逐引脚赋值的方式,速度会慢不少。读 ID 还有个用途:某些山寨屏的 ID 读出来不是 0x9325 而是 0x9320,这种屏的电源时序要求略宽松,你完全可以靠这个 ID 在固件里做分支,一套驱动兼容两种面板。

2.3 最小初始化序列:哪些寄存器必须写,哪些可以不碰

ILI9325 不像一些新屏那样给个“魔法命令”就能全自动初始化。它的寄存器表很长,但你不需要全部照抄,真正影响显示的最小集合大概十几个。我一般会把初始化拆成三个块:基础时序、电源与伽马、窗口和颜色格式。

下面这个初始化示例是参考序列,特别注意:不同模组厂的出厂配置可能有差异,如果屏厂给了初始化表,以屏厂的为准;没有的话,这套序列能点亮绝大多数 9325 模组。

// ili9325_init.c —— 参考初始化序列 void ili9325_init(uint16_t id) { // 硬件复位:RESET 拉低至少 10ms,再拉高等 50ms LCD_RESET_LOW(); delay_ms(10); LCD_RESET_HIGH(); delay_ms(50); // 软件复位与驱动输出控制 ili9325_write_reg(0x0000, 0x0001); // 软复位 delay_ms(10); ili9325_write_reg(0x0001, 0x0100); // 驱动输出控制:扫描方向 ili9325_write_reg(0x0002, 0x0700); // LCD 驱动 AC 控制 ili9325_write_reg(0x0003, 0x1030); // Entry Mode:AM/ID 决定扫描方向 ili9325_write_reg(0x0008, 0x0207); // 显示控制:背光相关 // 电源控制块:顺序不要乱 ili9325_write_reg(0x0010, 0x0000); // Power Control 1 ili9325_write_reg(0x0011, 0x0007); // Power Control 2 ili9325_write_reg(0x0012, 0x0000); // Power Control 3 ili9325_write_reg(0x0013, 0x0000); // Power Control 4 ili9325_write_reg(0x0007, 0x0021); // Display Control:开显示 // 伽马曲线,颜色偏色主要看这里 ili9325_write_reg(0x0030, 0x0000); ili9325_write_reg(0x0031, 0x0707); ili9325_write_reg(0x0032, 0x0000); ili9325_write_reg(0x0033, 0x0600); ili9325_write_reg(0x0034, 0x0000); // 设置 GRAM 扫描窗口为全屏 240x320 ili9325_write_reg(0x0050, 0x0000); // 水平起始 ili9325_write_reg(0x0051, 0x00EF); // 水平结束 239 ili9325_write_reg(0x0052, 0x0000); // 垂直起始 ili9325_write_reg(0x0053, 0x013F); // 垂直结束 319 // 颜色格式与驱动输出控制 ili9325_write_reg(0x0060, 0xA700); // 65K 色 RGB 格式 ili9325_write_reg(0x0061, 0x0001); // 基图像显示控制 ili9325_write_reg(0x0090, 0x0010); // 面板接口控制 ili9325_write_reg(0x0007, 0x0133); // 全显示开 }

几个关键参数说明:

  • 0x0003 寄存器是 Entry Mode,里面的 AM 位和 ID0/ID1 位共同决定 GRAM 地址自增方向和屏幕的显示方向。如果你的屏显示内容左右镜像或者上下颠倒,改这个寄存器比改坐标映射函数更直接。
  • 0x0007 是显示控制寄存器,最后写成 0x0133 表示基图像显示开、全显示开。在初始化过程中,前面先写 0x0021 只开显示但不开输出,是为了避免在电源未稳定时屏幕上出现花屏。
  • 0x0060 的 0xA700 是 65K 色模式。这里要注意 RGB 还是 BGR 是有方向性的,具体看模组上 FPC 的接法,后面偏色排查章节细说。

3. GRAM 写入与画点函数:让像素按你的顺序落到屏幕上

3.1 窗口设置的本质是限制地址自增范围

ILI9325 内部维护着一组 GRAM 读写地址,当 MCU 往 0x0022 寄存器写入像素数据时,数据落到当前地址处,地址再按 Entry Mode 里设定的方向自动加一。如果你希望只更新屏幕的一部分区域,比如显示一个弹窗,就要先把水平起始/结束和垂直起始/结束四个寄存器设好,让 GRAM 地址只在窗口内自增。

// ili9325_window.c —— 设置写入窗口并连续填充 void ili9325_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { ili9325_write_reg(0x0050, x0); // 水平起始 ili9325_write_reg(0x0051, x1); // 水平结束 ili9325_write_reg(0x0052, y0); // 垂直起始 ili9325_write_reg(0x0053, y1); // 垂直结束 } void ili9325_write_data(uint16_t color) { LCD_CS_LOW(); LCD_RS_HIGH(); LCD_WR_LOW(); *LCD_DATA_PORT = color; LCD_WR_HIGH(); LCD_CS_HIGH(); } void ili9325_fill(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t pixel_count = (uint32_t)(x1 - x0 + 1) * (y1 - y0 + 1); ili9325_set_window(x0, y0, x1, y1); // 只写一次窗口,然后连续灌数据 LCD_CS_LOW(); LCD_RS_HIGH(); for (uint32_t i = 0; i < pixel_count; i++) { LCD_WR_LOW(); *LCD_DATA_PORT = color; LCD_WR_HIGH(); } LCD_CS_HIGH(); }

这段代码里最值得注意的就是ili9325_fill:窗口只设一次,后续通过连续翻转 WR 来写入像素。因为 ILI9325 的 GRAM 地址会自动加一,所以整个过程不需要重复发送坐标命令,速度比“每画一个点调用一次 set_window + write_data”快几十倍。我在实际项目里用 GPIO 模拟总线时,一次全屏填充 240×320 共 76800 个像素,16 位并行线 + 72MHz 主频的 STM32F103,实测单帧耗时大概在 80ms 到 120ms 之间,够看,但做动画就吃力了。

3.2 画点函数的正确姿势:先设窗口还是先写数据

画点是最基础的绘图原语。它不追求速度,但要保证坐标准确和边界不越界。

// ili9325_pixel.c —— 单点绘制 void ili9325_draw_pixel(uint16_t x, uint16_t y, uint16_t color) { if (x >= LCD_WIDTH || y >= LCD_HEIGHT) { return; // 越界直接丢弃,防止 GRAM 地址写飞 } ili9325_set_window(x, y, x, y); // 窗口锁成 1x1 ili9325_write_data(color); // 写入颜色 }

参数方面,颜色值是 RGB565 格式,比如纯红色是 0xF800,纯绿色 0x07E0,纯蓝色 0x001F。如果把两个字节写反,画面会明显偏蓝偏红,这是新手特别容易碰到的字节序问题。用 16 位并行总线时,数据在物理线上是高字节在前还是低字节在前,取决于你接线时 DB15~DB0 和高低字节的对应关系,代码里没法统一处理,只能按实际接线来。

3.3 显示英文和数字:一张点阵表就够了

用画点函数显示字符,早期做法是维护一张 ASCII 点阵表。最常见的是 5×8 点阵,每个字符 8 个字节,每位对应一列。显示时逐字节扫描,把每个 bit 映射到一个像素。

// font5x8.h —— 英文字符点阵,5 宽 8 高 static const uint8_t font5x8[95][8] = { // 0x20 空格 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0x21 叹号 {0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00}, // ... 实际使用时填充完整 95 个字符 }; void ili9325_draw_char(uint16_t x, uint16_t y, char c, uint16_t color) { const uint8_t *glyph = font5x8[c - 0x20]; for (int row = 0; row < 8; row++) { for (int col = 0; col < 5; col++) { if (glyph[row] & (0x10 >> col)) { // 高位在前,逐列取位 ili9325_draw_pixel(x + col, y + row, color); } } } }

取模方向的坑就在这里:0x10 >> col表示点阵的最高位对应最左列,如果你拿到的字模数据是低位在前,显示出来的字符就会左右翻转。这种问题看单个字符不容易发现,如果显示一串英文字母后整体像照镜子,不要怀疑坐标计算,先检查取模方向。

4. LCD 屏显示中文:字模、取模工具与字库存储

4.1 中文显示的核心是字模,不是字体

LCD 控制芯片不认识 Unicode,也不认识 GB2312,它只认像素点阵。中文字符显示要比英文麻烦,主要原因是 16×16 点阵一个字符占 32 字节,比英文的 5×8 多了将近 8 倍存储。开发者在网上找“LCD 显示中文”的代码时,最大的困惑是拿到手的字模数组不知道是怎么生成的,以及该按什么顺序解析。

先看取模工具是怎么生成字模的。下面这个 Python 脚本把一个 16×16 点阵汉字转成 C 语言数组,这也是我常用的方式。

# gen_font.py —— 将 16x16 点阵字模转成 C 数组 # 输入:一个包含 32 个十六进制字节的字模文本,每行一个字节 import sys def parse_hex_lines(lines): rows = [] for line in lines: s = line.strip() if not s: continue rows.append(int(s, 16)) return rows def format_c_array(rows): parts = [] for i in range(0, len(rows), 8): chunk = rows[i:i+8] parts.append(", ".join(f"0x{v:02X}" for v in chunk)) return "{\n " + ",\n ".join(parts) + "\n}" raw = sys.stdin.read().strip().splitlines() rows = parse_hex_lines(raw) print("static const uint8_t glyph[32] = ") print(format_c_array(rows)) print(";")

常见的取模工具有 PCtoLCD2002 这类。设置时重点看三个参数:阴码还是阳码(笔画部分的位是 1 还是 0),逐行式还是逐列式,以及字节高位在前还是低位在前。取模工具里的“阴码、逐行式、顺向”能对应到上面 C 代码里的直接顺序解析;如果选了“逆向”或者“逐列式”,在代码里就要做位反转,否则字会颠倒或按列错位。

4.2 GB2312 区位码索引:查字模比想象中简单

中文显示的标准流程是:拿到一个 GB2312 编码的汉字,计算出它在字库文件中的偏移量,然后读出 32 字节点阵数据。GB2312 的汉字在字库中按区位码连续排列,区码从 16 区到 87 区,位码从 1 到 94。计算公式很简单:

// font_chinese.c —— 按 GB2312 区位码查字模 static uint32_t gb2312_offset(uint16_t gb_code) { // gb_code 是两个字节:高字节为区码+0xA0,低字节为位码+0xA0 uint8_t zone = (gb_code >> 8) - 0xA0; // 区码 uint8_t pos = (gb_code & 0xFF) - 0xA0; // 位码 // 汉字区从 16 区开始(第一级汉字 16~55 区) if (zone < 16) { return 0; // 非汉字字符,做特殊处理 } return ((uint32_t)(zone - 16) * 94 + pos) * 32; }

这段代码的逻辑:GB2312 编码的汉字在 ROM 里的存储顺序是按区位号递增的,每个汉字占 32 字节。只要知道“第一个汉字字模的起始地址”,后面所有汉字的偏移都能算出来。注意返回值是偏移而不是地址,具体读取时要再加上字库在 Flash 或外部存储中的基地址。这里有个边界坑:GB2312 收录的汉字区码范围是 16~87,但 16~55 区是一级汉字(按拼音排序),56~87 区是二级汉字(按部首排序),查询前最好先判断区码是否在这个区间内,否则容易越界读出花屏数据。

4.3 字库存到哪:直接编译进固件还是外挂 SPI Flash

16×16 点阵的 GB2312 全字库大约 240KB。小容量 MCU 放不下,就产生了两种路线。

第一种是直接把常用汉字做成小字库编译进固件。比如产品界面只需要显示几百个汉字,用工具把字符串里的汉字去重,生成一个“个体字库”,大小可能只有十几 KB,放在内部 Flash 完全没问题。缺点就是后期加需求要重新取模烧录。

第二种是外挂 SPI Nor Flash,存全字库。这就是 W25Q32 这类 Flash 在 LCD 项目中经常出现的原因:4MB 容量,存一个 GB2312 全字库再加几张图片毫无压力。上电后把字库从 W25Q32 读到内存,或者直接在 SPI Flash 中按偏移读取点阵数据。读取路径是:GB2312 码 → 区位码 → 字模偏移 → SPI Flash 读取 32 字节 → 送到 LCD 刷新。要注意的是,SPI Flash 的读取速度和 LCD 刷屏速度不匹配时,要加一层缓存,否则字会一个一个蹦出来。

5. 驱动移植与常见踩坑:换 MCU 平台前必须想清楚的事

5.1 从裸机到 HAL 库:用 STM32CubeMX 搭一套 16 位总线

很多读者拿到代码时用的是 STM32 标准外设库,现在新项目大多转到 HAL 库。移植时最省事的方案不是用 GPIO 模拟,而是把 16 条数据线接到 STM32 的 FSMC 接口上。FSMC 的 NOR/SRAM 控制器原生支持 8080 时序,只要配置好时序参数,MCU 就能像访问外部 SRAM 一样操作 ILI9325,效率比 GPIO 模拟高一个量级。

在 CubeMX 里配置 FSMC 时,地址线用不到的场合可以把 RS 接到任意一个地址线管脚上,比如 A0。这样写寄存器时往偶数地址写,写数据时往奇数地址写,硬件自动把地址线的电平变化映射成 RS 信号。时序参数里最关键的是写脉冲宽度,ILI9325 数据手册要求 WR 低电平脉宽不小于 20ns 左右(实际模组会稍宽松),FSMC 的地址建立时间、数据建立时间可以先用适中值起步,出现花屏再逐项缩减。

代码层面,移植后原来的ili9325_write_reg可以简化成对地址空间的赋值:

// ili9325_fsmc.c —— FSMC 方式下的寄存器写 #define LCD_REG_ADDR ((volatile uint16_t *)0x60000000) // RS = 0 #define LCD_DATA_ADDR ((volatile uint16_t *)0x60000002) // RS = 1 static void ili9325_write_reg(uint16_t reg, uint16_t val) { *LCD_REG_ADDR = reg; // 写寄存器号 *LCD_DATA_ADDR = val; // 写数据 }

这段代码看着简单,但有个隐藏前提:地址线 A0 必须接在 LCD 的 RS 引脚上,且 FSMC 的基地址是 0x60000000(Bank1 第三区)。如果 RS 接到了别的地址线,地址偏移量也要跟着变。另外一个常见错误是指针类型用了uint8_t*,导致每次写操作只发出低 8 位数据,屏幕显示出乱码。

5.2 Linux 字符设备驱动框架下的 LCD 驱动:寄存器操作变成文件操作

如果你不是在裸机上跑,而是在 Linux 环境下接 ILI9325,那内核里已经有现成的抽象层——fbtft 或者自己实现的 framebuffer 驱动。这里要注意的是,Linux 下的 LCD 驱动和裸机驱动完全是两种写法:裸机把寄存器操作直接写在业务代码里,Linux 则需要把硬件操作封装在platform_driver的probe()函数里,通过file_operations或 framebuffer 接口让用户态程序访问。

一个常见但容易走偏的做法:很多人把裸机初始化代码原封不动放进内核驱动里,用gpio_set_value代替*LCD_DATA_PORT = val。能跑,但性能极差,因为每次 GPIO 操作都要经过内核的gpio_chip回调。正路是把并口或 FSMC 映射成内存地址,用ioremap获得虚拟地址后直接读写寄存器。初学 Linux 驱动时容易忽略的是:ioremap之后的地址是处理器虚拟地址,不是物理地址,调试时要用/proc/iomem对照检查。

5.3 避坑排查:5 个让人怀疑人生的坏现象

这里把我在调试 ILI9325 时遇到的坑集中写一下,每一条都是真实踩过的,按“现象 → 原因 → 解决”列出来,排查顺序可以从前到后走一遍。

现象一:复位后读 ID 全为 0xFF 或者全为 0x00。

原因基本在硬件时序或者 IO 配置,不在代码本身。最常见的是 RESET 引脚悬空,或者复位低电平时间不够;其次是数据引脚没有配好上下拉,读数据时总线浮空。解决方法是先给 RESET 一个 10ms 以上的低电平,拉高后延时至少 50ms 再操作;然后用逻辑分析仪抓一下 WR/RD/CS 的波形,看 RS 是否在发寄存器号时确实为低电平。另外,有些 STM32 的 GPIO 默认是复用状态,如果之前被别的外设占用了,读回来的数据就是乱的。

现象二:ID 读到 0x9325,初始化后还是白屏。

白屏意味着背光正常、控制芯片已经工作,但显示数据通路没建立。优先排查电源控制寄存器 0x0010~0x0013 的写入顺序。ILI9325 内部电荷泵要求先设置基础电压,再开显示,顺序反了屏幕就白着。解决方法是把你手头初始化表里所有电源相关寄存器挑出来,对照数据手册确认每一步的依赖关系,不要跳步。还有一种少见情况:写寄存器时数据线高低字节接反,寄存器值被翻转,电源控制字完全错乱。

现象三:初始化成功但显示是镜像的,左右颠倒或者上下颠倒。

这个现象在自制的板子上尤其常见,因为面板的 FPC 绑线方向你无法控制。IL9325 的 0x0003 寄存器 Entry Mode 中,AM 位控制 240×320 还是 320×240 的行列映射,ID0/ID1 位控制 GRAM 自增方向。解决方法是先写一个纯色填充全屏,如果看到颜色有方向性渐变,再逐位调整这三个位,直到显示的 ASCII 字符方向正确。改初始化寄存器比在每帧数据里做坐标变换省事得多。

现象四:颜色整体偏红或者偏蓝,白底发紫。

这是典型的 RGB565 字节序问题。用 16 位并行总线写入 0xF800 应该是纯红,如果屏幕显示纯蓝,说明你接线时把 DB15~DB8 和 DB7~DB0 互换了,或者取色函数里高低字节组装反了。排查方式很简单:初始化完成后,手动往 GRAM 写入 0xF800、0x07E0、0x001F 三个值,看屏幕分别显示什么颜色。不对就交换高低字节,改一处接线或者改一处代码,不要两边同时改,否则反而查不明白。

现象五:用 SPI 初始化完屏幕,写数据时花屏或完全无显示。

ILI9325 的 SPI 模式只能访问寄存器,不能访问 GRAM。很多人看芯片手册支持 SPI,就直接用了串行模式,结果发现显示数据写不进去。另外,某些低成本模块把数据线只引出了 8 位,16 位色下写一个像素要分两次发送,速度下降且很容易在高速时序下出错。解决方法是确认你的模组是不是把 DB15~DB8 也引出来了,引出来了就坚决走 16 位并行。

6. 刷屏提速与验证:把驱动从“能亮”做到“好用”

6.1 用连续 GRAM 写取代画点循环,省掉坐标开销

整屏刷新时最忌讳的做法是在循环里反复调用“设置窗口 + 写一个像素”。每写一个像素,MCU 要多执行四五条寄存器写指令,浪费的时间全花在地址传输上。提速的正确思路是:先把窗口设到全屏或目标区域,然后通过循环体内部只翻转 WR 写数据。如果 MCU 支持 DMA 和 FSMC 同时使用,可以直接把一块内存数据通过 DMA 搬运到 FSMC 地址空间,一次刷新几千个像素才产生一次 CPU 中断。

在 8 位总线上如果头铁写 16 位色,刷新一帧的时间会翻倍。这就是很多产品明明用 9325 却卡成幻灯片的深层原因。如果硬件已经固定死在 8 位总线,可以考虑把颜色格式从 RGB565 降到 RGB444(12 位)或者写一个 16 位转 8 位的查表,但显示质量会肉眼可见地下降。

6.2 用 GPIO 翻转测刷新耗时,做基准验证

刷屏代码改完之后不要只靠眼睛看流畅度,拿一个 GPIO 引脚拉高拉低,用示波器量一下整帧刷新的时间,这才是可靠的验证手段。

// timing_test.c —— 全屏填充计时 void lcd_frame_timing_test(void) { TIM_PIN_HIGH(); ili9325_fill(0, 0, 239, 319, 0xFFFF); // 白色全屏 TIM_PIN_LOW(); }

这段代码把刷屏动作夹在两个 IO 翻转之间,示波器量到的低电平脉宽就是填充耗时。我一般会分别测三个场景:白色全屏、黑色全屏、随机颜色全屏。如果白色和黑色的耗时差异明显,说明写入数据里存在某种总线切换开销,正常情况三种场景耗时应该几乎一致。另一种值得测的是小窗口填充:把窗口设成 100×100 后连续写数据,和全屏填充对比每像素平均耗时,这个比值能反映你的地址建立优化空间有多大。

6.3 调试技巧:先把屏刷成纯色,再写字

给一块新板子调 LCD 驱动时,我从来不直接跑完整界面。第一步是初始化后刷纯白,确认写通路没问题;第二步刷纯红,确认字节序正确;第三步依次刷纯绿、纯蓝,确认 RGB 三通道都对;第四步用画点函数画一个十字交叉线,确认坐标递增方向;最后才写字符和图片。每一步卡住,都能明确锁定问题在数据线、时序还是寄存器配置上。这个习惯帮我少走很多弯路,也希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询