【STM32】STM32之flash

版权声明:喝水不忘挖井人,转载请注明出处,897503845@qq.com。 https://blog.csdn.net/feilusia/article/details/63168904

本篇博文最后修改时间: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),所以在32K0x8007D00)之后的位置肯定都不是代码段,都可用于自定义的数据存储空间。
保险起见可参考香瓜下文使用的方式,使用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、在应用层中调用

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);	
}	

添加测试代码的调用(main.c的main函数中)
//测试代码    
GUA_Test(); 
测试代码要放在初始化之后。

七、注意事项
1、香瓜在驱动中暂时分配flash最末尾4K为自定义数据存储区,读写。
2、读、写函数的地址范围为自定义数据存储区范围,0x0000~0x1000(实际上是0x0800F000~0x08010000)。
3、擦除、写flash时,要注意地址必须为有效的地址、写必须按半字的倍数来写。

八、实验结果

仿真并设置断点在测试代码处,单步执行并观察sbGUA_Flash_Buf数组的数值,可知香瓜写的驱动可跨页读写(1025字节)。

因此实验成功。




阅读更多

没有更多推荐了,返回首页