Skip to content

Commit 522f7aa

Browse files
[fal/sample] readme 添加 (#10175)
* 更改FLASH_PAGE_SIZE拼写错误
1 parent 312f9db commit 522f7aa

File tree

2 files changed

+230
-11
lines changed

2 files changed

+230
-11
lines changed

components/fal/samples/porting/README.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,222 @@ Flash 分区基于 Flash 设备,每个 Flash 设备又可以有 N 个分区,
106106
- 分区的起始地址和大小 **不能超过 Flash 设备的地址范围** ,否则会导致包初始化错误
107107

108108
> 注意:每个分区定义时,除了填写上面介绍的参数属性外,需在前面增加 `FAL_PART_MAGIC_WORD` 属性,末尾增加 `0` (目前用于保留功能)
109+
## 3、如何实现读写擦除等操作
110+
我们以fal_norflash_port.c为例,简单介绍一下。
111+
首先 介绍一下这两个宏定义
112+
```C
113+
#define FAL_ALIGN_UP( size, align ) \
114+
( ( ( size ) + ( align ) - 1 ) - ( ( ( size ) + ( align ) - 1 ) % ( align ) ) )
115+
#define FAL_ALIGN_DOWN( size, align ) ( ( ( size ) / ( align ) ) * ( align ) )
116+
```
117+
ALIGN_UP(16,4)=16 ALIGN_UP(15,4)=16 ALIGN_UP(17,4)=20
118+
ALIGN_DOWN(16,4)=16 ALIGN_DOWN(15,4)=12 ALIGN_DOWN(17,4)=16
119+
不难看出 ALIGN_UP是一个size向上取整到align的倍数,ALIGN_DOWN则是向下取整到align的倍数。
120+
然后 介绍FLASH的特性
121+
FLASH都是按块擦除 norflash的块大小一般为4K 单片机内部FLASH的块大小为1K,2K,16K不等
122+
同时有最少写入数据的限制
123+
norflash中 是按页写入 一次最少写256个字节数据 超过则覆盖起始数据 如第257个数据会覆盖第1个数据的位置
124+
单片机内部flash中 一次最少写2个字节数据(STM32F105RC) 且只能将地址2字节对齐写入 只写一个字节时 给后面的字节补成FF
125+
实现擦除
126+
```C
127+
static int32_t get_sector( uint32_t address );//获取当前属于第一个扇区
128+
extern void norflash_erase_sector( uint32_t saddr );//负责擦除单个扇区的全部数据
129+
//FLASH都是按块擦除 我们假定在调用擦除函数时 用户知道自己将会擦除扇区内的全部数据
130+
static int erase( long offset, size_t size )
131+
{
132+
int32_t cur_erase_sector;
133+
uint32_t addr = FLASH_START_ADDR + offset;
134+
uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_SECTOR_SIZE );
135+
136+
uint32_t addr_end = addr + size;
137+
uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_SECTOR_SIZE );
138+
uint32_t cur_addr = addr_down;
139+
140+
while ( cur_addr < addr_end_up ) {
141+
cur_erase_sector = get_sector( cur_addr );
142+
if ( cur_erase_sector == -1 ) {//获取第几个扇区失败 说明地址超出范围
143+
return cur_addr - addr;
144+
}
145+
norflash_erase_sector( cur_erase_sector );
146+
cur_addr += FLASH_SECTOR_SIZE;//这里如果每个扇区的大小不同 需要实现从当前地址获取扇区实际大小的函数
147+
}
148+
return size;
149+
}
150+
```
151+
实现读取
152+
```c
153+
//这个比较简单 直接调用norflash_read即可
154+
static int read( long offset, uint8_t* buf, size_t size )
155+
{
156+
norflash_read( buf, offset + FLASH_START_ADDR, size );
157+
return size;
158+
}
159+
```
160+
最后 也是最关键的一步 实现写入
161+
```c
162+
/* 写入任意长数据到NOR Flash函数 */
163+
static int write( long offset, const uint8_t* buf, size_t size )
164+
{
165+
// 计算实际物理地址(相对于Flash起始地址的偏移)
166+
uint32_t addr = FLASH_START_ADDR + offset;
167+
// 计算起始地址的扇区向上对齐地址(例如0x1007 -> 0x2000 当扇区大小4K)
168+
uint32_t addr_up = FAL_ALIGN_UP( addr, FLASH_SECTOR_SIZE );
169+
// 计算起始地址的扇区向下对齐地址(例如0x1007 -> 0x1000)
170+
uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_SECTOR_SIZE );
171+
172+
// 计算写入结束地址
173+
uint32_t addr_end = addr + size;
174+
// 结束地址的扇区向上对齐地址
175+
uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_SECTOR_SIZE );
176+
// 结束地址的扇区向下对齐地址
177+
uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FLASH_SECTOR_SIZE );
178+
uint32_t cur_addr = addr_down; // 当前处理的扇区起始地址
179+
180+
uint32_t max_write_len = 0; // 单次最大可写入长度
181+
uint32_t write_len = 0; // 实际写入长度
182+
183+
// 地址有效性检查:结束地址超过Flash范围 或 起始地址在Flash区域外
184+
if ( addr_end_up > FLASH_END_ADDR || ( int )addr_end_down < FLASH_START_ADDR ) return -1;
185+
186+
// 分配扇区大小的缓冲区(用于处理部分写入时需要保存原始数据的情况)
187+
uint8_t* read_sector_buf = FAL_MALLOC( FLASH_SECTOR_SIZE );
188+
if ( read_sector_buf == RT_NULL ) {
189+
return -2; // 内存分配失败
190+
}
191+
192+
// 按扇区逐个处理(从起始扇区到结束扇区)
193+
while ( cur_addr < addr_end_up ) {
194+
/* 情况1:处理起始地址不在扇区边界的情况(首扇区部分写入) */
195+
if ( cur_addr < addr ) {
196+
// 读取整个扇区原始数据到缓冲区
197+
read( cur_addr - FLASH_START_ADDR, read_sector_buf, FLASH_SECTOR_SIZE );
198+
199+
// 计算首扇区可写入的最大长度(从起始地址到扇区末尾)
200+
max_write_len = ( addr_up - addr );
201+
// 确定实际写入长度(不超过剩余数据大小)
202+
write_len = size >= max_write_len ? max_write_len : size;
203+
204+
// 判断是否需要擦除(检查目标区域是否包含需要从0->1的位)
205+
if ( judge_whether_erase( read_sector_buf + addr - cur_addr, write_len ) ){
206+
// 需要擦除时:执行擦除->修改缓冲区->写入整个扇区
207+
norflash_erase_sector( get_sector( cur_addr ) );
208+
// 将新数据合并到缓冲区对应位置
209+
FAL_MEMCPY( read_sector_buf + ( addr - cur_addr ), buf, write_len );
210+
// 写入整个扇区
211+
write_sector( cur_addr, read_sector_buf, FLASH_SECTOR_SIZE );
212+
}
213+
else {
214+
// 无需擦除时直接写入数据(NOR Flash允许直接写入0位)
215+
write_sector( addr, buf, write_len );
216+
}
217+
buf += write_len; // 移动数据指针
218+
}
219+
/* 情况2:处理结束地址不在扇区边界的情况(末扇区部分写入) */
220+
else if ( cur_addr == addr_end_down ) {
221+
// 读取整个扇区原始数据
222+
read( cur_addr - FLASH_START_ADDR, read_sector_buf, FLASH_SECTOR_SIZE );
223+
224+
// 计算最大可写入长度(整个扇区)
225+
max_write_len = FLASH_SECTOR_SIZE;
226+
// 计算实际需要写入的长度(从扇区起始到结束地址)
227+
write_len = addr_end - cur_addr;
228+
write_len = write_len >= max_write_len ? max_write_len : write_len;
229+
230+
// 判断是否需要擦除
231+
if ( judge_whether_erase( read_sector_buf, write_len ) ) {
232+
// 需要擦除时:合并数据->擦除->写入整个扇区
233+
FAL_MEMCPY( read_sector_buf, buf, write_len );
234+
norflash_erase_sector( get_sector( cur_addr ) );
235+
write_sector( cur_addr, read_sector_buf, FLASH_SECTOR_SIZE );
236+
}
237+
else {
238+
// 直接写入数据
239+
write_sector( cur_addr, buf, write_len );
240+
}
241+
}
242+
/* 情况3:完整扇区写入(中间扇区) */
243+
else {
244+
// 直接擦除整个扇区(完整覆盖不需要保留数据)
245+
norflash_erase_sector( get_sector( cur_addr ) );
246+
// 写入整个扇区数据
247+
write_sector( cur_addr, buf, FLASH_SECTOR_SIZE );
248+
buf += FLASH_SECTOR_SIZE; // 移动数据指针
249+
}
250+
cur_addr += FLASH_SECTOR_SIZE; // 移动到下一个扇区
251+
}
252+
FAL_FREE( read_sector_buf ); // 释放缓冲区内存
253+
return size; // 返回成功写入的字节数
254+
}
255+
```
256+
关键逻辑说明:
257+
地址对齐处理:通过向上/向下对齐计算确定实际需要操作的扇区范围
258+
三种写入场景:
259+
首扇区部分写入:需要读取原始数据,合并新数据后判断擦除必要性
260+
中间完整扇区:直接擦除后全量写入,提高效率
261+
末扇区部分写入:处理方式类似首扇区,但数据位置不同
262+
擦除判断:通过judge_whether_erase函数检测是否需要执行擦除操作(基于NOR Flash的特性,只有需要将0变为1时才必须擦除)
263+
数据合并:使用临时缓冲区保存原始数据,仅修改需要写入的部分,最大限度减少擦除操作
264+
内存管理:动态分配扇区大小的缓冲区,处理完成后立即释放
265+
到这里 工作似乎做完了 但是 我们没有写入扇区的函数 只有页写入函数 norflash_write_page
266+
扇区写入逻辑和任意写入逻辑基本相同
267+
下面实现扇区写入函数
268+
```c
269+
/* 扇区写入函数:处理按页对齐的NOR Flash写入操作 */
270+
static int write_sector( long offset, const uint8_t* buf, size_t size )
271+
{
272+
// 计算实际物理地址(FLASH起始地址 + 偏移量)
273+
uint32_t addr = FLASH_START_ADDR + offset;
274+
275+
// 计算地址的页对齐上边界和下边界(按FLASH_PAGE_SIZE对齐)
276+
uint32_t addr_up = FAL_ALIGN_UP( addr, FLASH_PAGE_SIZE );
277+
uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_PAGE_SIZE );
278+
279+
// 计算写入结束地址及其页对齐边界
280+
uint32_t addr_end = addr + size;
281+
uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_PAGE_SIZE );
282+
uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FLASH_PAGE_SIZE );
283+
284+
// 初始化当前处理地址和长度变量
285+
uint32_t cur_addr = addr_down; // 从页对齐起始地址开始处理
286+
uint32_t max_write_len = 0; // 单次最大可写入长度
287+
uint32_t write_len = 0; // 实际写入长度
288+
289+
// 循环处理所有需要写入的页
290+
while ( cur_addr < addr_end_up ) {
291+
// 处理起始未对齐部分(跨页起始边界)
292+
if ( cur_addr < addr ) {
293+
// 计算当前页剩余可写空间(页结束地址 - 实际起始地址)
294+
max_write_len = ( addr_up - addr );
295+
// 取实际剩余长度和总长度的最小值
296+
write_len = size >= max_write_len ? max_write_len : size;
297+
298+
// 执行页写入:参数依次是数据指针、物理地址、写入长度
299+
norflash_write_page( buf, addr, write_len );
300+
buf += write_len; // 移动数据指针
301+
}
302+
// 处理结束未对齐部分(跨页结束边界)
303+
else if ( cur_addr == addr_end_down ) {
304+
// 单页最大写入长度
305+
max_write_len = FLASH_PAGE_SIZE;
306+
// 计算实际需要写入的长度(结束地址 - 当前页起始地址)
307+
write_len = addr_end - cur_addr;
308+
// 确保不超过页最大长度
309+
write_len = write_len >= max_write_len ? max_write_len : write_len;
310+
311+
// 执行页写入
312+
norflash_write_page( buf, cur_addr, write_len );
313+
}
314+
// 处理完整页写入
315+
else {
316+
// 整页写入(FLASH_PAGE_SIZE长度)
317+
norflash_write_page( buf, cur_addr, FLASH_PAGE_SIZE );
318+
buf += FLASH_PAGE_SIZE; // 移动数据指针整页长度
319+
}
320+
321+
// 移动到下一页起始地址
322+
cur_addr += FLASH_PAGE_SIZE;
323+
}
324+
return size; // 返回成功写入的总字节数
325+
}
326+
```
327+
至此 我们就完成了Flash驱动的移植 实现了读写擦除等操作 上面的思路对于大部分flash驱动来说是通用的

components/fal/samples/porting/fal_norflash_port.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
#define FLASH_END_ADDR 0x01000000U // 16*1024*1024
77

88
#define FLASH_PROGRAM_MIN_SIZE 256 // 256 bytes
9-
//每次对falsh写入时 底层可以写入的最大字节数为 FALSH_PAGE_SIZE
10-
#define FALSH_PAGE_SIZE FLASH_PROGRAM_MIN_SIZE // 256 bytes
9+
//每次对falsh写入时 底层可以写入的最大字节数为 FLASH_PAGE_SIZE
10+
#define FLASH_PAGE_SIZE FLASH_PROGRAM_MIN_SIZE // 256 bytes
1111

1212
/**
1313
* @brief 需要实现以下函数
@@ -71,12 +71,12 @@ static uint32_t judge_whether_erase( uint8_t* sector_buf, uint16_t len )
7171
static int write_sector( long offset, const uint8_t* buf, size_t size )
7272
{
7373
uint32_t addr = FLASH_START_ADDR + offset;
74-
uint32_t addr_up = FAL_ALIGN_UP( addr, FALSH_PAGE_SIZE );
75-
uint32_t addr_down = FAL_ALIGN_DOWN( addr, FALSH_PAGE_SIZE );
74+
uint32_t addr_up = FAL_ALIGN_UP( addr, FLASH_PAGE_SIZE );
75+
uint32_t addr_down = FAL_ALIGN_DOWN( addr, FLASH_PAGE_SIZE );
7676

7777
uint32_t addr_end = addr + size;
78-
uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FALSH_PAGE_SIZE );
79-
uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FALSH_PAGE_SIZE );
78+
uint32_t addr_end_up = FAL_ALIGN_UP( addr_end, FLASH_PAGE_SIZE );
79+
uint32_t addr_end_down = FAL_ALIGN_DOWN( addr_end, FLASH_PAGE_SIZE );
8080

8181
uint32_t cur_addr = addr_down;
8282
uint32_t max_write_len = 0;
@@ -89,17 +89,17 @@ static int write_sector( long offset, const uint8_t* buf, size_t size )
8989
buf += write_len;
9090
}
9191
else if ( cur_addr == addr_end_down ) {
92-
max_write_len = FALSH_PAGE_SIZE;
92+
max_write_len = FLASH_PAGE_SIZE;
9393
write_len = addr_end - cur_addr;
9494
write_len = write_len >= max_write_len ? max_write_len : write_len;
9595
norflash_write_page( buf, cur_addr, write_len );
9696
}
9797
else {
98-
norflash_write_page( buf, cur_addr, FALSH_PAGE_SIZE );
99-
buf += FALSH_PAGE_SIZE;
98+
norflash_write_page( buf, cur_addr, FLASH_PAGE_SIZE );
99+
buf += FLASH_PAGE_SIZE;
100100
}
101101

102-
cur_addr += FALSH_PAGE_SIZE;
102+
cur_addr += FLASH_PAGE_SIZE;
103103
}
104104
return size;
105105
}
@@ -118,7 +118,7 @@ static int write( long offset, const uint8_t* buf, size_t size )
118118
uint32_t write_len = 0;
119119

120120
if ( addr_end_up > FLASH_END_ADDR || ( int )addr_end_down < FLASH_START_ADDR ) return -1;
121-
//如果不使用内存分配可以定义一个static FLASH_SECTOR_SIZE 长度的buf
121+
//如果不使用内存分配可以定义一个static FLASH_SECTOR_SIZE 长度的buf
122122
uint8_t* read_sector_buf = FAL_MALLOC( FLASH_SECTOR_SIZE );
123123
if ( read_sector_buf == RT_NULL ) {
124124
return -2;

0 commit comments

Comments
 (0)