目录
一、常用FLASH擦写规则
最小擦除单位:扇区
可选择擦除单位:扇区、块、全片
最大编程(写入)单位:页( 256 Byte),大于256 Byte则需要循环写入。
最小编程(写入)单位:1 Byte,即一次可写入 1~256 Byte的任意长度字节。
未写入时FLASH里面的数据为全1,即0xFF。
只能由 1 —> 0 写入,不能由 0 —> 1 写入,即如果已经写入过了,则需要先擦除(擦除后数据变为全1)再写入。
示例:0xF0(1111 0000),即高4位可写入,低4位不可写入。
二、字节/页/块转化
1字节(Byte)=8位(bit)
1页=256字节
1扇区=16页=4K
1块=16扇区
1KB=1024B
1MB=1024KB=256扇区
三、超过256字节循环写入
#define PAGE_SIZE 256
INT32U FLASH_PAGE_WRITE(uint8_t* buf, uint32_t wICAddr, uint16_t numByteToWrite)
{
//擦除FLASH
//FlashErase(&QspiInstance, TEST_ADDRESS, MAX_DATA);
uint8_t numOfPage, numOfSingle, offsetAddr, count;
//写入整页数
numOfPage = numByteToWrite / PAGE_SIZE;
//写入整页后剩余字节数
numOfSingle = numByteToWrite % PAGE_SIZE;
//写入地址在页中的偏移量
offsetAddr = wICAddr % PAGE_SIZE;
//页中剩余可写入字节
count = PAGE_SIZE - offsetAddr;
//写入地址为整
if (offsetAddr == 0)
{
//不足一页
if (numOfPage == 0)
{
FlashWrite(&QspiInstance,wICAddr,numOfSingle,buf,WRITE_CMD);
}
//超过一页
else
{
//整页处理
while (numOfPage --)
{
FlashWrite(&QspiInstance,wICAddr,PAGE_SIZE,buf,WRITE_CMD);
wICAddr += PAGE_SIZE;
buf += PAGE_SIZE;
}
//不足一页处理
if (numOfSingle != 0)
{
FlashWrite(&QspiInstance,wICAddr,numOfSingle,buf,WRITE_CMD);
}
}
}
//写入地址不为整
else
{
//不足一页
if (numOfPage == 0)
{
//写入字节数不超过页中剩余可写入字节
if (numOfSingle <= count)
{
FlashWrite(&QspiInstance,wICAddr,numOfSingle,buf,WRITE_CMD);
}
//写入字节数超过页中剩余可写入字节
else
{
FlashWrite(&QspiInstance,wICAddr,count,buf,WRITE_CMD);
wICAddr += count;
buf += count;
FlashWrite(&QspiInstance,wICAddr,numOfSingle - count,buf,WRITE_CMD);
}
}
//超过一页
else
{
FlashWrite(&QspiInstance,wICAddr,count,buf,WRITE_CMD);
wICAddr += count;
buf += count;
numOfPage = (numByteToWrite - count) / PAGE_SIZE;
numOfSingle = (numByteToWrite - count) % PAGE_SIZE;
//整页处理
while (numOfPage --)
{
FlashWrite(&QspiInstance,wICAddr,PAGE_SIZE,buf,WRITE_CMD);
wICAddr += PAGE_SIZE;
buf += PAGE_SIZE;
}
//不足一页的处理
if (numOfSingle != 0)
{
FlashWrite(&QspiInstance,wICAddr,numOfSingle,buf,WRITE_CMD);
}
}
}
return 2;
}
四、滚动存储日志信息
1、最少是两个扇区循环存储,flash是4字节对齐,所以写的要是四的倍数,本程序写的是256个字节,也就是一页,这样比较好计算,每个扇区要保留256个字节用来存序号,(256个字节不一定全部用完,我是只用了前8个字节存序号,剩下的保留了)来判断是否到了新扇区。
.h文件
#ifndef __LOGDRIVER_H__
#define __LOGDRIVER_H__
#include "main.h"
#define LOG_SECTOR_RSV_BYTES 256
//日志存储器的扇区保留字节数
#define FLASHPARA_ADDR 0xA00000 //128K
#define FLASHPARA_SIZE 0x20000
#define LOGSYSPARA_START_ADDR FLASHPARA_ADDR
#define LOGSYSPARA_SECTOR_NUM FLASHPARA_SIZE/0x1000 //32个扇区
#define LOG_SECTOR_SIZE 0x1000 //4096
//日志存储器的扇区大小
#define PARA_LENGTH (256-4)//减去的4字节是结构体的类型和校验
#define SSC_LOGDRIVER_CRC16 1
#define LOG_READ_RETCODE_ALL_FF 0
#define LOG_READ_RETCODE_POS_ERR 1
#define LOG_READ_RETCODE_CHKSUM_ERR 2
#define LOG_READ_RETCODE_OK 3
typedef struct
{
INT16U version; // 数据结构的版本!!!
INT8U paras[PARA_LENGTH]; //实际参数
INT8U crc[2]; //校验
} PARACB;
typedef struct tag_LOGCB
{
INT32U flash_start_addr; //该日志存放在flash中的物理起始地址
INT16U items_per_sector; //一个扇区可存放的条目数, (LOG_SECTOR_SIZE-8)/item_size 28条
INT16U total_items; //该日志总条目数, < 32000 items_per_sector*sectors
INT8U sectors; //该日志占用的扇区数
INT16U item_size; //该日志每条目的大小,为item实际大小 + (chkmode + 1).
//多加的(chkmode + 1)字节为item的校验,由logdiver内部处理
INT8U chkmode; //0:chksum 1:crc16
} SSC_log_def;
//定义日志系统的基本信息数据结构,一般放在code段中,减少对ram的占用
typedef struct //<256BYTE
{
//以下可以为通信或其他在程序中自己实时更改 124
u8 VOL[4];
u8 CUR[4];
u8 POW[4];
//ARC Management
u8 RATE[8];
u8 arc_con[2];
u8 micro[9];
u8 hard[21];
u8 arc_burst[5];
u8 counters;
//Communication
u8 ACTIVE;
u8 INITIAL;
u8 ACT_SOURCE;
u8 RS_485[2];
u8 BIT;
u8 PROFIBUS[7];
u8 BIG;
u8 COM[2];
//Configuration
u8 BIP;
u8 FR_DU[16];
u8 PUL;
u8 BLINK[26];
u8 PRO[2];
u8 BL_S;
u8 POW1;
u8 PAR;
u8 COM1;
}FLASHPARA;
#define flash_para (*(FLASHPARA*)(&pkg_para.paras[0]))
#define SSC_para ((INT8U*)(&pkg_para.paras[0]))
void SSC_paralog_save(void);
void SSC_paralog_read(void);
INT16U SSC_logdriver_init(SSC_log_def * plogcb, INT8U * pdlog);
void SSC_paralog_init(void);
void setmem_0(INT8U *buf,INT16U n);
INT8U isFF(INT8U *buf, INT16U n); //最多判断前8字节是否全0xff
INT16U SSC_logdriver_save(SSC_log_def * plogcb, INT8U * pslog, INT16U item_pos);
INT16U SSC_logdriver_read(SSC_log_def * plogcb, INT8U * pdlog, INT16U item_pos);
#endif
INT16U SSC_logdriver_save(SSC_log_def * plogcb, INT8U * pslog, INT16U item_pos)
{
INT16U total_items = plogcb->total_items,crc;
INT16U item_size = plogcb->item_size;
INT32U flash_off;
INT32U wbuf[2];
//将改变的结构体数据存放到另一个结构体(写入flash)里
sdn_memcpy((INT8U *)(&USER_TOTAL),SSC_para,PARA_LENGTH);
if(plogcb->chkmode == SSC_LOGDRIVER_CRC16)
{
crc = SSC_logdiver_crc16(pslog, item_size-2);
pslog[item_size-2] = (crc>>8);
pslog[item_size-1] = crc;
}
else
pslog[item_size-1] = SSC_logdiver_chksum(pslog, item_size-1);
if(item_pos >= total_items)
item_pos = 0;
flash_off = plogcb->flash_start_addr + LOG_SECTOR_SIZE * (item_pos/plogcb->items_per_sector);
if((item_pos%plogcb->items_per_sector) == 0) //new sector 新扇区,该日志一共32个扇区
{
SSC_logdiver_eraseflash(flash_off, LOG_SECTOR_SIZE);
wbuf[0] = log_seq_no;
wbuf[1] = log_seq_no;//保留的8字节中存放相同的序号,用来校验。
SSC_logdiver_writeflash(flash_off, 8, (INT8U *)wbuf);
log_seq_no ++;
}
else
flash_off += ((item_pos%plogcb->items_per_sector) * item_size);
flash_off += LOG_SECTOR_RSV_BYTES;
SSC_logdiver_writeflash(flash_off, item_size, pslog);
item_pos ++;
if(item_pos >= total_items)
item_pos = 0;
return item_pos;
}
void SSC_logdiver_readflash(INT32U addr, INT16U n, INT8U *rbuf)
//读flash函数
{
FlashRead(&QspiInstance,addr,n,rbuf,QUAD_READ_CMD); //读数据
}
void SSC_logdiver_eraseflash(INT32U addr, INT32U size)
//擦除flash函数,size指擦除的字节数
{
FlashErase(&QspiInstance,addr,size);
}
void SSC_logdiver_writeflash(INT32U addr, INT16U n, INT8U *wbuf)
//写flash函数
{
FLASH_PAGE_WRITE(wbuf,addr, n);//写数据
}
unsigned char gen_chksum(unsigned char *p, INT32U p_len)
{
unsigned char chksum = 0;
while(p_len)
{
chksum += (*p);
p++;
p_len--;
}
return chksum;
}
INT8U SSC_logdiver_chksum(INT8U *buf,INT16U n)
//和校验计算函数
{
return gen_chksum(buf, n);
}
INT16U SSC_logdiver_crc16(INT8U *buf,INT16U n)
{
return gen_crc(buf, n, 0);
}
INT16U gen_crc(unsigned char * buf, INT32U len, INT16U precrc)
{
INT16U crc = precrc;
INT32U i;
for (i = 0; i < len; i++)
crc = crc_table[((crc >> 8) ^ buf[i]) & 0xFF] ^ (crc << 8);
return crc;
}
void sdn_memcpy(INT8U * src, INT8U * dst, INT32U n)
{
while(n)
{
n--;
*dst++ = *src++;
}
}
寻找最新的日志信息是用的二分法,可以提高效率
//参数版本,如果参数发生更新,则版本需要更新
static const FLASHPARA default_para =
{
{0x00,0x00,0x00,0x00},//电压
{0x00,0x00,0x00,0x00},//电流
{0x00,0x00,0x00,0x00},//功率
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},//urate arc rate
{0x06,0x01},//ARC CONTROL
{0x00,0x00,0x20,0x42,0x14,0x00,0x00,0x48,0x42},//MICRO ARC
{0x00,0x00,0xDD,0x42,0x00,0x00,0xFA,0x42,0x00,0x00,0xA0,0x42,0x00,0x00,0xC8,0x42,0x00},//HARD ARC
{0x03,0xE8,0x03,0x00,0x78},//ARC BURST
0x00,//ARC CNT
0x9B,
0x10,
0x10,
{0x00,0x01},
0x01,
{0x78,0x27,0x10,0x27,0x10,0x27,0x10},
0x01,
{0x00,0x1E},
0x04,
{0x00,0x00,0x20,0x42,0x00,0x00,0x48,0x42,0x00,0x00,0x20,0x42,0x00,0x00,0x48,0x42},
0x05,
{0x01,0x00,0x00,0xC0,0x40,0x00,0x00,0x8C,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x40,0x00,0x00,0x8C,0x42,0x00,0x00,0x00,0x00},
{0x00,0x00},
0x07,
0x01,
0x00,
0x00
};
//参数部分所占的字节数,一般建议保留一些字节,已备扩展
const SSC_log_def SSC_paralog_def =
{
LOGSYSPARA_START_ADDR,
(LOG_SECTOR_SIZE - LOG_SECTOR_RSV_BYTES)/256, //32条
(LOG_SECTOR_SIZE - LOG_SECTOR_RSV_BYTES)/256 * LOGSYSPARA_SECTOR_NUM,
LOGSYSPARA_SECTOR_NUM, //32,该日志占用的扇区数
256, //4字节对齐
1
};
void SSC_paralog_init(void)
/*初始化参数log*/
{
paralog_next_pos = SSC_logdriver_init(paralogcb, (INT8U *)(&pkg_para));
if( pkg_para.version!=PKGPARA_VERSION )
{
SSC_default_para();
paralog_next_pos = 0;
return;
}
}
//寻找新的扇区日志信息
INT16U SSC_logdriver_init(SSC_log_def * plogcb, INT8U * pdlog)
{
INT32U i, flash_off;
INT32U big_sector=0xffffUL, big_seq = 0;
INT32U rbuf[2];
INT16U item_size;
INT16U item_start, item_end;
item_size = plogcb->item_size;//256
flash_off = plogcb->flash_start_addr;//0xA00000
for(i=0; i<plogcb->sectors; i++) //寻找最新sector plogcb->sectors 32扇区
{
//判断每个扇区的前8个字节
SSC_logdiver_readflash(flash_off, 8, (INT8U *)rbuf);
if(rbuf[0]==rbuf[1] && rbuf[0]!=0xffffffffUL)
{
if(rbuf[0] > big_seq)
{
big_seq = rbuf[0];
big_sector = i;
}
}
flash_off += LOG_SECTOR_SIZE;
}
if(big_sector == 0xffffUL) //没找到有效sector
{
//初始化结构体参数
setmem_0(pdlog,item_size);
return 0;
}
if(log_seq_no <= big_seq)
log_seq_no = big_seq + 1;
//二分法寻找最新item
flash_off = plogcb->flash_start_addr + big_sector*LOG_SECTOR_SIZE + LOG_SECTOR_RSV_BYTES;
item_start = 0;
item_end = plogcb->items_per_sector - 1;
while(item_start < item_end)
{
if(item_start ==(item_end-1))
i = item_end;
else
i = (item_start + item_end)/2;
SSC_logdiver_readflash(flash_off + i*item_size, item_size, pdlog);
if(isFF(pdlog, item_size))
item_end = i-1;
else
item_start = i;
}
big_seq = big_sector * plogcb->items_per_sector + item_start; //item_pos --> free item
SSC_logdiver_readflash(flash_off + item_start*item_size, item_size, pdlog);
if(!isFF(pdlog, item_size))
big_seq++;
for(i=0; i<8; i++)
{
if(SSC_logdriver_read(plogcb, pdlog, big_seq-i-1)==LOG_READ_RETCODE_OK)
return big_seq;
}
setmem_0(pdlog,item_size);
return 0;//big_seq;
}
INT16U SSC_logdriver_read(SSC_log_def * plogcb, INT8U * pdlog, INT16U item_pos)
//读1个item, return:0 -- all FF,1 -- item_pos error,2 -- chksum error, 3 -- read ok
// item_pos <= 2*total_items - 1 and item_pos >= 0 - (total_items-1)
{
INT16U total_items = plogcb->total_items;
INT16U item_size = plogcb->item_size;
INT32U flash_off;
if(item_pos > 0x8000U)//序号为负数?
item_pos += total_items;
else if(item_pos >= total_items)
item_pos -= total_items;
if(item_pos >= total_items)
return LOG_READ_RETCODE_POS_ERR;
flash_off = plogcb->flash_start_addr + LOG_SECTOR_SIZE * (item_pos/plogcb->items_per_sector) + LOG_SECTOR_RSV_BYTES;
flash_off += ((item_pos%plogcb->items_per_sector) * item_size);
SSC_logdiver_readflash(flash_off, item_size, pdlog);
if(isFF(pdlog, item_size))
return LOG_READ_RETCODE_ALL_FF;
if(plogcb->chkmode == SSC_LOGDRIVER_CRC16)
{
if(SSC_logdiver_crc16(pdlog, item_size-2) != (pdlog[item_size-2]*256U+pdlog[item_size-1]))
return LOG_READ_RETCODE_CHKSUM_ERR;
}
else
{
if(SSC_logdiver_chksum(pdlog, item_size-1) != pdlog[item_size-1])
return LOG_READ_RETCODE_CHKSUM_ERR;
}
sdn_memcpy(SSC_para,(INT8U *)(&USER_TOTAL),PARA_LENGTH);
return LOG_READ_RETCODE_OK;
}
五、注意
代码不全,只是提供思路:初始化先寻找扇区序号,如果flash里有日志信息,也就是扇区的前8字节不是ffffffff,之后在二分法寻找这个扇区的最新的日志,保存日志前俩字节是结构体类型,最后俩字节是校验