一、简介
本文介绍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
香瓜单片机之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_U8 *pGUA_Data, 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, pGUA_Data, 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_U8 *pGUA_Data, GUA_U32 nGUA_Data_Num);
-
extern eGUA_FLASH_STATUS GUA_Flash_Write(GUA_U32 nGUA_Flash_CustomOffsetAddr, GUA_U8 *pGUA_Data, GUA_U32 nGUA_Data_Num);
-
extern void GUA_Flash_Init(void);
-
-
#endif
3) 工程中添加GUA_Flash.c
4)在MDK设置中添加驱动源文件路径
2、添加库的驱动
1)添加库的驱动文件
3、在应用层中调用
2) 添加初始化代码(main.c的main函数中)
1)添加驱动头文件(main.c中)
#include "GUA_Flash.h"
2) 添加初始化代码(main.c的main函数中)
-
//flash初始化
-
GUA_Flash_Init();
3) 添加测试代码(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, sbGUA_Flash_Buf, GUA_FLASH_SIZE);
-
-
//读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,
0x55, GUA_FLASH_SIZE);
-
GUA_Flash_Write(GUA_FLASH_ADDR, sbGUA_Flash_Buf, GUA_FLASH_SIZE);
-
-
//读flash
-
memset(sbGUA_Flash_Buf,
0x00, GUA_FLASH_SIZE);
-
GUA_Flash_Read(GUA_FLASH_ADDR, sbGUA_Flash_Buf, GUA_FLASH_SIZE);
-
}
-
//测试代码
-
GUA_Test();
测试代码要放在初始化之后。
七、注意事项
1、香瓜在驱动中暂时分配flash最末尾4K为自定义数据存储区,读写。
2、读、写函数的地址范围为自定义数据存储区范围,0x0000~0x1000(实际上是0x0800F000~0x08010000)。
3、擦除、写flash时,要注意地址必须为有效的地址、写必须按半字的倍数来写。
八、实验结果
仿真并设置断点在测试代码处,单步执行并观察sbGUA_Flash_Buf数组的数值,可知香瓜写的驱动可跨页读写(1025字节)。
因此实验成功。