STM32 LCD屏幕学习
写于2024/8/25日中午 该文章仅供自己学习参考
1. OLED简介
显示器 | 举例 | 优点 | 缺点 |
---|---|---|---|
断码屏 | 数码管、计算器、遥控器 | 成本低,驱动简单,稳定 | 色彩单一,显示内容少 |
点阵屏 | 户外广告屏 | 任意尺寸,亮度高 | 贵,耗电,体积大 |
LCD屏 | 显示器、电视屏、手机屏 | 成本低,色彩好,薄,寿命长 | 全彩稍差,漏光,拖影 |
OLED屏 | 显示器、电视屏、手机屏 | 自发光,色彩最好,超薄,功耗低 | 比较贵,寿命短 |
1.1 LCD基本组成
- 玻璃基板
- 背光
- 驱动IC
1.2 LCD分类
如下:
- 按信号类型分为 TTL/LVDS/EDP/MIPI 几大类别
- 按材质分类分为(针对 TFT-LCD) TFT-TN/TFT-IPS/TFT-VA。
- 接口类型分为:RGB 模式、SPI 模式、MDDI 模式、VSYNC 模式、DSI 模式、MCU 模式等
分类 | 类别 | 说明 |
---|---|---|
信号类型 | TTL(RGB) | TTL电平信号,属于并行接口,包含RGB信号、时钟和控制信号 |
信号类型 | LVDS | Low-Voltage Differential Signaling,低压差分信号技术 |
信号类型 | EDP | Embedded DisplayPort,适用于平板、笔记本电脑、一体机、未来高分辨率手机,传输速率比LVDS快 |
信号类型 | MIPI | Mobile Industry Processor Interface,移动产业处理接口,差分传输 |
材质 | TFT-TN | 液晶分子排列和通电扭转方式不同,TN采用正性液晶 |
材质 | TFT-IPS | 同等级IPS和VA材质屏的效果要优于TN材质的 |
材质 | TFT-VA | 液晶分子排列和通电扭转方式不同,VA采用负性液晶 |
接口类型 | RGB模式 | 16/18/24位并行接口,一般还包括VSYNC,HSYNC,DOTCLK,CS,RST |
接口类型 | MDDI模式 | 高通2004年提出的,连线 |
接口类型 | VSYNC模式 | 该模式是在MCU模式上加了一个VSYNC信号,在这种模式下,内部的显示操作与外部VSYNC信号同步,可以实现比内部操作更高速率的动画显示 |
接口类型 | DSI模式 | 该模式串行的双向高速命令传输模式,连线有D0P,D0N,D1P,D1N,CLKP,CLKN |
接口类型 | MCU模式 | 英特尔I8080和摩托罗拉M6800两种接口 |
其中
MCU | ≤800*480 | 带SRAM,无需频繁刷新,无需大内存,驱动简单 |
---|---|---|
RGB | ≤1280*800 | 不带SRAM,需要实时刷新,需要大内存,驱动稍微复杂 |
MIPI | 4K | 不带SRAM,支持分辨率高,省电,大部分手机屏用此接口 |
2. 液晶显示控制器(ILI9341)
TFTLCD 模块其驱动芯片有:ILI9341/ST7789/NT35310/NT35510/SSD1963等,我学习的这块LCD采用ILI9341芯片,下面学习也会围绕此芯片进行。
ILI9341 液晶控制器自带显存,可配置支持 8/9/16/18 位的总线中的一种,可以通过 3/4 线串行协议或 8080 并口驱动。正点原子的 TFT-LCD 模块上的电路配置为 8080 并口方式,其显存总大小为 172800(24032018/8),即 18 位模式(26 万色)下的显存量。在 16 位模式下,ILI9341 采用 RGB565 格式存储颜色数据,此时 ILI9341 的 18 位显存与 MCU 的 16 位数据线以及 RGB565 的对应关系如图 25.1.2.1 所示:
从图中可以看出,ILI9341 在 16 位模式下面,18 位显存的 B0 和 B12 并没有用到,对外的数据线使用 DB0-DB15 连接 MCU 的 D0-D15 实现 16 位颜色的传输。MCU 的 16 位数据,最低 5 位代表蓝色,中间 6 位为绿色,最高 5 位为红色。数值越大,表示该颜色越深。另外,特别注意 ILI9341 所有的指令都是 8 位的(高 8 位无效),且参数除了读写 GRAM 的时候是 16 位,其他操作参数,都是 8 位的。
2.1 芯片功能框图
2.2 接口逻辑信号
Pin Name | I/o | 类型 | 描述 |
---|---|---|---|
IM[3:0] | I | (VDDI/VSS) | 设定MCU接口模式,8-bits,9-bits,16bits,18b-bits 的8080-I/8080-II串行MCU接口等 |
RESX | I | MCU(VDDI/VSS) | 低电平有效。将重置设备 |
EXTC | I | MCU(VDDI/VSS) | 扩展命令集有效,LOW无效,HIGH有效。把EXTC连到VDDI才能读写扩展寄存器(RB0hRCFh,RE0hRFFh) |
CSX | I | MCU(VDDI/VSS) | 低电平有效,芯片片选信号。此脚 只能在MPU接口模式下永久固定在“LOW”电平上。并行接口模式中,如果CSX连接VSS,则显示模块不会出现异常可见效果。此外,也不会限制使用并行读写协议,电源开关序列或其他功能。当CSX='1’时,对并行和串行接口没有影响。 |
D/CX(SCL) | I | MCU(VDDI/VSS) | 该引脚用于在并行接口或4线8位串行数据接口中选择“数据或命令”。DCX=1时数据被选择,DCX=0时命令被选择。此引脚在3线9bit或4线8bit串行数据接口中被作为串行接口时钟信号 |
RDX | I | MCU(VDDI/VSS) | 8080-I/8080-II系统(RDX):作为一个读取信号,在其上升沿时MCU读取数据。如果不使用时,把RDX接到VDDI电平上 |
WRX(D/CX) | I | MCU(VDDI/VSS) | 8080-I/8080-II系统(WRX):做为一个写入信号,当在其上升沿时写入数据。4线系统(D/CX):作为命令或参数选择不用时接VDDI电平上 |
D[17:0] | I/o | MCU(VDDI/VSS) | 18bits 双向并行数据总线,用于MCU系统和RGB接口模式 |
SDI/SDA | O | MCU(VDDI/VSS) | 当IM[3]=0时,串口模式下该脚为in/out信号(SDA);当IM[3]=1时,串口模式下该脚为in模式(SDI);该数据适用于SCL信号的上升沿。如果不用时,该脚需要接VDDI或VSS |
SDO | O | MCU(VDDI/VSS) | 串口输出信号。数据是在SCL信号下降沿输出。不例用时,需悬空 |
TE | O | MCU(VDDI/VSS) | 撕裂效果输出引脚用于同步MPU的帧输出,由S/W命令激活。当这个引脚没有激活时,输出低电平。如果没使用,需悬空该引脚 |
DOTCLK | I | MCU(VDDI/VSS) | RGB接口操作的点时钟信号。不使用把该引脚引到VDDI或VSS电平上。 |
VSYNC | I | MCU(VDDI/VSS) | RGB接口操作 帧同步信号。不用时把该引脚连接到VDDI或VSS电平上。 |
HSYNC | I | MCU(VDDI/VSS) | RGB接口操作的线同步信号。不用时把该引脚连接到VDDI或VSS电平上。 |
DE | I | MCU(VDDI/VSS) | RGB接口操作的数据使能信号。不用时把该引脚连接到VDDI或VSS电平上。 |
其中IM[3:0]引脚取值来选择接口模式
2.3 时序逻辑
8080-I/8080-II串并接口,通过D[17:0]数据引脚实现寄存器的存取
以写周期为例,8080 方式下的操作时序如图
TFLCD模块引脚为
通过原理图对比可操控对应的GPIO口输出正确的写时序和读时序
2.4 指令格式
指令(HEX) | 名称 | 作用 |
---|---|---|
0xD3 | 读ID | 用于读取LCD控制器的ID,区分型号用 |
0x36 | 访问控制 | 设置GRAM读写方向,控制显示方向 |
0x2A | 列地址 | 一般用于设置X坐标 |
0x2B | 页地址 | 一般用于设置Y坐标 |
0x2C | 写GRAM | 用于往LCD写GRAM数据 |
0x2E | 读GRAM | 用于读取LCD的GRAM数据 |
2.4.1 读ID(0xD3)
0xD3 指令后面跟了 4 个参数,最后 2 个参数,读出来是 0x93 和 0x41,刚好是我们控制器 ILI9341 的数字部分,从而,通过该指令,即可判别所用的 LCD 驱动器是什么型号,这样,我们的代码,就可以根据控制器的型号去执行对应驱动 IC 的初始化代码,从而兼容不同驱动 IC 的屏,使得一个代码支持多款 LCD。
值得一提的是,该指令发出后读出的第一个参数为假读,是没有意义的,丢弃即可。
2.4.2 访问控制(0x36)
从上表可以看出,0x36 指令后面,紧跟一个参数,这里主要关注:MY、MX、MV 这三个位,通过这三个位的设置,我们可以控制整个 ILI9341 的全部扫描方向,如表 25.1.2.4 所示:
2.4.3 X坐标设置指令(0X2A)
SC:起始坐标
EC:结束坐标
0≤SP≤EP≤319(LCD像素高度)
2.4.4 Y坐标设置指令(0X2B)
SP:起始坐标
EP:结束坐标
0≤SP≤EP≤319(LCD像素高度)
2.4.5 写GRAM指令(0X2C)
由表 25.1.2.7 可知,在收到指令 0X2C 之后,数据有效位宽变为 16 位,我们可以连续写入LCD GRAM 值,而 GRAM 的地址将根据 MY/MX/MV 设置的扫描方向进行自增。例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过 SC,SP 设置)后,每写入一个颜色值,GRAM 地址将会自动自增 1(SC++),如果碰到 EC,则回到 SC,同时 SP++,一直到坐标:EC,EP 结束,期间无需再次设置的坐标,从而大大提高写入速度。
2.4.6 读GRAM指令(0X2E)
该指令用于读取 GRAM,如表 25.1.2.7 所示,ILI9341 在收到该指令后
第一次输出的是dummy 数据,也就是无效的数据
第二次开始,读取到的才是有效的 GRAM 数据(从坐标:SC,SP 开始),输出规律为:每个颜色分量占 8 个位,一次输出 2 个颜色分量。比如:第一次输出是 R1G1,随后的规律为:B1R2→G2B2→R3G3→B3R4→G4B4→R5G5…以此类推。
如果我们只需要读取一个点的颜色值,那么只需要接收到参数 3 即可,如果要连续读取(利用 GRAM地址自增,方法同上),那么就按照上述规律去接收颜色数据
2.4.7 代码实例
uint16_t lcd_rd_data(void)
{
uint16_t ram; /* 定义变量 */
DATA_IN_MODE(); /* 设置数据输入 */
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_RD(0); /* RD低电平 */
ram = LCD_DATA_IN; /* 读取数据 */
LCD_RD(1); /* RD高电平 */
LCD_CS(1); /* 释放片选 */
DATA_OUT_MODE(); /* 设置数据输出 */
return ram; /* 返回读数 */
}
uint16_t lcd_read_point (uint16_t x, uint16_t y)
{
uint16_t r = 0, g = 0, b = 0; /* 定义变量 */
lcd_set_cursor(x, y); /* 设置坐标 */
lcd_wr_regno(0X2E); /* 发读点命令 */
r = lcd_rd_data(); /* 假读 */
r = lcd_rd_data(); /* 读rg */
b = lcd_rd_data(); /* 读b */
g = r & 0XFF; /* 得到g值 */
return (((r >> 11) << 11) | ((g >> 2) << 5) | (b >> 11));
3. 使用FSMC控制LCD
3.1 FSMC简介
ILI9341 的 8080 通讯接口时序可以由 STM32 使用 GPIO 接口进行模拟,但这样效率太低,STM32 提供了一种更高效的控制方法——使用 FSMC 接口实现 8080 时序,但 FSMC 是 STM32片上外设的一种,并非所有的 STM32 都拥有这种硬件接口,使用何种方式驱动需要在芯片选型时就确定好。我们的开发板支持 FSMC 接口,下面我们来了解一下这个接口的功能。
FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和 16 位 PC 存储器卡连接,FSMC 接口可以通过地址信号,快速地找到存储器对应存储块上的数据。STM32F1 的 FSMC 接口支持包括 SRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存储器。F1 系列的大容量型号,且引脚数目在 100 脚及以上的 STM32F103 芯片都带有 FSMC 接口,正点原子战舰STM32F103 的主芯片为 STM32F103ZET6,是带有 FSMC 接口的。
3.2 程序框图
3.3 FMC通信引脚介绍
我们可以看出,STM32 的 FSMC 可以驱动 NOR/PSRAM、NAND、PC 卡这 3类设备,他们具有不同的 CS 以区分不同的设备。本部分我们要用到的是 NOR/PSRAM 的功能。
①为 FSMC 的总线和时钟源
②为 STM32 内部的 FSMC 控制单元
③是连接硬件的引脚
④是 NOR/PSRAM 会使用到的信号控制线,
这里的“公共信号”表示不论我们驱动前面提到的 3 种设备中的哪种,这些 IO 是共享的,所以如果需要用到多种功能的情况,程序上还要考虑分时复用。③和④这些信号比较重要,它们的功能如表
3.4 FMC地址映射
STM32F1 的 FSMC 将外部存储器划分为固定大小为 256M 字节的四个存储块,FSMC 的外部设备地址映像如图 25.1.3.2 所示:
从上图可以看出,FSMC 总共管理 1GB 空间,拥有 4 个存储块(Bank),FSMC 各 Bank 配置寄存器如表 25.1.3.2:
本章,我们用到的是块 1,所以在本章我们仅讨论块 1 的相关配置
STM32F1 的 FSMC 存储块 1(Bank1)被分为 4 个区,每个区管理 64M 字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1 的 256M 字节空间可以通过 28 根地址线(HADDR[27:0])寻址后访问。这里 HADDR 是内部 AHB 地址总线,其中 HADDR[25:0]来自外部存储器地址 FSMC_A[25:0],而 HADDR[26:27]对 4 个区进行寻址。如表 25.1.3.3 所示:
3.5 HADDR与FMC_A关系
FSMC不同位宽操作
HADDR总线是转换到外部存储器的内部AHB地址线,简单来说,从CPU通过AHB总线到外部信号线之间的关系。
HADDR是字节地址,而存储器访问不都是按字节访问,接到存储器的地址线与其数据宽度相关。
这里要注意的是FSMC中每一个地址,对应的是存储器中的一个字节
假设我们用BANK1的区1,那么0x6000 0000-0x63ff ffff (共64M个字节byte地址) 对应的就是存储器的地址就是64Mx8=512M地址
也就是FSMC的64M个bit地址映射着存储器64M个字节byte地址
那也就是默认情况下存储器地址数据为8位,可以正常读取,但是如果存储器地址数据为16位,32位,就是两个字节一个地址,四个字节一个地址,那么控制16位,32位宽度的存储设备,且不支持单字节访问就比较麻烦了。比如16位宽度的NOR Flash,写入仅支持16位或者32位(通过写入两次16位),写入8位数据是不支持的 。
这样的话,我们本来对应64M的字节byte地址,就变成了32M的双字节byte地址,也就是最后一位无效了,因为地址一次要加2
这个时候,为了方便操作,FMC在硬件设计上就提供了一种解决办法,将内部数据总线ADDR[25:0]措位后接到FMC_A地址引脚上,“丢弃”地址的bit-0,从bit-1开始对地址计数(相当于只计算偶数地址,总共有32M个地址)
FSMC在实际输出地址时,将地址的值右移一位(相当于除以2,变成了偶数地址),输出到实际的地址线上。
当 Bank1 接的是 16 位宽度存储器的时候:HADDR[25:1]→FSMC_A[24:0]。
当 Bank1 接的是 8 位宽度存储器的时候:HADDR[25:0] →FSMC_A[25:0]。
不论外部接8位/16 位宽设备,FSMC_A[0]永远接在外部设备地址A[0]。
这里,TFTLCD使用的是 16 位数据宽度,所以 HADDR[0]并没有用到,只有 HADDR[25:1]是有效的,对应关系变为:HADDR[25:1]→FSMC_A[24:0],相当于右移了一位。具体来说,比如地址:0x7E,对应二进制是:01111110,此时 FSMC_A6 是 0 而不是 1,因为要右移一位,这里请特别注意。
另外,HADDR[27:26]的设置,是不需要我们干预的,比如:当你选择使用 Bank1 的第三个区,即使用 FSMC_NE3 来连接外部设备的时候,即对应了 HADDR[27:26]=10,我们要做的就是配置对应第 3 区的寄存器组,来适应外部设备即可。对于 NOR FLASH 控制器,主要是通过FSMC_BCRx、FSMC_BTRx 和 FSMC_BWTRx 寄存器设置(其中 x=1~4,对应 4 个区)。通过这 3 个寄存器,可以设置 FSMC 访问外部存储器的时序参数,拓宽了可选用的外部存储器的速度范围。
3.6 FMC时序介绍
FMC是Flexible灵活的,可以产生多种时序来控制外部存储器**。FSMC 外设挂载在 AHB 总线上,时钟信号来自于 HCLK(默认 72MHz),控制器的同步时钟输出就是由它分频得到。**我们控制LCD主要应用FMC的异步时序
3.6.1时序参数设置
- 地址建立(ADDSET),地址保存(ADDHLD),数据建立(DATAST) 这三个设置是异步存储器专属的
- 总线周转是每次操作的间隔时间
- **时钟分频比(CLKDIV)和数据延迟(DATLAT)**是同步存储器的
3.6.1 同步时序
FSMC 的 NORFLASH 控制器支持同步和异步突发两种访问方式。选用同步突发访问方式时,FSMC 将 HCLK(系统时钟)分频后,发送给外部存储器作为同步时钟信号 FSMC_CLK。
此时需要的设置的时间参数有 2 个:
-
HCLK与FSMC_CLK的分频系数(CLKDIV),可以为2~16分频;
-
同步突发访问中获得第1个数据所需要的等待延迟(DATLAT)。
3.6.2 异步时序
对于异步突发访问方式,FSMC 主要设置 3 个时间参数:
-
地址建立时间(ADDSET)
-
数据建立时间(DATAST)
-
地址保持时间(ADDHLD)。
-
FSMC 综合了 SRAM/ROM、PSRAM 和 NOR Flash 产品的信号特点,定义了 4 种不同的异步时序模型。选用不同的时序模型时,需要设置不同的时序参数,如表 25.1.3.4 所列:
在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/写周期参数指标之间的计算关系;利用该计算关系和存储芯片数据手册中给定的参数指标,可计算出 FSMC 所需要的各时间参数,从而对时间参数寄存器进行合理的配置。
模式A支持独立的读写时序控制。这个对我们驱动TFTLCD来说非常有用,因为TFTLCD在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,那么只能以读的时序为基准,从而导致写的速度变慢,或者在读数据的时候,重新配置FSMC的延时,在读操作完成的时候,再配置回写的时序,这样虽然也不会降低写的速度,但是频繁配置,比较麻烦。而如果有独立的读写时序控制,那么我们只要初始化的时候配置好,之后就不用再配置,既可以满足速度要求,又不需要频繁改配置。
模式A与模式1的区别是NOE的变化和相互独立的读写时序
写时序类似,区别是它的一个存储器操作周期仅由地址建立周期(ADDSET)和数据建立周期(DATAST)组成,且在数据建立周期期间写使能信号线发出写信号,接着 FSMC 把数据通过数据线传输到存储器中。
图 25.1.3.3 和图 25.1.3.4 中的 ADDSET 与 DATAST,是通过不同的寄存器设置的。
以读时序为例,该图表示一个存储器读操作周期由地址建立周期(ADDSET)、**数据建立周期(DATAST)**以及 2 个 HCLK 周期组成。
在地址建立周期中,地址线发出要访问的地址,数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片;
地址建立周期结束后读使能信号线发出读使能信号,接着存储器通过数据信号线把目标数据传输给 FSMC,FSMC 把它交给内核。
3.7 FSMC寄存器介绍
寄存器 | 名称 | 作用 |
---|---|---|
FMC_BCRx(x=1~4) | 片选控制寄存器 | 包含存储器块的信息(存储器类型/数据宽度等) |
FMC_BTR1x(x=1~4) | 片选时序寄存器 | 设置读操作时序参数(ADDSET/DATAST) |
FMC_BWTR1x(x=1~4) | 写时序寄存器 | 设置写操作时序参数(ADDSET/DATAST) |
3.7.1 FMC_BCRx 片选控制寄存器
-
EXTMOD:扩展模式使能位,也就是是否允许读写不同的时序,很明显,我们本章需要读写不同的时序,故该位需要设置为 1。
-
WREN:写使能位。我们需要向 TFTLCD 写数据,故该位必须设置为 1。
-
MWID[1:0]:存储器数据总线宽度。我们的 TFTLCD 是 16 位数据线,所以设置该值为 01。
-
MTYP[1:0]:存储器类型。前面提到,我们把 TFTLCD 当成 SRAM 用,所以需要设置该值为 00。
-
MBKEN:存储块使能位。这个容易理解,我们需要用到该存储块控制 TFTLCD,当然要使能这个存储块了。
3.7.2 FSMC_BTRx 片选时序寄存器
这个寄存器包含了每个存储器块的控制信息,可以用于SRAM、ROM和NOR闪存存储器。
如果FSMC_BCRx寄存器中设置了EXTMOD位,则有两个时序寄存器分别对应读(本寄存器)和写操作(FSMC_BWTRx寄存器)。因为我们要求读写分开时序控制,所以EXTMOD是使能了的,也就是本寄存器是读操作时序寄存器,控制读操作的相关时序。本章我们要用到的设置有:ACCMOD、DATAST和ADDSET这三个设置。
- ACCMOD[1:0]:访问模式。本章我们用到模式A,故设置为00。
- DATAST[7:0]:数据保持时间。
- ADDSET[3:0]:地址建立时间。
DATAST数据保持时间。对 ILI9341 来说,其实就是 RD 低电平持续时间,最小为355ns。而一个 HCLK 时钟周期为 13.9ns 左右(1/72Mhz),为了兼容其他屏,我们这里设置DATAST 为 15,也就是 16 个 HCLK 周期,时间大约是 222ns(未计算数据存储的 2 个 HCLK时间,对 9341 来说超频了,但是实际上是可以正常使用的)
ADDSET[3:0]:地址建立时间。其建立时间为:ADDSET 个 HCLK 周期,最大为 15 个 HCLK周期。对 ILI9341 来说,这里相当于 RD 高电平持续时间,为 90ns,本来这里我们应该设置和DATAST 一样,但是由于 STM32F103 FSMC 的性能问题,就算设置 ADDSET 为 0,RD 的高电平持续时间也超过 90ns。所有,我们这里可以设置 ADDSET 为较小的值,本章我们设置 ADDSET为 1,即 2 个 HCLK 周期。
3.7.3 FSMC_BWTRx写时序寄存器
该寄存器在本章用作写操作时序控制寄存器,需要用到的设置同样是:ACCMOD、DATAST和 ADDSET 这三个设置。这三个设置的方法同 FSMC_BTRx 一模一样,只是这里对应的是写操作的时序
3.8 FMC寄存器组合说明
在 MDK 的寄存器定义里面,并没有定义 FSMC_BCRx、FSMC_BTRx、FSMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。
FSMC_BCRx 和 FSMC_BTRx,组合成 BTCR[8]寄存器组,他们的对应关系如下:
-
BTCR[0]对应 FSMC_BCR1,
-
BTCR[1]对应 FSMC_BTR1
-
BTCR[2]对应 FSMC_BCR2
-
BTCR[3]对应 FSMC_BTR2
-
BTCR[4]对应 FSMC_BCR3
-
BTCR[5]对应 FSMC_BTR3
-
BTCR[6]对应 FSMC_BCR4
-
BTCR[7]对应 FSMC_BTR4
FSMC_BWTRx 则组合成 BWTR[7],他们的对应关系如下:
-
BWTR[0]对应 FSMC_BWTR1,
-
BWTR[2]对应 FSMC_BWTR2,
-
BWTR[4]对应 FSMC_BWTR3,
-
BWTR[6]对应 FSMC_BWTR4,
-
BWTR[1]、BWTR[3]和 BWTR[5]保留,没有用到。
3.6 流程总结
当 FSMC 外设被配置成正常工作,并且外部接了 NOR FLASH 时,若向 0x60000000 地址写入数据如 0xABCD,FSMC 会自动在各信号线上产生相应的电平信号,写入数据。FSMC 会控制片选信号 NE1 输出低电平选择相应的 NOR 芯片,然后使用地址线 A[25:0]输出0x60000000,在 NWE 写使能信号线上发出低电平的写使能信号,而要写入的数据信号0xABCD 则从数据线 D[15:0]输出,然后数据就被保存到 NOR FLASH 中了。
4. 代码实例
4.1 FSMC初始化顺序
这里我们先说下FSMC的总体初始化顺序:
- FSMC硬件层初始化:使能 FSMC 和 GPIO 时钟,初始化 IO 口配置,设置映射关系
- 设置FSMC接口句柄初始化
- 设置FSMC读时序/写时序初始化
在STM32技术手册中,针对FSMC引脚的GPIO模式配置,已经进行了说明,具体FSMC总线配置方法如下。下表指的是整个FSMC接口的引脚初始化配置说明,应该根据实际初始化相关的引脚
FSMC 接口支持多种存储器,包括 SDRAM,NOR,NAND 和 PC CARD等。HAL 库为每种支持的存储器类型都定义了一个独立的 HAL 库文件,并且在文件中定义了独立的初始化函数,这里我们就列出几种存储器的初始化函数:
HAL_SRAM_Init();//SRAM 初始化函数,省略入口参数
HAL_SDRAM_Init();//SDRAM 初始化函数,省略入口参数
HAL_NOR_Init();//NOR 初始化函数,省略入口参数
HAL_NAND_Init();//NAND 初始化函数,省略入口参数
我们以异步SRAM,hal库来说下具体的初始化流程
4.2 FSMC硬件层初始化
STM32的管脚排列很没有规律,而且分布在多个不同端口上,初始化要十分小心.需要用到的引脚都要先初始化成”复用功能推挽输出”模式.(GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP )并且开启时钟 (RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); )使能 FSMC 和 GPIO 时钟,初始化 IO 口配置,设置映射关系
void FSMC_SRAM_MspInit(void)
{
GPIO_InitTypeDef GPIO_Init_Structure;
/* Enable FMC clock */
__HAL_RCC_FSMC_CLK_ENABLE();
/* Enable GPIOs clock */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/* Common GPIO configuration */
GPIO_Init_Structure.Mode = GPIO_MODE_AF_PP;
GPIO_Init_Structure.Pull = GPIO_PULLUP;
GPIO_Init_Structure.Speed = GPIO_SPEED_FREQ_HIGH;
/* 地址线:F0---F5,F12---F15,G0---G5,D11---D13;
数据信号线;D0,D1,D8---D10,D14,D15,E7---E15;
CS片选:G10;
WE写使能:D5;
OE读使能;D4;
UB数据掩码;E1;
LB数据掩码;E0;
*/
/* GPIOD configuration */
GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 |\
GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 |\
GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOD, &GPIO_Init_Structure);
/* GPIOE configuration */
GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 |\
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 |\
GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &GPIO_Init_Structure);
/* GPIOF configuration */
GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2| GPIO_PIN_3 | GPIO_PIN_4 |\
GPIO_PIN_5 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOF, &GPIO_Init_Structure);
/* GPIOG configuration */
GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2| GPIO_PIN_3 | GPIO_PIN_4 |\
GPIO_PIN_5 | GPIO_PIN_10;
HAL_GPIO_Init(GPIOG, &GPIO_Init_Structure);
}
在完成硬件的MSP初始化之后,就要初始化SRAM了,使用的函数为 HAL_SRAM_Init()
HAL_SRAM_Init(SRAM_HandleTypeDef *hsram,
FSMC_NORSRAM_TimingTypeDef *Timing,
FSMC_NORSRAM_TimingTypeDef *ExtTiming;
该函数有三个入口参数,
- 第一个参数 hsram,它是SRAM_HandleTypeDef 结构体指针类型 设置SRAM的基本参数
- 第二个参数 Timing 设置NORSRAM存储器的读时序
- 第三个参数 ExtTiming 设置NORSRAM存储器的写时序
4.3 FMC 接口句柄初始化
我们使用的结构体为:SRAM_HandleTypeDef
结构体 SRAM_HandleTypeDef 定义如下:
typedef struct
{
FMC_NORSRAM_TypeDef *Instance; //寄存器基地址
FMC_NORSRAM_EXTENDED_TypeDef *Extended; //扩展模式寄存器基地址
FMC_NORSRAM_InitTypeDef Init;
HAL_LockTypeDef Lock;
__IO HAL_SRAM_StateTypeDef State;
DMA_HandleTypeDef *hdma;
}SRAM_HandleTypeDef;
- Instance: 寄存器基地址,如果读写时序一样则配置基地址就行
- Extended 扩展模式寄存器基地址 也就是读写时序不同的时候, 指定写操作时序寄存器地址
- FMC_NORSRAM_InitTypeDef Init 是 FSMC_NORSRAM_InitTypeDef 结构体指针类型,是真正用来设置 SRAM 控制接口参数的
- 成员变量 Lock 和 State 是 HAL 库处理状态标识变量
- 成员变量 hdma 在使用 DMA 时候才使用
也就是我们只需要关心前三个参数,最主要的是第三个Init ,我们来看下:
/**
* @brief FSMC NOR/SRAM Init structure definition
*/
typedef struct
{
uint32_t NSBank; /*设置要控制的 Bank 区域 */
uint32_t DataAddressMux; /*设置地址总线与数据总线是否复用 */
uint32_t MemoryType; /*设置存储器的类型 */
uint32_t MemoryDataWidth; /*设置存储器的数据宽度*/
uint32_t BurstAccessMode; /*设置是否支持突发访问模式,只支持同步类型的存储器 */
uint32_t WaitSignalPolarity; /*设置等待信号的极性*/
uint32_t WrapMode; /*设置是否支持对齐的突发模式 */
uint32_t WaitSignalActive; /*配置等待信号在等待前有效还是等待期间有效 */
uint32_t WriteOperation; /*设置是否写使能 */
uint32_t WaitSignal; /*设置是否使能等待状态插入 */
uint32_t ExtendedMode; /*设置是否使能扩展模式 */
uint32_t WriteBurst; /*设置是否使能写突发操作*/
uint32_t AsynchronousWait; /*设置是否使能等待信号*/
uint32_t PageSize; /*指定页的大小*/
} FSMC_NORSRAM_InitTypeDef;
NSBank 用来指定使用到的存储块区号,
DataAddressMux 用来设置是否使能地址/数据复用,该变量仅对 NOR/PSRAM 有效
MemoryType 用来设置存储器类型
MemoryDataWidth用来设置存储器数据总线宽度,可选 8 位还是 16 位,
WriteOperation 用来设置存储器写使能,也就是是否允许写入。毫无疑问我们会进行存储器写操作
ExtendedMode 用来设置是否使能扩展模式,也就是是否允许读写使用不同时序
ContinuousClock 用来设置启用/禁止 FSMC 时钟输出到外部存储设备 ,
其他参数 WriteBurst,BurstAccessMode,WaitSignalPolarity,WrapMode,WaitSignalActive,WaitSignal,AsynchronousWait 等是用在突发访问和同步时序情况下,这里我们不做过多讲解。
4.4 设置FSMC读时序/写时序初始化
2个参数Timing和ExtTiming,它们都是FSMC_NORSRAM_TimingTypeDef结构体指针类型,分别用来设置 FMC 接口读和写时序,主要涉及地址建立保持时间,数据建立时间等等配置,
如果是异步存储器,读写时序不一样,读写速度要求不一样,参数Timing 和 ExtTiming 需要设置了不同的值。
如果是同步存储器,读写时序设置一致就可
FMC_NORSRAM_TimingTypeDef 结构体定义如下:
typedef struct
{
uint32_t AddressSetupTime; //地址建立时间
uint32_t AddressHoldTime; //地址保持时间
uint32_t DataSetupTime; //数据建立时间
uint32_t BusTurnAroundDuration; //总线恢复时间
uint32_t CLKDivision; // 时钟分频因子
uint32_t DataLatency; //同步突发 NOR FLASH 的数据延迟
uint32_t AccessMode; //异步模式配置
}FSMC_NORSRAM_TimingTypeDef;
4.5 最终初始化代码
/**
* @brief Initializes the SRAM device.
* @retval SRAM status
*/
void FSMC_SRAM_Init(void)
{
//结构体句柄初始化
SRAM_HandleTypeDef SRAM_Handler;
FSMC_NORSRAM_TimingTypeDef FSMC_ReadWriteTim;
FSMC_NORSRAM_TimingTypeDef FSMC_WriteTim;
//FSMC初始化寄存器
SRAM_Handler.Instance = FSMC_NORSRAM_DEVICE;
SRAM_Handler.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
SRAM_Handler.Init.NSBank=FSMC_NORSRAM_BANK4; //使用NE4
SRAM_Handler.Init.DataAddressMux=FSMC_DATA_ADDRESS_MUX_DISABLE; //地址/数据线不复用
SRAM_Handler.Init.MemoryType=FSMC_MEMORY_TYPE_SRAM; //SRAM
SRAM_Handler.Init.MemoryDataWidth=FSMC_NORSRAM_MEM_BUS_WIDTH_16; //16位数据宽度
SRAM_Handler.Init.BurstAccessMode=FSMC_BURST_ACCESS_MODE_DISABLE; //是否使能突发访问,仅对同步突发存储器有效,此处未用到
SRAM_Handler.Init.WaitSignalPolarity=FSMC_WAIT_SIGNAL_POLARITY_LOW;//等待信号的极性,仅在突发模式访问下有用
SRAM_Handler.Init.WaitSignalActive=FSMC_WAIT_TIMING_BEFORE_WS; //存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT
SRAM_Handler.Init.WriteOperation=FSMC_WRITE_OPERATION_ENABLE; //存储器写使能
SRAM_Handler.Init.WaitSignal=FSMC_WAIT_SIGNAL_DISABLE; //等待使能位,此处未用到
SRAM_Handler.Init.ExtendedMode=FSMC_EXTENDED_MODE_DISABLE; //读写使用相同的时序
SRAM_Handler.Init.AsynchronousWait=FSMC_ASYNCHRONOUS_WAIT_DISABLE; //是否使能同步传输模式下的等待信号,此处未用到
SRAM_Handler.Init.WriteBurst=FSMC_WRITE_BURST_DISABLE; //禁止突发写
//FSMC读时序控制寄存器
FSMC_ReadWriteTim.AddressSetupTime=0x06; //地址建立时间(ADDSET)为7个HCLK 13.8ns*7=96.6ns
FSMC_ReadWriteTim.AddressHoldTime=0;
FSMC_ReadWriteTim.DataSetupTime=26; //数据保存时间为27个HCLK =13.8*27=372.6ns
FSMC_ReadWriteTim.AccessMode=FSMC_ACCESS_MODE_A;//模式A
//FSMC写时序控制寄存器
FSMC_WriteTim.BusTurnAroundDuration=0; //总线周转阶段持续时间为0,此变量不赋值的话会莫名其妙的自动修改为4。导致程序运行正常
FSMC_WriteTim.AddressSetupTime=3; //地址建立时间(ADDSET)为4个HCLK =55.2ns
FSMC_WriteTim.AddressHoldTime=0;
FSMC_WriteTim.DataSetupTime=0x06; //数据保存时间为13.8ns*7个HCLK=96.6ns
FSMC_WriteTim.AccessMode=FSMC_ACCESS_MODE_A; //模式A
/* SRAM controller initialization */
FSMC_SRAM_MspInit();
HAL_SRAM_Init(& SRAM_Handler, &FSMC_ReadWriteTim, &FSMC_WriteTim);
}