From a7ee83789cfd148122907a070cbb6eec23f570d8 Mon Sep 17 00:00:00 2001 From: liuyucai Date: Wed, 9 Apr 2025 09:59:16 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E5=AE=8C=E5=96=84components\fal\samples\po?= =?UTF-8?q?rting\README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/dfs/dfs_v2/src/dfs.c | 4 +- components/fal/samples/porting/README.md | 219 +++++++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) diff --git a/components/dfs/dfs_v2/src/dfs.c b/components/dfs/dfs_v2/src/dfs.c index 2ba4870e26c..0c698fc6661 100644 --- a/components/dfs/dfs_v2/src/dfs.c +++ b/components/dfs/dfs_v2/src/dfs.c @@ -1037,11 +1037,11 @@ int dfs_dlog(int argc, char** argv) } } - return 0; + return 0; } MSH_CMD_EXPORT(dfs_dlog, dfs dlog on|off); #endif #endif -/** @} */ +/** @} */ \ No newline at end of file diff --git a/components/fal/samples/porting/README.md b/components/fal/samples/porting/README.md index c804644aa17..4de2e32a85e 100644 --- a/components/fal/samples/porting/README.md +++ b/components/fal/samples/porting/README.md @@ -106,3 +106,222 @@ Flash 分区基于 Flash 设备,每个 Flash 设备又可以有 N 个分区, - 分区的起始地址和大小 **不能超过 Flash 设备的地址范围** ,否则会导致包初始化错误 > 注意:每个分区定义时,除了填写上面介绍的参数属性外,需在前面增加 `FAL_PART_MAGIC_WORD` 属性,末尾增加 `0` (目前用于保留功能) +## 3、如何实现读写擦除等操作 +我们以fal_norflash_port.c为例,简单介绍一下。 +首先 介绍一下这两个宏定义 +```C +#define FAL_ALIGN_UP( size, align ) \ + ( ( ( size ) + ( align ) - 1 ) - ( ( ( size ) + ( align ) - 1 ) % ( align ) ) ) +#define FAL_ALIGN_DOWN( size, align ) ( ( ( size ) / ( align ) ) * ( align ) ) +``` +ALIGN_UP(16,4)=16 ALIGN_UP(15,4)=16 ALIGN_UP(17,4)=20 +ALIGN_DOWN(16,4)=16 ALIGN_DOWN(15,4)=12 ALIGN_DOWN(17,4)=16 +不难看出 ALIGN_UP是一个size向上取整到align的倍数,ALIGN_DOWN则是向下取整到align的倍数。 +然后 介绍FLASH的特性 + FLASH都是按块擦除 norflash的块大小一般为4K 单片机内部FLASH的块大小为1K,2K,16K不等 + 同时有最少写入数据的限制 +norflash中 是按页写入 一次最少写256个字节数据 超过则覆盖起始数据 如第257个数据会覆盖第1个数据的位置 +单片机内部flash中 一次最少写2个字节数据(STM32F105RC) 且只能将地址2字节对齐写入 只写一个字节时 给后面的字节补成FF +实现擦除 +```C +static int32_t get_sector( uint32_t address );//获取当前属于第一个扇区 +extern void norflash_erase_sector( uint32_t saddr );//负责擦除单个扇区的全部数据 +//FLASH都是按块擦除 我们假定在调用擦除函数时 用户知道自己将会擦除扇区内的全部数据 +static int erase( long offset, size_t size ) +{ + int32_t cur_erase_sector; + uint32_t addr = FLASH_START_ADDR + offset; + uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_SECTOR_SIZE ); + + uint32_t addr_end = addr + size; + uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_SECTOR_SIZE ); + uint32_t cur_addr = addr_down; + + while ( cur_addr < addr_end_up ) { + cur_erase_sector = get_sector( cur_addr ); + if ( cur_erase_sector == -1 ) {//获取第几个扇区失败 说明地址超出范围 + return cur_addr - addr; + } + norflash_erase_sector( cur_erase_sector ); + cur_addr += FLASH_SECTOR_SIZE;//这里如果每个扇区的大小不同 需要实现从当前地址获取扇区实际大小的函数 + } + return size; +} +``` +实现读取 +```c +//这个比较简单 直接调用norflash_read即可 +static int read( long offset, uint8_t* buf, size_t size ) +{ + norflash_read( buf, offset + FLASH_START_ADDR, size ); + return size; +} +``` +最后 也是最关键的一步 实现写入 +```c +/* 写入任意长数据到NOR Flash函数 */ +static int write( long offset, const uint8_t* buf, size_t size ) +{ + // 计算实际物理地址(相对于Flash起始地址的偏移) + uint32_t addr = FLASH_START_ADDR + offset; + // 计算起始地址的扇区向上对齐地址(例如0x1007 -> 0x2000 当扇区大小4K) + uint32_t addr_up = FAL_ALIGN_UP( addr, FLASH_SECTOR_SIZE ); + // 计算起始地址的扇区向下对齐地址(例如0x1007 -> 0x1000) + uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_SECTOR_SIZE ); + + // 计算写入结束地址 + uint32_t addr_end = addr + size; + // 结束地址的扇区向上对齐地址 + uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_SECTOR_SIZE ); + // 结束地址的扇区向下对齐地址 + uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FLASH_SECTOR_SIZE ); + uint32_t cur_addr = addr_down; // 当前处理的扇区起始地址 + + uint32_t max_write_len = 0; // 单次最大可写入长度 + uint32_t write_len = 0; // 实际写入长度 + + // 地址有效性检查:结束地址超过Flash范围 或 起始地址在Flash区域外 + if ( addr_end_up > FLASH_END_ADDR || ( int )addr_end_down < FLASH_START_ADDR ) return -1; + + // 分配扇区大小的缓冲区(用于处理部分写入时需要保存原始数据的情况) + uint8_t* read_sector_buf = FAL_MALLOC( FLASH_SECTOR_SIZE ); + if ( read_sector_buf == RT_NULL ) { + return -2; // 内存分配失败 + } + + // 按扇区逐个处理(从起始扇区到结束扇区) + while ( cur_addr < addr_end_up ) { + /* 情况1:处理起始地址不在扇区边界的情况(首扇区部分写入) */ + if ( cur_addr < addr ) { + // 读取整个扇区原始数据到缓冲区 + read( cur_addr - FLASH_START_ADDR, read_sector_buf, FLASH_SECTOR_SIZE ); + + // 计算首扇区可写入的最大长度(从起始地址到扇区末尾) + max_write_len = ( addr_up - addr ); + // 确定实际写入长度(不超过剩余数据大小) + write_len = size >= max_write_len ? max_write_len : size; + + // 判断是否需要擦除(检查目标区域是否包含需要从0->1的位) + if ( judge_whether_erase( read_sector_buf + addr - cur_addr, write_len ) ){ + // 需要擦除时:执行擦除->修改缓冲区->写入整个扇区 + norflash_erase_sector( get_sector( cur_addr ) ); + // 将新数据合并到缓冲区对应位置 + FAL_MEMCPY( read_sector_buf + ( addr - cur_addr ), buf, write_len ); + // 写入整个扇区 + write_sector( cur_addr, read_sector_buf, FLASH_SECTOR_SIZE ); + } + else { + // 无需擦除时直接写入数据(NOR Flash允许直接写入0位) + write_sector( addr, buf, write_len ); + } + buf += write_len; // 移动数据指针 + } + /* 情况2:处理结束地址不在扇区边界的情况(末扇区部分写入) */ + else if ( cur_addr == addr_end_down ) { + // 读取整个扇区原始数据 + read( cur_addr - FLASH_START_ADDR, read_sector_buf, FLASH_SECTOR_SIZE ); + + // 计算最大可写入长度(整个扇区) + max_write_len = FLASH_SECTOR_SIZE; + // 计算实际需要写入的长度(从扇区起始到结束地址) + write_len = addr_end - cur_addr; + write_len = write_len >= max_write_len ? max_write_len : write_len; + + // 判断是否需要擦除 + if ( judge_whether_erase( read_sector_buf, write_len ) ) { + // 需要擦除时:合并数据->擦除->写入整个扇区 + FAL_MEMCPY( read_sector_buf, buf, write_len ); + norflash_erase_sector( get_sector( cur_addr ) ); + write_sector( cur_addr, read_sector_buf, FLASH_SECTOR_SIZE ); + } + else { + // 直接写入数据 + write_sector( cur_addr, buf, write_len ); + } + } + /* 情况3:完整扇区写入(中间扇区) */ + else { + // 直接擦除整个扇区(完整覆盖不需要保留数据) + norflash_erase_sector( get_sector( cur_addr ) ); + // 写入整个扇区数据 + write_sector( cur_addr, buf, FLASH_SECTOR_SIZE ); + buf += FLASH_SECTOR_SIZE; // 移动数据指针 + } + cur_addr += FLASH_SECTOR_SIZE; // 移动到下一个扇区 + } + FAL_FREE( read_sector_buf ); // 释放缓冲区内存 + return size; // 返回成功写入的字节数 +} +``` +关键逻辑说明: +地址对齐处理:通过向上/向下对齐计算确定实际需要操作的扇区范围 +三种写入场景: + 首扇区部分写入:需要读取原始数据,合并新数据后判断擦除必要性 + 中间完整扇区:直接擦除后全量写入,提高效率 + 末扇区部分写入:处理方式类似首扇区,但数据位置不同 +擦除判断:通过judge_whether_erase函数检测是否需要执行擦除操作(基于NOR Flash的特性,只有需要将0变为1时才必须擦除) +数据合并:使用临时缓冲区保存原始数据,仅修改需要写入的部分,最大限度减少擦除操作 +内存管理:动态分配扇区大小的缓冲区,处理完成后立即释放 +到这里 工作似乎做完了 但是 我们没有写入扇区的函数 只有页写入函数 norflash_write_page +扇区写入逻辑和任意写入逻辑基本相同 +下面实现扇区写入函数 +```c +/* 扇区写入函数:处理按页对齐的NOR Flash写入操作 */ +static int write_sector( long offset, const uint8_t* buf, size_t size ) +{ + // 计算实际物理地址(FLASH起始地址 + 偏移量) + uint32_t addr = FLASH_START_ADDR + offset; + + // 计算地址的页对齐上边界和下边界(按FLASH_PAGE_SIZE对齐) + uint32_t addr_up = FAL_ALIGN_UP( addr, FALSH_PAGE_SIZE ); + uint32_t addr_down = FAL_ALIGN_DOWN( addr, FALSH_PAGE_SIZE ); + + // 计算写入结束地址及其页对齐边界 + uint32_t addr_end = addr + size; + uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FALSH_PAGE_SIZE ); + uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FALSH_PAGE_SIZE ); + + // 初始化当前处理地址和长度变量 + uint32_t cur_addr = addr_down; // 从页对齐起始地址开始处理 + uint32_t max_write_len = 0; // 单次最大可写入长度 + uint32_t write_len = 0; // 实际写入长度 + + // 循环处理所有需要写入的页 + while ( cur_addr < addr_end_up ) { + // 处理起始未对齐部分(跨页起始边界) + if ( cur_addr < addr ) { + // 计算当前页剩余可写空间(页结束地址 - 实际起始地址) + max_write_len = ( addr_up - addr ); + // 取实际剩余长度和总长度的最小值 + write_len = size >= max_write_len ? max_write_len : size; + + // 执行页写入:参数依次是数据指针、物理地址、写入长度 + norflash_write_page( buf, addr, write_len ); + buf += write_len; // 移动数据指针 + } + // 处理结束未对齐部分(跨页结束边界) + else if ( cur_addr == addr_end_down ) { + // 单页最大写入长度 + max_write_len = FALSH_PAGE_SIZE; + // 计算实际需要写入的长度(结束地址 - 当前页起始地址) + write_len = addr_end - cur_addr; + // 确保不超过页最大长度 + write_len = write_len >= max_write_len ? max_write_len : write_len; + + // 执行页写入 + norflash_write_page( buf, cur_addr, write_len ); + } + // 处理完整页写入 + else { + // 整页写入(FLASH_PAGE_SIZE长度) + norflash_write_page( buf, cur_addr, FALSH_PAGE_SIZE ); + buf += FALSH_PAGE_SIZE; // 移动数据指针整页长度 + } + + // 移动到下一页起始地址 + cur_addr += FALSH_PAGE_SIZE; + } + return size; // 返回成功写入的总字节数 +} +``` +至此 我们就完成了Flash驱动的移植 实现了读写擦除等操作 上面的思路对于大部分flash驱动来说是通用的 \ No newline at end of file From f50111706ea5fdc5383cfd3825ac34fc2f51bb9c Mon Sep 17 00:00:00 2001 From: liuyucai Date: Wed, 9 Apr 2025 10:13:39 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E5=AE=8C=E5=96=84components\fal\samples\po?= =?UTF-8?q?rting\README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/dfs/dfs_v2/src/dfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/dfs/dfs_v2/src/dfs.c b/components/dfs/dfs_v2/src/dfs.c index 0c698fc6661..0ae12e9981c 100644 --- a/components/dfs/dfs_v2/src/dfs.c +++ b/components/dfs/dfs_v2/src/dfs.c @@ -1037,7 +1037,7 @@ int dfs_dlog(int argc, char** argv) } } - return 0; + return 0; } MSH_CMD_EXPORT(dfs_dlog, dfs dlog on|off); From 88d2cc4afce7a99d8e0dd622a2625171b13b80da Mon Sep 17 00:00:00 2001 From: liuyucai Date: Wed, 9 Apr 2025 10:17:12 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E5=AE=8C=E5=96=84components\fal\samples\po?= =?UTF-8?q?rting\README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/dfs/dfs_v2/src/dfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/dfs/dfs_v2/src/dfs.c b/components/dfs/dfs_v2/src/dfs.c index 0ae12e9981c..2ba4870e26c 100644 --- a/components/dfs/dfs_v2/src/dfs.c +++ b/components/dfs/dfs_v2/src/dfs.c @@ -1044,4 +1044,4 @@ MSH_CMD_EXPORT(dfs_dlog, dfs dlog on|off); #endif #endif -/** @} */ \ No newline at end of file +/** @} */ From 616119cd561db6574bc9dcad7ab372a0efb2e860 Mon Sep 17 00:00:00 2001 From: liuyucai Date: Fri, 11 Apr 2025 15:34:49 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=9B=B4=E6=94=B9FLASH=5FPAGE=5FSIZE?= =?UTF-8?q?=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/fal/samples/porting/README.md | 16 +++++++-------- .../fal/samples/porting/fal_norflash_port.c | 20 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/components/fal/samples/porting/README.md b/components/fal/samples/porting/README.md index 4de2e32a85e..0f54fcef9a7 100644 --- a/components/fal/samples/porting/README.md +++ b/components/fal/samples/porting/README.md @@ -273,13 +273,13 @@ static int write_sector( long offset, const uint8_t* buf, size_t size ) uint32_t addr = FLASH_START_ADDR + offset; // 计算地址的页对齐上边界和下边界(按FLASH_PAGE_SIZE对齐) - uint32_t addr_up = FAL_ALIGN_UP( addr, FALSH_PAGE_SIZE ); - uint32_t addr_down = FAL_ALIGN_DOWN( addr, FALSH_PAGE_SIZE ); + uint32_t addr_up = FAL_ALIGN_UP( addr, FLASH_PAGE_SIZE ); + uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_PAGE_SIZE ); // 计算写入结束地址及其页对齐边界 uint32_t addr_end = addr + size; - uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FALSH_PAGE_SIZE ); - uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FALSH_PAGE_SIZE ); + uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_PAGE_SIZE ); + uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FLASH_PAGE_SIZE ); // 初始化当前处理地址和长度变量 uint32_t cur_addr = addr_down; // 从页对齐起始地址开始处理 @@ -302,7 +302,7 @@ static int write_sector( long offset, const uint8_t* buf, size_t size ) // 处理结束未对齐部分(跨页结束边界) else if ( cur_addr == addr_end_down ) { // 单页最大写入长度 - max_write_len = FALSH_PAGE_SIZE; + max_write_len = FLASH_PAGE_SIZE; // 计算实际需要写入的长度(结束地址 - 当前页起始地址) write_len = addr_end - cur_addr; // 确保不超过页最大长度 @@ -314,12 +314,12 @@ static int write_sector( long offset, const uint8_t* buf, size_t size ) // 处理完整页写入 else { // 整页写入(FLASH_PAGE_SIZE长度) - norflash_write_page( buf, cur_addr, FALSH_PAGE_SIZE ); - buf += FALSH_PAGE_SIZE; // 移动数据指针整页长度 + norflash_write_page( buf, cur_addr, FLASH_PAGE_SIZE ); + buf += FLASH_PAGE_SIZE; // 移动数据指针整页长度 } // 移动到下一页起始地址 - cur_addr += FALSH_PAGE_SIZE; + cur_addr += FLASH_PAGE_SIZE; } return size; // 返回成功写入的总字节数 } diff --git a/components/fal/samples/porting/fal_norflash_port.c b/components/fal/samples/porting/fal_norflash_port.c index 8d6e0ad75f9..26ef4f6e526 100644 --- a/components/fal/samples/porting/fal_norflash_port.c +++ b/components/fal/samples/porting/fal_norflash_port.c @@ -6,8 +6,8 @@ #define FLASH_END_ADDR 0x01000000U // 16*1024*1024 #define FLASH_PROGRAM_MIN_SIZE 256 // 256 bytes -//每次对falsh写入时 底层可以写入的最大字节数为 FALSH_PAGE_SIZE -#define FALSH_PAGE_SIZE FLASH_PROGRAM_MIN_SIZE // 256 bytes +//每次对falsh写入时 底层可以写入的最大字节数为 FLASH_PAGE_SIZE +#define FLASH_PAGE_SIZE FLASH_PROGRAM_MIN_SIZE // 256 bytes /** * @brief 需要实现以下函数 @@ -71,12 +71,12 @@ static uint32_t judge_whether_erase( uint8_t* sector_buf, uint16_t len ) static int write_sector( long offset, const uint8_t* buf, size_t size ) { uint32_t addr = FLASH_START_ADDR + offset; - uint32_t addr_up = FAL_ALIGN_UP( addr, FALSH_PAGE_SIZE ); - uint32_t addr_down = FAL_ALIGN_DOWN( addr, FALSH_PAGE_SIZE ); + uint32_t addr_up = FAL_ALIGN_UP( addr, FLASH_PAGE_SIZE ); + uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_PAGE_SIZE ); uint32_t addr_end = addr + size; - uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FALSH_PAGE_SIZE ); - uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FALSH_PAGE_SIZE ); + uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_PAGE_SIZE ); + uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FLASH_PAGE_SIZE ); uint32_t cur_addr = addr_down; uint32_t max_write_len = 0; @@ -89,17 +89,17 @@ static int write_sector( long offset, const uint8_t* buf, size_t size ) buf += write_len; } else if ( cur_addr == addr_end_down ) { - max_write_len = FALSH_PAGE_SIZE; + max_write_len = FLASH_PAGE_SIZE; write_len = addr_end - cur_addr; write_len = write_len >= max_write_len ? max_write_len : write_len; norflash_write_page( buf, cur_addr, write_len ); } else { - norflash_write_page( buf, cur_addr, FALSH_PAGE_SIZE ); - buf += FALSH_PAGE_SIZE; + norflash_write_page( buf, cur_addr, FLASH_PAGE_SIZE ); + buf += FLASH_PAGE_SIZE; } - cur_addr += FALSH_PAGE_SIZE; + cur_addr += FLASH_PAGE_SIZE; } return size; } From 30d3fc69aeb8325177908c2755af764fda304cfe Mon Sep 17 00:00:00 2001 From: liuyucai Date: Fri, 11 Apr 2025 15:38:04 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/fal/samples/porting/fal_norflash_port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/fal/samples/porting/fal_norflash_port.c b/components/fal/samples/porting/fal_norflash_port.c index 26ef4f6e526..6d5b4756f72 100644 --- a/components/fal/samples/porting/fal_norflash_port.c +++ b/components/fal/samples/porting/fal_norflash_port.c @@ -118,7 +118,7 @@ static int write( long offset, const uint8_t* buf, size_t size ) uint32_t write_len = 0; if ( addr_end_up > FLASH_END_ADDR || ( int )addr_end_down < FLASH_START_ADDR ) return -1; - //如果不使用内存分配可以定义一个static FLASH_SECTOR_SIZE 长度的buf + //如果不使用内存分配可以定义一个static FLASH_SECTOR_SIZE 长度的buf uint8_t* read_sector_buf = FAL_MALLOC( FLASH_SECTOR_SIZE ); if ( read_sector_buf == RT_NULL ) { return -2;