FMC(EXMC)总线个人总结

  接今年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)总线个人总结 - 宋马
以下 地址区域分配(来自STM32)
图片[2] - FMC(EXMC)总线个人总结 - 宋马
二、个人应用:
   总线外扩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)总线个人总结 - 宋马
以上原理图参考 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 模拟芯片,数据采样也是正常的。
要注意:硬件原理图设计时,片选的连线。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容