STM32F429单片机FMC接口驱动TFT LCD和SDRAM

1、FMC接口介绍

FMC 接口(即可变存储控制器)是一种用于管理外部存储器的外设接口,支持多种类型的存储器,主要分为三大类:NOR/SRAM/PSRAM设备(TFTLCD相当于SRAM)、NOR FLASH/NAND FLASH/PC卡设备和SDRAM 设备。他们共用地址数据总线等信号,他们具有不同的CS以区分不同的设备。FMC 的框图如下图所示,从图中可以看到FMC接口中那些信号接口是所有设备共享,哪些信号接口是某些类型设备独享。

FMC接口一共管理1.5GB空间,拥有6个存储块(Bank),每个存储块256MB空间,不同类型的设备占用不同的Bank,它们的地址映射如下表。

FMC地址映射表

Bank号

大小

地址映射

设备类型

Bank1

4*64MB

0X6000 0000 - 0X6FFF FFFF

NOR/SRAM/PSRAM设备

Bank2

4*64MB

0X7000 0000 - 0X7FFF FFFF

NOR FLASH设备/

NAND FLASH设备

Bank3

4*64MB

0X8000 0000 - 0X8FFF FFFF

Bank4

4*64MB

0X9000 0000 - 0X9FFF FFFF

PC卡设备

Bank5

4*64MB

0XC000 0000 - 0XCFFF FFFF

SDRAM设备

Bank6

4*64MB

0XD000 0000 - 0XDFFF FFFF

NOR/SRAM/PSRAM设备占用的是FMC接口的Bank1,它们内部再次被分割成了4个区,每个区64M的空间,通过FMC_NE1、FMC_NE2、FMC_NE3、FMC_NE4这4个脚来确定使用的是哪个区。

SDRAM设备占用的是Bank5和Bank6,通过(SDNE0、SDCKE0)和(SDNE1、SDCKCKE1)两组引脚来确定使用的是哪个Bank。

NAND FLASH设备占用的是Bank2和Bank3,通过NCE2和NCE3两个脚来确定使用的是哪个Bank。

2、使用FMC接口驱动TFT LCD

2.1、FCM接口驱动TFT LCD原理

使用FMC解接口驱动TFT LCD时实际是把LCD当成SRAM设备使用。

SRAM设备的控制有以下接口:地址线( A0~A25最多)、数据线(如 D0~D31最多)、写信号(WE)、读信号(OE)、片选信号(CS),如果 SRAM支持字节控制,那么还有 UB/LB信号。

TFT LCD设备的控制有以下接口:RS(命令/数据标志:0,读写命令;1,读写数据)、D0~D15(16 位双向数据线)、WR(向 TFTLCD 写入数据标志)、RD(从 TFTLCD 读取数据标志)、CS(片选)、RST(硬件复位) 和 BL(背光控制)。

通过SRAM设备和TFT LCD设备控制接口对比可以发现,两者控制方式完全类似,唯一不同的是TFT LCD 有 RS 信号,但是没有地址信号。TFT LCD 通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如我们把RS接在A0上面,那么当FMC控制器写地址0的时候会使得A0变为0,对TFT LCD来说就是写命令。而FMC 写地址1的时候,A0将会变为1,对TFT LCD 来说就是写数据了。这样就把数据和命令区分开了。他们其实就是对应 SRAM 操作的两个连续地址,当然 RS 也可以接在其他地址线上,正点原子的开发板把 RS 连接在 A18 上面的。

2.2、SRAM设备的寻址和FMC访问SRAM设备时的地址偏移

SRAM设备占用的是FMC接口的Bank1,它内部再次被分割成了4个区,每个区64M的空间,通过单片机内部的AHB地址总线HADDR[27:0]进行寻址,其中AHB地址总线HADDR[25:0]对应FMC的地址线 A0~A25,而HADDR[27:26]则由片选信号决定,从FMC_NE1到FMC_NE4对应的HADDR[27:26]分别是:00 01 10 11。

由于单片机内部的AHB地址总线HADDR[27:0]是按字节寻址,而FMC外接的存储器按字寻址,所以根据存储器数据宽度的不同,单片机通过FMC访问外部存储器时发送的地址也不同,地址的偏移情况与存储器数据宽度的关系如下:

因此,FMC 内部 HADDR 与存储器寻址地址的实际对应关系就是:

当接的是 32 位宽度存储器的时候:HADDR[25:2]→ FMC_A [23:0]。

当接的是 16 位宽度存储器的时候:HADDR[25:1]→ FMC_A [24:0]。

当接的是 8 位宽度存储器的时候:HADDR[25:0]→ FMC_A [25:0]。

注意:这里的偏移只是FMC访问外部存储器时的地址偏移(软件上的偏移),但硬件连接时FMC的地址线A0到A25还是要和存储器的A0到A25对应连接。

正点原子的TFT LCD都是16位的数据宽度,所以单片机内部的AHB地址总线的HADDR[0]没有用到,只有HADDR[25:1]是有效的,对应关系变为:HADDR[25:1]→ FMC_A[24:0],相当于右移了一位。

2.3、FMC给LCD发送数据时的地址计算

在了解了SRAM设备的寻址方式和FMC访问SRAM设备时的地址偏移以后接下来就是要知道FMC给LCD发送数据时的地址计算了。

首先是确定SRAM的基地址。当我们决定了接哪一个片选信号时,那就决定了用的是Bank1的哪一个区,也就决定了SRAM的基地址。每个区的基地址都不一样,计算公式为[ 0x6000 0000 + (X-1)*0x400 0000](X表示哪一个区)。

接下来就是根据RS脚确定给LCD发送命令/数据时的地址了,RS=0是命令;RS=1是数据。正点原子F429开发板中RS接的是A18,所以当A18脚为0时发送的是命令,为1时发送的是数据。由于FMC是并行通信,所以当地址线为0时表示发送的是命令,当地址线为0x80000时表示发送的是数据。计算公式为:数据地址=[2^(y+1)](y表示RS脚接的地址线的编号,比如此时接的A18,则数据地址等于0x80000)。

经过上述计算得到FMC给LCD发送数据的地址:0x6008 0000,即&LCD_RAM = 0x6008 0000,而FMC给LCD时的地址只要不等于给LCD发命令的地址就行。为了方便,通常会使用一个 LCD 地址结构体进行管理:

typedef struct

{

    volatile  uint16_t  LCD_REG;

    volatile  uint16_t  LCD_RAM;

} LCD_TypeDef;

LCD_TypeDef  LCD={  NULL };

通过上述计算的得知,LCD结构体中LCD_RAM的地址为0x6008 0000,所以LCD的首地址就是(0x6008 0000 - 2 = 0x6007 FFFE),所以定义时LCD结构体的地址就需要手动指定,上述语句需要写成:LCD_TypeDef LCD __attribute__((at(0x6007 FFFE))) = { NULL };

除了这种方法外,正点原子还提供了一种结构体指针的管理方式:

#define LCD  ((LCD_TypeDef *) 0x6007 FFFE);

声明LCD是一个LCD_TypeDef类型的指针,并且指向0x6007 FFFE这个地址,所以结构体指针中的变量LCD->LCD_RAM的地址就是0x6008 0000,一样的效果。

2.4、NOR/SRAM/PSRAM设备的时序

NOR/SRAM/PSRAM设备主要是通过FMC_BCRx、FMC_BTRx和 FMC_BWTRx 寄存器设置访问外部存储器的时序参数(其中 x=1~4,对应 4 个区)。对于该类型的设备,FMC

控制器支持同步和异步突发两种访问方式。选用同步突发访问方式时,FMC 将 HCLK(系统时钟)分频后,发送给外部存储器作为同步时钟信号 FMC_CLK。此时需要的设置的时间参数有 2 个:

 1,HCLK 与 FMC_CLK 的分频系数(CLKDIV),可以为 2~16 分频;

 2,同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。

对于异步突发访问方式,FMC 主要设置 3 个时间参数:地址建立时间(ADDSET)、数据建立时间(DATAST)和地址保持时间(ADDHLD)。FMC综合了 SRAM、PSRAM和 NOR Flash产品的信号特点,定义了 4 种不同的异步时序模型,选用不同的时序模型时,需要设置不同的时序参数,如下表:

在驱动TFT LCD时我们采用异步模式 A(ModeA)时序,这种时序支持独立的读写时序控制,这个特点在驱动 TFTLCD时非常有用。因为TFTLCD在读的时候一般比较慢,而在写的时候可以比较快。如果读写用一样的时序,那么只能以读的时序为基准,从而导致写的速度变慢;或者在读数据的时候,重新配置 FMC 的延时,在读操作完成的时候,再配置回写的时序,这样虽然也不会降低写的速度,但是频繁配置,比较麻烦。而如果有独立的读写时序控制,那么我们只要初始化的时候配置好,之后就不用再配置,既可以满足速度要求,又不需要频繁改配置。

2.5、NOR/SRAM/PSRAM设备的FMC寄存器

2.5.1 NOR/PSRAM 片选控制寄存器 1/2/3/4(FMC_BCR1/2/3/4)

上面说过SRAM类型设备占用的是FMC的BANK1区域,但此区域内部又被分成了4个bank,每一个bank都有一个片选控制寄存器FMC_BCR,描述如下图:

该寄存器我在驱动TFTLCD时用到的设置有:EXTMOD、WREN、MWID、MTYP和 MBKEN,接下来将逐个介绍。

EXTMOD:扩展模式使能位,也就是是否允许读写不同的时序。驱动TFTLCD时读写采用不同的时序,故该位需要设置为 1。

WREN:写使能位。我们需要向 TFTLCD 写数据,故该位必须设置为 1。

MWID[1:0]:存储器数据总线宽度。00表示8位数据模式;01表示16位数据模式;10

表示 32 位数据模式;11 保留。此次使用的TFT LCD是16位数据,所以此位是01.

MTYP[1:0]:存储器类型。00表示SRAM;01表示PSRAM;10表示NOR FLASH / OneNAND FLASH; 11 保留。在驱动TFT LCD时是当成 SRAM 用,所以需要设置 MTYP[1:0]=00。

MBKEN:存储块使能位。我们需要用到该存储块控制 TFTLCD,所以要使能该存储块。

2.5.2 SRAM/NOR-Flash 闪存片选时序寄存器 1/2/3/4 (FMC_BTR1/2/3/4)

该寄存器用于设置每个存储器块的控制信息。在开启EXTMOD时此寄存器用于设置读操作时序,描述如下图:

该寄存器我在驱动TFTLCD时用到的设置有:ACCMOD、DATAST 和 ADDSET,接下来将逐个介绍。

ACCMOD[1:0]:访问模式。00 表示模式 A;01 表示模式 B;10 表示模式 C;11 表示模式 D;前面已经说过在驱动TFT LCD时采用模式 A,故设置为 00。

DATAST[7:0]:数据保持时间。0 为保留设置,其他设置则代表保持时间为: DATAST 个

HCLK时钟周期,最大为 255个 HCLK周期。对 ILI9341来说,其实就是 RD低电平持续时间,一般为 355ns。当FMC时钟为192M时一个 HCLK 时钟周期为 5.2ns 左右(1/192Mhz),为了兼容其他屏,我们这里设置 DATAST 为 70,也就是 70 个 HCLK 周期,时间大约是 364ns(略超,但不影响使用)。

ADDSET[3:0]:地址建立时间。其建立时间为:ADDSET 个 HCLK 周期,最大为 15 个

HCLK 周期。对 ILI9341 来说,这里相当于 RD 高电平持续时间,为 90ns,我们设置 ADDSET为最大 15,即 15*5.2=78ns。

2.5.3 SRAM/NOR-Flash 写入时序寄存器 1/2/3/4 (FMC_BWTR1/2/3/4)

该寄存器用于设置写操作时序,设置方式与FMC_BTR一致,描述如下图:

这个寄存器的设置方式与读操作时序寄存器的设置完全一致,不同的只是DATAST和ADDSET设置的时间不同。在进行写操作时DATAST 和 ADDSET 则对应 WR 的低电平和高电平持续时间。对 ILI9341 来说,这两个时间只需要 15ns 就够了,比读操作快得多。所以我们这里设置 DATAST 为 3,即 3 个 HCLK 周期,时间约为 15.6ns。然后 ADDSET 设置为 3,也是 15.6ns。(略超,但不影响使用)

注意:在 MDK 的寄存器定义里面,并没有定义FMC_BCRx、FMC_BTRx、FMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。

FMC_BCRx 和 FMC_BTRx,组合成 BTCR[8]寄存器组,他们的对应关系如下:

BTCR[0]对应 FMC_BCR1,BTCR[1]对应 FMC_BTR1

BTCR[2]对应 FMC_BCR2,BTCR[3]对应 FMC_BTR2

BTCR[4]对应 FMC_BCR3,BTCR[5]对应 FMC_BTR3

BTCR[6]对应 FMC_BCR4,BTCR[7]对应 FMC_BTR4

FMC_BWTRx 则组合成 BWTR[7],他们的对应关系如下:

BWTR[0]对应 FMC_BWTR1,

BWTR[2]对应 FMC_BWTR2,

BWTR[4]对应 FMC_BWTR3,

BWTR[6]对应 FMC_BWTR4,

BWTR[1]、BWTR[3]和 BWTR[5]保留,没有用到。

2.6、TFT LCD的驱动流程

一般TFT LCD的驱动流程如下:首先是硬件复位(LCD_RST先拉低100毫秒再拉高),然后发送LCD驱动芯片的初始化序列(厂家提供),初始化完成后就可以进行访问了。如果要写数据时先设置坐标,之后再发送写GRAM指令,发送颜色数据,LCD收到后就显示;如果要读数据同样要先设置坐标,之后再发送读GRAM指令,之后再读出颜色数据,单片机获取到数据后再进行处理。

3、使用FMC接口驱动SDRAM

3.1 SDRAM存储器相关知识介绍

SDRAM设备有以下控制接口:时钟线(CLK)、时钟使能(CKE,禁止时钟时,SDRAM 会进入自刷新模式)、片选信号(CS,低电平有效)、行地址选通信号(RAS,低电平表示选通)、列地址选通信号(CAS,低电平表示选通)、写信号(WE,低电平有效)、地址线( A0~A12)、数据线( DQ0~DQ15)、BANK地址线(BA0、BA1)、数据掩码(DQML,DQMH,表示DQ的有效部分)。

SDRAM设备通常被分成4个BANK,每个BANK都是一个表格,只需要给定行地址和列地址,就能确定其唯一的地址。所以在SDRAM内部寻址的时候,先指定BANK号和行地址,然后再指定列地址,就可以查找到目标地址。

SDRAM寻址的时候首先 RAS 信号为低电平,选通行地址,地址线 A0~A12 所表示的地址,会被传输并锁存到行地址译码器里面,作为行地址;同时 BANK 地址线上面的 BS0,BS1 所表示的 BANK 地址也会被锁存,选中对应的BANK;然后 CAS 信号为低电平,选通列地址,地址线A0~A12 所表示的地址会被传输并锁存到列地址译码器里面作为列地址,这样就完成了一次寻址。在完成寻址以后,数据线 DQ0~DQ15 上面的数据会写入(或读出)存储阵列。

注意:因为SDRAM 的位宽,可以达到32位,也就是最多有32条数据线,在实际使用的时候,我们可能会以8位16位、24位和 32位等宽度来读写数据,这样的话,并不是每条数据线,都会被使用到,未被用到的数据线上面的数据,必须被忽略,这个时候就需要用到数据掩码( DQM)线来控制了,每一个数据掩码线,对应 8 个位的数据,低电平表示对应数据位有效,高电平表示对应数据位无效

W9825G6KH的存储结构为:行地址8192个(2的13次方),列地址512个(2的9次方), BANK数4个,位宽:16 位。这样,整个芯片的容量为: 8192*512*4*16=32M 字节。

W9825G6KH 最快可以到 200M。

3.2 SDRAM存储器初始化流程

① 上电:此步给 SDRAM 供电,使能 CLK 时钟,并发送 NOP(No Operation 命令,空操作命令)。注意上电后,要等待最少 200us再发送其他指令

② 发送预充电命令(Percharge),给所有 Bank 预充电。

③ 发送自动刷新命令(Refresh),至少要发送 8 次自刷新命令,每一个自刷新命令之间的间隔时间为 tRC。

④ 设置模式寄存器(Mode Register Set):发送模式寄存器的值,配置 SDRAM 的工作参数。配置完成后,需要等待tMRD(也叫 tRSC),使模式寄存器的配置生效,才能发送其他命令。

注意:这里提到的 tRC、tMRD 和 tRSC 见 SDRAM 的芯片数据手册。

经过前面四步的操作,SDRAM 的初始化就完成了,接下来就可以发送激活命令和读/写命令,进行数据的读/写了。

3.3 SDRAM存储器的相关参数及计算方法

突发传输:指定起始列地址与突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址。此时除了第一个数据的传输需要若干个周期外,其后每个数据只需一个周期的即可获得。

突发长度(BL):是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度。 BL的数值是在初始化的时候,通过模式寄存器设置命令,进行设置。 目前可用的选项是 1、 2、 4、 8、全页( Full Page),常见的设定是4和8

非突发连续读取模式:不采用突发传输而是依次单独寻址,此时可等效于 BL=1。虽然可以让数据是连续的传输,但每次都要发送列地址与命令信息,控制资源占用极大。

突发连续读取模式:只要指定起始列地址与突发长度,寻址与数据的读取自动进行,而只要控制好两段突发读取命令的间隔周期(与 BL 相同)即可做到连续的突发传输。

CAS Latency,即列地址选通延迟(简称 CL)。在读命令(同时发送列地址)发送完之后需要等待几个时钟周期DQ数据线上的数据才会有效,这个延迟时间就叫CL,一般设置为 23 个时钟周期,即CAS的值通常为2或3

SDRAM设置结构体详解:

typedef struct

{

uint32_t SDBank;//SDRAM的哪一个BANK,BANK1还是BANK2,硬件连接决定。

uint32_t ColumnBitsNumber;//SDRAM芯片的行数量

uint32_t RowBitsNumber;//SDRAM芯片的列数量

uint32_t MemoryDataWidth;//SDRAM芯片的数据宽度

uint32_t InternalBankNumber;//SDRAM芯片的BANK数

uint32_t CASLatency;//列地址选通延迟,通常为2或3

uint32_t WriteProtection;//写保护是否开启,通常是关

uint32_t SDClockPeriod;//时钟分频数,FMC时钟来源于HCLK分频(HCLK就是系统时钟)

uint32_t ReadBurst;//是否是能突发读取模式

uint32_t ReadPipeDelay;//读通道延时,即CAS延时多少个时钟周期去读取数据。

} FMC_SDRAM_InitTypeDef;

SDRAM时间参数结构体详解:

typedef struct

{

 uint32_t LoadToActiveDelay;用来设置加载模式寄存器命令和激活或刷新命令之间的延迟,

对应寄存器 FMC_SDTRx 的TMRD位,其值应手册上的tRSC;

 uint32_t ExitSelfRefreshDelay;用来设置从发出自刷新命令到发出激活命令之间的延迟,对应TXSR 位,其值由手册上的tXSR除以FMC时钟周期得到;

 uint32_t SelfRefreshTime;用来设置自刷新周期,对应TRAS位,其值由手册上的tRC除以FMC时钟周期得到。

 uint32_t RowCycleDelay;用来设置刷新命令和激活命令之间的延迟以及两个相邻刷新命令之间的延迟,对应TRC位,其值对应手册上的tRRD。

 uint32_t WriteRecoveryTime;用来设置写命令和预充电命令之间的延迟,对应 TWR 位,其值对应手册上的tWR。

 uint32_t RPDelay;用来设置预充电命令与其它命令之间的延迟,对应位 TRP位,其值由手册上的tRP除以FMC时钟周期得到。

 uint32_t RCDDelay; 用来设置激活命令与读/写命令之间的延迟,对应位 TRCD位,其值由手册上的tRCD除以FMC时钟周期得到。

} FMC_SDRAM_TimingTypeDef;

SDRAM的刷新率计算:刷新率=(SDRAM 刷新周期[tREF]*1000000/(2^行数))/FMC时钟周期-20;(注意:这里乘1000000主要是单位不同,tREF的单位是毫秒,FMC时钟周期的单位是纳秒,相差2个量级,所以要乘1000000)

3.4 F429驱动W9825G6KH-5的参数计算

F429主频180M、SDRAM芯片接在单片机第一个 SDRAM BANK、芯片是9列13行、数据宽度为16位、芯片内有4个BANK、时钟2分频、CAS等于3:

时钟周期=1S/90M=11.1ns;

LoadToActiveDelay = tRSC = 2;

ExitSelfRefreshDelay = tXSR/FMC时钟周期=72/11.1=7;

SelfRefreshTime = tRC/FMC时钟周期=60/11.1=6;

RowCycleDelay = tRRD = 2;

WriteRecoveryTime = tWR = 2(CL等于3);

RPDelay = tRP/FMC时钟周期 = 15/11.1=2;

RCDDelay = tRCD/FMC时钟周期 = 15/11.1=2;

刷新率 =( tREF * 1000000 /( 2^行数))/ FMC时钟周期 - 20  =( 64 * 1000000 / (2^13))/ 11.1-20=683;

3.5 F429驱动W9825G6KH-5时CubeMX配置

CubeMX:6.4.0

F4 PACK包:V1.26.0

3.6 CubeMX生成代码修改

fmc.h文件:

#define BANK5_SDRAM_ADDR    ((uint32_t)(0XC0000000))  /* SDRAM开始地址 */

/* SDRAM配置参数 */

/* 突发长度 */

#define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)

#define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)

#define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)

#define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)

/* 突发类型 */

#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000) /*连续*/

#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008) /*交错*/

/* CAS值 */

#define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)

#define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)

/* 操作模式 */

#define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)

/* 突发写模式 */

#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000) /*连续访问*/

#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200) /*单点访问*/

fmc.c文件:

/**

 * @brief       向SDRAM发送命令

 * @param       bankx:0,向BANK5上面的SDRAM发送指令

 * @param             1,向BANK6上面的SDRAM发送指令

 * @param       cmd:指令(0,正常模式/1,时钟配置使能/2,预充电所有存储区/3,自动刷新/4,加载模式寄存器/5,自刷新/6,掉电)

 * @param       refresh:自刷新次数

 * @param       返回值:0,正常;1,失败.

 * @retval      模式寄存器的定义

 */

static uint8_t sdram_send_cmd(uint8_t bankx, uint8_t cmd, uint8_t refresh, uint16_t regval)

{

    uint32_t target_bank = 0;

    FMC_SDRAM_CommandTypeDef command;

    

    if (bankx == 0) target_bank = FMC_SDRAM_CMD_TARGET_BANK1;

    else if (bankx == 1) target_bank = FMC_SDRAM_CMD_TARGET_BANK2;

    command.CommandMode = cmd;           /* 命令 */

    command.CommandTarget = target_bank;    /* 目标SDRAM存储区域 */

    command.AutoRefreshNumber = refresh;    /* 自刷新次数 */

    command.ModeRegisterDefinition = regval;  /* 要写入模式寄存器的值 */

/*发送命令 */

    if (HAL_SDRAM_SendCommand(&hsdram1, &command, 0X1000) == HAL_OK) return 0;

    else return 1;

}

void sdram_initialization_sequence(void)//发送SDRAM初始化序列

{

    uint32_t temp = 0;

    /* SDRAM控制器初始化完成以后还需要按照如下顺序初始化SDRAM */

    sdram_send_cmd(0, FMC_SDRAM_CMD_CLK_ENABLE, 1, 0);    /* 时钟配置使能 */

HAL_Delay(1); /* 至少延时500us */

    sdram_send_cmd(0, FMC_SDRAM_CMD_PALL, 1, 0);    /* 对所有存储区预充电 */

    sdram_send_cmd(0, FMC_SDRAM_CMD_AUTOREFRESH_MODE, 8, 0);   /* 设置自刷新次数 */

    /* 配置模式寄存器,SDRAM的bit0~bit2为指定突发访问的长度,

     * bit3为指定突发访问的类型,bit4~bit6为CAS值,bit7和bit8为运行模式

     * bit9为指定的写突发模式,bit10和bit11位保留位 */

    temp = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1  |   /* 设置突发长度:1(可以是1/2/4/8) */

      SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL  |  /* 设置突发类型:连续(可以是连续/交错) */

      SDRAM_MODEREG_CAS_LATENCY_3          |    /* 设置CAS值:3(可以是2/3) */

      SDRAM_MODEREG_OPERATING_MODE_STANDARD|   /* 设置操作模式:0,标准模式 */

      SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;    /* 设置突发写模式:1,单点访问 */

    sdram_send_cmd(0, FMC_SDRAM_CMD_LOAD_MODE, 1, temp);   /* 设置SDRAM的模式寄存器 */

}

最后在HAL_SDRAM_Init后添加以下语句:

sdram_initialization_sequence();/* 发送SDRAM初始化序列 */

HAL_SDRAM_ProgramRefreshRate(&hsdram1, 683);     /* 设置刷新频率*/

至此SDRAM已经初始化完成可以使用了。

最后main.c增加一下测试代码:

uint16_t testsdram[250000] __attribute__((at(0XC0000000)));   /* 测试用数组 */

void sdram_test(uint16_t x, uint16_t y)

{  

    uint32_t i = 0;

    uint32_t temp = 0;

    uint32_t sval = 0;       /* 在地址0读到的数据 */

    /* 每隔16K字节,写入一个数据,总共写入2048个数据,刚好是32M字节 */

    for (i = 0; i < 32 * 1024 * 1024; i += 16 * 1024)

    {

        *(volatile uint32_t *)(BANK5_SDRAM_ADDR + i) = temp;

        temp++;

    }

    for (i = 0; i < 32 * 1024 * 1024; i += 16 * 1024) /* 依次读出进行校验 */

    {

        temp =*(volatile uint32_t*)(BANK5_SDRAM_ADDR + i);

        if (i == 0) sval = temp;

        else if (temp <= sval) break; /* 后面读出的数据一定要比第一次读到的数据大 */

        printf("SDRAM Capacity:%dKB\r\n", (uint16_t)(temp - sval + 1) * 16); /*打印容量 */

    }

}

以下代码添加到main函数:

uint32_t ts = 0;

sdram_test(30, 170);

for (ts = 0; ts < 250000; ts++)

{

testsdram[ ts ] = ts;                                    /* 预存测试数据 */

}

for (ts = 0; ts < 250000; ts+= 10)

{

if(testsdram[ts] != (uint16_t)ts)  printf("第 %d 个数据测试错误\n",ts);

HAL_Delay(1);

}

printf("测试完成\n");

4、附录1 FMC引脚复用表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/36757.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ollama不安装到c盘,安装到其他盘

ollama 安装包默认安装到c盘&#xff0c;安装程序并没有提供选择文件夹安装功能&#xff0c;本来c盘就快满了&#xff0c;下几个模型c盘都快爆了&#xff0c;如何将ollma安装到其他盘呢&#xff1f; ollama 默认安装位置 C:\Users\Admin\.ollama 是 Ollama 用来放大模型的文件夹…

java项目之基于ssm的少儿编程在线培训系统(源码+文档)

项目简介 少儿编程在线培训系统实现了以下功能&#xff1a; 用户信息管理&#xff1a; 用户信息新增 用户信息修改 教师信息管理&#xff1a; 教师信息添加 教师信息删除 教师信息修改 课程信息管理&#xff1a; 课程信息添加 课程信息修改 课程信息删除 课程类型管理&…

Cinema4D安装及基本操作

一、简介 Cinema 4D&#xff08;C4D&#xff09;是德国 Maxon Computer 开发的 3D 软件&#xff0c;具备强大的建模、动画、材质、渲染功能&#xff0c;以易用高效著称&#xff0c;广泛应用于影视、游戏、设计等领域&#xff0c;是行业内主流 3D 创作工具。 二、安装 1.下载安…

为什么TCP需要三次握手?一次不行吗?

文章目录 1. 三次握手的过程2. 为什么需要三次握手&#xff1f;3. 握手过程中每一步的具体作用4. 简单比喻5. 为什么是三次握手&#xff0c;而不是两次或四次&#xff1f;6. 三次握手中的序列号有什么作用&#xff1f;7. 总结 1. 三次握手的过程 三次握手是建立 TCP 连接的过程…

大数据在人力资源管理中的洞察与决策

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 在数字化转型浪潮中&#xff0c;人力资源管理&#xff08;HRM&#xff09;正经历着前所未有的变革。…

让vscode远程开发也可以图形显示

目录 0. 摘要1. 保存查看2. jupyter内置inline渲染3. jupyter浏览器4. matplot修改后端5. SSH X11转发[※]6. 参考 0. 摘要 vscode登录远程服务器进行开发遇到图形显示需求时&#xff0c;该怎么处理&#xff1f;一般有几种方式&#xff1a; 保存下来查看jupyter内置的inline图…

Blender制作次表面材质

效果: 主要用沃罗诺伊纹理做出云絮感 然后EV开启次表面设置

服务器数据恢复—服务器raid故障导致上层分区不可用的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 一台服务器中有一组由三块SAS硬盘组建的raid阵列。服务器上部署的数据库存储在D分区&#xff0c;数据库备份存储在E分区。 服务器上一块硬盘指示灯显示红色。D分区不可识别。E分区虽然可以识别&#xff0c;但是E分区拷贝文件报错。 管…

PyTorch PINN实战:用深度学习求解微分方程

神经网络技术已在计算机视觉与自然语言处理等多个领域实现了突破性进展。然而在微分方程求解领域&#xff0c;传统神经网络因其依赖大规模标记数据集的特性而表现出明显局限性。物理信息神经网络(Physics-Informed Neural Networks, PINN)通过将物理定律直接整合到学习过程中&a…

关于“碰一碰发视频”系统的技术开发文档框架

以下是关于“碰一碰发视频”系统的技术开发文档框架&#xff0c;涵盖核心功能、技术选型、开发流程和关键模块设计&#xff0c;帮助您快速搭建一站式解决方案 --- 随着短视频平台的兴起&#xff0c;用户的创作与分享需求日益增长。而如何让视频分享更加便捷、有趣&#xff0c…

【VUE】day05-ref引用

这里写目录标题 1. ref引用1.1 使用ref引用组件 2. this.$nextTick(cb)方法3. 购物车案例3.1 数组中的方法 - some循环3.2 数组中的方法 - every循环3.3 数组中的方法 - reduce 4. 购物车案例 1. ref引用 ref用来辅助开发者在不依赖于jQuery的情况下&#xff0c;获取DOM元素或…

docker安装milvus向量数据库Attu可视化界面

Docker 部署 Milvus 及 Attu 可视化工具完整指南 一、环境准备 安装 Docker 及 Docker Compose Docker 版本需 ≥20.10.12Docker Compose 版本需 ≥2.20.0&#xff08;推荐 V2&#xff09; 验证 Docker 环境 docker --version && docker-compose --version若出现&…

nacos安装,服务注册,服务发现,远程调用3个方法

安装 点版本下载页面 服务注册 每个微服务都配置nacos的地址&#xff0c;都要知道 服务发现 2个是知道了解 远程调用基本实现 远程调用方法2&#xff0c;负载均衡API测试 远程调用方法3&#xff0c;注解 负载均衡的远程调用&#xff0c; 总结 面试题

MySQL:数据库基础

数据库基础 1.什么是数据库&#xff1f;2.为什么要学习数据库&#xff1f;3.主流的数据库&#xff08;了解&#xff09;4.服务器&#xff0c;数据库&#xff0c;表之间的关系5.数据的逻辑存储6.MYSQL架构7.存储引擎 1.什么是数据库&#xff1f; 数据库(Database,简称DB)&#x…

Kotlin 基础语法

1. &#x1f31f; Kotlin&#xff1a;Java 的“超级进化体”? Kotlin 是一门由 JetBrains 开发的 现代静态类型编程语言&#xff0c;支持 JVM、Android、JavaScript、Native 等多平台&#xff1a; Kotlin 与 Java 深度兼容&#xff0c;Kotlin 会编译为 JVM 字节码&#xff0c…

基于RAGFlow本地部署DeepSeek-R1大模型与知识库:从配置到应用的全流程解析

作者&#xff1a;后端小肥肠 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; DeepSpeek服务器繁忙&#xff1f;这几种替代方案帮你流畅使用&#xff01;&#xff08;附本地部署教程&#xff09;-CSDN博客 10分钟上手…

uniapp APP权限弹框

效果图 第一步 新建一个页面&#xff0c;设置透明 {"path": "pages/permissionDisc/permissionDisc","style": {"navigationBarTitleText": "","navigationStyle": "custom","app-plus": {&…

【深度学习与大模型基础】第7章-特征分解与奇异值分解

一、特征分解 特征分解&#xff08;Eigen Decomposition&#xff09;是线性代数中的一种重要方法&#xff0c;广泛应用于计算机行业的多个领域&#xff0c;如机器学习、图像处理和数据分析等。特征分解将一个方阵分解为特征值和特征向量的形式&#xff0c;帮助我们理解矩阵的结…

麒麟V10 arm cpu aarch64 下编译 RocketMQ-Client-CPP 2.2.0

国产自主可控服务器需要访问RocketMQ消息队列&#xff0c;最新的CSDK是2020年发布的 rocketmq-client-cpp-2.2.0 这个版本支持TLS模式。 用默认的版本安装遇到一些问题&#xff0c;记录一下。 下载Releases apache/rocketmq-client-cpp GitHubhttps://github.com/apache/roc…

Moonlight-16B-A3B: 变革性的高效大语言模型,凭借Muon优化器打破训练效率极限

近日&#xff0c;由Moonshot AI团队推出的Moonlight-16B-A3B模型&#xff0c;再次在AI领域引发了广泛关注。这款全新的Mixture-of-Experts (MoE)架构的大型语言模型&#xff0c;凭借其创新的训练优化技术&#xff0c;特别是Muon优化器的使用&#xff0c;成功突破了训练效率的极…