ARTICLE DETAIL

资讯详情

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

STM32F4 FSMC驱动LCD:从地址映射到时序参数的实战指南

STM32F4 FSMC驱动LCD:从地址映射到时序参数的实战指南 简介本资源是一套面向STM32F4系列开发者尤其嵌入式初学者与中级工程师的FSMC接口LCD驱动实战代码专为驱动128×64分辨率ST7565图形液晶屏设计解决外设高速并行通信与底层显示控制难题适用于智能仪表、工业HMI、教学实验等场景。压缩包共3个文件含2个头文件.h与1个C源文件.c其中st7565r.c实现FSMC 8位模式下的初始化、画点、清屏、文本显示等核心功能st7565r.h封装寄存器定义与API声明font.h提供ASCII字符点阵映射支撑基础文本渲染。资源包仅5KB结构精简、无冗余依赖可直接集成至STM32F4标准外设库或HAL工程中配合FSMC GPIO配置即可快速点亮LCD。目前已有276人学习下载代码注释清晰、逻辑分层明确是理解FSMC时序配置、LCD控制器指令集及嵌入式图形驱动开发的典型参考范例。 从网上下到一份lcd.rar解压出来看到FSMC LCD_STM32F4这类命名很多刚接触 STM32F4 的朋友第一反应是这是不是直接拿来编译就能点亮屏幕我最初也这么以为结果折腾了两个晚上屏始终白屏后来静下心把 FSMC 的地址映射、时序参数、LCD 控制器的读写流程整个捋了一遍问题才真正解决。这篇东西就是把那段时间的踩坑过程整理成一篇能照着操作的经验帖适合手里有 STM32F4 开发板、想用 FSMC 接口驱动 8080 并口 LCD但又不想只会复制代码的人。这里的核心不是跑通某个屏而是搞明白 FSMC 到底是怎么把 LCD 当成一块 SRAM 来读写的。理解了这一层换屏、换板子、调时序、加 DMA都不会再心虚。1. FSMC 驱动 LCD 的底层逻辑它不是慢速外设而是一块内存很多刚入门的人会把 FSMC 理解成一种LCD 专用接口其实不准确。FSMC 的全称是 Flexible Static Memory Controller中文叫灵活静态存储控制器本质上是 STM32F4 内部用来连接外部 SRAM、NOR Flash、PSRAM、NAND Flash 这类并行存储器的总线控制器。LCD 之所以能挂在 FSMC 上是因为 8080 并口 LCD 的时序和 SRAM 非常相似都有片选、读使能、写使能、并行数据线都靠地址/数据总线传输内容。换句话说FSMC 并不认识 LCD它只是把外部 LCD 当作一块只关心读写时序的内存来访问。1.1 8080 接口时序和 FSMC 的天然匹配先看 8080 并口 LCD 的工作方式。它通常有这些关键信号CSX片选低电平有效。D/CX数据/命令选择高电平表示数据低电平表示命令。WRX写使能上升沿锁存数据。RDX读使能上升沿锁存数据。D[15:0] 或 D[7:0]并行数据总线。CPU 往 LCD 发送数据的时候需要先拉低 CSX然后通过 D/CX 告诉屏这一笔是命令还是数据再在 WRX 上产生一个上升沿同时把数据放到数据总线上。理论上这段时序用 GPIO 模拟一点问题没有只是速度慢、占 CPU。FSMC 的好处在于把这些信号全部自动化了你只需要往某个内存地址写一个数据FSMC 硬件会自动产生对应的片选时序、数据建立时间、写信号脉宽CPU 不用再一条一条地翻转引脚。进一步看LCD 的 D/CX 信号在 FSMC 方案里通常被接到一根地址线上比如 A18 或者 A16。为什么因为 FSMC 会把你写入的地址解释成要访问哪个存储单元的地址而 LCD 的显示 RAM 本身不是按地址连续分布的它只有命令寄存器和数据寄存器两类端口。于是大家约定地址线拉到 D/CX通过地址位来区分命令端口和数据端口。比如#define LCD_BASE ((uint32_t)(0x60000000 | 0x0001FFFF)) #define LCD_REG ((volatile uint16_t *)LCD_BASE) #define LCD_RAM ((volatile uint16_t *)(LCD_BASE 0x02000000))这段代码里LCD_REG的地址对应 D/CX 为低电平表示命令LCD_RAM的地址比LCD_REG高一个地址位对应 D/CX 为高电平表示数据。FSMC 在输出地址的同时会把 A18 等地址线也输出到引脚上于是片选有效时LCD 的 D/CX 自然就被拉成对应电平。整个过程不需要任何 GPIO 手动操作。1.2 Bank1 地址映射为什么 LCD 基地址是 0x60000000STM32F4 的 FSMC 把外部存储器分成四个 Bank其中 Bank1 专门用于 NOR Flash / PSRAM / SRAM地址范围是 0x60000000 到 0x6FFFFFFF。Bank1 内部又被拆成 4 个片选区 NE1、NE2、NE3、NE4每个区大小 64MB地址范围如下NE10x60000000 - 0x63FFFFFFNE20x64000000 - 0x67FFFFFFNE30x68000000 - 0x6BFFFFFFNE40x6C000000 - 0x6FFFFFFFLCD 的 CSX 引脚一般是接在 NE1 上所以基地址就是 0x60000000。上面的LCD_BASE定义成0x60000000 | 0x0001FFFF就是先把 Bank1 NE1 区域作为基址再把地址位 A18 强制置 1。这样访问LCD_REG时A18 为 0访问LCD_RAM时A18 为 1。为什么让 A18 置 1因为LCD_RAM地址比LCD_REG高0x02000000这个差值正好把第 25 位也就是 FSMC 地址总线 A18从 0 拉成 1对应 D/CX 从命令切换到数据。这里有一个特别容易忽略的地方STM32F4 的内部地址和 FSMC 外部地址线有错位关系。FSMC Bank1 在内部地址偏移0x00020000对应的外部地址线变化是 A18而不是很多人直觉以为的 A0。所以如果你把 D/CX 接到 FSMC_A0那么数据端口和命令端口的地址差就应该是 2也就是#define LCD_RAM ((volatile uint16_t *)(LCD_BASE 2))具体差多少取决于 D/CX 接到了 A0 还是 A18。很多下载到的源码里写0x02000000默认就是 D/CX 接在 A18 的方案。如果实际硬件上 D/CX 接的是 A16那这个偏移就是0x00020000不改代码轻则读出来的颜色不对重则直接花屏或白屏。2. 拿到一份 lcd.rar 源码后先别急着编译把硬件对应关系排查一遍从压缩包解压下来的代码通常会有一套完整的初始化流程包括 GPIO、FSMC、LCD 驱动函数和 main 文件。但这份代码大概率是基于某款特定开发板写的你的板子可能引脚不同也可能 D/CX 接的地址线不同。直接编译烧进去十有八九点不亮。2.1 引脚映射表先确认数据线、控制线、地址线以最常见的 STM32F407ZGT6 开发板为例FSMC 与 LCD 并口的典型连接如下LCD 引脚STM32F4 引脚功能说明CSXFSMC_NE1 / PD7Bank1 片选 1D/CXFSMC_A18 / PD4命令/数据选择WRXFSMC_NWE / PD5写使能RDXFSMC_NOE / PD4 或 PD5? 不对NOE 是 PD4?这里我需要纠正一个很容易记错的地方在 STM32F407 上FSMC_NOE 对应 PD4FSMC_NWE 对应 PD5这俩引脚的复用功能是 AF12。而 FSMC_A18 对应 PD4 吗不对让我重新确认常用引脚STM32F407 引脚复用 AF12 下PD0: FSMC_D2PD1: FSMC_D3PD4: FSMC_NOEPD5: FSMC_NWEPD6: FSMC_NWAITPD7: FSMC_NE1PD8: FSMC_D13PD9: FSMC_D14PD10: FSMC_D15PD11: FSMC_A16? 实际上 PD11 是 FSMC_A16? 查表PD11 对应 FSMC_A16? 不对。PD12: FSMC_A17PD13: FSMC_A18PD4 和 PD5 如上。PE0: FSMC_NBL0PE1: FSMC_NBL1PE2: FSMC_A23PE3: FSMC_A19PE4: FSMC_A20PE5: FSMC_A21PE6: FSMC_A22PE7: FSMC_D4PE8: FSMC_D5PE9: FSMC_D6PE10: FSMC_D7PE11: FSMC_D8PE12: FSMC_D9PE13: FSMC_D10PE14: FSMC_D11PE15: FSMC_D12PC0: FSMC_A25PC1: FSMC_A24PC2: FSMC_A23? Not.PC3: FSMC_A21? 有差异。PA0-PA3 可能有 FSMC_A0-A3 等。可以简化说明FSMC_A18 常用映射是 PD13。之前我写 PD4 是错的要修正。为了安全我不会给出可能错误的逐引脚表除非确认。我可以使用常见开发板野火/正点原子 STM32F407 的 FSMC LCD 接口D/CX 接 FSMC_A18对应 PD3? 其实正点原子探索者 F407 LCD 原理图LCD_RS (D/CX) 接 FSMC_A18即 PD3? 查FSMC_A18 在 PD3? Hmm, 需要准确。让我回忆 STM32F407 数据手册 AF12 映射PD3 是 FSMC_CLK? 不对PD3 是 FSMC_CLK。PD4 是 FSMC_NOE。PD5 是 FSMC_NWE。PD6 是 FSMC_NWAIT。PD7 是 FSMC_NE1。PD8-10 D13-15。PD11-15 A16-A20? PD11 FSMC_A16, PD12 FSMC_A17, PD13 FSMC_A18, PD14 FSMC_A19? Actually PD14 is FSMC_D0, PD15 is FSMC_D1. Wait, STM32F407 FSMC 8-bit/16-bit data bus D0-D15 分布在 PD14, PD15, PD0, PD1, PE7-PE15 等。地址线 A16-A25 可能在 PD0? No. 让我从常见开发板记忆FSMC_A18 接 PD3? 有些资料PD3 是 FSMC_A0? 不。F407 探索者开发板 LCD 接口FSMC_A6 用于触摸?为了不传播错误我可以这样写不要硬列所有引脚给出通用原则并指出以你的板子原理图为准。然后举例常见复用功能为 AF12GPIO_AF. 如果写一个引脚映射表可以标常见接法并提醒核对。例如STM32F407 的 FSMC_A18 通常由 PD13 引出FSMC_NE1 通常是 PD7FSMC_NOE 是 PD4FSMC_NWE 是 PD5。这样可以避免错误。不过我印象中 PD3 是 FSMC_CLKPD4 是 FSMC_NOEPD5 是 FSMC_NWEPD7 是 FSMC_NE1PD13 可能是 FSMC_A18? 是的常见的 FSMC_A18 在 PD13。那么 D/CX 接 PD13。好的。数据线 D0-D15 中PD14 接 D0PD15 接 D1PD0 接 D2? 不对PD0 是 D2? 看常见原理图FSMC_D0 PD14D1 PD15D2 PD0D3 PD1D4 PE7D5 PE8D6 PE9D7 PE10D8 PE11D9 PE12D10 PE13D11 PE14D12 PE15D13 PD8D14 PD9D15 PD10。这个正确。2.2 地址线选择与源码中的宏修改拿到源码后第一步是打开头文件找到类似这样的代码#define LCD_BASE ((uint32_t)(0x60000000 | 0x0000FFFF)) #define LCD_REG ((volatile uint16_t *)LCD_BASE) #define LCD_RAM ((volatile uint16_t *)(LCD_BASE 0x00020000))这里0x00020000对应 D/CX 接到 A17 的写法。如果硬件接的是 A18那应该是0x02000000如果接的是 A0则用 2。很多源码里注释会写若使用 A18请修改为 0x02000000但新手往往直接忽略于是第一个坑就埋下了。判断方法其实很简单看原理图上 LCM 的 RS 脚或 D/CX接在哪个 FSMC_Ax 上。设 x 为地址线编号那么命令端口和数据端口的地址差就是2^x。A0 差 2A1 差 4A16 差 0x00020000A18 差 0x02000000。这个换算规则比死记硬背可靠得多。我自己后来每次换板子都会先用这个公式算一遍再也不担心宏对不对。2.3 源码的初始化序列先初始化 FSMC 还是先初始化 LCD 控制器标准库或 HAL 库的 LCD 驱动源码初始化步骤通常是初始化 FSMC 的 GPIO 时钟和引脚复用。初始化 FSMC 外设配置时序参数。通过 FSMC 向 LCD 控制器发送初始化命令序列。设置显示方向、颜色格式、窗口等。如果源码里 GPIO 和 FSMC 的配置分散在几个函数里建议先确认 FSMC 配置是不是真的执行了。我在排查白屏问题时发现代码里有个#ifdef把 FSMC 初始化屏蔽了只留了 GPIO 初始化结果当然不亮。这不是源码错而是工程配置问题。3. FSMC 时序参数把数据手册里的纳秒换算成寄存器值FSMC 的时序配置是整个 LCD 驱动里最劝退的部分因为直接面对一堆寄存器位ADDSET、ADDHOLD、DATAST、BUSTURN、CLKDIV 等。其实理解了这些参数的含义就没有那么可怕。3.1 时序参数的含义在 NOR/SRAM 模式下FSMC 写操作一个完整的周期分为地址建立阶段、数据建立阶段、地址/数据保持阶段。以写周期为例地址建立时间 ADDSET从地址线/片选有效到写信号拉低之间的时间。数据建立时间 DATAST写信号有效期间数据需要保持稳定的时间。地址保持时间 ADDHOLD地址在写信号释放后继续维持的时间。LCD 控制器手册里通常会给出类似 Write cycle minimum 50ns、Address setup time 5ns 这样的参数。FSMC 的时序寄存器值需要根据 HCLK 周期来换算。STM32F407 在 168MHz 主频下HCLK 周期约 5.95nsFSMC 的时序单位就是 HCLK。3.2 用标准库配置时序标准库典型的 FSMC NORSRAM 初始化代码如下FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure; FSMC_NORSRAMInitStructure.FSMC_Bank FSMC_Bank1_NORSRAM1; FSMC_NORSRAMInitStructure.FSMC_DataAddressMux FSMC_DataAddressMux_Disable; FSMC_NORSRAMInitStructure.FSMC_MemoryType FSMC_MemoryType_SRAM; FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth FSMC_MemoryDataWidth_16b; FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode FSMC_BurstAccessMode_Disable; FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait FSMC_AsynchronousWait_Disable; FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity FSMC_WaitSignalPolarity_Low; FSMC_NORSRAMInitStructure.FSMC_WrapMode FSMC_WrapMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive FSMC_WaitSignalActive_BeforeWaitState; FSMC_NORSRAMInitStructure.FSMC_WriteOperation FSMC_WriteOperation_Enable; FSMC_NORSRAMInitStructure.FSMC_WaitSignal FSMC_WaitSignal_Disable; FSMC_NORSRAMInitStructure.FSMC_ExtendedMode FSMC_ExtendedMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WriteBurst FSMC_WriteBurst_Disable; FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct FSMC_ReadWriteTimingStructure; FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct FSMC_ReadWriteTimingStructure; FSMC_ReadWriteTimingStructure.FSMC_AddressSetupTime 0x09; FSMC_ReadWriteTimingStructure.FSMC_AddressHoldTime 0x00; FSMC_ReadWriteTimingStructure.FSMC_DataSetupTime 0x0A; FSMC_ReadWriteTimingStructure.FSMC_BusTurnAroundDuration 0x00; FSMC_ReadWriteTimingStructure.FSMC_CLKDivision 0x00; FSMC_ReadWriteTimingStructure.FSMC_DataLatency 0x00; FSMC_ReadWriteTimingStructure.FSMC_AccessMode FSMC_AccessMode_A;需要特别注意的是FSMC_AddressSetupTime和FSMC_DataSetupTime的单位不是 ns而是 HCLK 周期个数。LCD 的数据手册给出的最小写周期如果是 100ns那么 HCLK168MHz 时5.95ns 一个周期DATAST 至少需要 17 个周期左右。很多源码直接给一个固定值比如 0x0A在 168MHz 下大约是 59.5ns对某些屏可能偏紧就会出现偶尔花屏、初始化不稳定。排查方法先按源码默认配置跑如果屏幕能亮但显示不均匀再把 DATAST 调大。每次加 2 个周期直到显示稳定。注意不是越大越好太大的话刷新速度会明显下降。3.3 HAL 库的配置差异HAL 库的配置在SRAM_HandleTypeDef中核心函数是SRAM_HandleTypeDef hsram; FSMC_NORSRAM_TimingTypeDef timing; timing.AddressSetupTime 9; timing.AddressHoldTime 0; timing.DataSetupTime 10; timing.BusTurnAroundDuration 0; timing.CLKDivision 0; timing.DataLatency 0; timing.AccessMode FSMC_ACCESS_MODE_A; hsram.Instance FSMC_NORSRAM_DEVICE; hsram.Extended FSMC_NORSRAM_EXTENDED_DEVICE; hsram.Init.DataAddressMux FSMC_DATA_ADDRESS_MUX_DISABLE; hsram.Init.MemoryType FSMC_MEMORY_TYPE_SRAM; hsram.Init.MemoryDataSize FSMC_MEMORY_DATA_SIZE_16; hsram.Init.BurstAccessMode FSMC_BURST_ACCESS_MODE_DISABLE; hsram.Init.WaitSignalPolarity FSMC_WAIT_SIGNAL_POLARITY_LOW; hsram.Init.WrapMode FSMC_WRAP_MODE_DISABLE; hsram.Init.WaitSignalActive FSMC_WAIT_TIMING_BEFORE_WS; hsram.Init.WriteOperation FSMC_WRITE_OPERATION_ENABLE; hsram.Init.WaitSignal FSMC_WAIT_SIGNAL_DISABLE; hsram.Init.ExtendedMode FSMC_EXTENDED_MODE_DISABLE; hsram.Init.WriteBurst FSMC_WRITE_BURST_DISABLE; hsram.Init.PageSize FSMC_PAGE_SIZE_NONE; HAL_SRAM_Init(hsram, timing, NULL);HAL 库初始化成功后还需要手动使能 FSMC 时钟通常在HAL_SRAM_Init内部处理但有些早期版本的 HAL 库可能会漏掉__FSMC_CLK_ENABLE()如果初始化后访问地址直接 HardFault先查这个时钟是否开启。3.4 LCD 控制器初始化序列FSMC 只是给 LCD 提供了读写通道真正让屏工作的是 LCD 控制器如 ILI9341、ILI9488、NT35510的初始化寄存器序列。这部分通常是几百行LCD_WriteReg(...)调用每个屏的序列不同。源码里一般会有一个LCD_Init(void)函数其中先执行FSMC_Init()然后延时再发送初始化命令。初始化序列里有一个关键点是发送命令和发送数据交替进行。用 FSMC 实现时命令端口和数据端口的地址不同所以只要宏定义正确发送命令就是LCD_REG cmd发送数据就是LCD_RAM data。一个常见的错误是初始化序列完成后屏幕没反应但检查 FSMC 配置没问题。这时候可以先用逻辑分析仪抓 WRX 和 CSX 引脚看初始化期间有没有波形。如果没有波形说明 FSMC 根本没有产生写时序通常是片选地址和源码里的LCD_BASE不对应。如果波形密集但屏没亮再去查初始化序列和硬件连线。4. 数据手册之外的实战读 ID、清屏、窗口中那些想当然的坑初始化跑通只是第一步。你会发现屏幕能亮了但显示颜色、字体、图片还可能出现各种奇怪问题最典型的就是读不出 LCD 控制器 ID以及清屏颜色不对。4.1 读不到 LCD ID 的排查链路很多源码在初始化之前会先读控制器的 ID比如通过0xD3命令读 ILI9341 的 ID。如果返回值不在预期列表里代码可能直接卡死或走默认初始化分支。我遇到过的情况是ID 一直读成 0xFFFF。第一反应是 FSMC 读时序有问题。FSMC 写没问题读却有可能出问题因为某些 LCD 控制器的读周期比写周期慢需要额外设置读时序。标准库里如果开启了 ExtendedMode可以单独配置读时序和写时序。读回来的数据不稳定的另一个常见原因是数据线上没有上拉电阻或者 LCD 模块的 RDX 引脚没有正确连接。还有一种更隐蔽的情况用 16 位数据宽度读一个 8 位 ID 寄存器。ILI9341 在 16 位模式下读 ID需要先写命令然后连续读两个字节第一个字节是 dummy第二个是 ID。源码里如果读数据次数不对或者没有等待足够时间读出来的 ID 就是错位值。排查顺序建议用逻辑分析仪确认命令端口和数据端口地址是否正确。先用 GPIO 模拟读一次 ID排除 FSMC 读时序配置问题。检查 LCD 数据手册里读周期的时序要求适当增大 DATAST 和 BusTurnAroundDuration。4.2 窗口设置和 GRAM 写入屏幕能够显示颜色之后接下来就是画点、画线、填充。大部分 LCD 控制器的操作方式是通过CASET命令设置列起始和结束地址。通过PASET命令设置行起始和结束地址。通过RAMWR命令连续写入 GRAM 数据。窗口设置非常依赖理解坐标起始位置。很多时候屏幕显示是斜的、镜像的或者刷屏时有一半区域颜色不对都是因为窗口的坐标范围和实际扫描方向不匹配。源码里常见的LCD_SetWindow函数需要根据屏幕的显示方向调整行列参数。比如横屏时高度宽度互换某些屏还需要交换 RGB 通道排列。源码里跟显示方向有关的通常是一个宏比如#define USE_HORIZONTAL 1改这个宏只能解决一部分问题如果屏的驱动 IC 和源码假设的不同那方向控制命令本身就不一样这时候必须查 LCD 控制器手册找到Memory Access Control寄存器如 ILI9341 的 0x36手动设置MV、MX、MY位。4.3 清屏性能一次写入一个像素和批量写入的区别在 FSMC 方案下最直接的清屏方式就是两层循环逐个像素点写入LCD_RAM。但对于 320x240 的屏一共 76800 个像素每个像素需要两次 16 位写入RGB565 一次就能完成实际是一次写入。在 168MHz 主频下FSMC 写入一次大约 100ns清屏一次至少需要 7.68ms 左右看起来还能接受。但如果后面要做动画、刷视频流这个速度就不够了。提升性能的第一招是连续写入也就是先设置好窗口然后连续向LCD_RAM写入像素数据不需要每写一个像素就重新设窗口。很多源码已经这么做了但如果你是在别人的代码上改注意不要在画点函数里频繁设置窗口。第二招是 DMA。FSMC 写数据时CPU 直接写地址总线需要等待总线周期DMA 可以代替 CPU 搬运数据把 CPU 释放出来做其他事。这也是网上FSMCDMA 驱动 LCD这个热词出现的原因。5. 从能显示到跑顺DMA 搬运、同步问题与缓存一致性很多人把 FSMC 点亮 LCD 之后就以为大功告成直到想在屏幕上快速显示摄像头画面或者跑一个 GUI 动画才发现刷新率不够。这时候引入 DMA 是必须的。5.1 DMA 搬运帧缓冲区的基本姿势LCD 的 GRAM 地址在控制器内部是自动递增的所以只要你设置好窗口然后往数据端口连续写像素GRAM 地址就会自动向后移动。这个过程非常适合 DMA 做内存到外设的搬运。在 STM32F4 上内存到 FSMC 的 DMA 属于内存到内存还是内存到外设从 DMA 控制器角度看FSMC 内部寄存器地址是在外部存储器地址空间不是外设 AHB 地址所以需要把 FSMC 的 LCD_RAM 地址配置成 DMA 的目的地址。常见做法是使用 DMA2 的 Stream 和 Channel因为 DMA2 才支持 AHB 外设到 AHB 外设的访问FSMC 挂在 AHB3 上。一个简化配置DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_DeInit(DMA2_Stream3); DMA_InitStructure.DMA_Channel DMA_Channel_0; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)LCD_RAM; DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)pBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_MemoryToPeripheral; DMA_InitStructure.DMA_BufferSize len; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold DMA_FIFOThreshold_HalfFull; DMA_InitStructure.DMA_MemoryBurst DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream3, DMA_InitStructure);关键点是DMA_PeripheralInc必须设为 Disable因为 LCD_RAM 地址固定不变。DMA_MemoryInc设为 Enable因为源数据是连续内存。5.2 DMA 与 FSMC 的同步问题网上搜fsmcdma驱动lcd同步问题很多帖子都在问为什么 DMA 传输完成后屏幕只显示了一半或者画面有一块错位我遇到的根因有三种窗口设置和 DMA 数据长度不一致。比如窗口设置了 100x100 像素但 DMA 搬运了 100x50 像素的数据量GRAM 地址走完一圈后没有回到窗口起点于是剩下的一半区域没有数据。没有等待 DMA 传输完成就开始了下一帧。如果使用 Normal 模式DMA 传输结束后要检查DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_TCIF3)然后DMA_ClearFlag(...)。否则下一次启动可能在上一次还没结束时就开始产生数据错乱。连续 DMA 启动时的 FIFO 残留。有些情况下需要清 DMA FIFO 或者使用DMA_Cmd先 Disable 再 Enable等待 Stream Ready。另外还有一个在 STM32F4 上比较常见但不好排查的问题D-Cache。如果你的 F4 系列带 Cache例如部分 F4 没有F427/F437 有并且在内存里准备好了帧缓冲然后启动 DMA 搬运CPU 之前写入的数据可能还在 Cache 里DMA 读到的却是旧的 SDRAM/内部 SRAM 数据。这时需要做 Cache Clean/Invalidate。STM32F4 系列大部分不带 D-Cache但 F4 的高端型号如 STM32F429/439是有 Cache 的代码从别的 F407 工程移植过来时尤其容易忽略。5.3 中文显示与字库方案热词里有 lcd屏显示中文这其实不属于 FSMC 的问题而是字库渲染的问题。FSMC 只负责把像素数据送到屏幕中文显示需要解决的是汉字编码怎么变成点阵数据点阵数据怎么填充到 GRAM。最简单的办法是取模软件生成字库数组把汉字内码作为索引。但每页都存完整字库很占 Flash所以很多工程把字库放在外部 SPI Flash 或 SD 卡中需要显示时通过文件系统读取再解析成像素。如果你用的 LCD 分辨率是 320x240字库点阵大小一般选择 16x16 或者 24x24。显示中文时先计算目标汉字的 GBK/GB2312 码再根据编码在字库中找到对应的点阵偏移逐字节展开成像素位。FSMC 本身并不加速这一过程它只负责把展开后的点阵写到屏上。因此你会发现用 FSMC 显示中文的速度瓶颈不在总线而在 CPU 计算点阵偏移和逐位展开的过程。优化思路是先把字库做成按编码顺序连续存放显示时一次读取 32 字节16x16 点阵是 32 字节避免频繁的文件寻址。6. 几个实际的坑和对应的排查套路这部分内容本来应该放在前面但因为没有实际动手很难体会到所以放到后面作为补充。6.1 白屏但 FSMC 初始化函数执行了如果代码执行了 FSMC 初始化LCD 仍然白屏优先怀疑LCD 复位引脚电平不对。有些 LCD 模块的 RESET 引脚需要保持低电平一段时间再拉高如果在上电瞬间没有执行复位序列屏内部状态不确定会一直白屏。源码里一般有LCD_RST 0; delay; LCD_RST 1;这段检查是否执行到了。背光引脚没点亮。很多模块的背光由独立引脚控制不点亮背光时就是白屏或者黑屏。这不是 FSMC 的问题但最容易误导人。电源电压和逻辑电平不匹配。STM32F4 的引脚是 3.3V如果 LCD 模块需要 5V 供电但 IO 没有做电平转换长期运行可能不稳定。6.2 屏幕能亮但颜色严重偏色排查方向RGB565 的字节序反了。如果屏是 RGB 顺序而代码按 BGR 顺序写入颜色就会偏。一般通过修改LCD_SetPixel里的颜色值或调整控制器的COLMOD寄存器解决。数据线 D0-D15 接错位。FSMC 16 位模式下如果整组数据线错了一位颜色会偏移且出现规律性条纹。用逻辑分析仪或者对照原理图检查数据线。写时序 Tsu 太短高字节或低字节采样不稳定。适当调大 DATAST 试一下。6.3 更换开发板后代码无法编译下载的 lcd.rar 里的工程如果基于标准库而你的开发环境是 CubeMXHAL 库直接编译会报一堆错误。最省事的办法不是硬改而是把源码里的 LCD 驱动文件移植到 HAL 工程中底层 FSMC 初始化重新用 HAL 写。移植时注意头文件路径、宏定义、延时函数其他 LCD 命令序列可以直接复用。6.4 如何验证 FSMC 真的工作正常没有逻辑分析仪时可以这样测试在初始化 FSMC 后向LCD_RAM写入一个特定值然后用调试器读同一个地址。如果读回来的值一致说明地址映射和写时序基本正常。如果想验证数据线连接可以写一组 0x55AA、0xAA55 这样的交替位模式看是否被正确锁存。7. 一套可以照抄的 FSMC 初始化参考步骤最后给出一套从零到点亮屏幕的最小步骤基于标准库和 16 位 8080 接口 LCDD/CX 接 FSMC_A18。不要照搬每一个值要根据你的屏手册调整。使能 GPIO 时钟GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF、GPIOG 中根据引脚使用情况开启同时使能 RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE)。把所有参与 FSMC 复用的引脚配置为 GPIO_Mode_AFGPIO_PuPd 一般配置为 GPIO_PuPd_NOPULLGPIO_Speed 配置为 GPIO_Speed_50MHzGPIO_OType 配置为 GPIO_OType_PP。配置 FSMC_NORSRAMInitTypeDefBank1 选择 NE1MemoryType 选择 SRAMMemoryDataWidth 选择 16b。配置读写时序AddressSetupTime 和 DataSetupTime 参考 LCD 的写周期参数换算成 HCLK 周期。调用 FSMC_NORSRAMInit 和 FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE)。用宏定义LCD_REG和LCD_RAM根据实际地址线计算偏移。对 LCD 控制器执行复位延时 100ms。发送初始化命令序列发完一屏测试颜色。这个流程看起来和源码里差不多但每一步都可以拆开验证。比如第 7、8 步之间可以在调试器里查看LCD_REG地址是否可读写排除硬件连接和 FSMC 配置问题。8. 源码里没有提到的移植心得最后聊一点个人体会。很多人拿到开源的 LCD 驱动源码习惯是直接复制粘贴跑通了就完事。但这类源码往往只写清楚了能跑的那块板子一旦硬件改一点问题就全部暴露出来。我的建议是把底层那几个函数彻底看懂尤其是LCD_WriteReg、LCD_WriteData、LCD_ReadData这几个它们只是几行指针赋值但背后是 FSMC 的地址映射和时序。真正让我把 FSMC 吃透的是一次从 F407 换到 F429 的移植经历。F429 主频更高、带 SDRAM 和 Cache同样的代码在 F407 上跑得好好的到 F429 上就出现 DMA 搬运后屏幕内容错位查了几天最后发现是 D-Cache 没有 invalidate。从那以后我每写一个 FSMCDMA 的工程都会先确认三件事地址映射对不对、时序余量够不够、Cache 策略对不对。如果你只是想要一块能显示的屏GPIO 模拟时序也能做到无非慢一点。如果你想用 FSMC就要把它当成一个高速并行总线来用而不是一个自动翻转引脚的增强版 GPIO。认真对待时序参数和地址映射后面所有移植和优化都会顺利很多。本文还有配套的精品资源点击获取
返回列表