嵌入式 | U-Boot的源码stm_flash.c详细剖析
FLASH存储器
FLASH存储器又称为闪存,它也是可重复擦写的储器,部分书籍会把FLASH存储器称为FLASH ROM,但它的容量一般比EEPROM大得多,且在擦除时,一般以多个字节为单位。
根据存储单元电路的不同,FLASH存储器又分为NOR FLASH和NAND FLASH。
<div align=center>
支持XIP,才能直接运行代码,SD卡,硬盘,U盘其实都是NAND FLASH,STM32F429芯片其实是集成1M NOR FLASH
<div align=center>
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 数据缓存
<div align=center>
解锁 :
- 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
- 再往 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 操作
- 在 FLASH_CR 寄存器中,将 SER 位置 1,并从主存储块的 12 个 (STM32F405xx/07xx 和 STM32F415xx/17xx) 或 24 个 (STM32F42xxx 和 STM32F43xxx) 扇区中选择要擦除的扇区 (SNB)
- 将 FLASH_CR 寄存器中的 STRT 位置 1
- 等待 BSY 位清零
批量擦除,建议采用以下步骤:
1.检查 FLASH_SR 寄存器中的 BSY 位,以确认当前未执行任何 Flash 操作
- 将 FLASH_CR 寄存器中的 MER 位置 1(STM32F405xx/07xx 和 STM32F415xx/17xx 器件)
- 将 FLASH_CR 寄存器中的 MER 和 MER1 位置 1(STM32F42xxx 和 STM32F43xxx 器件)
- 将 FLASH_CR 寄存器中的 STRT 位置 1
- 等待 BSY 位清零批量擦除
mb():
#define mb() asm volatile("dmb 3\n" : : : "memory")
告诉编辑器,内存已经被修改,编辑器就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。
<div align=center>
内嵌汇编语法
__asm__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分
内存屏障
1) asm用于指示编译器在此插入汇编语句
2) volatile用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
3) memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作 废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
4)"":::表示这是个空指令。(双引号里面是指令)
难点分析:
C语言关键字volatile:
用来修饰变量,表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。
Memory描述符:
- 告诉GCC,不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
- 告诉GCC,不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。