资讯动态

STM32 HAL库驱动LCD屏:FSMC与SPI初始化配置及避坑指南

发布时间:2026/10/15 4:55:46 来源:尧图企业网站定制
简介这份资源面向嵌入式开发初学者与需要快速搭建显示交互界面的工程师聚焦STM32平台下LCD显示与触摸屏的初始化配置。内容围绕HAL库展开涵盖接口选择、GPIO与时钟配置、分辨率与颜色深度设定、初始化命令序列发送以及触摸屏I2C/SPI通信与中断处理等关键环节帮助读者理解从底层驱动到业务逻辑的完整链路。压缩包共179个文件以h头文件、c源文件、o目标文件与d依赖文件为主另含uvprojx工程、hex固件、map映射及少量说明文档整体约1.14MB工程结构完整可直接参考。目前已有763人学习下载适合作为STM32显示模块开发的实践模板便于对照代码梳理初始化流程与排错思路。1. 从一块点不亮的屏说起STM32HAL 的 LCD 初始化到底在配什么很多人第一次用 STM32 驱动 LCD代码烧进去屏幕要么全白、要么全黑、要么花屏串口打印一切正常就是没图像。问题十有八九不在 LCD 本身而在初始化配置这一层。STM32HAL 的 LCD 显示初始化本质上是三件事的叠加把 FSMC/FMC 或 SPI 外设配成 LCD 能听懂的时序把 GPIO 复用和片选信号接对再把 LCD 控制器自己的寄存器序列按手册写进去。这三件事任何一件错位屏幕都不会给你面子。这篇笔记面向正在用 HAL 库点屏的嵌入式开发者尤其是从标准库转过来、对 CubeMX 生成代码一知半解的人。我会按“先讲清总线选型再给可复现的配置步骤最后把踩过的坑摊开”的顺序写目标是你照着能点亮一块 8080 并口或 SPI 接口的 TFT 屏并且知道每一行配置在改什么。2. 选 FSMC 还是 SPI先看屏的接口再看你的刷新需求2.1 两种接口的本质差别与选型判断LCD 模块常见接口分两类并行接口8080/6800和串行接口SPI/I2C。STM32 这边对应的驱动路径完全不同。并行屏一般走 FSMCF1/F4 系列叫 FSMCF7/H7 叫 FMC把 LCD 当成一个外部 SRAM 来读写优点是刷屏快320x240 的屏整屏刷新可以做到毫秒级缺点是占引脚多数据线 8 位或 16 位加上控制线轻松吃掉 20 多个 IO。SPI 屏接线少四根线就能跑但刷新率受 SPI 时钟限制全屏刷新动辄几十毫秒适合参数显示、小面积更新不适合视频或高频波形。选型判断可以按这个顺序走先看手头屏的排线有 D0-D15 和 RD/WR/CS/RS 的就是并行屏只有 SCL/SDA/CS/DC/RES 的就是 SPI 屏。再看你的刷新需求如果每秒要刷超过 10 帧全屏SPI 基本没戏老老实实上 FSMC。最后看引脚余量FSMC 会占用特定端口的连续引脚布线前先查芯片数据手册的复用表别等到 PCB 画完才发现引脚冲突。我一般会建议新手先用 SPI 屏跑通流程因为接线简单、时序容错高等逻辑跑顺了再换 FSMC 屏做性能优化。下面两节分别给两条路径的最小配置。2.2 FSMC 并行接口的 CubeMX 配置与地址映射以常见的 16 位 8080 并口屏为例FSMC 配置的核心是把 LCD 的 RS 信号接到某根地址线上这样写不同地址就等于写命令或写数据。常见做法是 RS 接 A0或 A6取决于你选的 bank那么基地址 0 是命令基地址 2 是数据16 位宽度下地址步进为 2。CubeMX 里的操作步骤在 Connectivity 里选 FSMCLCD Interface 选 LCD Interface。Chip Select 选 NE1对应 Bank1Memory type 选 LCD Interface。LCD Register Select 选 A0Data 宽度选 16 位。时序参数先填保守值AddressSetupTime2AddressHoldTime1DataSetupTime5BusTurnAround1ClockDivisionHCLK/1DataLatency 不用管那是同步模式才用。生成代码后HAL 会初始化 FSMC但不会帮你定义读写宏。你需要自己加/* lcd_fsmc.h —— FSMC 地址映射与读写宏 */ #define LCD_BASE ((uint32_t)0x60000000) /* Bank1 NE1 基地址 */ #define LCD_CMD_ADDR (LCD_BASE) /* A00 写命令 */ #define LCD_DATA_ADDR (LCD_BASE 2) /* A01 写数据16位步进2 */ #define LCD_WR_CMD(cmd) (*(__IO uint16_t *)LCD_CMD_ADDR (cmd)) #define LCD_WR_DATA(dat) (*(__IO uint16_t *)LCD_DATA_ADDR (dat)) #define LCD_RD_DATA() (*(__IO uint16_t *)LCD_DATA_ADDR)逻辑说明FSMC 把外部地址映射到 0x60000000 开始的区域NE1 对应 Bank1 的片选。A0 接 RS 后地址最低位决定 RS 电平所以基地址写命令、基地址加 2 写数据。参数说明AddressSetupTime 是地址建立时间屏手册里一般写 tAS单位是 HCLK 周期DataSetupTime 对应写脉冲宽度 tPW太小会丢数据太大只是慢一点所以宁可先大后调小。BusTurnAround 是读写切换的等待周期读 LCD 的 ID 或状态寄存器时如果读回 0xFFFF 或 0x0000先把这个值加到 2 以上试试。2.3 SPI 接口的时钟极性与分频设置SPI 屏的初始化重点在时钟极性和相位。绝大多数 LCD 控制器比如常见的 ILI 系列要求 CPOL0、CPHA0也就是空闲低电平、第一个边沿采样。CubeMX 里 SPI 模式选 Full-Duplex Master 或 Transmit Only Master 都行实际只用 MOSIMISO 可以不接。关键参数参数推荐值说明Clock PolarityLow空闲低匹配多数 LCD 控制器Clock Phase1 Edge第一个边沿采样Prescaler4 或 8先慢后快跑通再提Data Size8 bit命令和参数都是 8 位First BitMSB高位先出初始化代码里SPI 屏通常需要先拉低 CS发命令再发数据最后拉高 CS。如果你用硬件 NSS注意 HAL 的 SPI 传输函数不会自动控制 NSS得手动写 GPIO/* lcd_spi.c —— SPI 写命令与写数据 */ static void LCD_SPI_WriteByte(uint8_t byte) { HAL_SPI_Transmit(hspi1, byte, 1, HAL_MAX_DELAY); } void LCD_WriteCmd(uint8_t cmd) { LCD_DC_Clr(); /* DC0 表示命令 */ LCD_CS_Clr(); LCD_SPI_WriteByte(cmd); LCD_CS_Set(); } void LCD_WriteData(uint8_t dat) { LCD_DC_Set(); /* DC1 表示数据 */ LCD_CS_Clr(); LCD_SPI_WriteByte(dat); LCD_CS_Set(); }逻辑说明DC 引脚区分命令和数据CS 每次传输都拉低再拉高保证时序干净。参数说明Prescaler 先设 8 或 16等屏幕能正常显示后再逐步降到 2 或 4。如果降频后花屏说明你的排线太长或没有加匹配电阻不是 SPI 配置的问题。HAL_MAX_DELAY 在调试阶段可以用量产代码里建议换成具体超时值避免总线卡死时程序挂住。3. 上电初始化序列LCD 控制器寄存器怎么写才不花屏3.1 从复位到 Sleep Out 的标准流程LCD 控制器上电后不能直接写显示数据必须按手册给的初始化序列走一遍。这个序列通常包括硬件复位RES 引脚拉低至少 10ms 再拉高、软件复位写 0x01、Sleep Out写 0x11、设置像素格式写 0x3A、设置显示方向写 0x36、最后开显示写 0x29。每一步之间要留延时尤其是 Sleep Out 之后手册一般要求 120ms 以上。我一般会把这些步骤写成一个初始化数组用查表方式执行方便换屏时只改表不改逻辑/* lcd_init_table.h —— 初始化命令表以 ILI 类控制器为例 */ typedef struct { uint8_t cmd; uint8_t data[16]; uint8_t len; uint16_t delay_ms; } LCD_InitSeq; static const LCD_InitSeq lcd_init_table[] { {0x01, {0}, 0, 120}, /* 软件复位等 120ms */ {0x11, {0}, 0, 120}, /* Sleep Out等 120ms */ {0x3A, {0x55}, 1, 10}, /* 像素格式 16bit/pixel */ {0x36, {0x00}, 1, 10}, /* 显示方向先默认 */ {0x29, {0}, 0, 50}, /* Display ON */ };执行逻辑void LCD_Init(void) { LCD_Reset(); /* 硬件复位 */ for (int i 0; i sizeof(lcd_init_table)/sizeof(lcd_init_table[0]); i) { LCD_WriteCmd(lcd_init_table[i].cmd); for (int j 0; j lcd_init_table[i].len; j) { LCD_WriteData(lcd_init_table[i].data[j]); } if (lcd_init_table[i].delay_ms) { HAL_Delay(lcd_init_table[i].delay_ms); } } }参数说明0x3A 的参数 0x55 表示 RGB565 格式如果你写 0x66 就是 RGB666但多数小屏只支持 16 位。0x36 的参数控制扫描方向和 RGB 顺序常见值有 0x00、0x60、0xA0、0xC0分别对应四个旋转方向如果显示镜像或颜色反了先改这个寄存器。延时不能省尤其是复位和 Sleep Out省了会出现“第一次上电正常、复位后花屏”的玄学问题。3.2 设置显示窗口与写显存的正确姿势初始化完成后要往屏幕上画东西必须先设置显示窗口Column Address Set 和 Page Address Set再写显存。窗口设置命令是 0x2A列地址和 0x2B页地址每个命令后面跟四个参数起始高 8 位、起始低 8 位、结束高 8 位、结束低 8 位。设完窗口后写 0x2C后面连续发的数据就会自动填充窗口区域。/* 设置显示窗口x0,y0 左上角x1,y1 右下角 */ void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCmd(0x2A); LCD_WriteData(x0 8); LCD_WriteData(x0 0xFF); LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y0 8); LCD_WriteData(y0 0xFF); LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteCmd(0x2C); /* 准备写显存 */ } /* 填充纯色用于测试 */ void LCD_Fill(uint16_t color) { LCD_SetWindow(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); for (uint32_t i 0; i LCD_WIDTH * LCD_HEIGHT; i) { LCD_WriteData(color 8); LCD_WriteData(color 0xFF); } }逻辑说明窗口设好后0x2C 之后的每个像素数据会自动按从左到右、从上到下的顺序填充超出窗口会自动换行。参数说明x0、x1 是列地址范围 0 到屏宽减一y0、y1 是页地址范围 0 到屏高减一。如果你的屏是 240x320但显示出来只有一半或者错位检查窗口设置是否超出了实际分辨率有些屏的显存比可视区域大窗口设错会导致偏移。4. 避坑与排查点屏失败时先查这五个地方4.1 现象屏幕全白或全黑背光亮但无图像原因初始化序列没执行完或者复位引脚没接对。有些模块的 RES 引脚内部有上拉悬空也能工作但有些必须外部拉低再拉高。另外如果你用的是 FSMC片选信号 NE1 没有正确映射到 LCD 的 CS写操作根本到不了屏。解决先用万用表量 RES 引脚上电后应该是高电平复位瞬间有一个低脉冲。如果没有检查 GPIO 初始化和复位函数。FSMC 的话用调试器看 0x60000000 地址写数据时CS 引脚有没有拉低没有就是 FSMC 配置里片选没使能。4.2 现象花屏、噪点、颜色错乱原因时序参数太快或者数据线有干扰。FSMC 的 DataSetupTime 设成 1 或 2 的时候长排线或高时钟下很容易出现建立时间不足。SPI 的话Prescaler 太小导致时钟超过屏的手册上限。解决FSMC 先把 DataSetupTime 加到 10 以上AddressSetupTime 加到 5看是否改善。如果改善再逐步往下调找到稳定工作的最小值。SPI 先把 Prescaler 翻倍如果花屏消失说明是时钟太快不是数据错。另外检查排线杜邦线超过 10cm 就容易出问题能焊就焊。4.3 现象显示方向不对或者镜像原因0x36 寄存器的参数不对。这个寄存器的 bit7 控制页地址顺序bit6 控制列地址顺序bit5 控制行刷新方向不同厂商的定义有细微差别。解决把 0x36 的参数在 0x00、0x60、0xA0、0xC0 四个值里轮换试一遍同时配合窗口设置的坐标映射。如果旋转后触摸坐标不对那是触摸屏的事显示和触摸要分开调。我一般会在初始化表里把 0x36 单独拎出来方便快速切换。4.4 现象读 LCD ID 返回 0xFFFF 或 0x0000原因读时序不对或者 RS 地址映射错了。FSMC 读操作需要 BusTurnAround 时间写操作不需要。如果 BusTurnAround 设成 0读回来的数据就是乱的。解决把 BusTurnAround 设成 2 或 3再读。如果还是不对检查 RS 接的是 A0 还是 A6地址偏移要跟着改。有些屏的读时序要求 RD 信号有最小脉冲宽度FSMC 的 DataSetupTime 对读也生效适当加大。4.5 现象换一块同型号屏代码不变却不亮原因不同批次的屏控制器可能换了或者初始化序列有细微差别。尤其是那些没有明确型号的模块卖家给的例程只能参考。解决先读 ID确认控制器型号。读不到 ID 就按最通用的 ILI 序列试再根据显示效果微调。我习惯在代码里留一个 LCD_ID 的读取和打印换屏时先看 ID再决定用哪套初始化表。这个习惯帮我省了很多次“代码没动但屏不亮”的后悔药。5. 进阶技巧用 DMA 和局部刷新把刷屏速度提上去5.1 FSMC DMA 的配置要点FSMC 写显存是 CPU 逐字节搬刷全屏时 CPU 基本被占满。用 DMA 可以把数据从内存搬到 FSMC 的数据地址CPU 腾出来干别的。配置时注意DMA 的外设地址填 LCD_DATA_ADDR内存地址填你的显存缓冲区传输宽度选半字16 位模式选 Normal 或 Circular。每次刷屏前重新设窗口然后启动 DMA。/* 用 DMA 刷全屏color_buf 是预先填好的显存缓冲区 */ void LCD_Fill_DMA(uint16_t *color_buf, uint32_t pixel_count) { LCD_SetWindow(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); HAL_DMA_Start(hdma_memtomem, (uint32_t)color_buf, LCD_DATA_ADDR, pixel_count); /* 等待传输完成实际项目里可以用中断或标志位 */ while (HAL_DMA_PollForTransfer(hdma_memtomem, HAL_DMA_FULL_TRANSFER, 100) ! HAL_OK); }参数说明pixel_count 是像素个数不是字节数DMA 配置里 Data Width 选 Half Word 才能对应 16 位像素。如果传输一半就停检查 DMA 的传输长度寄存器是不是 16 位有些型号最大 65535超过要分多次。另外FSMC 的地址在 DMA 传输期间不能变所以窗口设好后不要再写其他命令。5.2 局部刷新与脏矩形思路全屏刷新再快也有上限实际 UI 里大部分区域是不变的。我一般会维护一个“脏矩形”列表只刷新变化区域。实现上就是每次要更新某个控件时把它的坐标范围记下来合并重叠区域最后只对合并后的矩形设窗口、写数据。这样即使 SPI 屏也能做到流畅的局部更新。验证方法在屏幕上开一个每秒计数的数字用全屏刷新时数字闪烁明显用局部刷新只更新数字区域闪烁基本消失。这个对比能直观看出局部刷新的价值。我自己的习惯是任何 LCD 项目先跑通全屏填充再改成局部刷新最后才上 DMA。顺序反了出了问题很难定位是 DMA 配置错还是窗口设错。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑