欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

STM32内部FLASH使用简易流程

程序员文章站 2022-04-07 18:22:08
#include “flash.h”#include “led.h”FLASH_T flashstm;/*******************************************************************@brief 关闭所有串口中断@param None@retval None********************************************************************/static void Disa...

    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_

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

本文地址:https://blog.csdn.net/qq_16504163/article/details/109250804