用nRF52810做flash驱动时遇到一个问题
flash写函数的拷贝缓存需要开4K数组,ram不够用
于是做了一个用4K空间的flash区域代替数组的实现函数
signed char fs_write(uint32_t wr_addr,uint8_t *p_wr,uint16_t len_to_wr)
其中测试函数是
void fs_write_test()
#include "my_fs.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "nrf_fstorage_sd.h"
#include "nrf_fstorage.h"
#include "nrf.h"
#include "nrf_soc.h"
#include "nordic_common.h"
#include <stdint.h>
#include "app_error.h"
#include "nrf_delay.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
/********************************************************************************************************
*使能协议栈 用fs或fds的方式操作flash
*1、nRF52810最小擦写次数 10000次
*2、写和读都是32bit的,如写入0x123456芯片的存入是这样的 56 34 12 00,所以你可以将8bit的转成32bit的再存,高低位可以自己控制
*3、读数据的时候可以不用nrf_fstorage_read();读的数据多了,会报错,
* 直接用memcpy()读地址,将地址里的数copy出来,就不会报错了。
* 用memcpy()读地址和长度都不需要对齐
*4、sdk_config.h 中需要修改:#define NRF_FSTORAGE_ENABLED 1
*5、nRF_Libraries添加以下目录中的文件
* xxx\nRF5_SDK_15.3.0_59ac345\components\libraries\fstorage
*6、写完要等待 wait_for_flash_ready(&fstorage);
*7、写入的源数据地址和flash的物理地址一样需要4字节对齐
*8、一次写入的长度不能超过 4096 可以跨页写
*********************************************************************************************/
//static uint8_t fs_page_buf[4096] = {0};//写操作的页缓存
static void fstorage_evt_handler(nrf_fstorage_evt_t * p_evt);
static void print_flash_info(nrf_fstorage_t * p_fstorage);
static uint32_t nrf5_flash_end_addr_get();
void wait_for_flash_ready(nrf_fstorage_t const * p_fstorage);
/*flash start 0 ~ 0x30000*/
NRF_FSTORAGE_DEF(nrf_fstorage_t fstorage) =
{
/* Set a handler for fstorage events. */
.evt_handler = fstorage_evt_handler,
.start_addr = MY_FLASH_DATA_START,
.end_addr = MY_FLASH_DATA_END,
};
//使能协议栈后 fstorage操作 flash 的初始化函数
void ble_fs_init()
{
ret_code_t rc;
nrf_fstorage_api_t * p_fs_api;
p_fs_api = &nrf_fstorage_sd;
rc = nrf_fstorage_init(&fstorage, p_fs_api, NULL);
APP_ERROR_CHECK(rc);
print_flash_info(&fstorage);
(void) nrf5_flash_end_addr_get();
}
//fs写,等待写结束 传入3个参数必须4字节对齐
static void fs_wr_drv(uint32_t addr,uint8_t *p_buf,uint16_t len)
{
ret_code_t rc;
rc = nrf_fstorage_write(&fstorage, addr, p_buf, len, NULL);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
}
//fs擦除,等待擦除结束
static void fs_erase_drv(uint32_t page_addr,uint8_t len)
{
ret_code_t rc;
rc = nrf_fstorage_erase(&fstorage,page_addr,len,NULL);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
}
/*函数功能:写入参数不需考虑4字节对齐
*起始地址和结束地址均需在同一页
*只能被fs_write调用!
*/
static signed char fs_write_nocheck (uint32_t wr_start,uint8_t *p_wr,uint32_t wr_end)
{
/*分 3 次写
头: 起始地址到首个4整除的地址
中间:首个4整除的地址到 最后一个4整除的地址
尾: 最后一个4整除的地址到最后*/
/*
待写入flash示例:x:不改写的数据, 其他:要改写的字节
xxx1 ABCD ... DCBA 10xx
wr_start : xxx1 中 1 的地址; wr_end: 10xx 中 0 的地址.*/
uint32_t addr_temp = 0; //写入地址
uint16_t len_temp = 0, num_to_wr = 0;
uint8_t remain_temp = 0, buff_temp[32] = {0};
if((wr_start > wr_end)||(wr_end > (wr_start+4096))) //地址错误
{
return -1;
}
NRF_LOG_DEBUG("wr nocheck");
num_to_wr = 1 + wr_end - wr_start; //待写入的字节数
//NO.1 write head.........................................................................................
remain_temp = wr_start%4; //xxx1 中不要改写的字节数 3
len_temp = 4 - remain_temp; //xxx1 中要改写的字节数 1
if(len_temp > num_to_wr)
len_temp = num_to_wr;
addr_temp = wr_start - remain_temp; //xxx1 第一个字节在flash中的物理地址
if((addr_temp % 4) != 0)
{
return -1;
}
memcpy(buff_temp,(uint32_t *)addr_temp,4);
memcpy(&buff_temp[remain_temp],p_wr,len_temp);
fs_wr_drv(addr_temp,buff_temp,4);
//更新写入地址和写入数据缓存地址
p_wr += len_temp;
addr_temp += 4;
//是否写完
if(num_to_wr <= len_temp)
return 0;
else
num_to_wr -= len_temp;
//NO.2 write body...........................................................................................
if(num_to_wr > 4)
{
uint16_t wr_body_ok_len = 0;
//除掉尾巴的长度
len_temp = num_to_wr - num_to_wr%4;
while(wr_body_ok_len < len_temp)
{
memcpy(buff_temp,p_wr+wr_body_ok_len,4);
fs_wr_drv(addr_temp,buff_temp,4);
wr_body_ok_len += 4;
addr_temp += 4;
}
//是否写完
if(num_to_wr <= len_temp)
return 0;
else
p_wr += len_temp;
}
//NO.3 write tail............................................................................................
remain_temp = wr_end%4;
len_temp = 1 + remain_temp;
addr_temp = wr_end - remain_temp;
memcpy(buff_temp,(uint32_t *)addr_temp,4);
memcpy(buff_temp,p_wr,len_temp);
fs_wr_drv(addr_temp, buff_temp,4);
return 0;
}
/*
*拷贝 src 页到 dest页
*pos_src:缓存页首地址 pos_dest:待修改的数据页首地址
*/
void fs_copy_flash_page(uint32_t pos_dest,uint32_t pos_src)
{
uint8_t buff_temp[32] = {0}; //每次拷贝的缓存
uint8_t copy_time = 0; //拷贝次数
//先删除待写入页
fs_erase_drv(pos_dest, 1);
//分多次拷贝
while(copy_time++ < 128)
{
memcpy(buff_temp,(uint32_t *)pos_src,32);
fs_wr_drv(pos_dest,buff_temp,32);
//更新待写入 flash 和 源数据 地址
pos_src += 32; pos_dest += 32;
}
}
/*流程类似fs_write_nocheck
*使用前提:被拷贝进去的区域[dest_start : (dest_start+copy_len)]已被置成0xff
*使用前提:src_start 和 dest_start必须相对各自页首的偏移相同
*函数功能:将flash区域[src_start:(src_start + copy_len - 1)]拷贝到
flash[dest_start:(dest_start + copy_len - 1)]
*传入地址不需对齐
*不影响dest页不用被写的数据
*/
signed char fs_copy_flash_data(uint32_t dest_start,uint32_t src_start,uint16_t copy_len)
{
uint8_t buff[4] = {0}; //拷贝缓存
uint16_t num_to_copy = 0; //每拷贝完一段长度,就减少成未拷贝完的长度
uint16_t wr_body_ok_len = 0; //用于更新写完成的body字节数
uint16_t wr_len_temp = 0, remain_len_temp = 0; //写入长度 4字节对齐用的temp
uint32_t dest_addr_temp = 0, src_addr_temp = 0; //目标地址 源地址
if((src_start % 4096) != (dest_start % 4096)) //src_start 和 dest_start必须相对各自页首的偏移相同
{
NRF_LOG_DEBUG("copy flash data err :");
NRF_LOG_DEBUG("err_src_start:0x08x,err_dest_start:0x%08x",src_start,dest_start);
return -1;
}
/*要拷贝到dest的数据示例: [xxx1 ABCD ... DCBA 10xx], x表示不改变的旧数据*/
//1、copy head : [xxx1] 的 1 ...............................................
remain_len_temp = src_start % 4; //[xxx1]x的长度,起始地址的4字节对齐偏移量
wr_len_temp = 4 - remain_len_temp; //head的修改长度
src_addr_temp = src_start; //[xxx1]中1的地址
dest_addr_temp = dest_start - remain_len_temp; //[xxx1]中第一个字节地址
memcpy(buff,(uint32_t *)dest_addr_temp,4); //拷贝旧数据到buff
memcpy(&buff[remain_len_temp],(uint32_t *)src_addr_temp,wr_len_temp);//修改要拷贝进去的新数据
fs_wr_drv(dest_addr_temp, buff, 4);
num_to_copy = copy_len - wr_len_temp; //更新未拷贝完的长度
if(num_to_copy == 0) //是否结束
{
return 0;
}
//2、copy body : [ABCD ... DCBA] ...........................................
if(num_to_copy >= 4)
{
src_addr_temp = src_start + wr_len_temp; //此时wr_len_temp为头的改变字节数
dest_addr_temp = dest_start + wr_len_temp;
remain_len_temp = (dest_start + copy_len)%4; //tail:长度:[10xx]“10”的个数
wr_len_temp = num_to_copy - remain_len_temp; //body:长度:未拷贝完的长度减掉尾巴
while(wr_body_ok_len < wr_len_temp)
{
memcpy(buff,(uint32_t *)src_addr_temp,4);
fs_wr_drv(dest_addr_temp,buff,4);
//更新地址和长度参数
src_addr_temp += 4;
dest_addr_temp += 4;
wr_body_ok_len += 4;
}
if(remain_len_temp == 0) //是否结束
{
return 0;
}
num_to_copy -= wr_len_temp;
}
//3、copy tail : [10xx] 的 10...............................................
wr_len_temp = (dest_start + copy_len)%4; //tail:长度
dest_addr_temp = dest_start + copy_len - wr_len_temp;
src_addr_temp = src_start + copy_len - wr_len_temp;
memcpy(buff, (uint32_t *)dest_addr_temp,4);
memcpy(buff, (uint32_t *)src_addr_temp, wr_len_temp);
fs_wr_drv(dest_addr_temp,buff,4);
return 0;
}
/*函数功能:
*在 指定的数据区 任意起始位置 写入任意长度*/
signed char fs_write(uint32_t wr_addr,uint8_t *p_wr,uint16_t len_to_wr)
{
uint8_t copy_buff[4] = {0};
uint16_t loop = 0;
uint16_t page_off = wr_addr % 4096; //页内偏移
uint32_t page_pos = wr_addr - page_off;//写入地址的页首物理地址
uint16_t page_remain = 4096 - page_off;//页内待改写字节数
//起始地址或结束地址不在范围内
if((wr_addr < MY_FLASH_DATA_START) || ((wr_addr + len_to_wr - 1) >= PAGE_BUFF_ADDR))
{
NRF_LOG_DEBUG("ERR fs_write:addr is not in range!");
return -1;
}
while(len_to_wr)
{
//1、如果页剩余空间比待写入的长度大
if(page_remain > len_to_wr)
page_remain = len_to_wr;
//2、校验待写入的位置是否有非0xff的数据
for(loop = 0;loop < page_remain;loop++)
{
memcpy(copy_buff,(uint32_t *)(page_pos+page_off+loop),1);
if(copy_buff[0] != 0xff)//从偏移到页结束
{
break;//结束校验
}
}
//3、写入
if(loop < page_remain)
{
//待写入起始数据拷贝到 页缓存flash 的物理地址
uint32_t copy_dest_start = PAGE_BUFF_ADDR + page_off;
//待写入结束数据拷贝到 页缓存flash 的物理地址
uint32_t copy_dest_end = copy_dest_start + page_remain - 1;
//3.0、擦除缓存页
fs_erase_drv(PAGE_BUFF_ADDR, 1);
//3.1、将待写入数据按页内相对位置拷贝到缓存页
//====================================================
// NRF_LOG_DEBUG("Need Erase [0x%08X,0x%08X]",copy_dest_start,copy_dest_end);
if(fs_write_nocheck(copy_dest_start,p_wr,copy_dest_end) == -1)
return -1;
//3.2、拷贝页首地址 到 待写入起始地址
if(page_off != 0)
{
//===================================================
//NRF_LOG_DEBUG("Copy flash head:[0x%08X,0x%08x]",page_pos,page_pos + page_off);
fs_copy_flash_data(PAGE_BUFF_ADDR,page_pos,page_off);
}
//3.3、拷贝待写入结束地址 到 页结束地址
if(copy_dest_end != (PAGE_BUFF_ADDR + 4095))
{
//===========================================
//NRF_LOG_DEBUG("Copy flash end:[0x%08X,0x%08x]",page_pos + page_off + page_remain,page_pos+4095);
fs_copy_flash_data(copy_dest_end + 1, page_pos + page_off + page_remain, 4096 - page_off - page_remain);
}
//3.4、拷贝缓存页到待写入页
fs_copy_flash_page( page_pos, PAGE_BUFF_ADDR);
}
else
{
//不需要擦除
//===========================================
//NRF_LOG_DEBUG("NOT Need Erase:[0x%08x,0x%08x]",wr_addr,wr_addr + page_remain - 1);
if(fs_write_nocheck(wr_addr,p_wr,wr_addr + page_remain - 1) == -1)
return -1;
}
//4、更新地址长度
p_wr += page_remain;
wr_addr += page_remain;
len_to_wr -= page_remain;
page_off = 0;
page_pos += 4096;
page_remain = 4096;
}
return 0;
}
/*函数功能: fs_write的测试*/
void fs_write_test()
{
uint8_t buff[64] = {0};
uint8_t loop = 0;
//1、测试在删除了扇区的地方写
while(loop < 64)
{
buff[loop] = loop;
loop++;
}
fs_write(FLASH_PAGE_1_START,buff,17);
//2、测试在有数据的后面继续写
memset(buff,0x77,64);
fs_write(FLASH_PAGE_1_START+17,&buff[1],32);
//3、测试在有数据的中间改数据
memset(buff,0x55,64);
fs_write(FLASH_PAGE_1_START+23,&buff[1],15);
loop = 0;
while(loop < 64)//验证用memcpy读,地址和长度都可以不用4字节对齐
{
memcpy(&buff[loop],(uint32_t *)(FLASH_PAGE_1_START+loop),1);
loop++;
}
NRF_LOG_DEBUG("write test 1 ret!");
NRF_LOG_HEXDUMP_DEBUG(buff,64);
//4、测试跨页写
memset(buff,0xaa,64);
fs_write(FLASH_PAGE_2_END-30,buff,31+7);
memset(buff,0xff,64);
memcpy(buff,(uint32_t *)(FLASH_PAGE_2_END+1),38);
NRF_LOG_DEBUG("write test 2 ret!");
NRF_LOG_HEXDUMP_DEBUG(buff,38);
fs_erase_drv(PAGE_BUFF_ADDR,1);
fs_erase_drv(FLASH_PAGE_1_START,1);
fs_erase_drv(FLASH_PAGE_2_START,1);
}
/*函数功能:fs操作事件结果上报*/
static void fstorage_evt_handler(nrf_fstorage_evt_t * p_evt)
{
if (p_evt->result != NRF_SUCCESS)
{
NRF_LOG_DEBUG("--> Event received: ERROR while executing an fstorage operation.");
return;
}
switch (p_evt->id)
{
case NRF_FSTORAGE_EVT_WRITE_RESULT:
{
//NRF_LOG_DEBUG("--> Event received: wrote %d bytes at address 0x%x.",
// p_evt->len, p_evt->addr);
} break;
case NRF_FSTORAGE_EVT_ERASE_RESULT:
{
NRF_LOG_DEBUG("--> Event received: erased %d page from address 0x%x.",
p_evt->len, p_evt->addr);
} break;
default:
break;
}
}
/**@brief Helper function to obtain the last address on the last page of the on-chip flash that
* can be used to write user data.
*/
static uint32_t nrf5_flash_end_addr_get()
{
uint32_t const bootloader_addr = NRF_UICR->NRFFW[0];
uint32_t const page_sz = NRF_FICR->CODEPAGESIZE;
uint32_t const code_sz = NRF_FICR->CODESIZE;
return (bootloader_addr != 0xFFFFFFFF ?
bootloader_addr : (code_sz * page_sz));
}
/**@brief Sleep until an event is received. */
static void power_manage(void)
{
#ifdef SOFTDEVICE_PRESENT
(void) sd_app_evt_wait();
#else
__WFE();
#endif
}
/*函数功能:等待fs操作结果*/
void wait_for_flash_ready(nrf_fstorage_t const * p_fstorage)
{
/* While fstorage is busy, sleep and wait for an event. */
while (nrf_fstorage_is_busy(p_fstorage))
{
power_manage();
}
}
static void print_flash_info(nrf_fstorage_t * p_fstorage)
{
// NRF_LOG_DEBUG("========| flash info |========");
// NRF_LOG_DEBUG("erase unit: \t%d bytes", p_fstorage->p_flash_info->erase_unit);
// NRF_LOG_DEBUG("program unit: \t%d bytes", p_fstorage->p_flash_info->program_unit);
// NRF_LOG_DEBUG("==============================");
}
#ifndef __MY_FS_H_
#define __MY_FS_H_
#include <stdint.h>
/*
共48页 48*4k = 192k
s112 协议栈占用 100k
20k : 172k ~ 192k 倒数 5 页存用户数据
04k : 168k ~ 172k 倒数第 6 页做拷贝缓存用:ram不够用
*/
//数据区一页的大小
#define FLASH_PAGE_SIZE 4096
//Flash地址从后往前数,每页的地址
typedef enum{
FLASH_PAGE_0_START = 0x2F000,
FLASH_PAGE_0_END = 0x2FFFF,
FLASH_PAGE_1_START = 0x2E000,
FLASH_PAGE_1_END = 0x2EFFF,
FLASH_PAGE_2_START = 0x2D000,
FLASH_PAGE_2_END = 0x2DFFF,
FLASH_PAGE_3_START = 0x2C000,
FLASH_PAGE_3_END = 0x2CFFF,
FLASH_PAGE_4_START = 0x2B000,
FLASH_PAGE_4_END = 0x2BFFF,
FLASH_PAGE_5_START = 0x2A000,
FLASH_PAGE_5_END = 0x2AFFF,
}TYPE_FLASH_ADDR;
//存储区大小
#define FLASH_DATA_SIZE (6*FLASH_PAGE_SIZE)
//flash数据存储区起始地址
#define MY_FLASH_DATA_START FLASH_PAGE_5_START
//flash数据存储区结束地址
#define MY_FLASH_DATA_END 0x30000
//最后一页作拷贝缓存不能存储数据
#define PAGE_BUFF_ADDR FLASH_PAGE_0_START
extern void ble_fs_init();
extern signed char fs_write (uint32_t wr_addr,uint8_t *p_wr,uint16_t wr_len);
//库函数使用测试函数
extern void fs_nrf_drv_test();
//fs_write的测试函数
extern void fs_write_test();
#endif