用一块flash区域作拷贝缓存的flash算法

6 篇文章 0 订阅
2 篇文章 0 订阅

用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

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值