FLASH存储器

FLASH存储器又称为闪存,它也是可重复擦写的储器,部分书籍会把FLASH存储器称为FLASH ROM,但它的容量一般比EEPROM大得多,且在擦除时,一般以多个字节为单位。

根据存储单元电路的不同,FLASH存储器又分为NOR FLASH和NAND FLASH。

支持XIP,才能直接运行代码,SD卡,硬盘,U盘其实都是NAND FLASH,STM32F429芯片其实是集成1M NOR FLASH

stm32_flash.c源代码

//头文件引入
#include <common.h>
#include <asm/io.h>
#include <asm/arch/stm32.h>
#include "stm32_flash.h"

flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS];

#define STM32_FLASH     ((struct stm32_flash_regs *)FLASH_CNTL_BASE)

//配置 Flash等待周期 、预取指令、指令缓存和数据缓存
void stm32_flash_latency_cfg(int latency)
{
    writel(FLASH_ACR_WS(latency) | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN, &STM32_FLASH->acr);
}

static void stm32_flash_lock(u8 lock)
{
    if (lock) {
                          //加锁,将STM32_FLASH->cr的最高位置1
        setbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_LOCK);
    } else {

        //解锁
//往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123 
//再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB
        writel(STM32_FLASH_KEY1, &STM32_FLASH->key);
        writel(STM32_FLASH_KEY2, &STM32_FLASH->key);
    }
}

//flash初始化
unsigned long flash_init(void) 
{
    unsigned long total_size = 0;
    u8 i, j;
    for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
        flash_info[i].flash_id = FLASH_STM32;//使能stm32的flash
        flash_info[i].sector_count = CONFIG_SYS_MAX_FLASH_SECT;//最大分配扇区数量
        flash_info[i].start[0] = CONFIG_SYS_FLASH_BASE + (i << 20);//虚拟的扇区地址
        flash_info[i].size = sect_sz_kb[0];//每个扇区的大小空间
        for (j = 1; j < CONFIG_SYS_MAX_FLASH_SECT; j++) {
                                  /*下一个扇区的起始地址是上一个的起始地址+上一个扇区的大小*/
            flash_info[i].start[j] = flash_info[i].start[j - 1]
                + (sect_sz_kb[j - 1]);
                                             //累加,一个flash中每个扇区加起来的总大小
            flash_info[i].size += sect_sz_kb[j];
        }
        total_size += flash_info[i].size;//累加,多个flash总大小空间
    }
    return total_size; //返回总大小空间
}

void flash_print_info(flash_info_t *info)
{
    int i;

    if (info->flash_id == FLASH_UNKNOWN) {
        printf("missing or unknown FLASH type\n");
        return;
    } else if (info->flash_id == FLASH_STM32) {
        printf("stm32 Embedded Flash\n");
    }

    printf("  Size: %ld MB in %d Sectors\n",
           info->size >> 20, info->sector_count);

    printf("  Sector Start Addresses:");
    for (i = 0; i < info->sector_count; ++i) {
        if ((i % 5) == 0)
            printf("\n   ");
        printf(" %08lX%s",
               info->start[i],
            info->protect[i] ? " (RO)" : "     ");
    }
    printf("\n");
    return;
}

//flash擦除扇区
int flash_erase(flash_info_t *info, int first, int last)
{
    u8 bank = 0xFF;
    int i;
               //找到要擦除的flash
    for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; i++) {
        if (info == &flash_info[i]) {
            bank = i;
            break;
        }
    }
    if (bank == 0xFF)//不存在
        return -1;
               //解锁flash
    stm32_flash_lock(0);

    for (i = first; i <= last; i++) {
                           //检查 FLASH_SR 状态寄存器中的 BSY 位,以确认当前未执行任何 Flash 操作
        while (readl(&STM32_FLASH->sr) & STM32_FLASH_SR_BSY);
                            /*在写入新的扇区之前,先清除旧的扇区*/
        clrbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_SNB_MASK);
                            if (bank == 0) {
                                           //设置操作的扇区编号0 
            setbits_le32(&STM32_FLASH->cr, (i << STM32_FLASH_CR_SNB_OFFSET));
        } else if (bank == 1) {
                                            //设置扇区编号1
            setbits_le32(&STM32_FLASH->cr,((0x10 | i) << STM32_FLASH_CR_SNB_OFFSET));
        } else {
            stm32_flash_lock(1);//加锁,返回异常
            return -1;
        }
                              //在 FLASH_CR 寄存器中,将 SER 位置 1,激活扇区擦除
        setbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_SER);
                             //将 FLASH_CR 寄存器中的 STRT 位置 1,该位置 1 后可触发擦除操作。
                             //该位只能通过软件置 1,并在 BSY 位清零后随之清零。
        setbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_STRT);
        while (readl(&STM32_FLASH->sr) & STM32_FLASH_SR_BSY);//等待 BSY 位清零
                             //在 FLASH_CR 寄存器中,将 SER 位置 0
        clrbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_SER);
    }
    stm32_flash_lock(1);//擦除完加锁
    return 0;
}

int write_buff(flash_info_t *info, uchar *src, ulong addr, ulong cnt)
{
    ulong i;
          /*检查 FLASH_SR 状态寄存器中的 BSY 位,以确认当前未执行   
         任何 Flash 操作*/
    while (readl(&STM32_FLASH->sr) & STM32_FLASH_SR_BSY);
          //解锁
    stm32_flash_lock(0);
          //将cr寄存器的PG位置一,激活 Flash 编程 
    setbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_PG);

            for (i = 0; i < cnt; i++) {
                   //写入数据
        *(uchar *)(addr + i) = src[i];
                  //把当前CPU寄存器中的被修改过的数值存入内存
        mb();
                   //等待 BSY 位清零
        while (readl(&STM32_FLASH->sr) & STM32_FLASH_SR_BSY);
    }
         //将cr寄存器的PG位置一,关闭激活 Flash 编程
    clrbits_le32(&STM32_FLASH->cr, STM32_FLASH_CR_PG);
    stm32_flash_lock(1);//加锁
    return 0;
}

stm_flash.c的函数大致功能

//头文件
#include <common.h>
#include <asm/io.h>
#include <asm/arch/stm32.h>
#include "stm32_flash.h“
//flash信息结构体
flash_info_t flash_info[CONFIG_SYS_MAX_FLASH_BANKS];
//flash基地址
#define STM32_FLASH ((struct stm32_flash_regs *)FLASH_CNTL_BASE)

//配置 Flash等待周期 、预取指令、指令缓存和数据缓存
void stm32_flash_latency_cfg(int latency)

//加解锁
static void stm32_flash_lock(u8 lock)

//flash初始化
unsigned long flash_init(void)

//flash打印信息
void flash_print_info(flash_info_t *info)

//flash擦除扇区
int flash_erase(flash_info_t *info, int first, int last)

//flash写入
int write_buff(flash_info_t *info, uchar *src, ulong addr, ulong cnt)

逐行讲解

//flash基地址
#define STM32_FLASH ((struct stm32_flash_regs *)FLASH_CNTL_BASE)

采用相对寻址的方式:

#define FLASH_CNTL_BASE (AHB1_PERIPH_BASE + 0x3C00)
#define AHB1_PERIPH_BASE    (PERIPH_BASE + 0x00020000)
#define PERIPH_BASE     0x40000000UL

flash基地址在stm32中:0x4002 3C00,挂在高速总线AHB1上

stm32总线基地址:
APB1 0x4000 0000 0x0(相对总线基地址偏移量)
APB2 0x4001 0000 0x0001 0000
AHB1 0x4002 0000 0x0002 0000
AHB2 0x5000 0000 0x1000 0000
AHB3 0x6000 0000 不在片上外设范围内
总线的基址:APB1 0x4000 0000

AHB系统总线(高速):主要用于高性能模块之间的连接,如CPU、DMA和DSP等。

APB外围总线(低速)APB主要用于低带宽的周边外设之间的连接,如UART等。

内联函数定义:static inline void writel(u32 val, void addr)
函数内容:
(u32 *)addr = val;//将对应的值写入地址

内联函数减少了对栈的进出时间的开销,却扩大了主存的空间来容纳本来在栈里的函数。

FLASH_ACR_WS(latency)
为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地编程等待周期数 (LATENCY)。
FLASH时序延迟几个周期,等待总线同步操作。

推荐按照单片机系统运行频率,
0~24MHz时,取Latency=0;
24~48MHz时,取Latency=1;
48~72MHz时,取Latency=2;

FLASH_ACR_PRFTEN 预取指令

FLASH_ACR_ICEN 指令缓存

FLASH_ACR_DCEN 数据缓存

 

 

解锁 :

  1. 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
  2. 再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB

flash_info[i].start[0] = CONFIG_SYS_FLASH_BASE + (i << 20);//虚拟的扇区起始地址0xbfc00000,
i<<20:表示每个flash最大分配空间1024KB

CONFIG_SYS_MAX_FLASH_SECT:最大被分配扇区数量

flash_info[i].size:每个扇区的大小

Flash的扇区的大小排列(Byte):
static const u32 sect_sz_kb[CONFIG_SYS_MAX_FLASH_SECT] = {
    [0 ... 3] = 32 * 1024,
    [4] =       128 * 1024,
    [5 ... 7] = 256 * 1024
};

总共1024KB,被分成8个扇区

0~3 32KB
4 128KB
5~7 256KB

知识扩展

扇区擦除 :

扇区擦除的具体步骤如下:

1.检查 FLASH_SR 寄存器中的 BSY 位,以确认当前未执行任何 Flash 操作

  1. 在 FLASH_CR 寄存器中,将 SER 位置 1,并从主存储块的 12 个 (STM32F405xx/07xx 和 STM32F415xx/17xx) 或 24 个 (STM32F42xxx 和 STM32F43xxx) 扇区中选择要擦除的扇区 (SNB)
  2. 将 FLASH_CR 寄存器中的 STRT 位置 1
  3. 等待 BSY 位清零

 

批量擦除,建议采用以下步骤:

1.检查 FLASH_SR 寄存器中的 BSY 位,以确认当前未执行任何 Flash 操作

  1. 将 FLASH_CR 寄存器中的 MER 位置 1(STM32F405xx/07xx 和 STM32F415xx/17xx 器件)
  2. 将 FLASH_CR 寄存器中的 MER 和 MER1 位置 1(STM32F42xxx 和 STM32F43xxx 器件)
  3. 将 FLASH_CR 寄存器中的 STRT 位置 1
  4. 等待 BSY 位清零批量擦除

mb():

#define mb()    asm volatile("dmb 3\n" : : : "memory")

告诉编辑器,内存已经被修改,编辑器就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。

内嵌汇编语法

__asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分

内存屏障

1) asm用于指示编译器在此插入汇编语句
2) volatile用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
3) memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作 废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
4)"":::表示这是个空指令。(双引号里面是指令)

难点分析
C语言关键字volatile:
用来修饰变量,表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。

Memory描述符:

  1. 告诉GCC,不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
  2. 告诉GCC,不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。

stm_flash讲解.pptx


文章目录