STM32内部FLASH使用简易流程

    STM32 没有自带 EEPROM,但是 STM32 具有 IAP(在应用编程)功能,所以我们可以把它的 FLASH 当成 EEPROM 来使用。内部FLASH可以省存储芯片和IO引脚,但是需要有额外的FLASH资源。

    数据保存,简单来说就是在特定的内存地址上插入一段想保存的数据, 先保存扇区(1~2K)的数据,然后擦除该片区,然后拼接插入改扇区,再把最终数据写入到扇区,因为写操作费时间也易出错,可以再写入前后分别进行和校验

● 写数据

/*******************************************************************
 * @brief   写数据到flash中,在STM32F302中,一页为1k(2K)
 * @param   [buf] 写入的数据
            [flash_addr] 写入的数据的地址
			[len] 写入的数据的长度
 * @retval  None
 * @note    暂不支持跨扇区写数据, 充分验证通过,所以写入的地址需要严格控制
 * @runtime 78.86ms 其中主要是写数据的时间,0.86ms为两次和校验的计算
            2048byte~78ms、1024byte~50ms、2byte~22ms
********************************************************************/
u8 WriteDataToFlash(void *buf, u32 flash_addr, u16 len)
{
    
    
/*0.关闭所有串口中断,因为串口中断没有关闭,而执行写flash操作时候,会优先执行flash操作,串口接收中断不被执行,会卡死整个系统*/
	DisableUsartInterrupt();
//1.校验地址和合理性                              
	if(flash_addr<STM32_FLASH_BASE_ADDR || (flash_addr>=STM32_FLASH_END_ADDR)) {
    
    
		return 1; 
	} 
	flashstm.addr    =  flash_addr - STM32_FLASH_BASE_ADDR ;
	flashstm.secPos  =  flashstm.addr / FLASH_PAGE_SIZE;                             //第几个扇区
	flashstm.secAddr = flashstm.secPos * FLASH_PAGE_SIZE + STM32_FLASH_BASE_ADDR;    //扇区的地址
	flashstm.secOff  = flash_addr - flashstm.secAddr;                                //相对于N号扇区首地址的偏移值
//2.读出扇区的数据	   
	ReadDataFromFlash(&flashstm.buf[0], flashstm.secAddr, FLASH_PAGE_SIZE);
//3.擦除扇区的数据
    HAL_FLASH_Unlock();
//	printf("addr is: %X\r\nsecPos is %d\r\nsecAddr is %X\r\nflashstm.secOff is %X\r\n\r\n", flashstm.addr,flashstm.secPos,flashstm.secAddr,flashstm.secOff);
	FlashErase(flashstm.secAddr, 1);       //一擦就是1页  1页为1024bytes
//4.插入数据到1K的buf中
	for(u8 i=0; i<len; i++) {
    
    
		flashstm.buf[flashstm.secOff+i] = ((u8 *)buf)[i];
	}
//5.计算写入前的校验和
	for(u16 j=0; j<FLASH_PAGE_SIZE; j++) {
    
    
		flashstm.chucksumold += flashstm.buf[j];
	}
//6.写入数据到扇区 ,利用了HAL库写的,函数里,需要解锁写数据再上锁的。
	FlashWrite(flashstm.secAddr, (void *)flashstm.buf, FLASH_PAGE_SIZE); //48byteS
    HAL_FLASH_Lock();
//7.读出写入的数据到buf中
	ReadDataFromFlash(&flashstm.buf[0], flashstm.secAddr, FLASH_PAGE_SIZE);
//8.计算读出的数据
	for(u16 j=0; j<FLASH_PAGE_SIZE; j++) {
    
    
		flashstm.chucksumnew += flashstm.buf[j];
	}	
//9.校验
	if(flashstm.chucksumold != flashstm.chucksumnew) {
    
    
        while (1) {
    
    
        /* Make LED2 blink (100ms on, 2s off) to indicate error in Erase operation */
            LED_ON();	HAL_Delay(100);
            LED_OFF();  HAL_Delay(2000);
        }
		return 2;
	}
//	printf("flashstm.chucksumold is: %d\r\nflashstm.chucksumnew is %d\r\n\r\n",flashstm.chucksumold,flashstm.chucksumnew);
/*10.打开所有串口中断*/
	EnableUsartInterrupt();
	return 0;
}

● 读数据:

/*******************************************************************
 * @brief  必须为4的倍数,这bug,后续再改进
 * @param  PRO_INFOm: 校准参数结构体
 * @retval None
********************************************************************/
void ReadDataFromFlash(void *buf, u32 flash_addr, u16 len)
{
    
    
    u8  val = 0;
    u32 tmp = 0;
	for(int i = 0; i < len; i += 4) //48  --49
	{
    
    
        val = len-i;
        switch(val)
        {
    
    
//            case 1:
//                *((char *)buf + i) = *((unsigned int *)(flash_addr + i)) & 0xff;            
//                break;
//            case 2:
//                *((u16 *)(char *)buf + i) = (*((unsigned int *)(flash_addr + i)) & 0xffff);            
//                break;
//            case 3:
//                tmp = (*((unsigned int *)(flash_addr + i)) & 0xffffff);      
//                break;
            default :
                *((unsigned int *)((char *)buf + i)) = *((unsigned int *)(flash_addr + i));
                break;            
        } 
	}
}

● 测试代码

//test.h: 
//**FLASH, 注意内存大小为4的倍数
typedef struct _stUserSave_t_
{
    
    
    u32            irPowerCode;
	u32            irFuncCode;
    u8             isPowerOpen;  //1:开机 0:关机
} stUserSave_t;

#define STM32_FLASH_SIZE             32 	 		        //FLASH 容量为32K
#define STM32_FLASH_BASE_ADDR        0x08000000 	        //STM32 FLASH起始地址
#define STM32_FLASH_END_ADDR         (STM32_FLASH_BASE_ADDR + 1024*STM32_FLASH_SIZE)
#define  LAST_SEC_FLASH_ADDR	      STM32_FLASH_BASE_ADDR + 31*1024

//test.c: 
stUserSave_t userSave;

void testWrite(void) {
    
    
	WriteDataToFlash(&userSave, LAST_SEC_FLASH_ADDR, sizeof(stUserSave_t));
}

void testRead(void) {
    
    
    ReadDataFromFlash(&userSave, LAST_SEC_FLASH_ADDR, sizeof(userSave));
}

经验证,重启单片机,数据和写入的一致,并且掉电不丢失:


完整的代码如下:

● flash.c


#include "flash.h"
#include "led.h"       
       
FLASH_T flashstm;   
/*******************************************************************
 * @brief   关闭所有串口中断
 * @param   None
 * @retval  None
********************************************************************/
static void DisableUsartInterrupt(void)
{
    
    
//	USART_Cmd(USART1, DISABLE);//失能串口1
//	USART_Cmd(USART2, DISABLE);//失能串口2
	__set_PRIMASK(1);
}

/*******************************************************************
 * @brief   打开所有串口中断
 * @param   None
 * @retval  None
********************************************************************/
static void EnableUsartInterrupt(void)
{
    
    
//	USART_Cmd(USART1, ENABLE);//使能串口1
//	USART_Cmd(USART2, ENABLE);//使能串口2
	__set_PRIMASK(0);
}

/*******************************************************************
 * @brief   写数据到flash中,在STM32F302中,一页为2K,本芯片K8U6共64K有32页,擦除以页为单位
 * @param   [buf] 写入的数据
            [flash_addr] 写入的数据的地址
			[len] 写入的数据的长度
 * @retval  None
 * @note    暂不支持跨扇区写数据,充分验证通过
 * @runtime 78.86ms 其中主要是写数据的时间,0.86ms为两次和校验的计算
            2048byte~78ms、1024byte~50ms、2byte~22ms
********************************************************************/
u8 WriteDataToFlash(void *buf, u32 flash_addr, u16 len)
{
    
    
/*0.关闭所有串口中断,因为串口中断没有关闭,而执行写flash操作时候,会优先执行flash操作,串口接收中断不被执行,会卡死整个系统*/
	DisableUsartInterrupt();
//1.校验地址和合理性                              
	if(flash_addr<STM32_FLASH_BASE_ADDR || (flash_addr>=STM32_FLASH_END_ADDR)) {
    
    
		return 1; 
	} 
	flashstm.addr    =  flash_addr - STM32_FLASH_BASE_ADDR ;
	flashstm.secPos  =  flashstm.addr / FLASH_PAGE_SIZE; //第几个扇区
	flashstm.secAddr = flashstm.secPos * FLASH_PAGE_SIZE + STM32_FLASH_BASE_ADDR;    //扇区的地址
	flashstm.secOff  = flash_addr - flashstm.secAddr;   //相对于N号扇区首地址的偏移值
//2.读出扇区的数据	   
	ReadDataFromFlash(&flashstm.buf[0], flashstm.secAddr, FLASH_PAGE_SIZE);
//3.擦除扇区的数据
    HAL_FLASH_Unlock();
//	printf("addr is: %X\r\nsecPos is %d\r\nsecAddr is %X\r\nflashstm.secOff is %X\r\n\r\n", flashstm.addr,flashstm.secPos,flashstm.secAddr,flashstm.secOff);
	FlashErase(flashstm.secAddr, 1);       //一擦就是1页  1页为1024bytes
//4.插入数据到1K的buf中
	for(u8 i=0; i<len; i++) {
    
    
		flashstm.buf[flashstm.secOff+i] = ((u8 *)buf)[i];
	}
//5.计算写入前的
	for(u16 j=0; j<FLASH_PAGE_SIZE; j++) {
    
    
		flashstm.chucksumold += flashstm.buf[j];
	}
//6.写入数据到扇区   
	FlashWrite(flashstm.secAddr, (void *)flashstm.buf, FLASH_PAGE_SIZE); //48byteS
    HAL_FLASH_Lock();

//7.读出写入的数据到buf中
	ReadDataFromFlash(&flashstm.buf[0], flashstm.secAddr, FLASH_PAGE_SIZE);
//8.计算读出的数据
	for(u16 j=0; j<FLASH_PAGE_SIZE; j++) {
    
    
		flashstm.chucksumnew += flashstm.buf[j];
	}	
//9.校验
	if(flashstm.chucksumold != flashstm.chucksumnew) {
    
    
        while (1) {
    
    
        /* Make LED2 blink (100ms on, 2s off) to indicate error in Erase operation */
            LED_ON();
            HAL_Delay(100);
            LED_OFF();
            HAL_Delay(2000);
        }
		return 2;
	}
//	printf("flashstm.chucksumold is: %d\r\nflashstm.chucksumnew is %d\r\n\r\n",flashstm.chucksumold,flashstm.chucksumnew);
/*10.打开所有串口中断*/
	EnableUsartInterrupt();
	return 0;
}

/*******************************************************************
 * @brief  必须为4的倍数
 * @param  PRO_INFOm: 校准参数结构体
 * @retval None
********************************************************************/
void ReadDataFromFlash(void *buf, u32 flash_addr, u16 len)
{
    
    
    u8  val = 0;
    u32 tmp = 0;
	for(int i = 0; i < len; i += 4) {
    
            
        val = len-i;
        switch(val) {
    
    
//            case 1:
//                *((char *)buf + i) = *((unsigned int *)(flash_addr + i)) & 0xff;            
//                break;
//            case 2:
//                *((u16 *)(char *)buf + i) = (*((unsigned int *)(flash_addr + i)) & 0xffff);            
//                break;
//            case 3:
//                tmp = (*((unsigned int *)(flash_addr + i)) & 0xffffff);      
//                break;
            default :
                *((unsigned int *)((char *)buf + i)) = *((unsigned int *)(flash_addr + i));
                break;            
        } 
	}
}

/*******************************************************************
 * @brief  从addr地址开始,连续擦除page_num页
 * @param  addr:要擦除页的首地址
   @param  page_num:页数 
 * @retval 1
********************************************************************/ 
int FlashErase(unsigned int addr, unsigned int page_num)
{
    
    
	int i;
    uint32_t PAGEError = 0;
    
    FLASH_EraseInitTypeDef EraseInitStruct;

    EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;  //页擦除
    EraseInitStruct.PageAddress = addr;
    EraseInitStruct.NbPages     = page_num;   //默认就是1页

    if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK) {
    
    
        while (1) {
    
    
        /* Make LED2 blink (100ms on, 2s off) to indicate error in Erase operation */
            LED_ON();
            HAL_Delay(100);
            LED_OFF();
            HAL_Delay(2000);
        }
    }
	return 1;
}

/*******************************************************************
 * @brief  从addr地址开始,连续写入len个字节,len应当是4的倍数,利用HAL库
 * @param  addr:要写入的首地址
   @param  buf: 数据源的首地址
   @param  len: 写入的长度
 * @retval 1
********************************************************************/ 
//flashstm.secAddr,     (void *)flashstm.buf, FLASH_PAGE_SIZE
int FlashWrite(unsigned int addr, void *buf, unsigned int len)
{
    
    

     for(int i = 0; i < len; i+=4) {
    
    
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, *((unsigned int *)((char *)buf + i))) != HAL_OK) {
    
    
            while (1) {
    
    
            /* Make LED2 blink (100ms on, 2s off) to indicate error in Write operation */
                LED_ON();
                HAL_Delay(100);
                LED_OFF();
                HAL_Delay(2000);
            }
        }
    }
	return 1;
}

● flash.h

#ifndef __FLASH_H
#define __FLASH_H

#include "system.h"

#define STM32_FLASH_SIZE             32 	 		        //FLASH 容量为32K
#define STM32_FLASH_BASE_ADDR        0x08000000 	//STM32 FLASH起始地址
#define STM32_FLASH_END_ADDR         (STM32_FLASH_BASE_ADDR + 1024*STM32_FLASH_SIZE)

#define    LAST_SEC_FLASH_ADDR	                 STM32_FLASH_BASE_ADDR + 31*1024
#define    USRE_FLASH_ADDR                       LAST_SEC_FLASH_ADDR

typedef struct
{
    
    
	u8   buf[1024]; //1024 2048 因SRAM不够只用512K
	u32  addr; //实际的偏移地址
	u8   secPos; //第几个扇区
	u32  secAddr; //扇区起始地址
	u32  secOff; //偏移值
	u8  chucksumold;
	u8  chucksumnew;
}FLASH_T;

u8 WriteDataToFlash(void *buf, u32 flash_addr, u16 len);
void ReadDataFromFlash(void *buf, u32 flash_addr, u16 len);
int FlashErase(unsigned int addr, unsigned int page_num);
int FlashWrite(unsigned int addr, void *buf, unsigned int len);

#endif //_FLASH_H_

需要复杂点的可以直接移植正点原子的,它考虑了各种跨扇区的操作,但是阅读性没那么舒服~

猜你喜欢

转载自blog.csdn.net/qq_16504163/article/details/109250804