内部Flash构成了解:
对于stm32f103zet6来说,由三部分组成:主存储器,信息块,闪存存储器接口寄存器。
主存储器:用来存储代码和数据常数
其地址范围为0x0800 0000~0x0807 ffff,分为256页,每页2KB(小中容量每页为1KB),BOOT0接地,系统将从0x0800 0000地址处开始读取代码(从主存储器启动)。存储器映像如图
信息块:系统存储(启动程序代码)和选项字节(用户选择字节)
系统存储大小为2KB,用来存储ST自带的启动程序,用来串口下载代码,选项字节为16B,一般用于设置内存的读保护和写保护。当BOOT0接VCC,BOOT1接GND(串口下载程序),系统运行的就是这部分
闪存存储器接口寄存器:
用于控制闪存读写等,是闪存模块的控制结构
FPEC 键寄存器(FLASH_KEYR):
用来解锁 FPEC,必须在该寄存器写入特定的序列(KEY1 和 KEY2)解锁后,才能对 FLASH_CR 寄存器进行写操作。
闪存控制寄存器(FLASH_CR):
LOCK位:用于查看 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列
后,硬件将其清零。
STRT 位:用于开始一次擦除操作,在该位写入 1,将执行一次擦除操作。
PER 位:用于选择页擦除操作,页擦除时将该位置 1
PG 位:用于选择编程操作,往 FLASH 写数据时该位置 1
闪存状态寄存器(FLASH_SR):
用来指示当前 FPEC 的操作编程状态,常看的是BSY位
闪存地址寄存器(FLASH_AR):
FLASH读写过程:
涉及读操作,写操作,擦除操作。FLASH物理特性:只能写0,不能写1,写1靠擦除
闪存的读取:
直接通过通用地址空间直接寻址即可。CPU通过ICode指令总线访问FLASH指令,通过DCode数据总线访问FLASH数据,但因为CPU运行速度比Flash快的多,FLASH<=24M,所以需加入等待时间。正确设置好等待周期后,利用指针读取数据。将addr强制转换为uintx_t指针,然后取该指针所指向地址的值,即可获得addr地址的数据。但需注意在进行写或擦除操作时,不能进行代码或数据的读取操作。
闪存的写入:
闪存编程由FPEC(闪存编程和擦除控制器)模块处理。
写操作有四步:解锁-擦除-写数据-上锁
解锁:将两个特定的解锁序列号key1和key2依次写入FLASH_KEYR键值寄存器中
擦除:分页擦除和批量擦除
闪存的擦除:读FLASH_CR的LOCK位,为1就解锁,设置FLASH_CR的PER位为1配置页擦除,设置FLASH_AR指定擦除地址,设置FLASH_CR的STRT位为1开始擦除,通过SR的BSY位去查询是否擦除完毕
写数据:擦除完成,可以向FALSH写数据,每次只能以16位方式写入数据(半字写入)
上锁:写入数据完成,设置FLASH_CR[LOCK]位1,重新上锁,以防数据不小心被修改。
FLASH的HAL库驱动:
1. FLASH解锁
HAL_StatusTypeDef HAL_FLASH_Unlock(void);
2.FLASH上锁
HAL_StatusTypeDef HAL_FLASH_Lock (void);
3FLASH写入
HAL_StatusTypeDef HAL_FLASHEx_Program(uint32_t TypeProgram, uint32_t Address,
uint64_t Data);
4FLASH擦除
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit,
uint32_t *SectorError);
5等待FALSH操作完成(读SR)
HAL_StatusTypeDef FLASH_WaitForLastOperation(uint32_t Timeout);
程序设计:
头文件:
//FLASH容量
#define STM32_FLASH_SIZE 512//单位为K
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000
//页大小
#define STM_SECTOR_SIZE 2048
C文件:
FLASH读:
uint16_t STMFLASH_BUF[STM_SECTOR_SIZE/2];//FLASH读写操作页缓存空间,2K
//FLASH读一个半字数据 字是32位,半字是16位
uint16_t STMFLASH_ReadHalfWord(uint32_t faddr)
{
return *(uint16_t*)faddr;//利用指针直接读取返回
}
//FLASH读(多个半字数据),
void STMFLASH_Read(uint32_t ReadAddr,uint16_t *PDataBuf,uint16_t Num)//参数,读取起始地址,数据指针,读取半字数
{
uint16_t i;
for(i=0;i<Num;i++)
{
PDataBuf = STMFLASH_ReadHalfWord(ReadAddr);
ReadAddr+=2;
}
}
FLASH写:
//FLASH写,不检查写入
void STMFLASH_Write_NoCheck(uint32_t WriteAddr,uint16_t *PDataBuf,uint16_t Num)
{
uint16_t i;
for(i=0;i<Num;i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,PDataBuf);
WriteAddr+=2;
}
}
//FLASH写
void STMFLASH_Write(uint32_t WriteAddr,uint16_t *PDataBuf,uint16_t Num)
{
uint32_t offset;//实际偏移地址
uint8_t sectornum;//扇区编号
uint16_t secoff;//扇区内偏移地址(16位字计算)
uint16_t secremain;//扇区剩余空间(16位字计算)
uint16_t i;
//地址校验
if((WriteAddr<STM32_FLASH_BASE)||(WriteAddr>=(STM32_FLASH_BASE+STM32_FLASH_SIZE*1024))) return ;//非法地址
//解锁
HAL_FLASH_Unlock();
offset=WriteAddr-STM32_FLASH_BASE;
sectornum=WriteAddr/STM_SECTOR_SIZE;
secoff=(WriteAddr%STM_SECTOR_SIZE)/2;//(2个字节为基本单位)
secremain=STM_SECTOR_SIZE/2-secoff;//(2个字节为基本单位)
if(Num<=secremain)secremain=Num;//不大于该扇区范围
while(1)
{
//读取该扇区内容,检验数据看是否需要擦除
STMFLASH_Read(STM32_FLASH_BASE+sectornum*STM_SECTOR_SIZE,STMFLASH_BUF,STM_SECTOR_SIZE/2);
for(i=0;i<secremain;i++)
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;
}
if(i<secremain)//需要擦除
{
FLASH_PageErase(STM32_FLASH_BASE+sectornum*STM_SECTOR_SIZE);
FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
for(i=0;i<secremain;i++)
{
STMFLASH_BUF[secoff+i]=PDataBuf;
}
STMFLASH_Write_NoCheck(STM32_FLASH_BASE+sectornum*STM_SECTOR_SIZE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
}
else
{
FLASH_WaitForLastOperation(FLASH_TIMEOUT_VALUE);
STMFLASH_Write_NoCheck(WriteAddr,PDataBuf,secremain);//写入扇区剩余区间
}
if(secremain==Num) break;//写入结束
else//写入未结束
{
sectornum++;
secoff=0;
WriteAddr+=secremain*2;
PDataBuf+=secremain;
Num-=secremain;
if(Num>STM_SECTOR_SIZE/2)
secremain=STM_SECTOR_SIZE/2;
else
secremain=Num;
}
};
//上锁
HAL_FLASH_Lock();
}
注意思考一下Buf作用和写入结束条件设置,以及跨扇区后的处理即可呢,还有地址分析。
主函数测试:
void SystemClock_Config(void);
const uint8_t TEXT_Buffer[]={"STM32F103 FLASH TEST"};
#define SIZE sizeof(TEXT_Buffer) //数组长度
#define FLASH_SAVE_ADDR 0X08070000 //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)
int main(void)
{
uint8_t i ;
uint8_t datatemp[SIZE];
HAL_Init();
SystemClock_Config();
Delay_init();
Led_Init();
UART_Init();
Lcd_Init();
LOG_INFO(Terminal0,"Chipid:%x", lcd_paramter.id);
STMFLASH_Write(FLASH_SAVE_ADDR,(uint16_t*)TEXT_Buffer,SIZE);
STMFLASH_Read(FLASH_SAVE_ADDR,(uint16_t*)datatemp,SIZE);
LCD_ShowString(30,190,datatemp,16);//显示读到的字符串
while (1)
{
HAL_GPIO_TogglePin(Led_DS1_GPIO_Port,Led_DS1_Pin);
delay_ms(500);
}
}
测试成功,屏幕正确显示了写入的字符,就不放图了。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_52117635/article/details/144240656
|