闪存模块存储器组织
按照不同容量,存储器组织成32个1K字节/页(小容量),128个1K字节/页(中容量),256个2K字节/页(大容量)的主存储器和一些信息块等
下面这张图是大容量产品的Flash内部图:
主要分为三个部分:主存储器、信息块和闪存存储器接口寄存器;
主存储器:也就是我们常说的储存量的大小,比如:stm32ZET6的flash有512K,说的就是这块的容量;该部分用来存放代码和数据常数(如const类型的数据)。对于大容量产品,其被划分为256页,每页2K字节。注意,小容量和中容量产品则每页只有1K字节。从上图可以看出主存储器的起始地址就是0X08000000;起始地址时说的是stm32通用地址空间的绝对地址,下面会说到通用地址空间。
信息块:该部分分为2个小部分,其中启动程序代码(系统存储器),是用来存储ST自带的启动程序,用于串口下载代码,当B0接V3.3,B1接GND的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能。
闪存存储器接口寄存器:该部分用于控制闪存读写等,是整个闪 存模块的控制机构。
三个部分中,主存储器和信息块是可以由stm32的外部引脚BOOT[1-0]来选择启动的,
下面是控制BOOT[1-0]改变启动空间:
信息块(系统存储器):B0接V3.3,B1接GND的时候
主存储器:B0、B1都接GND
通用地址空间
Cortex-M3的地址空间有4G,但它只对这4G空间作了预先的定义,把4G空间分成8个块,每块大小为512M,并指出各段该分给哪些设备。具体的实现由芯片厂商决定,厂商可以设计出具有自己特色的产品。下图是Cortex-M3的存储器映射图
可以看出前边讲的flash主存储器的起始地址就是0X08000000,对应的就是上面的地址空间,CPU访问外部器件以及数据都是通过地址线来访问的,所以每个器件都会占用一段通用地址空间的一段连续的地址。
FLASH闪存的读取
由上面的通用地址空间可以看出内置闪存模块可以在通用地址空间直接寻址,任何32位数据的读操作都能访问闪存模块的内容并得到相应的数据。
例如,我们要从地址addr,读取一个半字(半字为16为,字为32位),可以通过如下的语句读取:
data=(vu16)addr;
下面是stm32 Flash 模拟EEPROM的手册资料链接:
https://download.csdn.net/download/weixin_39780116/11019310
FLASH编程步骤:
解锁FLASH:
//解锁闪存器
void STMFLASH_Unlock()
{
FLASH->KEYR=FLASH_KEY1;
FLASH->KEYR=FLASH_KEY2;
}
对FLASH编程
在这//写入半字
//faddr:写入的地址 dat:写入的数据
u8 STMFLASH_WriteHalfWord(u32 faddr,u16 dat)
{
u8 res;
res=STMFLASH_WaitDone(0xff); //判断是否忙,有其他未完成操作
if(res==0) //不忙
{
FLASH->CR|=1<<0; //编程使能
*(vu16*) faddr=dat; //把faddr强制转换为指针,并把数据给它
res=STMFLASH_WaitDone(0xff); //判断是否忙,有其他未完成操作
if(res!=0) //可能超时可能成功
{
FLASH->CR&=~(1<<0); //清除PG位.
}
}
return res;
}
里插入代码片
FLASH擦除页:
//擦除页
//paddr:要擦除的地址
u8 STMFLASH_ErasePage(u32 paddr)
{
u8 res;
res=STMFLASH_WaitDone(0x5fff); //判断是否忙,有其他未完成操作
if(res==0) //不忙
{
FLASH->CR|=1<<1; //使能页擦除
FLASH->AR|=paddr; //选择要擦除的页
FLASH->CR|=1<<6; //开始擦除
res=STMFLASH_WaitDone(0x5fff); //等待擦除完成
if(res!=1) //非忙
{
FLASH->CR&=~(1<<1); //清除擦除使能位
}
}
return res;
}
驱动代码:
#ifndef __FLASH_H
#define __FLASH_H
#include "stm32f10x.h"
#define STM32_FLASH_SIZE 512 //用于控制大容量和小容量的选择
#define STM32_FLASH_WREN 1 //写使能
//stm32的其实地址
#define STM32_FLASH_BASE 0x08000000
//stm32的键值
#define FLASH_KEY1 0x45670123
#define FLASH_KEY2 0xCDEF89AB
void STMFLASH_Unlock(void); //flash 解锁
void STMFLASH_Lock(void); //flash上锁
u8 STMFLASH_GetStatues(void); //获取Flash的状态
u8 STMFLASH_WaitDone(u16 time); //等待写操作结束
u8 STMFLASH_ErasePage(u32 paddr); //擦除页
u8 STMFLASH_WriteHalfWord(u32 faddr,u16 dat); //写入半字
u16 STMFLASH_ReadHalfWord(u32 faddr); //读出半字
void STMFLASH_WriteLenByte(u32 WritAddr,u32 DataToWrite,u16 Len); //指定地址开始写入指定长度的数据
u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 len); //指定地址开始读取指定长度数据
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite); //从指定地址开始写入指定长度的数据
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead); //从指定地址开始读出指定长度的数据
#endif
#include "flash.h"
#include "delay.h"
//解锁闪存器
void STMFLASH_Unlock()
{
FLASH->KEYR=FLASH_KEY1;
FLASH->KEYR=FLASH_KEY2;
}
//flash上锁
void STMFLASH_Lock(void)
{
FLASH->CR|=1<<7; //上锁
}
//获取状态寄存器
u8 STMFLASH_GetStatues(void)
{
u32 SR;
SR=FLASH->SR;
if(SR&(1<<0)) return 1; //忙
else if(SR&(1<<2)) return 2; //编程错误。对非0xffff的地址编程了
else if(SR&(1<<4)) return 3; //写保护错误
return 0; //操作结束
}
//等待写操作结束
//time :延时多少US
u8 STMFLASH_WaitDone(u16 time)
{
u8 sr;
do{
sr=STMFLASH_GetStatues(); //获取状态
if(sr!=1) break; //不忙
delay_us(1);
time--;
}while(time);
if(time==0) return 0xff; //等待超时
return sr;
}
//擦除页
//paddr:要擦除的地址
u8 STMFLASH_ErasePage(u32 paddr)
{
u8 res;
res=STMFLASH_WaitDone(0x5fff); //判断是否忙,有其他未完成操作
if(res==0) //不忙
{
FLASH->CR|=1<<1; //使能页擦除
FLASH->AR|=paddr; //选择要擦除的页
FLASH->CR|=1<<6; //开始擦除
res=STMFLASH_WaitDone(0x5fff); //等待擦除完成
if(res!=1) //非忙
{
FLASH->CR&=~(1<<1); //清除擦除使能位
}
}
return res;
}
//写入半字
//faddr:写入的地址 dat:写入的数据
u8 STMFLASH_WriteHalfWord(u32 faddr,u16 dat)
{
u8 res;
res=STMFLASH_WaitDone(0xff); //判断是否忙,有其他未完成操作
if(res==0) //不忙
{
FLASH->CR|=1<<0; //编程使能
*(vu16*) faddr=dat; //把faddr强制转换为指针,并把数据给它
res=STMFLASH_WaitDone(0xff); //判断是否忙,有其他未完成操作
if(res!=0) //可能超时可能成功
{
FLASH->CR&=~(1<<0); //清除PG位.
}
}
return res;
}
//读出半字
//faddr:要读的地址 dat:
u16 STMFLASH_ReadHalfWord(u32 faddr)
{
return *(vu16*)faddr;
}
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0;i pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节. ReadAddr+=2;//偏移2个字节. } } #if STM32_FLASH_WREN //如果使能写使能 //不检查的写入 //WriteAddr:起始地址 //pBuffer:数据指针 //NumToWrite:半字(16位)数 void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) { u16 i; for(i=0;i STMFLASH_WriteHalfWord(WriteAddr,pBuffer[i]); WriteAddr+=2; } } #if STM32_FLASH_SIZE<256 #define STM_PAGE_SIZE 1024 //小容量1k一页 #else #define STM_PAGE_SIZE 2048 //大容量2K一页 #endif u16 STMFLASH_BUF[STM_PAGE_SIZE/2];//最多是2K字节,模拟开一个页区,用于拷贝数据到对应空间 void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) { u32 secpos; //页区号 u16 secoff; //页区内偏移地址(16位字计算) u16 secremain; //页区内剩余地址(16位字计算) u16 i; u32 offaddr; //去掉0X08000000后的地址 if(WriteAddr STMFLASH_Unlock(); offaddr=WriteAddr-STM32_FLASH_BASE; //减去基地址后的剩余偏移量 secpos=offaddr/STM_PAGE_SIZE; //地址在第几个页 secoff=(offaddr%STM_PAGE_SIZE)/2; //剩余不足一个页区偏移量在一个页偏移多少、 secremain=STM_PAGE_SIZE/2-secoff; //页区剩余空间大小 if(NumToWrite<=secremain) secremain=NumToWrite; while(1) { STMFLASH_Read(secpos*STM_PAGE_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_PAGE_SIZE/2); //读一个页区 for(i=0;i if(STMFLASH_BUF[secoff+i]!=0xffff) break; //如果非0xff写入会失败 } if(i STMFLASH_ErasePage(secpos*STM_PAGE_SIZE+STM32_FLASH_BASE); //擦除整个页区 for(i=0;i STMFLASH_BUF[secoff+i]=pBuffer[i]; //把数据拷贝到这个页剩余的空间 } STMFLASH_Write_NoCheck(secpos*STM_PAGE_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_PAGE_SIZE/2); //写入整个扇区 } else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. if(NumToWrite==secremain) break; else{ secpos+=1; //扇区号加1 secoff=0; //页内地址偏移清零,剩下的能写满一个页 pBuffer+=secremain; //指针偏移 WriteAddr+=secremain*2; //写地址偏移(16位数据地址,需要*2) NumToWrite-=secremain; //字节(16位)数递减 if(NumToWrite>(STM_PAGE_SIZE/2))secremain=STM_PAGE_SIZE/2;//下一个扇区还是写不完 else secremain=NumToWrite;//下一个扇区可以写完了 } } STMFLASH_Lock();//上锁 } #endif 注意flash的地址范围:0x08000000-0x807FFFF 还有程序大小,一般把数据从底部往上写