接今年2月份简单总结的IIC、 SPI 总线的个人使用,把FMC 总线也简单记录下。
一、总线简介:
STM32H7 中叫做 FMC总线;GD32H7 中 叫做 EXMC 总线。
STM32H7 的 FMC 总线是挂载 64 位带宽的 AXI 总线上,F1,F4 和 F7 是挂在 32 位总线上。
使用 FMC,可以用来外挂 NOR/PSRAM 型存储器,SRAM 型存储器,NAND 型存储器,SDRAM等存储器。支持8~32bit 总线带宽控制。
每个片选下的存储器空间配置都是独立的,有专门的寄存器,互不影响。这点比较重要,要牢记。(以下硬件框图,来自STM32)
![图片[1] - FMC(EXMC)总线个人总结 - 宋马](https://pic.songma.com/blogimg/20250613/f4e1ffdd578a458d9f4c1fb1ec9a9d92.png)
以下 地址区域分配(来自STM32)
![图片[2] - FMC(EXMC)总线个人总结 - 宋马](https://pic.songma.com/blogimg/20250613/cd07fd31f0a04c90bf440af3f02e436e.png)
二、个人应用:
总线外扩SDRAM。主控芯片使用的GD32H759,扩展芯片SDRAM 选用IS42S16400J_5BLI, 见其手册说明:
4个BANKS; 内存大小64Mb=8MB 字节;
自刷新模式(Self refresh),4096 次刷新每64ms (A1 );
CAS latency (2,3 clocks);
主频高达200MHz; 等等。
<1> 扩展芯片的管脚定义:

本SDRAM 的数据线是16bit ,数据通讯时,16位数据是同步传输的。实际使用时,我们可能用到8bit 或者16bit ,即: 16bit 线不是所有时候都同步使用。所以:用到了 LDQM线和UDQM线来配合,每根线对应8bit 数据。比如:LDQM为低电平,UDQM为高电平时,DQ[7:0]数据有效,DQ[15:8]数据无效。与MCU 硬件设计连接时,注意看下MCU 对应的两根线,不要接反,否则数据就错了。
ps:
这次的项目开发过程,硬件设计这两根线与MCU -EXMC 的连接就反了,结果是:16bit 数据读写正确,8bit 数据错了,我花了几天功夫帮硬件工程师排查到原因。
**<2> 原理图
![图片[3] - FMC(EXMC)总线个人总结 - 宋马](https://pic.songma.com/blogimg/20250613/46e2139d2f95476697983ec3e29baf69.png)
以上原理图参考 GD32H759 开发板的设计,用的是另一款SDRAM 芯片,都类似。
<3> 驱动程序
我设计时用的IS42S16400J_5BLI,时钟选150MHZ ,驱动实现如下:
void exmc_synchronous_dynamic_ram_init(uint32_t sdram_device) //150MHZ
{
exmc_sdram_parameter_struct sdram_init_struct;
exmc_sdram_timing_parameter_struct sdram_timing_init_struct={
0};
exmc_sdram_command_parameter_struct sdram_command_init_struct={
0};
exmc_sdram_struct_para_init(&sdram_init_struct);
uint32_t command_content = 0, bank_select;
uint32_t timeout = SDRAM_TIMEOUT;
rcu_exmc_clock_config(RCU_EXMCSRC_PLL1R); //选择PLL1R 作为SDRAM 的时钟源.//注意看MCU 手册的时钟树,EXMC的时钟源好几种选择。
/* enable EXMC clock */
rcu_periph_clock_enable(RCU_EXMC);
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_GPIOF);
rcu_periph_clock_enable(RCU_GPIOG);
/*common GPIO configuration */
//-----------------------PORT-C--------------------------------------------------//
/*SDNWE(PC0)*/
gpio_af_set(EXMC_SDNWE_PORT_C , GPIO_AF_12, EXMC_SDNWE_PIN);
gpio_mode_set(EXMC_SDNWE_PORT_C , GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_SDNWE_PIN);
gpio_output_options_set(EXMC_SDNWE_PORT_C , GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_SDNWE_PIN);
/*SDNE0(PC2)*/
gpio_af_set(EXMC_SDNE0_PORT_C, GPIO_AF_12, EXMC_SDNE0_PIN);
gpio_mode_set(EXMC_SDNE0_PORT_C, GPIO_MODE_AF,GPIO_PUPD_NONE, EXMC_SDNE0_PIN);
gpio_output_options_set(EXMC_SDNE0_PORT_C, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_SDNE0_PIN);
/*SDCKE0(PC5)*/
gpio_af_set(EXMC_SDCKE0_PORT_C, GPIO_AF_12, EXMC_SDCKE0_PIN);
gpio_mode_set(EXMC_SDCKE0_PORT_C, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_SDCKE0_PIN);
gpio_output_options_set(EXMC_SDCKE0_PORT_C, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_SDCKE0_PIN);
/*D6(PC12)pin cfghj2onfiguration */
gpio_af_set(EXMC_DATA_PORT_C, GPIO_AF_1, EXMC_D6_PIN);
gpio_mode_set(EXMC_DATA_PORT_C, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_D6_PIN);
gpio_output_options_set(EXMC_DATA_PORT_C, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_D6_PIN);
//-----------------------PORT-D--------------------------------------------------//
/*D2(PD0),D3(PD1),D13-D15(PD8~PD10),D0(PD14),D1(PD15), pin configuration */
gpio_af_set(GPIOD, GPIO_AF_12, EXMC_D0_PIN| EXMC_D1_PIN| EXMC_D2_PIN | EXMC_D3_PIN|
EXMC_D13_PIN| EXMC_D14_PIN| EXMC_D15_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_D0_PIN| EXMC_D1_PIN| EXMC_D2_PIN | EXMC_D3_PIN|
EXMC_D13_PIN| EXMC_D14_PIN| EXMC_D15_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_D0_PIN| EXMC_D1_PIN| EXMC_D2_PIN | EXMC_D3_PIN|
EXMC_D13_PIN| EXMC_D14_PIN| EXMC_D15_PIN);
/*D7(PD2)*/
gpio_af_set(GPIOD, GPIO_AF_1,EXMC_D7_PIN);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_D7_PIN);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_D7_PIN);
//-----------------------PORT-E--------------------------------------------------//
/*NBL0(PE0),NBL1(PE1),D4(PE7),D5(PE8),D8(PE11),D9(PE12),D10(PE13),D11(PE14), D12(PE15)pin configuration */
gpio_af_set(GPIOE, GPIO_AF_12, EXMC_NBL0_PIN | EXMC_NBL1_PIN | EXMC_D4_PIN | EXMC_D5_PIN|
EXMC_D8_PIN | EXMC_D9_PIN | EXMC_D10_PIN| EXMC_D11_PIN| EXMC_D12_PIN);
gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_NBL0_PIN | EXMC_NBL1_PIN | EXMC_D4_PIN | EXMC_D5_PIN|
EXMC_D8_PIN | EXMC_D9_PIN | EXMC_D10_PIN| EXMC_D11_PIN| EXMC_D12_PIN);
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_NBL0_PIN | EXMC_NBL1_PIN | EXMC_D4_PIN | EXMC_D5_PIN|
EXMC_D8_PIN | EXMC_D9_PIN | EXMC_D10_PIN| EXMC_D11_PIN| EXMC_D12_PIN);
//-----------------------PORT-F--------------------------------------------------//
/* A0(PF0),A1(PF1),A2(PF2),A3(PF3),A4(PF4),A5(PF5),NRAS(PF11),A6(PF12),A7(PF13),A8(PF14),A9(PF15) pin configuration */
gpio_af_set(GPIOF, GPIO_AF_12, EXMC_A0_PIN | EXMC_A1_PIN | EXMC_A2_PIN | EXMC_A3_PIN |EXMC_A4_PIN|EXMC_A5_PIN |
EXMC_SDNRAS_PIN | EXMC_A6_PIN | EXMC_A7_PIN| EXMC_A8_PIN | EXMC_A9_PIN);
gpio_mode_set(GPIOF, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_A0_PIN | EXMC_A1_PIN | EXMC_A2_PIN | EXMC_A3_PIN |EXMC_A4_PIN|EXMC_A5_PIN |
EXMC_SDNRAS_PIN | EXMC_A6_PIN | EXMC_A7_PIN| EXMC_A8_PIN | EXMC_A9_PIN);
gpio_output_options_set(GPIOF, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_A0_PIN | EXMC_A1_PIN | EXMC_A2_PIN | EXMC_A3_PIN |EXMC_A4_PIN|
EXMC_A5_PIN | EXMC_SDNRAS_PIN | EXMC_A6_PIN | EXMC_A7_PIN| EXMC_A8_PIN | EXMC_A9_PIN);
//-----------------------PORT-G--------------------------------------------------//
/*A10(PG0),A11(PG1),A12(PG2),BA0/A14(PG4),BA1/A15(PG5), SDCLK(PG8), NCAS(PG15) pin configuration */
gpio_af_set(GPIOG, GPIO_AF_12, EXMC_A10_PIN | EXMC_A11_PIN | EXMC_A12_PIN| EXMC_BA0_PIN |
EXMC_BA1_PIN | EXMC_SDCLK_PIN | EXMC_SDNCAS_PIN);
gpio_mode_set(GPIOG, GPIO_MODE_AF, GPIO_PUPD_NONE, EXMC_A10_PIN | EXMC_A11_PIN | EXMC_A12_PIN| EXMC_BA0_PIN |
EXMC_BA1_PIN | EXMC_SDCLK_PIN | EXMC_SDNCAS_PIN);
gpio_output_options_set(GPIOG, GPIO_OTYPE_PP, GPIO_OSPEED_85MHZ, EXMC_A10_PIN | EXMC_A11_PIN | EXMC_A12_PIN| EXMC_BA0_PIN |
EXMC_BA1_PIN | EXMC_SDCLK_PIN | EXMC_SDNCAS_PIN);
/*specify which SDRAM to read and write*/
if(EXMC_SDRAM_DEVICE0 == sdram_device)
{
bank_select = EXMC_SDRAM_DEVICE0_SELECT;
}
else
{
bank_select = EXMC_SDRAM_DEVICE1_SELECT;
}
/* EXMC SDRAM device initialization sequence --------------------------------*/
/* step 1 : configure SDRAM timing registers --------------------------------*/
//150MHZ 主频,周期6.7ns. 以下参数配置根据SDRAM用户手册中的说明。
/* LMRD: 2 clock cycles */
sdram_timing_init_struct.load_mode_register_delay = 2;
/*exit_selfrefresh=66ns*/ //XSRD
sdram_timing_init_struct.exit_selfrefresh_delay = 11;
/* ARFD: min=60ns */
sdram_timing_init_struct.auto_refresh_delay = 11; //自动刷新
/* RASD*/
sdram_timing_init_struct.row_address_select_delay = 11;
/* WRD: min=2 Clock cycles */
sdram_timing_init_struct.write_recovery_delay = 3;
/* RPD: min=12ns */
sdram_timing_init_struct.row_precharge_delay = 3;
/* RCD: min=15ns */
sdram_timing_init_struct.row_to_column_delay = 3;
/*step 2 : configure SDRAM control registers ---------------------------------*/
sdram_init_struct.sdram_device = sdram_device;
/*看SDRAM手册:12行8列.*/
sdram_init_struct.column_address_width = EXMC_SDRAM_COW_ADDRESS_8; //列
sdram_init_struct.row_address_width = EXMC_SDRAM_ROW_ADDRESS_12; //行
sdram_init_struct.data_width =EXMC_SDRAM_DATABUS_WIDTH_16B;
/*SDRAM内部bank数量*/
sdram_init_struct.internal_bank_number = EXMC_SDRAM_4_INTER_BANK;
/*CAS潜伏期 */
sdram_init_struct.cas_latency = EXMC_CAS_LATENCY_3_SDCLK;
/*禁止写保护*/
sdram_init_struct.write_protection = DISABLE;
/*时钟分频因子,对于300MHZ进行的分频*/
sdram_init_struct.sdclock_config = EXMC_SDCLK_PERIODS_2_CK_EXMC; //时钟2分频=150MHZ.//MCU 手册中EXMC 时钟分频因子有几种选择。
/*突发读模式设置*/
sdram_init_struct.burst_read_switch = ENABLE;
/*读延迟配置 */
sdram_init_struct.pipeline_read_delay = EXMC_PIPELINE_DELAY_1_CK_EXMC;
sdram_init_struct.timing = &sdram_timing_init_struct;
/*EXMC SDRAM bank initialization */
exmc_sdram_init(&sdram_init_struct);
/*step3 : configure CKE high command---------------------------------------*/
sdram_command_init_struct.command = EXMC_SDRAM_CLOCK_ENABLE;
sdram_command_init_struct.bank_select = bank_select;
sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK;
sdram_command_init_struct.mode_register_content = 0;
/* wait until the SDRAM controller is ready */
while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0))
{
timeout--;
}
/* send the command */
exmc_sdram_command_config(&sdram_command_init_struct);
/* step 4 : insert 10ms delay------------------------------------------------*/
_delay(100);
/* step 5 : configure precharge all command----------------------------------*/
sdram_command_init_struct.command = EXMC_SDRAM_PRECHARGE_ALL;
sdram_command_init_struct.bank_select = bank_select;
sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK;
sdram_command_init_struct.mode_register_content = 0;
/* wait until the SDRAM controller is ready检查SDRAM标志,等待至SDRAM空闲 */
timeout = SDRAM_TIMEOUT;
while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0))
{
timeout--;
}
/* send the command */
exmc_sdram_command_config(&sdram_command_init_struct);
/* step 6 : configure Auto-Refresh command-----------------------------------*/
sdram_command_init_struct.command = EXMC_SDRAM_AUTO_REFRESH;
sdram_command_init_struct.bank_select = bank_select;
// sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_8_SDCLK; //原始
sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_4_SDCLK; //2024-9-23 修改 for test.:也正常.
sdram_command_init_struct.mode_register_content = 0;
/* wait until the SDRAM controller is ready */
timeout = SDRAM_TIMEOUT;
/* 检查SDRAM标志,等待至SDRAM空闲 */
while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0))
{
timeout--;
}
/* send the command */ //发送第1次自动刷新命令.
exmc_sdram_command_config(&sdram_command_init_struct);
/* wait until the SDRAM controller is ready */
timeout = SDRAM_TIMEOUT;
/* 检查SDRAM标志,等待至SDRAM空闲 */
while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0))
{
timeout--;
}
/* send the command */ //发送第2次自动刷新命令.
exmc_sdram_command_config(&sdram_command_init_struct);
/* step 7 : configure load mode register command-----------------------------*/
/* program mode register */
command_content = (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;
sdram_command_init_struct.command = EXMC_SDRAM_LOAD_MODE_REGISTER;
sdram_command_init_struct.bank_select = bank_select;
sdram_command_init_struct.auto_refresh_number = EXMC_SDRAM_AUTO_REFLESH_1_SDCLK;
sdram_command_init_struct.mode_register_content = command_content;
/* wait until the SDRAM controller is ready */
timeout = SDRAM_TIMEOUT;
while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0))
{
timeout--;
}
/* send the command */
exmc_sdram_command_config(&sdram_command_init_struct);
/* step 8 : set the auto-refresh rate counter--------------------------------*/
/* 64ms, 4096cle refresh, 64ms/4096=15.625us
/* SDCLK_Freq = SYS_Freq/2 */ // GD32H759 的SDRAM 从300MHZ分过来,系数最小2,时钟最快150MHZ.
/* (15.625us*SDCLK_Freq) - 20 */ // 最大15.625us*150M-20=2323.
exmc_sdram_refresh_count_set(2323);
/* wait until the SDRAM controller is ready */
timeout = SDRAM_TIMEOUT;
while((exmc_flag_get(sdram_device, EXMC_SDRAM_FLAG_NREADY) != RESET) && (timeout > 0))
{
timeout--;
}
exmc_sdram_write_protection_config(sdram_device, DISABLE);
}
以上:GD32H759 扩展的IS42S6400J-5BLI是驱动成功了,编写测试小程序:从SDRAM 读写8bit和16 bit 数据到SRAM, 看内存都是正确的。
然后,就可以把SDRAM 当成片上内存一样使用了。
<4> 调试问题总结记录**
开发过程,遇到两次问题:
硬件LDQM 和UDQM 两线设计反了,影响我程序中8bit 数据读写错误,上文已述;
这款SDRAM 手册中说明最高200MHZ, 实际我驱动程序按照200MHZ 配置,且验证:从SDRAM 读写8bit和16 bit 数据到SRAM, 看内存都是正确的。然而:用到产品中(处理算法显示图像)就比较杂乱了,150MHZ 则是正常的,到目前没明确原因???
以上,简单记录。
后期若200MHZ 图像问题有进展,更新。
三. 其他
去年,还基于FMC 总线同时驱动过SDRAM 和 一款外部ADC 模拟芯片,数据采样也是正常的。
要注意:硬件原理图设计时,片选的连线。








![[三菱PLC] 三菱FX PLC控制MR-J4/JE-A伺服 - 宋马](https://pic.songma.com/blogimg/20250525/675f0592c0794262b0e5507d1ad84b73.jpg)








暂无评论内容