【STM32】STM32之flash
本篇博文最后修改时间:2017年03月18日,06:07。 一、简介
本文介绍STM32系列如何将flash的一部分当做eeprom来存储数据。 注:本驱动特点是可自定义数据存储空间、限制写入地址避免误写至代码段、支持跨页读写、允许保存非半字倍数的字节。
二、实验平台 库版本:STM32F10x_StdPeriph_Lib_V3.5.0 编译软件:MDK4.53 硬件平台:STM32开发板(主芯片stm32f103c8t6) 仿真器:JLINK 三、版权声明 博主:甜甜的大香瓜 声明:喝水不忘挖井人,转载请注明出处。 原文地址:http://blog.csdn.NET/feilusia 联系方式:897503845@qq.com 香瓜BLE之CC2541群:127442605 香瓜BLE之CC2640群:557278427 香瓜BLE之Android群:541462902
香瓜单片机之STM8/STM32群:164311667
甜甜的大香瓜的小店(淘宝店):https://shop217632629.taobao.com/?spm=2013.1.1000126.d21.hd2o8i
四、
实验前提
1、在进行本文步骤前,请先
阅读
以下博文:
1)《STM32F10xxx 闪存编程》(下载地址):http://blog.csdn.net/feilusia/article/details/49031709
2、在进行本文步骤前,请先
实现以下博文:
暂无
五、基础知识 1、flash的介绍
答:
香瓜使用的
stm32f103c8t6的flash为64K,详情如下图:
1)主存储器(BOOT1=x、BOOT0=0)
①地址:从0x08000000开始。
②页大小:小、中容量的flash为1K每页,大容量的flash为2K每页。
2)信息块
①系统存储器(
BOOT1=0、BOOT0=1):存放ST自带的启动代码。
②选项字节:一般用于配置写保护、读保护等功能。
3)闪存存储器/接口寄存器
flash所用到的寄存器。
2、闪存等待时间是什么?
由于flash的频率最高位24MHz,所以当STM32的频率小于24MHz时不需要等待时间、大于
24MHz时需要等待时间。
例如当STM32的频率为72MHz时,需要在工程中设置等待时间:“FLASH_SetLatency(FLASH_Latency_2);?”
3、闪存的读、写、擦除是如何的?
1)读:可以读有效地址内的任意字节。
2)写:需要先解闪存锁,然后按半字(2个字节)的倍数写入。
3)擦除:需要先
解闪存锁,然后可以页擦除、全片擦除。
4、如何解闪存锁?
1)首先要知道flash有三个键值:
①RDPRT键 = 0x000000A5
②KEY1 = 0x45670123
③KEY2 = 0xCDEF89AB
2)将KEY1与KEY2依次写入到FLASH_KEYR寄存器即可实现解闪存锁。
5、写flash只能按半字的倍数来写,但如果只有3个字节要保存,那该如何操作?
香瓜的驱动是把flash中的整页数据读出来,修改要写的3个字节,然后再按页写回去。
虽然此法浪费了些写多余字节的时间,但能只改动flash中所需的3个字节,而不是按半字的倍数(4个)来写。
6、如何避免写到代码段导致代码运行异常?
以
stm32f103c8t6为例,它的flash大小是64K,范围是0x8000000~0x8010000。
假设通过MDK编译后得知代码段大小为31K(0x7C00),所以在32K
(0x8007D00)之后的位置肯定都不是代码段,都可用于自定义的数据存储空间。
保险起见可参考香瓜下文使用的方式,使用flash的末尾4K。
六、实验步骤 1、编写并添加驱动 1)编写驱动GUA_Flash.c(存放在“……HARDWARE”) //****************************************************************************** //name: GUA_Flash.c //introduce: flash驱动 //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** #include "stm32f10x.h" #include "GUA_Flash.h" #include <string.h> /********************内部变量************************/ static GUA_U8 sbGUA_Flash_Data[GUA_FLASH_SECTOR_SIZE] = {0}; /*********************内部函数声明************************/ static GUA_U16 GUA_Flash_ReadHalfWord(GUA_U32 nGUA_Flash_CustomOffsetAddr); static GUA_U8 GUA_Flash_ReadByte(GUA_U32 nGUA_Flash_CustomOffsetAddr); static GUA_U32 GUA_Flash_ReadWord(GUA_U32 nGUA_Flash_CustomOffsetAddr); //****************************************************************************** //name: GUA_Flash_ReadHalfWord //introduce: 读取指定地址的字 //parameter: nGUA_Flash_CustomOffsetAddr:偏移地址 //return: 该地址的字 //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** static GUA_U32 GUA_Flash_ReadWord(GUA_U32 nGUA_Flash_CustomOffsetAddr) { GUA_U32 nGUA_Flash_Addr = GUA_FLASH_CUSTOM_ADDR_START + nGUA_Flash_CustomOffsetAddr; return *(GUA_U32*)nGUA_Flash_Addr; } //****************************************************************************** //name: GUA_Flash_ReadHalfWord //introduce: 读取指定地址的半字 //parameter: nGUA_Flash_CustomOffsetAddr:偏移地址 //return: 该地址的半字 //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** static GUA_U16 GUA_Flash_ReadHalfWord(GUA_U32 nGUA_Flash_CustomOffsetAddr) { GUA_U32 nGUA_Flash_Addr = GUA_FLASH_CUSTOM_ADDR_START + nGUA_Flash_CustomOffsetAddr; return *(GUA_U16*)nGUA_Flash_Addr; } //****************************************************************************** //name: GUA_Flash_ReadHalfWord //introduce: 读取指定地址的字节 //parameter: nGUA_Flash_CustomOffsetAddr:偏移地址 //return: 该地址的字节 //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** static GUA_U8 GUA_Flash_ReadByte(GUA_U32 nGUA_Flash_CustomOffsetAddr) { GUA_U32 nGUA_Flash_Addr = GUA_FLASH_CUSTOM_ADDR_START + nGUA_Flash_CustomOffsetAddr; return *(GUA_U8*)nGUA_Flash_Addr; } //****************************************************************************** //name: GUA_Flash_Read //introduce: 读取指定地址、指定数据长度的数据 //parameter: nGUA_Flash_CustomOffsetAddr:偏移地址 // pGUA_Data:数据缓存区 // nGUA_Data_Num:读取的字节数 //return: none //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** void GUA_Flash_Read(GUA_U32 nGUA_Flash_CustomOffsetAddr,GUA_U8 *pGUA_Data,GUA_U32 nGUA_Data_Num) { while(nGUA_Data_Num--) { *pGUA_Data++ = GUA_Flash_ReadByte(nGUA_Flash_CustomOffsetAddr++); } } //****************************************************************************** //name: GUA_Flash_Write //introduce: 写入指定地址、指定数据长度的数据 //parameter: nGUA_Flash_Addr:读地址 // pGUA_Data:数据缓存区(必须偶数个字节) // nGUA_Data_Num:数据长度(必须偶数个字节) //return: 执行情况,详情见eGUA_FLASH_STATUS //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** eGUA_FLASH_STATUS GUA_Flash_Write(GUA_U32 nGUA_Flash_CustomOffsetAddr,GUA_U32 nGUA_Data_Num) { GUA_U32 nGUA_Flash_Addr = GUA_FLASH_CUSTOM_ADDR_START + nGUA_Flash_CustomOffsetAddr; //要写入的地址 eGUA_FLASH_STATUS eGUA_Flash_Status = GUA_FLASH_STATUS_OK; //flash操作情况 GUA_U16 i; GUA_U32 nGUA_Flash_SectorPos = (nGUA_Flash_Addr - GUA_FLASH_ADDR_START)/GUA_FLASH_SECTOR_SIZE; //算出是第几个扇区(从0开始) GUA_U16 nGUA_Flash_SectorAddr_Offset = (nGUA_Flash_Addr - GUA_FLASH_ADDR_START)%GUA_FLASH_SECTOR_SIZE; //扇区内偏移地址 GUA_U16 nGUA_Flash_SectorAddr_Remain = GUA_FLASH_SECTOR_SIZE - nGUA_Flash_SectorAddr_Offset; //扇区内剩余字节 GUA_U16 nGUA_HalfWord = 0; GUA_U16 nGUA_HalfWord_Num; FLASH_Status eGUA_Flash_Lib_Status = FLASH_COMPLETE; //如果要写入的flash端超出范围,则报错退出 if((nGUA_Flash_Addr + nGUA_Data_Num) > GUA_FLASH_CUSTOM_ADDR_END) { eGUA_Flash_Status = GUA_FLASH_STATUS_ERROR_PARAMETER; return eGUA_Flash_Status; } //解锁 FLASH_Unlock(); //清除标志位 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); //写flash while(1) { //读出整个扇区的内容 GUA_Flash_Read(nGUA_Flash_SectorPos*GUA_FLASH_SECTOR_SIZE + GUA_FLASH_ADDR_START - GUA_FLASH_CUSTOM_ADDR_START,sbGUA_Flash_Data,GUA_FLASH_SECTOR_SIZE); //检查要写flash是否写过 for(i = 0; i < nGUA_Flash_SectorAddr_Remain; i++) { //如果写过则擦除该扇区 if(sbGUA_Flash_Data[nGUA_Flash_SectorAddr_Offset + i] != 0xFF) { eGUA_Flash_Lib_Status = FLASH_ErasePage(nGUA_Flash_SectorPos*GUA_FLASH_SECTOR_SIZE + GUA_FLASH_ADDR_START); if(eGUA_Flash_Lib_Status != FLASH_COMPLETE) { eGUA_Flash_Status = GUA_FLASH_STATUS_ERROR_ERASE; return eGUA_Flash_Status; } break; } } //如果要写的数据比该页剩余字节小,则本页内即可写完 if(nGUA_Data_Num <= nGUA_Flash_SectorAddr_Remain) { //复制要写的数据到写缓冲区 memcpy(sbGUA_Flash_Data + nGUA_Flash_SectorAddr_Offset,pGUA_Data,nGUA_Data_Num); //重新计算偏移值 nGUA_HalfWord_Num = GUA_FLASH_SECTOR_SIZE/2; nGUA_Flash_SectorAddr_Offset = 0; nGUA_Flash_SectorAddr_Remain = GUA_FLASH_SECTOR_SIZE; //将整个页的数据写到flash中 while(nGUA_HalfWord_Num--) { //将1字节整合为2字节 nGUA_HalfWord = sbGUA_Flash_Data[nGUA_Flash_SectorAddr_Offset]; nGUA_HalfWord |= sbGUA_Flash_Data[nGUA_Flash_SectorAddr_Offset + 1]<<8; //写入2个字节 eGUA_Flash_Lib_Status = FLASH_ProgramHalfWord(nGUA_Flash_SectorPos*GUA_FLASH_SECTOR_SIZE + GUA_FLASH_ADDR_START + nGUA_Flash_SectorAddr_Offset,nGUA_HalfWord); if(eGUA_Flash_Lib_Status != FLASH_COMPLETE) { eGUA_Flash_Status = GUA_FLASH_STATUS_ERROR_PROGRAM; return eGUA_Flash_Status; } //计算偏移值 nGUA_Flash_SectorAddr_Offset += 2; nGUA_Flash_SectorAddr_Remain -= 2; } //已全部写完,退出 break; } //如果要写的数据比该页剩余字节大,则本页内写不完,先写完本页 else { //复制要写的数据到写缓冲区 memcpy(sbGUA_Flash_Data + nGUA_Flash_SectorAddr_Offset,nGUA_Flash_SectorAddr_Remain); //重新计算偏移值 nGUA_HalfWord_Num = GUA_FLASH_SECTOR_SIZE/2; nGUA_Flash_SectorAddr_Offset = 0; nGUA_Flash_SectorAddr_Remain = GUA_FLASH_SECTOR_SIZE; //将整个页的数据写到flash中 while(nGUA_HalfWord_Num--) { //将1字节整合为2字节 nGUA_HalfWord = sbGUA_Flash_Data[nGUA_Flash_SectorAddr_Offset]; nGUA_HalfWord |= sbGUA_Flash_Data[nGUA_Flash_SectorAddr_Offset + 1]<<8; //写入2个字节 eGUA_Flash_Lib_Status = FLASH_ProgramHalfWord(nGUA_Flash_SectorPos*GUA_FLASH_SECTOR_SIZE + GUA_FLASH_ADDR_START + nGUA_Flash_SectorAddr_Offset,nGUA_HalfWord); if(eGUA_Flash_Lib_Status != FLASH_COMPLETE) { eGUA_Flash_Status = GUA_FLASH_STATUS_ERROR_PARAMETER; return eGUA_Flash_Status; } //计算偏移值 nGUA_Flash_SectorAddr_Offset += 2; nGUA_Flash_SectorAddr_Remain -= 2; pGUA_Data += 2; nGUA_Data_Num -= 2; } //计算新扇区偏移值 nGUA_Flash_SectorPos++; nGUA_Flash_SectorAddr_Offset = 0; nGUA_Flash_SectorAddr_Remain = GUA_FLASH_SECTOR_SIZE; } } //上锁 FLASH_Lock(); //返回操作状态 eGUA_Flash_Status = GUA_FLASH_STATUS_OK; return eGUA_Flash_Status; } //****************************************************************************** //name: GUA_Flash_Init //introduce: flash初始化 //parameter: none //return: none //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** void GUA_Flash_Init(void) { //设置延迟 FLASH_SetLatency(FLASH_Latency_2); //开启FLASH预读缓冲功能 FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); } 2)
编写驱动头文件
GUA_Flash.h
(存放在“
……
HARDWARE
”)
//****************************************************************************** //name: GUA_Flash.h //introduce: flash驱动的头文件 //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** #ifndef _GUA_FLASH_H_ #define _GUA_FLASH_H_ /*********************宏定义************************/ #ifndef GUA_U8 typedef unsigned char GUA_U8; #endif #ifndef GUA_8 typedef signed char GUA_8; #endif #ifndef GUA_U16 typedef unsigned short GUA_U16; #endif #ifndef GUA_16 typedef signed short GUA_16; #endif #ifndef GUA_U32 typedef unsigned long GUA_U32; #endif #ifndef GUA_32 typedef signed long GUA_32; #endif #ifndef GUA_U64 typedef unsigned long long GUA_U64; #endif #ifndef GUA_64 typedef signed long long GUA_64; #endif //flash页大小 #if GUA_FLASH_ALL_SIZE < (256*1024) #define GUA_FLASH_SECTOR_SIZE 1024 //1K #else #define GUA_FLASH_SECTOR_SIZE 2048 //2K #endif //flash配置宏 #define GUA_FLASH_ALL_SIZE (64*1024) #define GUA_FLASH_ADDR_START 0x08000000 //起始地址 #define GUA_FLASH_ADDR_END (GUA_FLASH_ADDR_START + GUA_FLASH_ALL_SIZE) //结束地址 #define GUA_FLASH_CUSTOM_SIZE (4*1024) //自定义的flash空间大小 #define GUA_FLASH_CUSTOM_ADDR_START (GUA_FLASH_ADDR_END - GUA_FLASH_CUSTOM_SIZE)//自定义的flash空间起始地址 #define GUA_FLASH_CUSTOM_ADDR_END GUA_FLASH_ADDR_END //自定义的flash空间结束地址 //flash操作情况 typedef enum { GUA_FLASH_STATUS_OK = 1,GUA_FLASH_STATUS_ERROR_PARAMETER,GUA_FLASH_STATUS_ERROR_ERASE,GUA_FLASH_STATUS_ERROR_PROGRAM,}eGUA_FLASH_STATUS; /*********************外部函数************************/ extern void GUA_Flash_Read(GUA_U32 nGUA_Flash_CustomOffsetAddr,GUA_U32 nGUA_Data_Num); extern eGUA_FLASH_STATUS GUA_Flash_Write(GUA_U32 nGUA_Flash_CustomOffsetAddr,GUA_U32 nGUA_Data_Num); extern void GUA_Flash_Init(void); #endif 3) 工程中添加GUA_Flash.c 4)在MDK设置中添加驱动源文件路径
2、添加库的驱动
1)添加库的驱动文件 3、在应用层中调用
1)添加驱动头文件(main.c中) #include "GUA_Flash.h" 2) 添加初始化代码(main.c的main函数中) //flash初始化 GUA_Flash_Init(); 添加测试代码(main.c中) ①添加测试函数(main.c中) //****************************************************************************** //name: GUA_Test //introduce: 测试代码 //parameter: none //return: none //author: 甜甜的大香瓜 //email: 897503845@qq.com //QQ group: 香瓜单片机之STM8/STM32(164311667) //changetime: 2017.03.18 //****************************************************************************** #define GUA_FLASH_ADDR 0x0000 #define GUA_FLASH_SIZE 1025 static GUA_U8 sbGUA_Flash_Buf[GUA_FLASH_SIZE]; static void GUA_Test(void) { //读flash memset(sbGUA_Flash_Buf,0x00,GUA_FLASH_SIZE); GUA_Flash_Read(GUA_FLASH_ADDR,sbGUA_Flash_Buf,GUA_FLASH_SIZE); //写flash memset(sbGUA_Flash_Buf,0x33,GUA_FLASH_SIZE); GUA_Flash_Write(GUA_FLASH_ADDR,GUA_FLASH_SIZE); //读flash memset(sbGUA_Flash_Buf,GUA_FLASH_SIZE); //写flash memset(sbGUA_Flash_Buf,0x55,GUA_FLASH_SIZE); } //测试代码 GUA_Test();测试代码要放在初始化之后。
七、注意事项
1、香瓜在驱动中暂时分配flash最末尾4K为自定义数据存储区,读写。
2、读、写函数的地址范围为自定义数据存储区范围,0x0000~0x1000(实际上是0x0800F000~0x08010000)。
3、擦除、写flash时,要注意地址必须为有效的地址、写必须按半字的倍数来写。
八、实验结果
仿真并设置断点在测试代码处,单步执行并观察sbGUA_Flash_Buf数组的数值,可知香瓜写的驱动可跨页读写(1025字节)。 因此实验成功。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |