一、目的
上一篇中《STM32CubeMX SDRAM的使用(一)》我们介绍了SDRAM的基本知识以及一些关键时间参数,本篇我们将介绍FMC中关于SDRAM控制器的相关知识并通过STM32CubeMX进行配置来测试我们的SDRAM芯片读写。
文章末尾有整个工程文件链接
关于STM32CubeMX的使用请参考:
手把手系列--使用STM32CubeMX生成代码工程_coder.mark的博客-CSDN博客_cubemx生成代码https://blog.csdn.net/tianizimark/article/details/121663108 关于SDRAM的知识点务必先看此篇
二、硬件信息
参考资料
开发板信息
STM32H750XBH6
W9825G6KH-6 13行9列4Bank,位宽16bit 总共32MByte;最大时钟166MHz/CL3或者133MHz/CL2
FMC--灵活存储控制器
包含三个存储控制器:
- NOR/PSRAM存储控制器
- NAND存储控制器
- 同步DRAM(SDRAM/Mobile LPSDR SDRAM)控制器
主要特性:
- 将AXI数据通信事务转换成适当的外部器件协议
- 满足外部存储器器件的访问时间要求
- 所有外部存储器共享地址、数据和控制信号,但有各自的片选信号,FMC一次只能访问一个外部器件
- 支持突发模式
- 支持8/16/32位宽的数据总线
- 每个存储区域独立的片选控制
- 每个存储区域可独立配置
- 16 x 32位深度写FIFO
FMC框图
FMC存储区域
FMC存储区域映射选项
SDRAM控制器描述
SDRAM 控制器的主要特性如下:
- 两个 SDRAM 存储区域,可独立配置
- 8 位、 16 位和 32 位数据总线宽度
- 13 位地址行, 11 位地址列, 4 个内部存储区域: 4x16Mx32bit (256 MB)、 4x16Mx16bit(128 MB)、 4x16Mx8bit (64 MB)
- 支持字、半字和字节访问
- SDRAM 时钟可以是 fmc_ker_ck/2 或 fmc_ker_ck/3
- 自动进行行和存储区域边界管理
- 多存储区域乒乓访问
- 可编程时序参数
- 支持自动刷新操作,可编程刷新速率
- 自刷新模式
- 掉电模式
- 通过软件进行 SDRAM 上电初始化
- CAS 延迟 1,2,3
- 读 FIFO 可缓存,支持 6 行 x 32 位深度( 6 x14 位地址标记)
接口信号描述
突发写入时序图
突发读时序
自刷新时序
FMC SDRAM自动进行行和存储区域边界管理(控制器时序)
三、实战
1、创建工程后配置外部时钟并设置系统主时钟480MHz
低速外部时钟(LSE)设置为32.768KHz
高速外部时钟(HSE)设置为25MHz
2、配置SDRAM时钟
STM32H750XBH6 FMC有两个SDRAM控制器,我们的外部SDRAM芯片接在第一个控制器上,片选和时钟使能为SDCKE0+SDNE0(地址空间首地址为0XC0000000),内部有4个Bank,位宽16bit
3、设置外部SDRAM芯片参数
Bank,即FMC SDRAM控制器选择bank1(注意跟SDRAM芯片内部逻辑bank是不同的意思)
Number of column address bits,即列地址为9位
Number of row address bits,即行地址为13位
CAS latency,即CL(读命令时数据潜伏期)为3个时钟周期
Write protection(写保护):禁止写保护
SDRAM common busrt read(突发读):使能突发读
SDRAM common read pipe delay:一般设置为0,指不再额外添加读取延迟(某些情况下由于布局布线的问题导致信号线延迟较大时可以通过此参数调整)
SDRAM common clock对FMC时钟进行2分频(因为FMC时钟现在是240MHz),这样设置后输入给SDRAM的就为120MHz(8.3ns)
从上图可以看到FMC的时钟源可以从HCLK3/PLL1Q/PLL2R/PER_CK中选择,我们选择的HCLK3
4、设置SDRAM关键时间参数(都是以当前SDRAM时钟周期为单位,我们现在配置为120MHz)
下表为W9825G6KH-6的时间信息表(注意-6那一列)
为了解释方便,将此处的每行配置对应到SDRAM相关时间参数以及代码中的字段做成表格方便大家理解
配置项 | FMC_SDRAM_TimingTypeDef结构体字段 | SDRAM手册中时间参数 | 说明 |
Load mode register to active delay | LoadToActiveDelay | tRSC | 模式寄存器命令与激活命令或者刷新命令间的延时,即模式寄存器配置生效时间 |
Exit self-refresh delay | ExitSelfRefreshDelay | tXSR | 退出自刷新命令与激活命令间的延时 |
Self-refresh time | SelfRefreshTime | tRAS | 激活和预充电命令之间的时间(bank激活后必须及时预充电,否则电荷会流失) |
SDRAM common row cycle delay | RowCycleDelay | tRC | 刷新命令和激活命令间、刷新命令间的延时 |
Write recovery time | WriteRecoveryTime | tWR | 写操作后必须等待此时间后才能进行预充电 |
SDRAM common row precharge delay | RPDelay | tRP | 预充电和其他命令间的延时 |
Row to column delay | RCDDelay | tRCD | 激活命令与读写命令间的延时 |
1、Load mode register to active delay,即tRSC为2个时钟(模式寄存器命令与激活命令或者刷新命令间的延时,即模式寄存器配置生效时间)
Load mode register to active delay
Load mode register to active delay must be between 1 and 16.
Parameter Description:
Specifies the delay between a Load Mode Register command and an Active or Refresh command in number of memory clock cycles.
2、Exit self-refresh delay,即tXSR为72ns(9 * 8.3 = 74.7ns),9个时钟
Exit self-refresh delay
Exit self-refresh delay must be between 1 and 16.
Parameter Description:
Specifies the delay from releasing the Self-refresh command to issuing the Activate command in number of memory clock cycles.
退出自刷新命令与激活命令间的延时
3、Self-refresh time,即tRAS为42ns(6 * 8.3 = 49.8ns)6个时钟
Self-refresh time
Self-refresh time must be between 1 and 16.
Parameter Description:
Specifies the minimum Self-refresh period in number of memory clock cycles.
自刷新周期
4、SDRAM common row cycle delay,即tRC为60ns(8 * 8.3 = 66.4ns),8个时钟
SDRAM common row cycle delay
SDRAM common row cycle delay must be between 1 and 16.
Parameter Description:
Specifies the delay between the Refresh command and the Activate command, as well as the delay between two consecutive Refresh commands.
刷新命令和激活命令间、刷新命令间的延时
5、Write recovery time,即tWR为2个时钟
6、SDRAM common row precharge delay,即tRP为15ns(2 * 8.3 = 16.6ns)2个时钟
SDRAM common row precharge delay
SDRAM common row precharge delay must be between 1 and 16.
Parameter Description:
Specifies the delay between a Precharge command and another command in number of memory clock cycles.
预充电和其他命令间的延时
7、Row to column delay,即tRCD为15ns(2 * 8.3 = 16.6ns)2个时钟
Row to column delay
Row to column delay must be between 1 and 16.
Parameter Description:
Specifies the delay between the Activate command and a Read/Write command in number of memory clock cycles.
激活命令与读写命令间的延时
5、设置引脚
每一个引脚都要选中后,然后引脚速度必须全部设置成Very High,其他配置项保持默认值即可(修改后的引脚Modify列会打上勾)
6、生成工程
7、代码分析
1、FMC引脚配置相关,一定和电路原理图一一对照(特别注意一下 PH5 ------> FMC_SDNWE,默认的配置是PC0,我们的开发板上用的是PH5)
static void HAL_FMC_MspInit(void){
/* USER CODE BEGIN FMC_MspInit 0 */
/* USER CODE END FMC_MspInit 0 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (FMC_Initialized) {
return;
}
FMC_Initialized = 1;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
/** Initializes the peripherals clock
*/
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_FMC;
PeriphClkInitStruct.FmcClockSelection = RCC_FMCCLKSOURCE_D1HCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
Error_Handler();
}
/* Peripheral clock enable */
__HAL_RCC_FMC_CLK_ENABLE();
/** FMC GPIO Configuration
PE1 ------> FMC_NBL1
PE0 ------> FMC_NBL0
PG15 ------> FMC_SDNCAS
PD0 ------> FMC_D2
PD1 ------> FMC_D3
PG8 ------> FMC_SDCLK
PF2 ------> FMC_A2
PF1 ------> FMC_A1
PF0 ------> FMC_A0
PG5 ------> FMC_BA1
PF3 ------> FMC_A3
PG4 ------> FMC_BA0
PG2 ------> FMC_A12
PF5 ------> FMC_A5
PF4 ------> FMC_A4
PC2 ------> FMC_SDNE0
PC3 ------> FMC_SDCKE0
PE10 ------> FMC_D7
PH5 ------> FMC_SDNWE
PF13 ------> FMC_A7
PF14 ------> FMC_A8
PE9 ------> FMC_D6
PE11 ------> FMC_D8
PD15 ------> FMC_D1
PD14 ------> FMC_D0
PF12 ------> FMC_A6
PF15 ------> FMC_A9
PE12 ------> FMC_D9
PE15 ------> FMC_D12
PF11 ------> FMC_SDNRAS
PG0 ------> FMC_A10
PE8 ------> FMC_D5
PE13 ------> FMC_D10
PD10 ------> FMC_D15
PD9 ------> FMC_D14
PG1 ------> FMC_A11
PE7 ------> FMC_D4
PE14 ------> FMC_D11
PD8 ------> FMC_D13
*/
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_10|GPIO_PIN_9
|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_15|GPIO_PIN_8
|GPIO_PIN_13|GPIO_PIN_7|GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_15|GPIO_PIN_8|GPIO_PIN_5|GPIO_PIN_4
|GPIO_PIN_2|GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_15|GPIO_PIN_14
|GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_1|GPIO_PIN_0|GPIO_PIN_3
|GPIO_PIN_5|GPIO_PIN_4|GPIO_PIN_13|GPIO_PIN_14
|GPIO_PIN_12|GPIO_PIN_15|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/* USER CODE BEGIN FMC_MspInit 1 */
/* USER CODE END FMC_MspInit 1 */
}
void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef* sdramHandle){
/* USER CODE BEGIN SDRAM_MspInit 0 */
/* USER CODE END SDRAM_MspInit 0 */
HAL_FMC_MspInit();
/* USER CODE BEGIN SDRAM_MspInit 1 */
/* USER CODE END SDRAM_MspInit 1 */
}
2、FMC时序参数相关
void MX_FMC_Init(void)
{
/* USER CODE BEGIN FMC_Init 0 */
FMC_SDRAM_CommandTypeDef Command = {0};
/* USER CODE END FMC_Init 0 */
FMC_SDRAM_TimingTypeDef SdramTiming = {0};
/* USER CODE BEGIN FMC_Init 1 */
/* USER CODE END FMC_Init 1 */
/** Perform the SDRAM1 memory initialization sequence
*/
hsdram1.Instance = FMC_SDRAM_DEVICE;
/* hsdram1.Init */
hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
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_3;
hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0;
/* SdramTiming */
SdramTiming.LoadToActiveDelay = 2;
SdramTiming.ExitSelfRefreshDelay = 9;
SdramTiming.SelfRefreshTime = 6;
SdramTiming.RowCycleDelay = 8;
SdramTiming.WriteRecoveryTime = 4;
SdramTiming.RPDelay = 2;
SdramTiming.RCDDelay = 2;
if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
{
Error_Handler( );
}
/* USER CODE BEGIN FMC_Init 2 */
SDRAM_Initialization_Sequence(&hsdram1, &Command);
sdram_test();
/* USER CODE END FMC_Init 2 */
}
3、关键结构体说明
SDRAM_HandleTypeDef结构体说明
#if (USE_HAL_SDRAM_REGISTER_CALLBACKS == 1)
typedef struct __SDRAM_HandleTypeDef
#else
typedef struct
#endif /* USE_HAL_SDRAM_REGISTER_CALLBACKS */
{
FMC_SDRAM_TypeDef *Instance; /*!< Register base address */
FMC_SDRAM_InitTypeDef Init; /*!< SDRAM device configuration parameters */
__IO HAL_SDRAM_StateTypeDef State; /*!< SDRAM access state */
HAL_LockTypeDef Lock; /*!< SDRAM locking object */
MDMA_HandleTypeDef *hmdma; /*!< Pointer DMA handler */
#if (USE_HAL_SDRAM_REGISTER_CALLBACKS == 1)
void (* MspInitCallback)(struct __SDRAM_HandleTypeDef *hsdram); /*!< SDRAM Msp Init callback */
void (* MspDeInitCallback)(struct __SDRAM_HandleTypeDef *hsdram); /*!< SDRAM Msp DeInit callback */
void (* RefreshErrorCallback)(struct __SDRAM_HandleTypeDef *hsdram); /*!< SDRAM Refresh Error callback */
void (* DmaXferCpltCallback)(MDMA_HandleTypeDef *hmdma); /*!< SDRAM DMA Xfer Complete callback */
void (* DmaXferErrorCallback)(MDMA_HandleTypeDef *hmdma); /*!< SDRAM DMA Xfer Error callback */
#endif /* USE_HAL_SDRAM_REGISTER_CALLBACKS */
} SDRAM_HandleTypeDef;
FMC_SDRAM_InitTypeDef结构体说明(对应上文实战第三小节)
typedef struct
{
uint32_t SDBank; /*!< Specifies the SDRAM memory device that will be used.
This parameter can be a value of @ref FMC_SDRAM_Bank */
uint32_t ColumnBitsNumber; /*!< Defines the number of bits of column address.
This parameter can be a value of @ref FMC_SDRAM_Column_Bits_number. */
uint32_t RowBitsNumber; /*!< Defines the number of bits of column address.
This parameter can be a value of @ref FMC_SDRAM_Row_Bits_number. */
uint32_t MemoryDataWidth; /*!< Defines the memory device width.
This parameter can be a value of @ref FMC_SDRAM_Memory_Bus_Width. */
uint32_t InternalBankNumber; /*!< Defines the number of the device's internal banks.
This parameter can be of @ref FMC_SDRAM_Internal_Banks_Number. */
uint32_t CASLatency; /*!< Defines the SDRAM CAS latency in number of memory clock cycles.
This parameter can be a value of @ref FMC_SDRAM_CAS_Latency. */
uint32_t WriteProtection; /*!< Enables the SDRAM device to be accessed in write mode.
This parameter can be a value of @ref FMC_SDRAM_Write_Protection. */
uint32_t SDClockPeriod; /*!< Define the SDRAM Clock Period for both SDRAM devices and they allow
to disable the clock before changing frequency.
This parameter can be a value of @ref FMC_SDRAM_Clock_Period. */
uint32_t ReadBurst; /*!< This bit enable the SDRAM controller to anticipate the next read
commands during the CAS latency and stores data in the Read FIFO.
This parameter can be a value of @ref FMC_SDRAM_Read_Burst. */
uint32_t ReadPipeDelay; /*!< Define the delay in system clock cycles on read data path.
This parameter can be a value of @ref FMC_SDRAM_Read_Pipe_Delay. */
} FMC_SDRAM_InitTypeDef;
SDBank:可选择的SDRAM控制器,我们设置为FMC_SDRAM_BANK1
#define FMC_SDRAM_BANK1 (0x00000000U)
#define FMC_SDRAM_BANK2 (0x00000001U)
ColumnBitsNumber:列地址数,我们设置为FMC_SDRAM_COLUMN_BITS_NUM_9
#define FMC_SDRAM_COLUMN_BITS_NUM_8 (0x00000000U)
#define FMC_SDRAM_COLUMN_BITS_NUM_9 (0x00000001U)
#define FMC_SDRAM_COLUMN_BITS_NUM_10 (0x00000002U)
#define FMC_SDRAM_COLUMN_BITS_NUM_11 (0x00000003U)
RowBitsNumber:行地址数,我们设置为FMC_SDRAM_ROW_BITS_NUM_13
#define FMC_SDRAM_ROW_BITS_NUM_11 (0x00000000U)
#define FMC_SDRAM_ROW_BITS_NUM_12 (0x00000004U)
#define FMC_SDRAM_ROW_BITS_NUM_13 (0x00000008U)
MemoryDataWidth:存储器数据位宽,我们设置为FMC_SDRAM_MEM_BUS_WIDTH_16
#define FMC_SDRAM_MEM_BUS_WIDTH_8 (0x00000000U)
#define FMC_SDRAM_MEM_BUS_WIDTH_16 (0x00000010U)
#define FMC_SDRAM_MEM_BUS_WIDTH_32 (0x00000020U)
InternalBankNumber:外部SDRAM的Bank数,我们设置为FMC_SDRAM_INTERN_BANKS_NUM_4
#define FMC_SDRAM_INTERN_BANKS_NUM_2 (0x00000000U)
#define FMC_SDRAM_INTERN_BANKS_NUM_4 (0x00000040U)
CASLatency:即CL,读数据潜伏期,我们设置为FMC_SDRAM_CAS_LATENCY_3
#define FMC_SDRAM_CAS_LATENCY_1 (0x00000080U)
#define FMC_SDRAM_CAS_LATENCY_2 (0x00000100U)
#define FMC_SDRAM_CAS_LATENCY_3 (0x00000180U)
WriteProtection:写保护使能,我们设置为FMC_SDRAM_WRITE_PROTECTION_DISABLE
#define FMC_SDRAM_WRITE_PROTECTION_DISABLE (0x00000000U)
#define FMC_SDRAM_WRITE_PROTECTION_ENABLE (0x00000200U)
SDClockPeriod:FMC时钟分频系数,我们设置为FMC_SDRAM_CLOCK_PERIOD_2,即二分频
#define FMC_SDRAM_CLOCK_DISABLE (0x00000000U)
#define FMC_SDRAM_CLOCK_PERIOD_2 (0x00000800U)
#define FMC_SDRAM_CLOCK_PERIOD_3 (0x00000C00U)
ReadBurst:读突发使能,我们设置为FMC_SDRAM_RBURST_ENABLE
#define FMC_SDRAM_RBURST_DISABLE (0x00000000U)
#define FMC_SDRAM_RBURST_ENABLE (0x00001000U)
ReadPipeDelay:读操作额外时延,我们设置为FMC_SDRAM_RPIPE_DELAY_0
#define FMC_SDRAM_RPIPE_DELAY_0 (0x00000000U)
#define FMC_SDRAM_RPIPE_DELAY_1 (0x00002000U)
#define FMC_SDRAM_RPIPE_DELAY_2 (0x00004000U)
其中FMC_SDRAM_TimingTypeDef结构体定义了关键时间参数(对应上文实战第四小节)
/**
* @brief FMC SDRAM Timing parameters structure definition
*/
typedef struct
{
uint32_t LoadToActiveDelay; /*!< Defines the delay between a Load Mode Register command and
an active or Refresh command in number of memory clock cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
uint32_t ExitSelfRefreshDelay; /*!< Defines the delay from releasing the self refresh command to
issuing the Activate command in number of memory clock cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
uint32_t SelfRefreshTime; /*!< Defines the minimum Self Refresh period in number of memory clock
cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
uint32_t RowCycleDelay; /*!< Defines the delay between the Refresh command and the Activate command
and the delay between two consecutive Refresh commands in number of
memory clock cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
uint32_t WriteRecoveryTime; /*!< Defines the Write recovery Time in number of memory clock cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
uint32_t RPDelay; /*!< Defines the delay between a Precharge Command and an other command
in number of memory clock cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
uint32_t RCDDelay; /*!< Defines the delay between the Activate Command and a Read/Write
command in number of memory clock cycles.
This parameter can be a value between Min_Data = 1 and Max_Data = 16 */
} FMC_SDRAM_TimingTypeDef;
有些小伙伴如果不能理解注释可以根据此信息找到对应的时间参数信息
LoadToActiveDelay:即tRSC,模式寄存器设置命令生效时间,经过这么长时间后才能发起行激活或者刷新命令
ExitSelfRefreshDelay:即tXSR,退出自刷新命令与行激活命令之间的延时
SelfRefreshTime:即tRAS,自刷新周期,激活命令与预充电命令之间的时间;行激活后如果不及时充电,电荷就会消失
RowCycleDelay:即tRC,刷新命令和激活命令之间的时延或者刷新命令之间的时延
WriteRecoveryTime:即tWR,写恢复时间也就回写时间
RPDelay:即tRP,预充电命令与其他命令之间的延时
RCDDelay:即tRCD,行选通与读写命令之间的延时
SDRAM上电初始化代码
从之前博文中我们介绍过SDRAM需要进行上电初始化
我们通过FMC提供的接口可以给外部SDRAM芯片发送指令,其上电初始化以及模式寄存器的设置的代码如下
static void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command)
{
__IO uint32_t tmpmrd = 0;
uint32_t target_bank = 0;
target_bank = FMC_SDRAM_CMD_TARGET_BANK1;
/* Configure a clock configuration enable command */
Command->CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
Command->CommandTarget = target_bank;
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);
/* Insert 100 ms delay */
/* interrupt is not enable, just to delay some time. */
for (tmpmrd = 0; tmpmrd < 0xffff; tmpmrd ++)
;
/* Configure a PALL (precharge all) command */
Command->CommandMode = FMC_SDRAM_CMD_PALL;
Command->CommandTarget = target_bank;
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);
/* Configure a Auto-Refresh command */
Command->CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
Command->CommandTarget = target_bank;
Command->AutoRefreshNumber = 8;
Command->ModeRegisterDefinition = 0;
/* Send the command */
HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);
/* Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2 |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_3 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD |
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
Command->CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
Command->CommandTarget = target_bank;
Command->AutoRefreshNumber = 1;
Command->ModeRegisterDefinition = tmpmrd;
/* Send the command */
HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);
/* Set the device refresh counter */
HAL_SDRAM_ProgramRefreshRate(hsdram, SDRAM_REFRESH_COUNT);
}
我们先介绍一下FMC_SDRAM_CommandTypeDef结构体
typedef struct
{
uint32_t CommandMode; /*!< Defines the command issued to the SDRAM device.
This parameter can be a value of @ref FMC_SDRAM_Command_Mode. */
uint32_t CommandTarget; /*!< Defines which device (1 or 2) the command will be issued to.
This parameter can be a value of @ref FMC_SDRAM_Command_Target. */
uint32_t AutoRefreshNumber; /*!< Defines the number of consecutive auto refresh command issued
in auto refresh mode.
This parameter can be a value between Min_Data = 1 and Max_Data = 15 */
uint32_t ModeRegisterDefinition; /*!< Defines the SDRAM Mode register content */
} FMC_SDRAM_CommandTypeDef;
ComandMode设置具体的操作命令,包括正常模式、时钟使能、所有Bank预充电、自动刷新、加载模式寄存器、自刷新模式、掉电模式
#define FMC_SDRAM_CMD_NORMAL_MODE (0x00000000U)
#define FMC_SDRAM_CMD_CLK_ENABLE (0x00000001U)
#define FMC_SDRAM_CMD_PALL (0x00000002U)
#define FMC_SDRAM_CMD_AUTOREFRESH_MODE (0x00000003U)
#define FMC_SDRAM_CMD_LOAD_MODE (0x00000004U)
#define FMC_SDRAM_CMD_SELFREFRESH_MODE (0x00000005U)
#define FMC_SDRAM_CMD_POWERDOWN_MODE (0x00000006U)
CommandTarget设置给哪个SDRAM控制器发送,我们选择FMC_SDRAM_CMD_TARGET_BANK1
#define FMC_SDRAM_CMD_TARGET_BANK2 FMC_SDCMR_CTB2
#define FMC_SDRAM_CMD_TARGET_BANK1 FMC_SDCMR_CTB1
#define FMC_SDRAM_CMD_TARGET_BANK1_2 (0x00000018U)
AutoRefreshNumber设置自动刷新命令时需要刷新的次数,其他命令设置为1即可
ModeRegisterDefinition设置SDRAM芯片上模式寄存器的值,具体设置之前已经讲解,不再赘述
通过HAL_SDRAM_ProgramRefreshRate设置自动刷新定时器周期
/* 64ms / 8192 = 7.8125us
* 7.8125 * 120MHz - 20 = 917.5 ==> 918
*/
至此我们已经配置好了整个SDRAM相关的选项
最后我们关注一下MPU的设置
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* Disable the MPU */
HAL_MPU_Disable();
/* Configure the MPU attributes as WT for AXI SRAM */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0X00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure the MPU attributes as WT for SDRAM */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enable the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
八、调试
我们写了一个测试函数用于测试SDRAM的读写
static int sdram_test(void)
{
int i = 0;
#if SDRAM_DATA_WIDTH == 8
char data_width = 1;
uint8_t data = 0;
#elif SDRAM_DATA_WIDTH == 16
char data_width = 2;
uint16_t data = 0;
#else
char data_width = 4;
uint32_t data = 0;
#endif
/* write data */
for (i = 0; i < SDRAM_SIZE / data_width; i++)
{
#if SDRAM_DATA_WIDTH == 8
*(__IO uint8_t *)(SDRAM_BANK_ADDR + i * data_width) = (uint8_t)0x55;
#elif SDRAM_DATA_WIDTH == 16
*(__IO uint16_t *)(SDRAM_BANK_ADDR + i * data_width) = (uint16_t)0x5555;
#else
*(__IO uint32_t *)(SDRAM_BANK_ADDR + i * data_width) = (uint32_t)0x55555555;
#endif
}
/* read data */
for (i = 0; i < SDRAM_SIZE / data_width; i++)
{
#if SDRAM_DATA_WIDTH == 8
data = *(__IO uint8_t *)(SDRAM_BANK_ADDR + i * data_width);
if (data != 0x55)
{
//ERROR
break;
}
#elif SDRAM_DATA_WIDTH == 16
data = *(__IO uint16_t *)(SDRAM_BANK_ADDR + i * data_width);
if (data != 0x5555)
{
//ERROR
break;
}
#else
data = *(__IO uint32_t *)(SDRAM_BANK_ADDR + i * data_width);
if (data != 0x55555555)
{
//ERROR
break;
}
#endif
}
if (i >= SDRAM_SIZE / data_width)
{
//OK
}
}
通过SDRAM_DATA_WIDTH 这个宏,我们可以分别测试8/16/32位读写测试
最后工程代码如下
链接:https://pan.baidu.com/s/1MgM2DUKRCadZUOIBEhIZgg
提取码:rnim
记得要三连哦