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
上一篇: Map详解