这里会显示出您选择的修订版和当前版本之间的差别。
| 后一修订版 | 前一修订版 | ||
| 
                    icore4tx_14 [2020/07/28 15:28] fmj 创建  | 
                
                    icore4tx_14 [2022/04/02 11:58] (当前版本) sean  | 
            ||
|---|---|---|---|
| 行 2: | 行 2: | ||
| |技术支持电话|**0379-69926675-801**||| | |技术支持电话|**0379-69926675-801**||| | ||
| |技术支持邮件|Gingko@vip.163.com||| | |技术支持邮件|Gingko@vip.163.com||| | ||
| - | |技术论坛|http://www.eeschool.org||| | ||
| ^ 版本  ^ 日期  ^ 作者  ^ 修改内容  ^ | ^ 版本  ^ 日期  ^ 作者  ^ 修改内容  ^ | ||
| | V1.0 | 2020-07-28  | gingko  | 初次建立  | | | V1.0 | 2020-07-28  | gingko  | 初次建立  | | ||
| 行 10: | 行 9: | ||
| \\ | \\ | ||
| - | =====STM32CubeMX教程十三——QSPI通信实验 ===== | + | =====STM32CubeMX教程十四——SDRAM实验 ===== | 
| \\ | \\ | ||
| \\ | \\ | ||
| 行 16: | 行 15: | ||
| 1.在主界面选择File-->New Project  或者直接点击ACCEE TO MCU SELECTOR。  | 1.在主界面选择File-->New Project  或者直接点击ACCEE TO MCU SELECTOR。  | ||
| - | {{ :icore4tx:icore4tx_cube_13_1.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_1.png?direct |}} | 
| 2.出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置在搜索栏的下面,提供的各  种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号为:STM32H750IBKx。 | 2.出现芯片型号选择,搜索自己芯片的型号,双击型号,或者点击Start Project进入配置在搜索栏的下面,提供的各  种查找方式,可以选择芯片内核,型号,等等,可以帮助你查找芯片。本实验选取的芯片型号为:STM32H750IBKx。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_2.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_2.png?direct |}} | 
| 3.配置RCC,使用外部时钟源。 | 3.配置RCC,使用外部时钟源。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_3.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_3.png?direct |}} | 
| 4.时基源选择SysTick。 | 4.时基源选择SysTick。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_4.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_4.png?direct |}} | 
| 5.将PA10,PB7,PB8设置为GPIO_Output。 | 5.将PA10,PB7,PB8设置为GPIO_Output。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_5.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_5.png?direct |}} | 
| 6.引脚模式配置。 | 6.引脚模式配置。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_6.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_6.png?direct |}} | 
| - | {{ :icore4tx:icore4tx_cube_13_7.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_7.png?direct |}} | 
| - | 7.设置串口。。 | + | 7.设置串口。 | 
| - | {{ :icore4tx:icore4tx_cube_13_8.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_8.png?direct |}} | 
| - | 8.在NVIC Settings一栏使能接收中断。 | + | 8.配置FMC | 
| - | {{ :icore4tx:icore4tx_cube_13_9.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_9.png?direct |}} | 
| - | 9.配置QUADSPI。 | + | 9.在 NVIC Settings 一栏使能接收中断。 | 
| - | {{ :icore4tx:icore4tx_cube_13_10.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_10.png?direct |}} | 
| 10.时钟源设置,选择外部高速时钟源,配置为最大主频。 | 10.时钟源设置,选择外部高速时钟源,配置为最大主频。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_11.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_11.png?direct |}} | 
| - | {{ :icore4tx:icore4tx_cube_13_12.png?direct |}} | + | 11.工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可。IDE我们使用的是MDK V5.27。 | 
| - | 11.工程文件的设置, 这里就是工程的各种配置 我们只用到有限几个,其他的默认即可  IDE我们使用的是 MDK V5.27。 | + | {{ :icore4tx:icore4tx_cube_14_12.png?direct |}} | 
| - | {{ :icore4tx:icore4tx_cube_13_13.png?direct |}} | + | |
| 12.点击Code Generator,进行进一步配置。 | 12.点击Code Generator,进行进一步配置。 | ||
| - | {{ :icore4tx:icore4tx_cube_13_14.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_13.png?direct |}} | 
| * **Copy all used libraries into the project folder** | * **Copy all used libraries into the project folder** | ||
| * **将HAL库的所有.C和.H都复制到所建工程中** | * **将HAL库的所有.C和.H都复制到所建工程中** | ||
| 行 54: | 行 52: | ||
| * 缺点:复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径 | * 缺点:复制到其他电脑上或者软件包位置改变,就需要修改相对应的路径 | ||
| * 自行选择方式即可 | * 自行选择方式即可 | ||
| - | 13.然后点击GENERATE CODE 创建工程。 | + | 13.然后点击GENERATE CODE创建工程。 | 
| - | {{ :icore4tx:icore4tx_cube_13_15.png?direct |}} | + | {{ :icore4tx:icore4tx_cube_14_14.png?direct |}} | 
| 创建成功,打开工程。 | 创建成功,打开工程。 | ||
| \\ | \\ | ||
| \\ | \\ | ||
| - | ===== 实验十三:QSPI通信实验——读写测试SPI FLASH ===== | + | ===== 实验十四:SDRAM实验——读写测试SDRAM ===== | 
| ==== 一、 实验目的与意义 ==== | ==== 一、 实验目的与意义 ==== | ||
| - | - 了解STM32 QSPI结构。 | + | - 了解STM32 SDRAM结构。 | 
| - | - 了解STM32 QSPI特征。 | + | - 了解STM32 SDRAM特征。 | 
| - | - 掌握QSPI的使用方法。 | + | - 掌握SDRAM的使用方法。 | 
| - | - 掌握STM32 HAL库中QSPI属性的配置方法。 | + | - 掌握STM32 HAL库中SDRAM属性的配置方法。 | 
| - | - 掌握KEILMDK 集成开发环境使用方法。 | + | - 掌握KEILMDK集成开发环境使用方法。 | 
| ==== 二、 实验设备及平台 ==== | ==== 二、 实验设备及平台 ==== | ||
| - iCore4TX 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.3.29da532fLkazHH&id=614919247574|点击购买]]。 | - iCore4TX 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.3.29da532fLkazHH&id=614919247574|点击购买]]。 | ||
| 行 75: | 行 73: | ||
| - 装有WIN XP(及更高版本)系统的计算机。 | - 装有WIN XP(及更高版本)系统的计算机。 | ||
| ==== 三、 实验原理 ==== | ==== 三、 实验原理 ==== | ||
| - | === 1.QSPI简介 === | + | === 1.SDRAM简介 === | 
| - | + | * 同步动态随机存取内存(synchronous dynamic random-access memory,简称 SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。通常 DRAM 是有一个异步接口的,这样它可以随时响应控制输入的变化。而 SDRAM 有一个同步接口,在响应控制输入前会等待一个时钟信号,这样就能和计算机的系统总线同步。时钟被用来驱动一个有限状态机,对进入的指令进行管线(Pipeline)操作。这使得 SDRAM 与没有同步接口的异 DRAM(asynchronousDRAM)相比,可以有一个更复杂的操作模式。管线意味着芯片可以在处理完之前的指令前,接受一个新的指令。在一个写入的管线中,写入命令在另一个指令执行完之后可以立刻执行,而不需要等待数据写入存储队列的时间。在一个读取的流水线中,需要的数据在读取指令发出之后固定数量的时钟频率后到达,而这个等待的过程可以发出其它附加指令。 | |
| - | * QSPI是Queued SPI的简写,是Motorola公司推出的SPI接口的扩展,比SPI应用更加广泛。在SPI协议的基础上,Motorola公司对其功能进行了增强,增加了队列传输机制,推出了队列串行外围接口协议(即QSPI协议)。QSPI是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash存储介质。 | + | * SDRAM 是多 Bank 结构,例如在一个具有两个 Bank 的 SDRAM 的模组中,其中一个Bank 在进行预充电期间,另一个 Bank 却马上可以被读取,这样当进行一次读取后,又马上去读取已经预充电 Bank 的数据时,就无需等待而是可以直接读取了,这也就大大提高了存储器的访问速度。为了实现这个功能,SDRAM 需要增加对多个 Bank 的管理,实现控制其中的 Bank 进行预充电。在一个具有 2 个以上 Bank 的 SDRAM 中,一般会多一根叫做 BAn的引脚,用来实现在多个 Bank 之间的选择。 | 
| - | * 该接口可以在以下三种模式下工作: | + | * SDRAM 具有多种工作模式,内部操作是一个复杂的状态机。SDRAM 器件的引脚分为以下几类。 | 
| - | * **① 间接模式**:使用QSPI寄存器执行全部操作。 | + | * (1)控制信号:包括片选、时钟、时钟使能、行列地址选择、读写有效及数据有效。 | 
| - | * **② 状态轮询模式**:周期性读取外部Flash状态寄存器,而且标志位置1时会产生中断(如擦除或烧写完成,会产生中断)。 | + | * (2)地址信号:时分复用引脚,根据行列地址选择引脚,控制输入的地址为行地址或列地址。 | 
| - | * **③ 内存映射模式**:外部Flash映射到微控制器地址空间,从而系统将其视作内部存储器。 | + | * (3)数据信号:双向引脚,受数据有效控制。 | 
| - | * 采用双闪存模式时,将同时访问两个Quad-SPI Flash,吞吐量和容量均可提高二倍。 | + | * SDRAM的所有操作都同步于时钟。根据时钟上升沿控制管脚和地址输入的状态,可以产生多种输入命令:模式寄存器设置命令、激活命令、预充命令、读命令、写命令、带预充的读命令、带预充的写命令、自动刷新命令、自我刷新命令、突发停命令、空操作命令。根据输入命令,SDRAM状态在内部状态间转移。内部状态包括模式寄存器设置状态、激活状态、预充状态、写状态、读状态、预充读状态、预充写状态、自动刷新状态及自我刷新状态。 | 
| - | === 2.QSPI命令序列 === | + | * SDRAM 支持的操作命令有初始化配置、预充电、行激活、读操作、写操作、自动刷新、自刷新等。所有的操作命令通过控制线 CS#、RAS#、CAS#、WE#和地址线、体选地址BA输入。 | 
| - | * QUADSPI通过命令与Flash通信,每条命令包括指令、地址、交替字节、空指令和数据这五个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。nCS在每条指令开始前下降,在每条指令完成后再次上升。如图为QSPI四线模式下的读命令时序。 | + | === 2.W9825G6JB命令表 === | 
| - | {{ :icore4tx:icore4tx_arm_hal_13_1.png?direct |}} | + | {{ :icore4tx:icore4tx_arm_hal_14_1.png?direct |}} | 
| - | == (1) 指令阶段 == | + | === 3.FMC简介 === | 
| - | + | * STM32H750使用FMC外设来管理扩展的存储器,FMC是Flexible Memory Controller的缩写,译为可变存储控制器。它可以用于驱动包括 SRAM、SDRAM、NOR FLASH以及NAND FLSAH类型的存储器。 | |
| - | * 这一阶段,将在QUADSPI_CCR[7:0]寄存器的INSTRUCTION字段中配置的一条8位指令发送到Flash,指定待执行操作的类型。尽管大多数 Flash从IO0/SO信号(单线 SPI 模式)只能以一次1位的方式接收指令,但指令阶段可选择一次发送2位(在双线SPI模式中通过IO0/IO1)或一次发送4位(在四线SPI模式中通过IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[9:8]寄存器中的IMODE[1:0]字段进行配置。 | + | * FMC 有 6 个存储区域,每个区域支持 256MB 的寻址空间。 | 
| - | * 若IMODE = 00,则跳过指令阶段,命令序列从地址阶段(如果存在)开始。 | + | * (1) 存储区域 1 可连接多达 4 个 NOR Flash 或 PSRAM 设备。此存储区域被划分为如下4个NOR/PSRAM子区域,带4个专用片选信号:存储区域 1 NOR/PSRAM 1、存储区域 1 NOR/PSRAM 2、存储区域 1 NOR/PSRAM 3、存储区域 1 NOR/PSRAM 4。 | 
| - | == (2) 地址阶段 == | + | * (2) 存储区域 2 用于 SDRAM 器件,具体是 SDRAM 存储区域 1 还是 SDRAM 存储区域 2 取决于 BMAP 位配置。 | 
| - | + | * (3) 存储区域 3 用于连接 NAND Flash 器件。此空间的 MPU 存储器特性必须通过软件重新配置到器件中。 | |
| - | * 在地址阶段,将1-4字节发送到Flash,指示操作地址。待发送的地址字节数在QUADSPI_CCR[13:12]寄存器的ADSIZE[1:0]字段中进行配置。在间接模式和自动轮询模式下,待发送的地址字节在QUADSPI_AR寄存器的ADDRESS[31:0]中指定在内存映射模式下,则通过 AHB(来自于 Cortex ® 或 DMA)直接给出地址。地址阶段可一次发送1 位(在单线SPI模式中通过SO)、2位(在双线SPI模式中通过IO0/IO1)或4位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过QUADSPI_CCR[11:10]寄存器中的ADMODE[1:0]字段进行配置。 | + | * (4) 存储区域 5 和 6 用于连接 SDRAM 器件(每个存储区域 1 个器件)。 | 
| - | * 若ADMODE = 00,则跳过地址阶段,命令序列直接进入下一阶段(如果存在)。 | + | * 对于每个存储区域,所要使用的存储器类型可由用户应用程序通过配置寄存器配置。 | 
| - | == (3) 交换字节阶段 == | + | * 本实验使用 FMC 控制 SDRAM。启动时,必须通过用户应用程序对用于连接 FMC SDRAM 控制器与外部 SDRAM 设备的 SDRAM I/O 引脚进行配置。应用程序未使用的 SDRAM 控制器 I/O 引脚可用于其它用途。 | 
| - | + | * FMC 的存储区域如图所示: | |
| - | * 在交替字节阶段,将1-4字节发送到Flash,一般用于控制操作模式。待发送的交替字节数在QUADSPI_CCR[17:16]寄存器的ABSIZE[1:0]字段中进行配置。待发送的字节在QUADSPI_ABR寄存器中指定。 | + | {{ :icore4tx:icore4tx_arm_hal_14_2.png?direct |}} | 
| - | * 交替字节阶段可一次发送1位(在单线 SPI 模式中通过 SO)、2位(在双线SPI模式中通过 IO0/IO1)或4位(在四线SPI模式中通IO0/IO1/IO2/IO3)。这可通过QUADSPI_CCR[15:14]寄存器中的ABMODE[1:0]字段进行配置。 | + | * FMC 框图如下: | 
| - | * 若ABMODE = 00,则跳过交替字节阶段,命令序列直接进入下一阶段(如果存在)。交替字节阶段存在仅需发送单个半字节而不是一个全字节的情况,比如采用双线模式并且仅使用两个周期发送交替字节时。在这种情况下,固件可采用四线模式(ABMODE = 11)并发送一个字节,方法是ALTERNATE的位7和3置“1”(IO3 保持高电平)且位6和2置“0”(IO2 线保持低电平)。此时,半字节的高 2 位存放在ALTERNATE的位 4:3,低 2位存放在位1和0中。例如,如果半字节2 (0010) 通过IO0/IO1发送,则ALTERNATE 应设置为0x8A (1000_1010)。 | + | {{ :icore4tx:icore4tx_arm_hal_14_3.png?direct |}} | 
| - | == (4) 空指令周期阶段 == | + | === 4.SDRAM的地址映射 === | 
| - | + | * 两个可用的 SDRAM 存储区域如图: | |
| - | * 在空指令周期阶段,给定的1-31个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给Flash留出准备数据阶段的时间。这一阶段中给定的周期数在QUADSPI_CCR[22:18]寄存器的DCYC[4:0]字段中指定。在SDR和DDR模式下,持续时间被指定为一定个数的全时钟周期。若DCYC为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。空指令周期阶段的操作模式由DMODE确定。为确保数据信号从输出模式转变为输入模式有足够的“周转”时间,使用双线和四线模式从Flash接收数据时,至少需要指定一个空指令周期。 | + | {{ :icore4tx:icore4tx_arm_hal_14_4.png?direct |}} | 
| - | == (5) 数据阶段 == | + | * 下表显示了 13 位行和 11 位列配置的 SDRAM 映射。 | 
| - | + | {{ :icore4tx:icore4tx_arm_hal_14_5.png?direct |}} | |
| - | * 在数据阶段,可从Flash接收或向其发送任意数量的字节。 | + | * (1) 连接 16 位存储器时, FMC 内部使用 ADDR[11:1]内部地址线进行外部寻址。连接32 位存储器时,FMC 内部使用 ADDR[12:2]地址线进行外部寻址。无论外部存储器的宽度是多少,FMC_A[0]都必须连接到外部存储器地址 A[0]。 | 
| - | * 在间接模式和自动轮询模式下,待发送/接收的字节数在QUADSPI_DLR寄存器中指定。在间接写入模式下,发送到Flash的数据必须写入QUADSPI_DR寄存器。在间接读取模式下,通过读取QUADSPI_DR寄存器获得从 Flash 接收的数据。在内存映射模式下,读取的数据通过AHB直接发送回Cortex或DMA。数据阶段可一次发送/接收1位(在单线SPI 模式中通过SO)、2位(在双线 SPI 模式中通过IO0/IO1)或4位(在四线SPI模式中通过IO0/IO1/IO2/IO3)。这可通过QUADSPI_CCR[15:14] 寄存器中的ABMODE[1:0]字段进行配置。若DMODE = 00,则跳过数据阶段,命令序列在拉高nCS时立即完成。这一配置仅可用于仅间接写入模式。 | + | * (2) 不支持 AutoPrecharge。FMC_A[10]必须连接到外部存储器地址 A[10],但始终为低电平。 | 
| + | === 5.SDRAM 控制寄存器 === | ||
| + | * 控制 SDRAM 的有 FMC_SDCR1/FMC_SDCR2 控制寄存器、FMC_SDTR1/FMC_SDTR2时序寄存器、FMC_SDCMR 命令模式寄存器以及FMC_SDRTR 刷新定时器寄存器。其中控制寄存器及时序寄存器各有 2 个,分别对应于 SDRAM 存储区域 1 和存储区域 2 的配置。 | ||
| + | * FMC_SDCR 控制寄存器可配置 SDCLK 的同步时钟频率、突发读使能、写保护、CAS延迟、行列地址位数以及数据总线宽度等。 | ||
| + | * FMC_SDTR 时序寄存器用于配置 SDRAM 访问时的各种时间延迟,如 TRP 行预充电延迟、TMRD 加载模式寄存器激活延迟等。 | ||
| + | * FMC_SDCMR 命令模式寄存器用于存储要发送到 SDRAM 模式寄存器的配置,以及要向 SDRAM 芯片发送的命令。 | ||
| + | * FMC_SDRTR 刷新定时器寄存器用于配置 SDRAM 的自动刷新周期。 | ||
| + | === 6.原理图 === | ||
| + | {{ :icore4tx:icore4tx_arm_hal_14_6.png?direct |}} | ||
| ==== 四、 实验程序 ==== | ==== 四、 实验程序 ==== | ||
| - | |||
| === 1.主函数 === | === 1.主函数 === | ||
| <code c> | <code c> | ||
| - | int main(void)  | + | int main(void) | 
| - | {  | + | { | 
| - | int i; | + | int i,j; | 
| - | int temp; | + | HAL_Init(); | 
| - | unsigned char write_buffer[4096];  | + | i2c.initialize(); | 
| - | unsigned char read_buffer[4096];  | + | axp152.initialize(); | 
| - | HAL_Init();  | + | axp152.set_dcdc1(3500);//[ARM & FPGA] | 
| - | + | axp152.set_dcdc2(1200);//[FPGA INT] | |
| - | SystemClock_Config();  | + | axp152.set_dcdc3(3300);//[DCOUT3] | 
| - | i2c.initialize();  | + | axp152.set_dcdc4(3300);//[DCOUT4] | 
| - | axp152.initialize();  | + | axp152.set_aldo1(3300);//[BK3] | 
| - | axp152.set_dcdc1(3500);//[ARM & FPGA] | + | axp152.set_aldo2(3300);//[ALDOOUT2] | 
| - | axp152.set_dcdc2(1200);//[FPGA INT] | + | axp152.set_dldo1(3300);//[BK0] | 
| - | axp152.set_dcdc3(3300);//[DCOUT3] | + | axp152.set_dldo2(3300);//[BK1] | 
| - | axp152.set_dcdc4(3300);//[DCOUT4] | + | HAL_Delay(200); | 
| - | + | SystemClock_Config(); | |
| - | axp152.set_aldo1(3300);//[BK3] | + | MX_GPIO_Init(); | 
| - | axp152.set_aldo2(3300);//[ALDOOUT2] | + | MX_USART2_UART_Init(); | 
| - | axp152.set_dldo1(3300);//[BK0] | + | MX_FMC_Init(); | 
| - | axp152.set_dldo2(3300);//[BK1] | + | BSP_SDRAM_Init(); | 
| - | HAL_Delay(200); | + | usart2.initialize(115200); | 
| - | + | usart2.printf("\x0c"); //清屏 | |
| - | MX_GPIO_Init();  | + | usart2.printf("\033[1;32;40m"); //设置终端字体为绿色 | 
| - | MX_USART2_UART_Init();  | + | usart2.printf("Hello,I am iCore4TX!\r\n\r\n"); | 
| - | MX_QUADSPI_Init();  | + | //向 SDRAM 中写入 0x0000~0xFFFF 并读取校验 | 
| - | BSP_QSPI_Init();  | + | for(j = 0; j < 256; j++){ | 
| - | usart2.initialize(115200);  | + | for(i = 0;i < 65536;i++){ | 
| - | usart2.printf("\x0c");  //清屏 | + | write_sdram((65536 * j + i),i); | 
| - | usart2.printf("\033[1;32;40m");  //设置终端字体为绿色 | + | } | 
| - | usart2.printf("Hello,I am iCore4TX!\r\n\r\n");   | + | } | 
| - | temp = BSP_QSPI_FLASH_ReadID();  | + | for(j = 0; j < 256; j ++){ | 
| - | usart2.printf("FLASH ID: 0x%X\r\n",temp);  | + | for(i = 0;i < 65536;i++){ | 
| - | for(i = 0;i < 4096;i ++){  | + | if(i != read_sdram((65536 * j + i))){ | 
| - | write_buffer[i] = i % 256; | + | usart2.printf("SDRAM ERROR!\r\n"); | 
| - | read_buffer[i] = 0;  | + | while(1){ //测试失败 LED 灯闪烁 | 
| - | } | + | LED_ON; | 
| - | BSP_QSPI_Write(write_buffer,0,4096); //写数据 | + | HAL_Delay(500); | 
| - | BSP_QSPI_Read(read_buffer,0,4096); //读数据 | + | LED_OFF; | 
| - | for(i = 0;i < 4096;i ++){  | + | HAL_Delay(500); | 
| - | if(read_buffer[i] != write_buffer[i]){ | + | } | 
| - | usart2.printf("FLASH ERROR!\r\n");  | + | usart2.printf("SDRAM TEST OK!\r\n"); | 
| - | while(1);  | + | HAL_Delay(1000); | 
| - | }  | + | LED_ON; //测试成功 LED 灯常亮 | 
| - | }  | + | while (1) | 
| - | usart2.printf("FLASH TEST OK!\r\n");  | + | { | 
| - | while (1) | + | } | 
| - | {  | + | } | 
| - | }  | + | |
| - | } | + | |
| </code> | </code> | ||
| - | === 2.QSPI初始化函数 === | + | === 2.SDRAM 初始化函数 === | 
| - | 初始化好QSPI外设后,还要初始化初始化QSPI存储器,需要先复位存储器,使能写操作,配置状态寄存器才可进行数据读写操作。 | + | |
| <code c> | <code c> | ||
| - | uint8_t BSP_QSPI_Init(void)  | + | uint8_t BSP_SDRAM_Init(void) | 
| - | { | + | { | 
| - | QSPIHandle.Instance = QUADSPI; | + | static uint8_t sdramstatus = SDRAM_OK; | 
| - | /* 调用DeInit函数重置驱动程序 */ | + | /* SDRAM 驱动配置 */ | 
| - | if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)  | + | sdramHandle.Instance = FMC_SDRAM_DEVICE; | 
| - | { | + | Timing.LoadToActiveDelay = 2; | 
| - | return QSPI_ERROR; | + | Timing.ExitSelfRefreshDelay = 7; | 
| - | }  | + | Timing.SelfRefreshTime = 4; | 
| - | /* 系统级初始化 */ | + | Timing.RowCycleDelay = 7; | 
| - | BSP_QSPI_MspInit(&QSPIHandle, NULL); | + | Timing.WriteRecoveryTime = 2; | 
| - | /* QSPI初始化 */  | + | Timing.RPDelay = 2; | 
| - | /* 时钟预分频器设置为1,因此QSPI时钟= 240MHz /(1 + 1)= 120MHz */ | + | Timing.RCDDelay = 2; | 
| - | QSPIHandle.Init.ClockPrescaler  = 1; | + | sdramHandle.Init.SDBank = FMC_SDRAM_BANK1; | 
| - | QSPIHandle.Init.FifoThreshold  = POSITION_VAL(W25Q64_FLASH_SIZE) - 1; | + | sdramHandle.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9; | 
| - | QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_NONE; | + | sdramHandle.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13; | 
| - | QSPIHandle.Init.FlashSize  = POSITION_VAL(W25Q64_FLASH_SIZE) - 1; | + | sdramHandle.Init.MemoryDataWidth = SDRAM_MEMORY_WIDTH; | 
| - | QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE; | + | sdramHandle.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; | 
| - | QSPIHandle.Init.ClockMode  = QSPI_CLOCK_MODE_0; | + | sdramHandle.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3; | 
| - | QSPIHandle.Init.FlashID  = QSPI_FLASH_ID_1; | + | sdramHandle.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; | 
| - | QSPIHandle.Init.DualFlash  = QSPI_DUALFLASH_DISABLE; | + | sdramHandle.Init.SDClockPeriod = SDCLOCK_PERIOD; | 
| - | if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)  | + | sdramHandle.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; | 
| - | { | + | sdramHandle.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1; | 
| - | return QSPI_ERROR; | + | /* SDRAM 控制器初始化 */ | 
| - | } | + | BSP_SDRAM_MspInit(&sdramHandle, NULL); | 
| - | return QSPI_OK; | + | if(HAL_SDRAM_Init(&sdramHandle, &Timing) != HAL_OK) | 
| - | } | + | { | 
| + | sdramstatus = SDRAM_ERROR; | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | /* SDRAM 初始化顺序 */ | ||
| + | BSP_SDRAM_Initialization_sequence(REFRESH_COUNT); | ||
| + | } | ||
| + | return sdramstatus; | ||
| + | } | ||
| </code>  | </code>  | ||
| - | === 3.QSPI读函数 === | + | === 3.SDRAM 读写函数 === | 
| - | 要从存取器中读取数据,首先要用一个指针指向读回来的数据,并确定数据的首地址,数据大小,通过库函数HAL_QSPI_Command发送配置命令,然后调用库函数HAL_QSPI_Receive接收数据,最后等待操作完成,代码如下: | + | |
| <code c> | <code c> | ||
| - | uint8_t BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size) | + | #define write_sdram(offset,data) *(volatile unsigned short int *)(SDRAM_DEVICE_ADDR + (offset << 1)) = data | 
| - | { | + | #define read_sdram(offset) *(volatile unsigned short int *)(SDRAM_DEVICE_ADDR + (offset << 1)) | 
| - | QSPI_CommandTypeDef s_command;  | + | |
| - | /* 初始化读取命令 */ | + | |
| - | s_command.InstructionMode  = QSPI_INSTRUCTION_1_LINE;  | + | |
| - | s_command.Instruction  = QUAD_OUT_FAST_READ_CMD;  | + | |
| - | s_command.AddressMode  = QSPI_ADDRESS_1_LINE;  | + | |
| - | s_command.AddressSize  = QSPI_ADDRESS_24_BITS;  | + | |
| - | s_command.Address  = ReadAddr;  | + | |
| - | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  | + | |
| - | s_command.DataMode  = QSPI_DATA_4_LINES;  | + | |
| - | s_command.DummyCycles  = W25Q64_DUMMY_CYCLES_READ_QUAD;  | + | |
| - | s_command.NbData  = Size; | + | |
| - | s_command.DdrMode  = QSPI_DDR_MODE_DISABLE;  | + | |
| - | s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  | + | |
| - | s_command.SIOOMode  = QSPI_SIOO_INST_EVERY_CMD;  | + | |
| - | /* 配置命令 */ | + | |
| - | if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) | + | |
| - | { | + | |
| - | return QSPI_ERROR;  | + | |
| - | } | + | |
| - | /* 数据接收 */ | + | |
| - | if (HAL_QSPI_Receive(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) | + | |
| - | { | + | |
| - | return QSPI_ERROR;  | + | |
| - | } | + | |
| - | return QSPI_OK;  | + | |
| - | } | + | |
| </code>  | </code>  | ||
| - | pData:指向要读取的数据的指针 | + | 定义 SDRAM 读写函数 | 
| - | ReadAddr:读取起始地址 | + | |
| - | Size:要读取的数据大小 | + | |
| - | === 4.QSPI写函数 === | + | |
| - | + | ||
| - | 要从存取器中写入数据,首先要用一个指针指向写入的数据,并确定数据的首地址,数据大小,根据写入地址及大小判断存储器的页面,然后通过库函数HAL_QSPI_Command发送配置命令,再调用库函数HAL_QSPI_Transmit逐页写入数据,最后等待操作完成。代码如下: | + | |
| <code c> | <code c> | ||
| - | uint8_t BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size) | + | uint8_t BSP_SDRAM_ReadData(uint32_t uwStartAddress, uint32_t *pData, | 
| - | { | + | uint32_t uwDataSize) | 
| - | QSPI_CommandTypeDef s_command;  | + | { | 
| - | uint32_t end_addr, current_size, current_addr;  | + | if(HAL_SDRAM_Read_32b(&sdramHandle, (uint32_t *)uwStartAddress, pData, uwDataSize) != HAL_OK) | 
| - | /* 计算写地址和页面末尾之间的大小 */ | + | { | 
| - | current_size = W25Q64_PAGE_SIZE - (WriteAddr % W25Q64_PAGE_SIZE);  | + | return SDRAM_ERROR; | 
| - | /* 检查数据大小是否小于页面中的剩余位置*/  | + | } | 
| - | if (current_size > Size) | + | else | 
| - | { | + | { | 
| - | current_size = Size; | + | return SDRAM_OK; | 
| - | } | + | } | 
| - | /* 初始化地址变量 */ | + | } | 
| - | current_addr = WriteAddr;  | + | |
| - | end_addr = WriteAddr + Size; | + | |
| - | /* 初始化程序命令 */ | + | |
| - | s_command.InstructionMode  = QSPI_INSTRUCTION_1_LINE;  | + | |
| - | s_command.Instruction  = QUAD_IN_FAST_PROG_CMD;  | + | |
| - | s_command.AddressMode  = QSPI_ADDRESS_1_LINE;  | + | |
| - | s_command.AddressSize  = QSPI_ADDRESS_24_BITS;  | + | |
| - | s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  | + | |
| - | s_command.DataMode  = QSPI_DATA_4_LINES;  | + | |
| - | s_command.DummyCycles  = 0; | + | |
| - | s_command.DdrMode  = QSPI_DDR_MODE_DISABLE;  | + | |
| - | s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  | + | |
| - | s_command.SIOOMode  = QSPI_SIOO_INST_EVERY_CMD;  | + | |
| - | /* 逐页执行写入*/  | + | |
| - | do | + | |
| - | { | + | |
| - | s_command.Address = current_addr;  | + | |
| - | s_command.NbData  = current_size;  | + | |
| - | /* 启用写操作 */ | + | |
| - | if (QSPI_WriteEnable(&QSPIHandle) != QSPI_OK)  | + | |
| - | { | + | |
| - | return QSPI_ERROR;  | + | |
| - | } | + | |
| - | /* 配置命令 */ | + | |
| - | if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)  | + | |
| - | { | + | |
| - | return QSPI_ERROR;  | + | |
| - | } | + | |
| - | /* 传输数据 */ | + | |
| - | if (HAL_QSPI_Transmit(&QSPIHandle, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)  | + | |
| - | { | + | |
| - | return QSPI_ERROR;  | + | |
| - | } | + | |
| - | /* 配置自动轮询模式以等待程序结束 */ | + | |
| - | if (QSPI_AutoPollingMemReady(&QSPIHandle, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)  | + | |
| - | { | + | |
| - | return QSPI_ERROR; | + | |
| - | } | + | |
| - | /* 为下一页编程更新地址和变量大小 */ | + | |
| - | current_addr += current_size;  | + | |
| - | pData += current_size;  | + | |
| - | current_size = ((current_addr + W25Q64_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : W25Q64_PAGE_SIZE;  | + | |
| - | } while (current_addr < end_addr);  | + | |
| - | return QSPI_OK;  | + | |
| - | } | + | |
| </code>  | </code>  | ||
| - | pData:指向要写入的数据的指针 | + | 函数功能:在轮询模式下从 SDRAM 存储器中读取大量数据。 | 
| - | ReadAddr:写入起始地址 | + | uwStartAddress:读取起始地址。 | 
| - | Size:要写入的数据大小 | + | pData:指向要读取的数据的指针。 | 
| - | === 5.QSPI_CommandTypeDe通信配置命令结构体 === | + | uwDataSize:从存储器读取的数据的大小。 | 
| <code c> | <code c> | ||
| - | typedef struct  | + | uint8_t BSP_SDRAM_WriteData(uint32_t uwStartAddress, uint32_t *pData,uint32_t uwDataSize) | 
| - | { | + | { | 
| - | uint32_t Instruction;  /* 设置通信指令,指定要发送到外部 SPI 设备的指令。仅可在 BUSY = 0 时修改该字段*/  | + | if(HAL_SDRAM_Write_32b(&sdramHandle, (uint32_t *)uwStartAddress, pData, uwDataSize) != HAL_OK) | 
| - | uint32_t Address; /* 指定要发送到外部 Flash 的地址,BUSY = 0 或 FMODE = 11(内存映射模式)时,将忽略写入该字段。在双闪存模式下,由于地址始终为偶地址,ADDRESS[0] 自动保持为“0” */  | + | { | 
| - | uint32_t AlternateBytes;  /* 指定要在地址后立即发送到外部 SPI 设备的可选数据,仅可在 BUSY = 0 时修改该字段。*/ | + | return SDRAM_ERROR; | 
| - | uint32_t AddressSize; /* 定义地址长度,可以是8位,16位,24位或者32位 */ | + | } | 
| - | uint32_t AlternateBytesSize; /* 定义交替字节长度,可以是8位,16位,24位或者32位 */ | + | else | 
| - | uint32_t DummyCycles; /* 定义空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31) */ | + | { | 
| - | uint32_t InstructionMode; /* 定义指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令*/ | + | return SDRAM_OK; | 
| - | uint32_t AddressMode;  /* 定义地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址*/ | + | } | 
| - | uint32_t AlternateByteMode; /* 定义交替字节阶段的操作模式00:无交替字节;01:单线传输交替字节;10:双线传输交替字节;11:四线传输交替字节 */ | + | } | 
| - | uint32_t DataMode; /* 定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。该字段还定义空指令阶段的操作模式 */ | + | </code> | 
| - | uint32_t NbData; /* 设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)*/  | + | 功能介绍:在轮询模式下将大量数据写入 SDRAM 存储器。 | 
| - | uint32_t DdrMode; /* 为地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式 */ | + | uwStartAddress:写入起始地址。 | 
| - | uint32_t DdrHoldHalfCycle;  /* 设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。仅在 DDR 模式下激活*/ | + | pData:指向要写入数据的指针。 | 
| - | uint32_t SIOOMode; /* 设置仅发送指令一次模式,IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令 */ | + | uwDataSize:向存储器写入的数据大小。 | 
| - | }QSPI_CommandTypeDef; | + | <code c> | 
| + | uint8_t BSP_SDRAM_Sendcmd(FMC_SDRAM_CommandTypeDef *SdramCmd) | ||
| + | { | ||
| + | if(HAL_SDRAM_SendCommand(&sdramHandle, SdramCmd, SDRAM_TIMEOUT) != HAL_OK) | ||
| + | { | ||
| + | return SDRAM_ERROR; | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | return SDRAM_OK; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | 功能介绍:向 SDRAM bank 发送命令。 | ||
| + | SdramCmd:指向 SDRAM 命令结构的指针 | ||
| + | === 4.FMC初始化函数 === | ||
| + | <code c> | ||
| + | void MX_FMC_Init(void) | ||
| + | { //本实验中我们只用到了 FMC 的引脚,时序配置使用官方提供的 SDRAM 驱动 | ||
| + | FMC_SDRAM_TimingTypeDef SdramTiming = {0}; | ||
| + | /* 执行 SDRAM1 存储器初始化序列 */ | ||
| + | hsdram1.Instance = FMC_SDRAM_DEVICE; | ||
| + | /* hsdram1 初始化 */ | ||
| + | hsdram1.Init.SDBank = FMC_SDRAM_BANK1; | ||
| + | hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; | ||
| + | hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13; | ||
| + | hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16; | ||
| + | hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; | ||
| + | hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_1; | ||
| + | hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; | ||
| + | hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_DISABLE; | ||
| + | hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_DISABLE; | ||
| + | hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0; | ||
| + | /* Sdram 时序 */ | ||
| + | SdramTiming.LoadToActiveDelay = 16; | ||
| + | SdramTiming.ExitSelfRefreshDelay = 16; | ||
| + | SdramTiming.SelfRefreshTime = 16; | ||
| + | SdramTiming.RowCycleDelay = 16; | ||
| + | SdramTiming.WriteRecoveryTime = 16; | ||
| + | SdramTiming.RPDelay = 16; | ||
| + | SdramTiming.RCDDelay = 16; | ||
| + | if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK) | ||
| + | { | ||
| + | Error_Handler( ); | ||
| + | } | ||
| + | } | ||
| </code>  | </code>  | ||
| + | |||
| ==== 五、 实验步骤 ==== | ==== 五、 实验步骤 ==== | ||
| 行 324: | 行 300: | ||
| - 也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。 | - 也可以进入Debug 模式,单步运行或设置断点验证程序逻辑。 | ||
| ==== 六、 实验现象 ==== | ==== 六、 实验现象 ==== | ||
| - | 读写测试成功,在终端显示出“FLASH TEST OK!”。测试失败,则在终端显示“FLASH ERROR!” | + | SDRAM 读写测试成功,LED 常亮,并在终端显示出“SDRAM TEST OK!”。测试失败 LED 灯闪烁,并在终端显示“SDRAM ERROR!” | 
| - | {{ :icore4t:icore4t_arm_hal_13_2.png?direct |}} | + | {{ :icore4tx:icore4tx_arm_hal_14_7.png?direct |}} |