STM32F1系列单片机的flash模拟EEPROM方案(简单易用)

您所在的位置:网站首页 奥迪a4l采用什么变速箱 STM32F1系列单片机的flash模拟EEPROM方案(简单易用)

STM32F1系列单片机的flash模拟EEPROM方案(简单易用)

2023-09-07 08:46| 来源: 网络整理| 查看: 265

目录 前言方案说明设计准则设计思路使用方法 源码分享试验结果注意事项

前言

在进行单片机开发的过程中,经常要求使用一些可以断电后不丢失信息的数据。STM32的片上flash就是具有断电保持功能的存储器,是单片机存储源代码的地方,其断电之后数据不丢失,所以可以利用flash来实现数据的断电保存。 但是flash的寿命是极其有限的,每次擦写flash都会消耗其寿命,而且读写过程比较繁琐,所以需要设计一套程序来管理flash的读写,尽量减少flash的损耗。

方案说明 设计准则 能够存储各种类型的数据,如整形、浮点型、字符串、自定义类型等。使用方便,无需手动分配存储空间或者指定储存地址。尽可能大的利用flash空间,尽可能减少擦除次数。能适应各种容量的单片机,移植、修改配置等操作尽量简单。 设计思路 对于一个需要断电保存的变量,给其分配一页flash用于存储,并在页的开头处记录该变量的地址,占用空间大小等信息。由于需要尽量减少擦除次数,所以该变量发生变化且需要保存最新值时,不会在flash中擦除原有的值,而是在原有值之后写入新的值,此时页的空白处渐渐减少。当一页flash的空白处少到写不下新值的时候,仍不擦除原本的页,而是寻找新的空白页继续写。原来的页标记为废弃。当所有可用的flash页全部分配完,没有空白页的时候,再选择一废弃的页进行擦除。把擦除的页分配给所需要的变量。每一次向flash写入数据,数据的存储地址都会改变,给读取造成不便。为了加快找到最新存储地址的速度,创建一个数组,用于记录各个页内部最新存储数据的地址。 使用方法 根据自己的机型修改头文件,设置flash大小,用于存储的flash起始页以及范围。使用函数 register_data(const void* pdata, uint16_t size) 来注册选中变量,pdata为该变量的地址,size为该变量占据的内存大小(单位字节)。对于已经注册的变量,可以使用函数 save_data(const void* pdata) 来向flash写入数据,写入的值可以断电保存。对于已经保存在flash中的变量,可以使用函数 flash_addr(const void* pdata) 来获取其存储的flash地址。如果变量要恢复到上一次写进flash的值,可以使用函数 load_data(void* pdata) 。如果从未写入flash,则不进行任何操作。如果之后不再需要该变量断电保存,则可以使用函数 remove_data(const void* pdata) 来注销该变量。注销之后,如果该变量没有再次注册,则不能够使用flash的存储空间来读写。 源码分享

文件:main.c

#include "delay.h" #include "sys.h" #include "usart.h" #include "stm32f1_flash.h" int32_t var_a = 0; int main() { int i = 0; delay_init(); uart_init(19200); printf("test start !\r\n"); register_data(&var_a, sizeof(var_a));//注册数据 load_data(&var_a);//加载数据 如果之前没有保存,则无动作 for (i = 0; i remove_data(&var_a);//删除flash中的数据 printf("remove \r\n"); } printf("over\r\n"); while(1); return 0; }

文件:stm32f1_flash.h

#ifndef STM32F1_FLASH_H #define STM32F1_FLASH_H #include "stm32f10x.h" /*****************用户根据需要设置*******************/ #define STM32_FLASH_SIZE 512 /*flash大小 单位KB*/ #define ADDR_START_PAGE (0x08006000) /*储存数据的FLASH起始页地址*/ #define ADDR_END_PAGE (0x08010000) /*储存数据的FLASH最后一页的后一页地址*/ /***************************************************/ void flash_init(void); int register_data(const void* pdata, uint16_t size);/*注册数据,注册之后才能使用flash*/ int save_data(const void* pdata);/*保存数据*/ int load_data(void* pdata);/*读取数据*/ int remove_data(const void* pdata);/*在flash中删除数据*/ uint32_t flash_addr(const void* pdata);/*返回保存该变量的flash地址*/ #endif

文件:stm32f1_flash.c

#include "stm32f1_flash.h" #include "stm32f10x_flash.h" #define PAGE_SIZE_1KB (0x00000400) #define PAGE_SIZE_2KB (0x00000800) #if STM32_FLASH_SIZE < 256 #define PAGE_SIZE PAGE_SIZE_1KB #else #define PAGE_SIZE PAGE_SIZE_2KB #endif #define STM32_FLASH_BASE (0x08000000) /*STM32 FLASH的起始地址*/ #define STM32_FLASH_END ((STM32_FLASH_BASE + STM32_FLASH_SIZE * 1024)) /*STM32 FLASH的结束地址*/ #if (STM32_FLASH_BASE >= ADDR_START_PAGE) ||\ ( ADDR_START_PAGE > ADDR_END_PAGE) ||\ ( ADDR_END_PAGE > STM32_FLASH_END) #error The address you set is out of range /*超出范围*/ #endif #if ( ADDR_START_PAGE % PAGE_SIZE != 0) ||\ ( ADDR_END_PAGE % PAGE_SIZE != 0) #error The address must be the first address of the page /*必须是页首地址*/ #endif #define BLANK_32 ((uint32_t)0xFFFFFFFF) /*32位未编辑区域*/ #define BLANK_16 ((uint16_t)0xFFFF) /*16位未编辑区域*/ #define BLANK_8 ((uint8_t)0xFF) /*8位未编辑区域*/ #define EraseTimeout ((uint32_t)0x000B0000) #define ProgramTimeout ((uint32_t)0x00002000) typedef struct PageHead{ uint32_t target_addr; uint16_t data_size; uint16_t flag; } PageHead; #define DATA_OFFSET sizeof(PageHead) /*数据开始位置 在页内的偏移量*/ #define blank_page(page_addr) (((PageHead*)page_addr)->target_addr == BLANK_32) /*判断为空白页*/ #define used_page(page_addr) (((PageHead*)page_addr)->flag != BLANK_16)/*判断为废弃页*/ #define using_page(page_addr, data_addr) (((PageHead*)page_addr)->target_addr == (uint32_t)data_addr && !used_page(page_addr)) /*判断为使用中的页*/ #define data_size(page_addr) (((PageHead*)page_addr)->data_size) static uint32_t find_new_page(void); static uint32_t save_addr[(ADDR_END_PAGE - ADDR_START_PAGE ) / PAGE_SIZE];/*每一页对应的 数据储存地址 或者 0:空白页 1:未存储 2:废弃页*/ static uint16_t initialized = 0;/*已经初始化的标志*/ void flash_init(void) { uint32_t page_addr = ADDR_START_PAGE; initialized = 0; for (page_addr = ADDR_START_PAGE; page_addr save_addr[index] = 0;/*空白页*/ } else if(used_page(page_addr)) { save_addr[index] = 2;/*废弃页*/ } else { uint16_t size = data_size(page_addr); uint32_t last_addr = page_addr + DATA_OFFSET; if (*(uint16_t *)(last_addr) == BLANK_16){ save_addr[index] = 1;/*没有存储数据*/ continue;/*看下一页*/ } while(*(uint16_t *)(last_addr + size + (size&1)) != BLANK_16) { last_addr = last_addr + size + (size&1);/*找到最后一个地址*/ } save_addr[index] = last_addr;/*存储地址*/ } } initialized = 1; } int register_data(const void* pdata, uint16_t size) { uint32_t page_addr = ADDR_START_PAGE; uint32_t select_page = 0; PageHead* ph = 0; if (size + DATA_OFFSET >= PAGE_SIZE) return -1;/*数据过大*/ if (!initialized) flash_init();/*确保已经初始化*/ for (;page_addr return 1;/*已经注册过了*/ } } select_page = find_new_page(); if (select_page == 0) return -1;/*没有页了*/ /*开始注册数据*/ ph = (PageHead*)select_page; FLASH_Unlock(); FLASH_ProgramWord((uint32_t)&(ph->target_addr), (uint32_t)pdata); FLASH_ProgramHalfWord((uint32_t)&(ph->data_size), size); FLASH_Lock(); FLASH_WaitForLastOperation(ProgramTimeout); save_addr[(select_page - ADDR_START_PAGE) / PAGE_SIZE] = 1;/*没有存储数据*/ return 0; } int load_data(void* pdata) { /*把数据从flash加载到静态变量*/ uint32_t page_addr = ADDR_START_PAGE; if (!initialized) flash_init();/*确保已经初始化*/ for (page_addr = ADDR_START_PAGE; page_addr /*找到对应的存储页*/ int index = (page_addr - ADDR_START_PAGE) / PAGE_SIZE; int r_bytes = 0;/*已经读取的字节数*/ uint16_t size = data_size(page_addr); if (save_addr[index] == 1) { return 0;/*没有数据可以读*/ } /*把数据填入*/ for (r_bytes = 0; r_bytes uint32_t page_addr = ADDR_START_PAGE; uint32_t target_addr = (uint32_t)pdata; uint32_t new_addr = 0; uint16_t size = 0; uint16_t w_bytes = 0; if (!initialized) flash_init();/*确保已经初始化*/ for (page_addr = ADDR_START_PAGE; page_addr /*找到页地址*/ int index = (page_addr - ADDR_START_PAGE) / PAGE_SIZE; size = data_size(page_addr); if (save_addr[index] == 1) { new_addr = page_addr + DATA_OFFSET;/*之前还没存储数据*/ } else { new_addr = save_addr[index] + size + (size&1);/*找到新地址*/ } if (new_addr % PAGE_SIZE + size + (size&1) >= PAGE_SIZE) { /*旧页装不下,要找新页*/ uint32_t find_page = find_new_page(); PageHead *ph = 0; if (find_page == 0) { /*没有新页,把原来的页擦除*/ FLASH_Unlock(); FLASH_ErasePage(page_addr);/*擦除包含该地址的页*/ FLASH_WaitForLastOperation(ProgramTimeout); FLASH_Lock(); } else { /*找到新页,原来的页废弃*/ save_addr[index] = 2;/*原来的页已经废弃*/ FLASH_Unlock(); FLASH_ProgramHalfWord((uint16_t)&((PageHead*)page_addr)->flag, 1); FLASH_Lock(); page_addr = find_page;/*用新的页储存*/ } new_addr = page_addr + DATA_OFFSET;/*新页新地址*/ /*新页需要新页头*/ ph = (PageHead*)page_addr; FLASH_Unlock(); FLASH_ProgramWord((uint32_t)&(ph->target_addr), (uint32_t)pdata); FLASH_ProgramHalfWord((uint16_t)&(ph->data_size), size); FLASH_Lock(); } break; } } if (new_addr == 0) return -1;/*之前没有注册过*/ /*保存新的数据*/ save_addr[(page_addr - ADDR_START_PAGE) / PAGE_SIZE] = new_addr;/*记录新的存储地址*/ FLASH_Unlock(); for (w_bytes = 0; w_bytes uint32_t page_addr = ADDR_START_PAGE; if (!initialized) flash_init();/*确保已经初始化*/ for (page_addr = ADDR_START_PAGE; page_addr /*找到页地址*/ save_addr[(page_addr - ADDR_START_PAGE) / PAGE_SIZE] = 2;/*页已经废弃*/ FLASH_Unlock(); FLASH_ProgramHalfWord((uint16_t)&((PageHead*)page_addr)->flag, 1); FLASH_Lock(); return 0; } } return 0; } uint32_t flash_addr(const void* pdata) { uint32_t page_addr = ADDR_START_PAGE; for (page_addr = ADDR_START_PAGE; page_addr /*找到页地址*/ return save_addr[(page_addr - ADDR_START_PAGE) / PAGE_SIZE]; } } return 0; } static uint32_t find_new_page(void) { uint32_t page_addr = ADDR_START_PAGE; for (page_addr = ADDR_START_PAGE; page_addr return page_addr;/*选到空白页*/ } } for (page_addr = ADDR_START_PAGE; page_addr /*擦除废弃页*/ FLASH_Unlock(); FLASH_ErasePage(page_addr);/*擦除该页*/ FLASH_WaitForLastOperation(ProgramTimeout); FLASH_Lock(); save_addr[(page_addr - ADDR_START_PAGE) / PAGE_SIZE] = 0;/*变成空白页*/ return page_addr;/*选到废弃页*/ } } return 0;/*找不到多余的页*/ } 试验结果

本次实验使用正点原子的STM32精英开发板,单片机型号为STM32F103ZET6。使用串口打印信息。 试验结果 这是单片机重启几次之后的结果,可以看到,变量var_a的值可以达到150,说明断电保存的功能可以正常使用,否则var_a的数值不会超过50。而当var_a被注销之后,断电保存的数据也一并失效了。

注意事项 由于在flash中0xffff表示的是未编辑,且程序上电第一次运行的时候,是根据最后的地址内容是否为0xffff来判断是否存储了数据的。所以,如果掉电前存储的数据为-1,65535等特殊值,那么该数据可能就会丢失。该方案不适合存储图片等大型数据,因为对数据大小的限制为一页flash能够存的下。该方案每次读写都要遍历每一页,读写速度不高,所以如果对读写速度的要求很苛刻,建议自行使用哈希表等进行优化。该方案用变量的地址来识别数据,不建议用于保存局部变量的信息。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3