为了账号安全,请及时绑定邮箱和手机立即绑定

RAM和ROM的结构和原理

标签:
C++

大家好,我是良许。

在嵌入式开发中,RAM和ROM是我们每天都要打交道的两种存储器。

无论是调试STM32单片机时查看内存分配,还是优化程序性能时分析存储结构,深入理解它们的工作原理都至关重要。

今天我们就从底层硬件结构出发,聊聊这两种存储器的本质区别和工作机制。

1. RAM的结构与工作原理

RAM(Random Access Memory,随机存取存储器)是一种易失性存储器,断电后数据会丢失。

它主要分为SRAM和DRAM两大类。

1.1 SRAM的结构原理

SRAM(Static RAM,静态随机存取存储器)使用双稳态触发器来存储数据。

每个存储单元由6个晶体管组成,形成一个锁存器电路。

这种结构的优点是只要供电,数据就能一直保持,不需要刷新操作。

SRAM的基本存储单元包含两个交叉耦合的反相器,它们形成一个正反馈回路。

当存储"1"时,一个反相器输出高电平,另一个输出低电平;存储"0"时则相反。

这种双稳态结构使得数据非常稳定,读写速度也很快。

在STM32等单片机中,片上SRAM就是这种类型。

比如STM32F103系列有20KB的SRAM,它被映射到0x20000000地址开始的空间。

当我们定义全局变量或使用malloc动态分配内存时,实际上就是在使用这块SRAM。

// SRAM使用示例
uint8_t global_buffer[1024];  // 全局变量存储在SRAM中

void sram_test(void)
{
    // 局部变量也在SRAM中(栈区)
    uint32_t local_var = 0x12345678;
    
    // 动态分配内存在SRAM的堆区
    uint8_t *dynamic_buffer = (uint8_t*)malloc(512);
    
    if(dynamic_buffer != NULL) {
        // 对SRAM进行读写操作
        for(int i = 0; i < 512; i++) {
            dynamic_buffer[i] = i & 0xFF;
        }
        
        free(dynamic_buffer);
    }
}

SRAM的访问速度非常快,通常只需要几纳秒,而且功耗相对较低(在静态状态下)。

但它的缺点是集成度低,每个bit需要6个晶体管,因此成本较高,容量也受限。

1.2 DRAM的结构原理

DRAM(Dynamic RAM,动态随机存取存储器)的结构要简单得多,每个存储单元只需要1个晶体管和1个电容。

电容充电表示"1",放电表示"0"。

这种结构使得DRAM的集成度非常高,可以做到很大的容量。

但DRAM有个致命缺点:电容会漏电。

即使不进行读写操作,电容上的电荷也会逐渐流失,导致数据丢失。

因此DRAM需要定期刷新,通常每隔几十毫秒就要重新充电一次。

这个刷新操作会消耗额外的功耗,也会占用一定的访问时间。

在嵌入式系统中,如果需要使用大容量内存(比如运行Linux系统的ARM开发板),通常会外接DRAM芯片,比如DDR、DDR2、DDR3等。

这些都是DRAM的改进版本,通过双倍数据速率等技术提升性能。

// 在Linux应用开发中使用DRAM的示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void dram_usage_example(void)
{
    // 在Linux系统中,malloc分配的内存来自DRAM
    size_t size = 10 * 1024 * 1024;  // 10MB
    char *large_buffer = (char*)malloc(size);
    
    if(large_buffer != NULL) {
        // 初始化内存
        memset(large_buffer, 0xAA, size);
        
        // 进行一些操作
        printf("Allocated %zu bytes in DRAM\n", size);
        
        // 释放内存
        free(large_buffer);
    }
}

1.3 RAM的读写时序

RAM的读写操作需要遵循特定的时序。

以SRAM为例,读操作的基本流程是:首先将地址信号放到地址总线上,然后拉低片选信号CS和读使能信号OE,经过一定的访问时间后,数据就会出现在数据总线上。

写操作则需要将地址和数据同时准备好,然后拉低片选信号CS和写使能信号WE,保持一定时间后完成写入。

在STM32中使用FSMC(灵活静态存储控制器)外接SRAM时,我们需要配置这些时序参数:

void FSMC_SRAM_Init(void)
{
    FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
    FSMC_NORSRAMTimingInitTypeDef ReadWriteTiming;
    
    // 配置读写时序
    ReadWriteTiming.FSMC_AddressSetupTime = 0x00;      // 地址建立时间
    ReadWriteTiming.FSMC_AddressHoldTime = 0x00;       // 地址保持时间
    ReadWriteTiming.FSMC_DataSetupTime = 0x03;         // 数据建立时间
    ReadWriteTiming.FSMC_BusTurnAroundDuration = 0x00; // 总线转换时间
    ReadWriteTiming.FSMC_CLKDivision = 0x00;
    ReadWriteTiming.FSMC_DataLatency = 0x00;
    ReadWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;
    
    // FSMC配置
    FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM3;
    FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
    FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
    FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
    FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
    FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
    FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
    FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
    FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
    FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
    FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &ReadWriteTiming;
    FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &ReadWriteTiming;
    
    FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE);
}

2. ROM的结构与工作原理

ROM(Read-Only Memory,只读存储器)是一种非易失性存储器,断电后数据不会丢失。

虽然叫"只读",但现代的ROM大多数都可以擦写,只是擦写次数和速度有限制。

2.1 Flash存储器的结构

在嵌入式系统中,我们最常用的ROM其实是Flash存储器。

Flash分为NOR Flash和NAND Flash两种类型。

STM32等单片机内部集成的就是NOR Flash。

NOR Flash的存储单元是浮栅晶体管。

每个晶体管有两个栅极:控制栅和浮栅。浮栅被绝缘层包围,可以长期保存电荷。

当浮栅中有电荷时,晶体管的阈值电压升高,表示"0";没有电荷时阈值电压较低,表示"1"。

Flash的编程(写入)操作是通过量子隧穿效应实现的。

在控制栅和漏极之间施加高电压,电子就会穿过绝缘层进入浮栅。

擦除操作则相反,通过施加反向高电压,让电子从浮栅中逸出。

2.2 Flash的扇区和页

Flash存储器不能像RAM那样随意读写,它有特定的组织结构。

Flash被分成多个扇区(Sector),每个扇区又分成多个页(Page)。

擦除操作只能以扇区为单位,而编程操作通常以页或字为单位。

以STM32F103为例,它的Flash被分成多个1KB或2KB的页。

在编程之前必须先擦除对应的页,擦除后所有bit都变成1(0xFF)。

然后才能进行编程操作,将某些bit从1变成0。

如果要将0变回1,必须重新擦除整个页。

// STM32 Flash操作示例
#include "stm32f1xx_hal.h"

#define FLASH_USER_START_ADDR   0x08010000  // 用户Flash起始地址
#define FLASH_USER_END_ADDR     0x0801FFFF  // 用户Flash结束地址

// 擦除Flash
HAL_StatusTypeDef Flash_Erase(uint32_t start_addr, uint32_t end_addr)
{
    HAL_StatusTypeDef status = HAL_OK;
    FLASH_EraseInitTypeDef EraseInitStruct;
    uint32_t PageError = 0;
    
    // 解锁Flash
    HAL_FLASH_Unlock();
    
    // 配置擦除参数
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
    EraseInitStruct.PageAddress = start_addr;
    EraseInitStruct.NbPages = (end_addr - start_addr) / FLASH_PAGE_SIZE;
    
    // 执行擦除
    status = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
    
    // 锁定Flash
    HAL_FLASH_Lock();
    
    return status;
}

// 写入Flash
HAL_StatusTypeDef Flash_Write(uint32_t addr, uint32_t *data, uint32_t len)
{
    HAL_StatusTypeDef status = HAL_OK;
    uint32_t i;
    
    // 解锁Flash
    HAL_FLASH_Unlock();
    
    // 按字写入
    for(i = 0; i < len; i++) {
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 
                                   addr + i * 4, 
                                   data[i]);
        if(status != HAL_OK) {
            break;
        }
    }
    
    // 锁定Flash
    HAL_FLASH_Lock();
    
    return status;
}

// 读取Flash
void Flash_Read(uint32_t addr, uint32_t *data, uint32_t len)
{
    uint32_t i;
    
    for(i = 0; i < len; i++) {
        data[i] = *(__IO uint32_t*)(addr + i * 4);
    }
}

2.3 Flash的寿命和磨损均衡

Flash存储器的擦写次数是有限的,通常NOR Flash可以擦写10万到100万次。

这是因为每次擦写都会对绝缘层造成微小的损伤,累积到一定程度后,浮栅就无法可靠地保持电荷了。

在实际应用中,如果频繁地对同一个扇区进行擦写,会导致该扇区提前损坏。

因此需要使用磨损均衡技术,尽量让各个扇区的擦写次数保持均衡。

比如在存储日志数据时,可以采用循环写入的方式,每次写入不同的扇区。

// 简单的循环写入示例
#define LOG_SECTOR_COUNT    10
#define LOG_SECTOR_SIZE     4096

typedef struct {
    uint32_t current_sector;
    uint32_t write_count[LOG_SECTOR_COUNT];
} LogManager_t;

LogManager_t log_mgr = {0};

void Log_Write(uint8_t *data, uint32_t len)
{
    uint32_t sector_addr = FLASH_USER_START_ADDR + 
                          log_mgr.current_sector * LOG_SECTOR_SIZE;
    
    // 擦除当前扇区
    Flash_Erase(sector_addr, sector_addr + LOG_SECTOR_SIZE - 1);
    
    // 写入数据
    Flash_Write(sector_addr, (uint32_t*)data, len / 4);
    
    // 更新写入计数
    log_mgr.write_count[log_mgr.current_sector]++;
    
    // 切换到下一个扇区
    log_mgr.current_sector = (log_mgr.current_sector + 1) % LOG_SECTOR_COUNT;
}

2.4 EEPROM的特点

EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程只读存储器)是另一种常见的非易失性存储器。

它的优点是可以按字节擦写,不需要像Flash那样以扇区为单位。

但EEPROM的容量通常较小,成本也较高。

一些STM32芯片没有集成EEPROM,但可以通过软件模拟的方式,使用Flash来实现EEPROM的功能。HAL库提供了相应的接口:

// 使用HAL库的EEPROM模拟功能
#include "stm32f1xx_hal.h"

// 写入一个变量到模拟EEPROM
HAL_StatusTypeDef EEPROM_WriteVariable(uint16_t VirtAddress, uint16_t Data)
{
    // HAL库会自动处理Flash的擦除和写入
    return HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, 
                            VirtAddress, 
                            Data);
}

// 从模拟EEPROM读取变量
uint16_t EEPROM_ReadVariable(uint16_t VirtAddress)
{
    return (*(__IO uint16_t*)VirtAddress);
}

3. RAM与ROM的对比与应用

在嵌入式开发中,RAM和ROM各有其适用场景。

RAM速度快、可以随机读写,适合存储程序运行时的临时数据,比如变量、堆栈、缓冲区等。

ROM虽然速度较慢、擦写次数有限,但它能够断电保存数据,适合存储程序代码、常量数据、配置参数等。

在实际项目中,我们需要合理分配这两种存储器的使用。

比如在STM32中,程序代码存储在Flash中,启动后CPU从Flash中读取指令执行。

全局变量、局部变量、动态分配的内存都在SRAM中。

如果需要保存用户配置或校准数据,可以使用Flash的某个扇区或外接EEPROM。

对于运行Linux的嵌入式系统,通常会使用NOR Flash存储bootloader和内核,使用NAND Flash存储文件系统,使用DRAM作为运行内存。

这种组合能够在成本、性能和可靠性之间取得良好的平衡。

理解RAM和ROM的底层结构和工作原理,不仅能帮助我们写出更高效的代码,还能在遇到存储相关问题时快速定位原因。

希望这篇文章能够帮助大家更深入地理解这两种基础而重要的存储器。

更多编程学习资源

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Linux系统工程师
手记
粉丝
103
获赞与收藏
288

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消