一、什么是FMC?

FMC(Flexible Memory Controller,灵活存储控制器)是嵌入式微控制器中用于高效扩展与管理外部存储器的核心外设,尤其在GD32/STM32等高性能MCU中广泛应用。以下从硬件架构、功能特性及实际应用三方面详细解析:


⚙️ 一、FMC的核心功能与硬件架构

  1. 地址空间划分
    FMC将外部存储器划分为多个独立存储块(Bank),每个Bank有专属配置寄存器:

    • Bank1:分为4个子区(64MB/区),共256MB空间,通过FMC_NE1~4片选信号控制
    • Bank2/3/4:各256MB空间,支持SDRAM(Bank3专用)、NOR/PSRAM等
    • 统一寻址:外部设备映射到MCU地址空间(如Bank1起始地址0x6000_0000),CPU可直接指针访问
  2. 自适应数据宽度
    FMC自动调整地址线连接方式,适配不同位宽的存储器:

    存储器宽度 内部HADDR与FMC_A关系 典型设备
    8位 HADDR[25:0] → FMC_A[25:0] NOR Flash
    16位 HADDR[25:1] → FMC_A[24:0] TFT LCD(RS接A18)
    32位 HADDR[25:2] → FMC_A[23:0] SDRAM
  3. 多协议支持

    • SRAM/PSRAM:静态存储器,直连读写
    • SDRAM:支持自动刷新、突发传输,需配置刷新计数器(如64ms刷新4096行)
    • NOR/NAND Flash:提供异步时序控制,NAND支持硬件ECC校验

⏱️ 二、关键配置要点

  1. 时序参数设置
    需根据存储器手册配置建立/保持时间:

    // 示例:NOR Flash时序配置(GD32库函数)
    fmc_norsram_timing_init(FMC_BANK1, &timing_cfg);
    timing_cfg.asyn_setuptime = 3;   // 地址建立时间(HCLK周期)
    timing_cfg.asyn_waittime  = 6;   // 数据等待时间
    timing_cfg.asyn_holdtime  = 2;   // 地址保持时间
    
  2. SDRAM专用配置

    • 刷新率刷新周期 = (刷新行数 × 刷新周期) / 时钟频率
    • 预充电延迟(tRP)、行选通延迟(tRCD)等需严格匹配颗粒规格
  3. 地址映射冲突规避
    FMC的Bank地址(如0x6000_0000~0x6FFF_FFFF)需避免与内部Flash或RAM重叠,否则访问异常


🖥️ 三、典型应用场景

  1. 图形显示系统

    • 驱动16位TFT LCD:将显存映射至FMC Bank1,MCU直接写入像素数据
    • 优化技巧:使用Chrom-ART加速器(DMA2D)实现图层混合,降低CPU负载
  2. 高速数据采集

    • 外扩32位SDRAM(Bank3)存储ADC数据:
      uint32_t *adc_buffer = (uint32_t*)0xC0000000; // SDRAM起始地址
      adc_buffer[0] = ADC_Read();                   // 直接写入
      
    • 配合DMA实现无人值守传输,适合音频/视频流处理
  3. 大容量存储

    • NOR Flash存储固件备份(Bank1),NAND Flash(Bank2)存储文件系统,硬件ECC提升可靠性

🔍 四、FMC vs EXMC(GD32命名)的区别

特性 FMC(STM32/GD32) EXMC(GD32)
命名规范 STM32称FMC,GD32后续系列沿用 GD32F4系列早期命名
功能覆盖 支持SDRAM专用控制、硬件ECC 基础异步存储器控制
性能 时钟同步设计(200MHz+) 异步时序(≤100MHz)

💡 :GD32F427的“FMC”实为EXMC升级版,保留相同功能但优化了时序引擎。


⚠️ 五、设计注意事项

  1. 信号完整性

    • SDRAM时钟线(CLK)需等长布线,长度差≤5mm
    • DQS信号(数据选通)串联22Ω电阻匹配阻抗
  2. 功耗管理
    未使用的Bank关闭时钟:

    rcu_periph_clock_disable(RCU_FMC); // 禁用FMC时钟
    
  3. 容错机制

    • 使能MPU(Memory Protection Unit)配置SDRAM为Write-Back缓存策略,避免数据丢失
    • SDRAM初始化后需执行自刷新命令进入低功耗模式

二、使用FMC读写内部Flash

操作GD32官方提供的标准库函数,能够直接进行读写内部flash,但是要记住,不论是读还是写,进行操作之前都要先将fmc锁打开,操作完成一定要重新上锁。下面附上fmc读写flash代码。

void gd32_EraseFlash(uint32_t start)
{
    fmc_state_enum state;
    uint32_t address = start;
    
    /* 检查地址是否4字节对齐 */
    if(address & 0x3) {
        printf("Error: Address not aligned to 4 bytes\n");
        return;
    }
    
    /* 检查地址范围 */
    if(address < 0x08000000 || address >= 0x08100000) {
        printf("Error: Address out of flash range\n");
        return;
    }
    
    fmc_unlock();
    
    /* 等待Flash就绪 */
    state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if(state != FMC_READY) {
        printf("Flash not ready before erase\n");
        fmc_lock();
        return;
    }
    
    state = fmc_page_erase(address);
    printf("Erase at 0x%08X, state: %d\n", address, state);
    
    /* 等待操作完成 */
    state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if(state != FMC_READY) {
        printf("Flash erase error: %d\n", state);
    }
    
    fmc_lock();
}

void gd32_WriteFlash(uint32_t start, uint32_t *pdata, uint32_t num)
{
    uint32_t saddress = start;
    fmc_state_enum state;
    
    /* 检查地址是否4字节对齐 */
    if(saddress & 0x3) {
        printf("Error: Address not aligned to 4 bytes\n");
        return;
    }
    
    /* 检查地址范围 */
    if(saddress < 0x08000000 || saddress >= 0x08100000) {
        printf("Error: Address out of flash range\n");
        return;
    }
    
    fmc_unlock();
    
    /* 等待Flash就绪 */
    state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if(state != FMC_READY) {
        printf("Flash not ready before write\n");
        fmc_lock();
        return;
    }
    
    while(num)  
    {
        state = fmc_word_program(saddress, *pdata);
        if(state != FMC_READY) {
            printf("Flash write error: %d\r\n", state);
            break;
        }
        
        /* 等待写入完成 */
        state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
        if(state != FMC_READY) {
            printf("Flash write completion error: %d\r\n", state);
            break;
        }
        
        num -= 4;
        saddress += 4;
        pdata++;
    }

    fmc_lock();
}

这个时候可能有人会问,怎么没有读取数据函数啊?
切记,我们操作的是flash,内部flash的内存地址我们是可以直接进行访问的。例如,你将一串数据存在0x2000000的起始地址,那么你可以直接访问0x2000000这个地址。如何访问呢?主要是通过指针的解引用操作,例如我要访问0x2000000,应该就是*(uint32_t *)0x2000000,开头的*代表我要解引用这个地址,使用uint32_t *来强转这个地址,这样我们就可以直接访问0x2000000地址的数据了。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐