在进行单片机开发的过程中,经常要求使用一些可以断电后不丢失信息的数据。STM32的片上flash就是具有断电保持功能的存储器,是单片机存储源代码的地方,其断电之后数据不丢失,所以可以利用flash来实现数据的断电保存。
但是flash的寿命是极其有限的,每次擦写flash都会消耗其寿命,而且读写过程比较繁琐,所以需要设计一套程序来管理flash的读写,尽量减少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 < 50; i++)
{
var_a += 1;//改变数据
printf("var_a is %d\r\n", var_a);
save_data(&var_a);//保存数据
printf("address is %p\r\n", (void*)flash_addr(&var_a));
delay_ms(50);
}
if (var_a > 120)
{
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 < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
int index = (page_addr - ADDR_START_PAGE)/PAGE_SIZE;
if (blank_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 < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (using_page(page_addr, pdata))
{
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 < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (using_page(page_addr, pdata))
{
/*找到对应的存储页*/
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 < size; r_bytes++)
{
((uint8_t *)pdata)[r_bytes] = ((uint8_t *)save_addr[index])[r_bytes];/*一个字节一个字节地填*/
}
}
}
return 0;/*读取成功*/
}
int save_data(const void* pdata)
{
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 < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (using_page(page_addr, pdata))
{
/*找到页地址*/
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 < size; w_bytes += 2)
{
FLASH_ProgramHalfWord(new_addr + w_bytes, *(uint16_t*)(target_addr + w_bytes));/*把数据填入*/
}
FLASH_Lock();
FLASH_WaitForLastOperation(ProgramTimeout);
return 0;/*保存成功*/
}
int remove_data(const void* pdata)
{
uint32_t page_addr = ADDR_START_PAGE;
if (!initialized) flash_init();/*确保已经初始化*/
for (page_addr = ADDR_START_PAGE; page_addr < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (using_page(page_addr, pdata))
{
/*找到页地址*/
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 < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (using_page(page_addr, pdata))
{
/*找到页地址*/
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 < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (blank_page(page_addr))
{
return page_addr;/*选到空白页*/
}
}
for (page_addr = ADDR_START_PAGE; page_addr < ADDR_END_PAGE; page_addr += PAGE_SIZE)
{
if (used_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被注销之后,断电保存的数据也一并失效了。