复习老掉牙的知识-STM32F407实现FLASH模拟EEPROM
程序使用办法和注意事项在代码下面介绍主要代码如下:
#if !defined(_FLASH_H)
#define _FLASH_H
#define FLASH_ADR 0x08010000
#define u8 unsigned char
#define u16 unsigned int
#define u32 unsigned long int
union union_temp16
{
unsigned int un_temp16;
unsigned char un_temp8;
}my_unTemp16;
typedef struct
{
u8 apn;
u8 useName;
u8 password;
u8 serverIP;
u8 port;
u8 useCall;
}configStruct;
void FlashWriteStr( u32 flash_add, u16 len, u16* data )
{ u16 byteN = 0;
FLASH_Unlock();
//FLASH_ErasePage(flash_add);
while( len )
{
my_unTemp16.un_temp8 = *(data+byteN);
my_unTemp16.un_temp8 = *(data+byteN+1);
FLASH_ProgramHalfWord( flash_add+byteN , my_unTemp16.un_temp16 );
if( 1==len ){
break;
}
else{
byteN += 2;
len -= 2;
}
}
FLASH_Lock();}
void FlashReadStr( u32 flash_add, u16 len, u16* data )
{
u16 byteN = 0;
while( len )
{
my_unTemp16.un_temp16 = *(vu16*)(flash_add+byteN);
if( 1==len )
{
*(data+byteN) = my_unTemp16.un_temp8;
break;
}
else
{
*(data+byteN) = my_unTemp16.un_temp8;
*(data+byteN+1) = my_unTemp16.un_temp8;
byteN += 2;
len -= 2;
}
}
}
#endif
使用办法:使用该程序无需调用多余库函数,写入时调用FlashWriteStr(FLASH_ADR,2,&data);读函数时使用FlashReadStr(FLASH_ADR,2,&data);
读写数据均需要使用数组。
注意事项:
1、在写函数时关掉了擦除扇区功能,FLASH_ErasePage(flash_add);函数可以在一个区块内完成数据在不同地址的独立读写功能
2、读写函数必须使用两字节以上定义方式例如int,long等,因为FLASH存储一次写入16Bit.
3、使用时着重注意看门狗,如果喂狗时间太长建议关闭看门狗功能,或者做好互斥
据资料说使用FLASH模拟EEPROM的寿命比EEPROM少一个数量级,不知哪位对此有相对客观的见解! 据资料说使用FLASH模拟EEPROM的寿命比EEPROM少一个数量级,不知哪位对此有相对客观的见解!
好像只有10000次,他这个应用肯定是一些不经常改变的数据,例如校准数据。
要是一天变三次的数据估计会把flash写坏。 用st官方的不就行了, 写N次才擦除一次
我用的:
flash_eeprom.h
/// @file flash_eeprom.h
#ifndef _FLASH_EEPROM_H
#define _FLASH_EEPROM_H
void FLASH_EEPROM_Config(void);
void FLASH_EEPROM_ReadAll(void);
void FLASH_EEPROM_WriteWord(unsigned short addr, unsigned short data);
unsigned short FLASH_EEPROM_ReadWord(unsigned short addr);
void FLASH_EEPROM_WriteData(unsigned short addr, void* data, int num);
void FLASH_EEPROM_ReadData(unsigned short addr, void* data, int num);
#endif
flash_eeprom.c
#include "flash_eeprom.h"
#include "usart.h" // for debug
// 在bootloader里只能读到bootloader区的结尾, 因此这时END_OF_CODE的值并不正确.
// 不过bootloader区不会很长, 因此只要在APP区读到END_OF_CODE不冲突就可以了.
#define END_OF_CODE ((unsigned long)&_etext + ((unsigned long)&_edata - (unsigned long)&_sdata))
#define PAGE_SIZE 2048 // for HD devices
//#define PAGE_SIZE 1024 // for LD & MD devices
//#define FLASH_SIZE ((*(unsigned long*)0x1ffff7cc) & 0xffff) // for stm32f0xx
#define FLASH_SIZE ((*(unsigned long*)0x1ffff7e0) & 0xffff) // for stm32f10x
//#define PAGE_MAIN (0x08000000 + (FLASH_SIZE - 3) * 1024) // for LD & MD devices
//#define PAGE_BACKUP (0x08000000 + (FLASH_SIZE - 2) * 1024)
#define PAGE_MAIN (0x08000000 + (FLASH_SIZE - 6) * 1024) // for HD devices
#define PAGE_BACKUP (0x08000000 + (FLASH_SIZE - 4) * 1024) // for HD devices
#define MAIN_ADDR(x) (PAGE_MAIN + (x))
#define BACKUP_ADDR(x) (PAGE_BACKUP + (x))
#define LOAD_ADDR(x) (*(unsigned short*)MAIN_ADDR(x))
#define LOAD_DATA(x) (*(unsigned short*)MAIN_ADDR((x) + 2))
#define LOAD_BACKUP_ADDR(x) (*(unsigned short*)BACKUP_ADDR(x))
#define LOAD_BACKUP_DATA(x) (*(unsigned short*)BACKUP_ADDR((x) + 2))
#define MAX_DUMMY_ADDR (PAGE_SIZE / 8 * 3 - 1) // LD & MD devices: 191
extern int _etext, _sdata, _edata;
/// @brief 取得第一个空地址
/// @param None
/// @retval 返回第一个空地址, 若不存在则返回-1
static int GetFirstEmptyAddr(void)
{
unsigned short i;
for(i = 0; i < PAGE_SIZE; i += 4) {
if(*(unsigned long*)MAIN_ADDR(i) == 0xffffffff)
return i;
}
return -1;// Full
}
/// @brief 从后备页读取数据
/// @param dummy_addr 伪地址
/// @retval 读到的数据, 若未读到则返回-1 (此处应有更好的处理方式)
static int ReadWordFromBackup(unsigned short dummy_addr) {
int addr = 1020;
while(addr >= 0) {
if(LOAD_BACKUP_ADDR(addr) == dummy_addr)
return LOAD_BACKUP_DATA(addr);
addr -= 4;
}
return -1; // Not found
}
/// @brief 主存储页满, 利用后备页进行轮转
/// @param None
/// @retval None
static void Rotate(void)
{
FLASH_Unlock();
// 1. Erase Backup Page
FLASH_ErasePage(PAGE_BACKUP);
// 2. Copy Main Page to Backup Page. Add checksum later!
for(int i = 0; i < 1024; i += 4) {
FLASH_ProgramWord(BACKUP_ADDR(i), *(unsigned long*)MAIN_ADDR(i)); // full copy
}
// 3. Erase Main Page
FLASH_ErasePage(PAGE_MAIN);
// 4. Copy Backup Page to Main Page. Add Checksum Later!
for(int i = 0; i <= MAX_DUMMY_ADDR; i++) {
int data = ReadWordFromBackup(i);
if(data != -1)
FLASH_EEPROM_WriteWord(i, data);
}
FLASH_Lock();
}
/// @brief 写入16位字
/// @param dummy_addr 伪地址
/// @param data 16位数据
/// @retval None
void FLASH_EEPROM_WriteWord(unsigned short dummy_addr, unsigned short data)
{
if(dummy_addr > MAX_DUMMY_ADDR) {
uprintf(USART_USB, "Address out of range!\n");
return;
}
int addr = GetFirstEmptyAddr();
if(addr == -1) { // Page full, rotate needed
Rotate();
addr = GetFirstEmptyAddr();
}
unsigned org_data = FLASH_EEPROM_ReadWord(dummy_addr);
if(org_data == data) // skip writing if data unchanged
return;
FLASH_Unlock();
FLASH_ProgramHalfWord(MAIN_ADDR(addr), dummy_addr);
FLASH_ProgramHalfWord(MAIN_ADDR(addr) + 2, data);
FLASH_Lock();
}
/// @brief 读取16位数据
/// @param dummy_addr 伪地址
/// @retval 读到的数据, 若未读到则返回0xffff
unsigned short FLASH_EEPROM_ReadWord(unsigned short dummy_addr)
{
int addr = GetFirstEmptyAddr();
// _dbg(); uprintf(USART_USB, "%d\n", addr);
if(addr == 0 || addr == -1)
return 0xffff; // No data
while(addr >= 0) {
if(LOAD_ADDR(addr) == dummy_addr)
return LOAD_DATA(addr);
addr -= 4;
}
return 0xffff; // Dummy_addr not found
}
/// @brief 读取全部数据, 仅用于测试
/// @param None
/// @retval None
void FLASH_EEPROM_ReadAll(void)
{
// todo
for(int i = 0; i < 1024; i += 4) {
uprintf(USART_USB, "%04d: %04x %04x", i,
*(unsigned short*)MAIN_ADDR(i),
*(unsigned short*)MAIN_ADDR(i + 2) );
if(i % 24 == 20)
uprintf(USART_USB, "\n");
}
}
/// @brief 写入数据
/// @param addr 伪地址
/// @param data 指向待写入数据缓冲区的指针
/// @param num 需要写入的数据字节数
/// @retval None
void FLASH_EEPROM_WriteData(unsigned short addr, void* data, int num)
{
// uprintf(USART_USB, "%04x %08x %d\n", addr, (unsigned long)data, num);
if(num % 2 != 0)
num++;
while (num > 0) {
// _dbg();
FLASH_EEPROM_WriteWord(addr, *(unsigned short*)data);
addr++;
data += 2;
num -= 2;
}
}
/// @brief 读取数据
/// @param addr 伪地址
/// @param data 指向待读取数据缓冲区的指针
/// @param num 需要读取的数据字节数
/// @retval None
void FLASH_EEPROM_ReadData(unsigned short addr, void* data, int num)
{
if(num % 2 != 0)
num++;
while(num > 0) {
*(unsigned short*)data = FLASH_EEPROM_ReadWord(addr);
addr++;
data += 2;
num -= 2;
}
}
/// @brief 初始化, 检查主存储页与代码页是否冲突
/// @param None
/// @retval None
void FLASH_EEPROM_Config(void)
{
uprintf(USART_USB, "%lu %08x\n", FLASH_SIZE, END_OF_CODE);
// Todo: flash config (Get base addr, ...)
if(END_OF_CODE >= PAGE_MAIN) {
uprintf(USART_USB, "Program code exceeds!\n");
while(1);
}
}
tomzbj 发表于 2019-1-10 17:54
用st官方的不就行了, 写N次才擦除一次
我用的:
flash_eeprom.h
不听看明白这段话
“// 在bootloader里只能读到bootloader区的结尾, 因此这时END_OF_CODE的值并不正确.
// 不过bootloader区不会很长, 因此只要在APP区读到END_OF_CODE不冲突就可以了.”
愿听你的详解。。。 话说 ST 缺少内置 EEPROM 也都是历史问题了 kinsno 发表于 2019-1-10 20:54
不听看明白这段话
“// 在bootloader里只能读到bootloader区的结尾, 因此这时END_OF_CODE的值并不正确.
我这个是用倒数第三页作为主存储区, 倒数第二页作为后备区用来轮转 (最后一页可能会另作他用)。
所以在程序启动时要判断一下,实际有用的程序大小就是_etext加上_edata减去_sdata。如果.text和.data写到了倒数第三页, 则直接报错。
想想其实查一下倒数第三页是否全ff也就可以了。但是理论上确实有可能全ff但却是有用数据的情况,所以还是算了。
bootload一般也就几k,顶多十几k,肯定不会写到倒数第几页去(不然app往哪写),所以在bootloader里肯定不会报错。
一般应该也不会有在bootloader里写eeprom的需求,顶多读个参数之类,所以这里无所谓了。 t3486784401 发表于 2019-1-10 21:24
话说 ST 缺少内置 EEPROM 也都是历史问题了
可能是工艺问题?STM32L系列就都有 tomzbj 发表于 2019-1-10 21:52
可能是工艺问题?STM32L系列就都有
早期的 ST 目测是工艺/专利有问题,导致没法内嵌 EEPROM,这点比起 ATMEL 差很多(AVR标配EEP)。
FLASH 模拟的 EEPROM,只能说是个补丁,本质上是损耗均衡算法。
不过要是 EEPROM 也用损耗均衡算法,就远不是 FLASH 模拟能赶上的了。 最好不要用内部flash存数据了,会导致cpu暂停长达 几十毫秒 数量级。
有可能导致中断丢失。 t3486784401 发表于 2019-1-10 22:00
早期的 ST 目测是工艺/专利有问题,导致没法内嵌 EEPROM,这点比起 ATMEL 差很多(AVR标配EEP)。
FLASH...
也就存储少量参数用,要存的东西多了还是得上外部eeprom
不过stm32f10x的i2c又不好用, 只能gpio模拟, 而且i2c eeprom写入还得延迟几个ms,也不爽
所以后来如果有外部spi flash,有文件系统的话,就直接在文件系统里写个文件来存储参数代替eeprom了。 meirenai 发表于 2019-1-10 22:23
最好不要用内部flash存数据了,会导致cpu暂停长达 几十毫秒 数量级。
有可能导致中断丢失。 ...
这个还好, 可以接受
有个大坑是如果用dma+dac读取flash里的波形数据然后输出, 写flash的暂停会导致输出波形相位混乱。
不过解决办法也简单, 把波形数据放在sram里就行了,写flash过程中波形不会中断。 tomzbj 发表于 2019-1-10 22:32
这个还好, 可以接受
有个大坑是如果用dma+dac读取flash里的波形数据然后输出, 写flash的暂停会导致输出 ...
奥,理解错了。 tomzbj 发表于 2019-1-10 22:30
也就存储少量参数用,要存的东西多了还是得上外部eeprom
不过stm32f10x的i2c又不好用, 只能gpio模拟,...
看来这个 i2c 不好用是实锤了,前阵子只是看到有人在说,猜测是:基本功能还行,抗干扰太差容易卡死。
看来 ST 还是欠一点,当初 atmel 卖给 PIC 的时候,ST 也只能眼巴巴瞅着 昨天程序经过测试发现在同时读写的情况下出现程序死机情况,因此有改进了一些,希望没有误导大家
#include "main.h"
#define QJ_BZ
#include "Global_Variable.h"
#include "stm32f4xx_it.h"
#include "stm32f4xx.h"
#include "stm32f4xx_flash.h"
#include "USART1.H"
#include "USART2.H"
unsigned char Timer_SendData_Flag;
unsigned char Timer_Collect_Flag;
unsigned char IWDG_Time_Conts;
int SCR1_Compensate;
int SCR2_Compensate;
unsigned char Rec_Flag;
unsigned char Rec_Buf;
///////////////////////////////////////////////////
unsigned int data ={0x71,0x12,0x23,0x54};
unsigned int data1={0x72,0x22,0x23,0x54};
unsigned int data2={0x73,0x32,0x23,0x54};
unsigned int data3={0x74,0x42,0x23,0x54};
unsigned int data4={0x75,0x52,0x23,0x54};
unsigned int data5={0x76,0x62,0x23,0x54};
unsigned int data6={0x77,0x72,0x23,0x54};
unsigned int data7={0x78,0x82,0x23,0x54};
unsigned int data8={0x79,0x92,0x23,0x54};
////////////////////////////////////////////////////////////////////////////////////////
//////////////
//用户根据自己的需要设置
#define STM32_FLASH_SIZE 512 //所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_WREN 1//使能FLASH写入(0,不是能;1,使能)
////////////////////////////////////////////////////////////////////////////////////////
//////////////
#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 4096 //字节
#else
#define STM_SECTOR_SIZE 4096
#endif
//FLASH起始地址
#define STM32_FLASH_BASE 0x08000000 //STM32 FLASH的起始地址
//FLASH解锁键值
u32 mUseHalfWord;//
u32 mStartAddress;//
u32 startAddress=(0x08000000+1000);
u32 useHalfWord=1;
//读取指定地址的半字(16位数据)
//faddr:读地址(此地址必须为2的倍数!!)
//返回值:对应数据.
u16 ReadHalfWord(u32 faddr);
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数 ??
void Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite) ;
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
void Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite);
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead) ;
u16 STMFLASH_BUF;//最多是2K字节
void STFLASH(uint32_t startAddress,u32 useHalfWord)
{
if(startAddress%STM_SECTOR_SIZE!=0)//不是页的开始,将开始处设置为下一个页开始的地
方
startAddress+=(STM_SECTOR_SIZE-(startAddress%STM_SECTOR_SIZE));
mStartAddress=startAddress;
mUseHalfWord=useHalfWord;
}
//读取指定地址的半字(16位数据)
//faddr:读地址(此地址必须为2的倍数!!)
//返回值:对应数据.
u16ReadHalfWord(u32 faddr)
{
return *(vu16*)faddr;
}
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u16 i;
for(i=0;i<NumToWrite;i++)
{
FLASH_ProgramHalfWord(WriteAddr,pBuffer);
WriteAddr+=2;//地址增加2.
}
}
//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
void Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; //扇区地址
u16 secoff; //扇区内偏移地址(16位字计算)
u16 secremain; //扇区内剩余地址(16位字计算)
u16 i;
u32 offaddr; //去掉0X08000000后的地址
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE
+1024*STM32_FLASH_SIZE)))return;//非法地址
FLASH_Unlock(); //解锁
offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址.
secpos=offaddr/STM_SECTOR_SIZE; //扇区地址0~127 for
STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单
位.)
secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小
if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
while(1)
{ /*
Read(secpos*STM_SECTOR_SIZE
+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
for(i=0;i<secremain;i++)//校验数据
{
if(STMFLASH_BUF!=0XFFFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除
这个扇区
for(i=0;i<secremain;i++)//复制
{
STMFLASH_BUF=pBuffer;
}
Write_NoCheck(secpos*STM_SECTOR_SIZE
+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
}else */Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接
写入扇区剩余区间.
if(NumToWrite==secremain)break;//写入结束了
else//写入未结束
{
secpos++; //扇区地址增1
secoff=0; //偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumToWrite-=secremain; //字节(16位)数递减
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//
下一个扇区还是写不完
else secremain=NumToWrite;//下一个扇区可以写完了
}
};
FLASH_Lock();//上锁
}
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
u16 i;
for(i=0;i<NumToRead;i++)
{
pBuffer=ReadHalfWord(ReadAddr);//读取2个字节.
ReadAddr+=2;//偏移2个字节.
}
}
const u8 TEXT_Buffer[]={"Flash_test"};
#define SIZE sizeof(TEXT_Buffer) //数组长度
#define FLASH_SAVE_ADDR0X0800FC00 //设置FLASH 保存地址(必须为偶数,且其值
要大于本代码所占用FLASH的大小+0X08000000)
u8 datatemp;
///////////////////////////////////////////////////
int main(void)
{
unsigned char sendconts,senddat;
unsigned int tab1;
SystemInit();
USART1_Configuration(115200);
NVIC_USART1_Configuration();
//Write(FLASH_SAVE_ADDR,(u16*)TEXT_Buffer,SIZE);//写数据,第一次下载程序到32,
第二次注释掉此行,断电重新编译下载 //keil watch查看datatemp数组的数据正是之前写进去的数
据
Write(FLASH_SAVE_ADDR,(u16*)data,4);
Write(FLASH_SAVE_ADDR+100,(u16*)data1,4);
Write(FLASH_SAVE_ADDR+200,(u16*)data2,4);
Write(FLASH_SAVE_ADDR+300,(u16*)data3,4);
Write(FLASH_SAVE_ADDR+400,(u16*)data4,4);
Write(FLASH_SAVE_ADDR+500,(u16*)data5,4);
Write(FLASH_SAVE_ADDR+600,(u16*)data6,4);
Write(FLASH_SAVE_ADDR+700,(u16*)data7,4);
Write(FLASH_SAVE_ADDR+800,(u16*)data8,4);
while(1)
{
for(i=0;i<9;i++)
{
Read(FLASH_SAVE_ADDR+i*100,(u16*)tab1,4);
senddat = tab1;
senddat = tab1;
senddat = tab1;
senddat = tab1;
senddat = senddat+senddat+senddat+senddat;
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
USART_Byte_Send(USART1, senddat);
delay_ms(5000);
}
}
}
mark.flash 损耗均衡算法 mark.flash 损耗均衡算法+1 这个好………… eeprom写flash 均衡算法
页:
[1]