拓冰建站拓冰建站
首页 / 资讯中心 / 正文

RT-Thread FAL 组件 Flash 设备与分区移植实战指南:基于 samples/porting 示例深度解析

操作系统嵌入式物联网嵌入式OSRTOS【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址https://gitcode.com/gh_mirrors/rt/rt-thread点击查看免费下载本篇技术指南以 RT-Thread 仓库components/fal/samples/porting/目录下的移植示例为核心完整讲解在 RT-Thread 的 FALFlash Abstraction LayerFlash 抽象层中如何移植 Flash 设备、定义 Flash 设备表与分区表并深入剖析 NOR Flash 读写擦除等底层操作的实现思路。读完本文后你将能够在自己的 RT-Thread 工程中独立完成片内 Flash 与片外 SPI NOR Flash 的 FAL 移植并正确配置分区以满足引导程序、应用程序、参数存储与 OTA 下载等典型场景。FAL 移植总体脉络从设备到分区的两级抽象FAL 是 RT-Thread 提供的一层 Flash 抽象组件它把物理 Flash 芯片抽象为Flash 设备flash device再把设备按地址划分成分区partition向上层应用文件系统、OTA、EasyFlash 等提供统一、带越界保护的数据读写接口。从源码结构看FAL 的移植工作由浅入深分为三步定义 Flash 设备对象实现init/read/write/erase四个操作函数声明struct fal_flash_dev结构体参考 fal_flash_stm32f2_port.c 与 fal_flash_sfud_port.c。定义 Flash 设备表在fal_cfg.h中用FAL_FLASH_DEV_TABLE宏把设备对象组织成表参考 fal_cfg.h。定义 Flash 分区表在fal_cfg.h中用FAL_PART_TABLE宏划分各分区。整个移植与初始化的调用关系在源码中清晰可见上层只需调用 fal.c 中的fal_init()它内部先调用 fal_flash.c 的fal_flash_init()初始化设备表再调用 fal_partition.c 的fal_partition_init()加载分区表并做合法性校验。FAL 的整体分层关系如下图所示。一、定义 Flash 设备四个操作函数与设备对象1.1 操作函数签名与参数约定针对 Flash 的不同操作需要实现如下四个函数其中init可选static int init(void)可选的初始化操作用于初始化 Flash 控制器、获取芯片信息等没有初始化过程时可为空。static int read(long offset, rt_uint8_t *buf, rt_size_t size)读取操作。参数描述offset读取数据的 Flash 偏移地址buf存放待读取数据的缓冲区size待读取数据的大小return返回实际读取的数据大小static int write(long offset, const rt_uint8_t *buf, rt_size_t size)写入操作。参数描述offset写入数据的 Flash 偏移地址buf存放待写入数据的缓冲区size待写入数据的大小return返回实际写入的数据大小static int erase(long offset, rt_size_t size)擦除操作。参数描述offset擦除区域的 Flash 偏移地址size擦除区域的大小return返回实际擦除的区域大小用户需要根据自己的 Flash 情况分别实现这些操作函数。注意offset是相对该 Flash 设备起始地址的偏移底层访问物理地址时通常需要再加上设备基地址如FLASH_START_ADDR offset。1.2 Flash 设备对象结构struct fal_flash_dev设备对象的核心数据结构定义在 fal_def.h 中移植时只需按实际硬件填写各字段struct fal_flash_dev { char name[FAL_DEV_NAME_MAX]; /* 设备名FAL_DEV_NAME_MAX 默认 24 */ rt_uint32_t addr; /* Flash 设备起始地址 */ rt_size_t len; /* 设备总长度字节 */ rt_size_t blk_size; /* 块大小即擦除的最小粒度 */ struct { int (*init)(void); int (*read)(long offset, rt_uint8_t *buf, rt_size_t size); int (*write)(long offset, const rt_uint8_t *buf, rt_size_t size); int (*erase)(long offset, rt_size_t size); } ops; /* 操作函数集合 */ rt_size_t write_gran; /* 写入最小粒度单位位 */ struct flash_blk blocks[FAL_DEV_BLK_MAX]; /* 块信息FAL_DEV_BLK_MAX 默认 6 */ };其中write_gran的单位是位bit取值约定1NOR Flash、8STM32F2/F4、32STM32F1、64STM32L4置 0 表示不生效blocks数组用于描述设备内部不均匀的物理块布局count或size为 0 时视为结束。1.3 示例一STM32F2 片内 Flash 设备以 fal_flash_stm32f2_port.c 为例。STM32F2 的片内 Flash 扇区大小不均匀前 4 个扇区各 16KB、第 5 个扇区 64KB、其余扇区各 128KB因此该示例在文件头部定义了 12 个扇区基地址宏ADDR_FLASH_SECTOR_0~ADDR_FLASH_SECTOR_11并实现了两个辅助函数stm32_get_sector(address)根据物理地址返回对应的FLASH_Sector_n编号stm32_get_sector_size(sector)返回指定扇区的大小16K/64K/128K用于擦除时累加实际擦除量。read实现为逐字节从地址空间读取write先FLASH_Unlock()解锁、清错误标志再逐字节调用FLASH_ProgramByte并读回校验最后FLASH_Lock()erase则在解锁后循环按扇区擦除直到累计擦除量不小于请求大小。文件底部定义的设备对象如下注意示例源码使用了 C99 指定初始化方式const struct fal_flash_dev stm32f2_onchip_flash { .name stm32_onchip, .addr 0x08000000, .len 1024*1024, .blk_size 128*1024, .ops {init, read, write, erase}, .write_gran 8 };各字段含义stm32_onchipFlash 设备的名字0x08000000对 Flash 操作的起始地址1024*1024Flash 的总大小1MB128*1024Flash 块/扇区大小。因为 STM32F2 各块大小不均匀所以擦除粒度为最大块的大小128K{init, read, write, erase}Flash 的操作函数如果没有 init 初始化过程第一个操作函数位置可以置空write_gran 8表示一次最少可写 8 位1 字节与该芯片按字节编程的特性一致。1.4 示例二基于 SFUD 的片外 SPI NOR Flash 设备fal_flash_sfud_port.c 演示了基于SFUD万能 SPI Flash 驱动的 Flash 设备移植整个文件用FAL_USING_SFUD_PORT宏包裹需配合 Kconfig 中的FAL_USING_SFUD_PORT选项启用。其特点是在init()中动态获取 SFUD 设备并回填芯片信息static int init(void) { #ifdef RT_USING_SFUD sfud_dev rt_sfud_flash_find_by_dev_name(FAL_USING_NOR_FLASH_DEV_NAME); #else extern sfud_flash sfud_norflash0; sfud_dev sfud_norflash0; #endif if (NULL sfud_dev) return -1; /* update the flash chip information */ nor_flash0.blk_size sfud_dev-chip.erase_gran; nor_flash0.len sfud_dev-chip.capacity; return 0; }即设备表的blk_size擦除粒度与len容量在运行时从 SFUD 芯片信息中获取随后read/write/erase直接透传sfud_read/sfud_write/sfud_erase即可。该文件默认设备名为norflash0可通过FAL_USING_NOR_FLASH_DEV_NAME宏覆盖。二、定义 Flash 设备表Flash 设备表定义在fal_cfg.h头文件中定义分区表前需新建fal_cfg.h文件FAL 的公共头文件 fal.h 会通过#include fal_cfg.h引入它因此该文件必须存在于工程 include 路径中。参考 示例文件 samples/porting/fal_cfg.h 或如下代码/* Flash device Configuration */ extern const struct fal_flash_dev stm32f2_onchip_flash; extern struct fal_flash_dev nor_flash0; /* flash device table */ #define FAL_FLASH_DEV_TABLE \ { \ stm32f2_onchip_flash, \ nor_flash0, \ }Flash 设备表中包含两个 Flash 对象一个为 STM32F2 的片内 Flashstm32f2_onchip_flash一个为片外的 Nor Flashnor_flash0。该宏会被 fal_flash.c 展开为static const struct fal_flash_dev * const device_table[] FAL_FLASH_DEV_TABLE;因此表中只能放置已定义的设备对象的地址。从fal_flash_init()的实现fal_flash.c可以看到初始化时的约束编译期强制检查FAL_FLASH_DEV_TABLE未定义会直接报错每个设备的read/write/erase通过RT_ASSERT断言非空若ops.init非空则先执行初始化随后打印设备信息日志。三、定义 Flash 分区3.1 分区表定义Flash 分区基于 Flash 设备每个 Flash 设备又可以有 N 个分区这些分区的集合就是分区表。在配置分区表前务必保证已定义好 Flash 设备及设备表。分区表也定义在fal_cfg.h头文件中参考 示例文件 samples/porting/fal_cfg.h 或如下代码#define NOR_FLASH_DEV_NAME norflash0 /* Partition Configuration */ #ifdef FAL_PART_HAS_TABLE_CFG /* partition table */ #define FAL_PART_TABLE \ { \ {FAL_PART_MAGIC_WORD, bl, stm32_onchip, 0, 64*1024, 0}, \ {FAL_PART_MAGIC_WORD, app, stm32_onchip, 64*1024, 704*1024, 0}, \ {FAL_PART_MAGIC_WORD, easyflash, NOR_FLASH_DEV_NAME, 0, 1024*1024, 0}, \ {FAL_PART_MAGIC_WORD, download, NOR_FLASH_DEV_NAME, 1024*1024, 1024*1024, 0}, \ } #endif /* FAL_PART_HAS_TABLE_CFG */上面这个分区表详细描述信息如下分区名Flash 设备名偏移地址大小说明blstm32_onchip064KB引导程序appstm32_onchip64*1024704KB应用程序easyflashnorflash001MBEasyFlash 参数存储downloadnorflash01024*10241MBOTA 下载区3.2 分区字段与注意事项分区结构体struct fal_partition定义于 fal_def.h依次包含magic_word魔法字、name分区名、flash_name所属 Flash 设备名、offset分区在 Flash 设备内部的偏移、len分区大小与reserved保留字段。示例中的分区定义采用{FAL_PART_MAGIC_WORD, 分区名, 设备名, 偏移, 大小, 保留位}的形式。用户需要修改的分区参数包括分区名称、关联的 Flash 设备名、偏移地址相对 Flash 设备内部、大小需要注意以下几点分区名保证不能重复关联的 Flash 设备务必已经在 Flash 设备表中定义好并且名称一致否则会出现无法找到 Flash 设备的错误分区的起始地址和大小不能超过 Flash 设备的地址范围否则会导致包初始化错误。注意每个分区定义时除了填写上面介绍的参数属性外需在前面增加FAL_PART_MAGIC_WORD属性末尾增加0目前用于保留功能。这些约束在源码中有对应的强制校验逻辑。FAL_PART_MAGIC_WORD的实际值为0x45503130见 fal_partition.ccheck_and_update_part_cache()会逐条检查分区通过fal_flash_device_find(table[i].flash_name)查找设备找不到则告警并跳过并检查table[i].offset flash_dev-len时直接报错并中止初始化。因此设备名不一致、偏移越界都会在fal_init()阶段暴露出来。此外Kconfigcomponents/fal/Kconfig提供了两种分区表加载模式默认开启的FAL_PART_HAS_TABLE_CFG编译期静态分区表即本文所述方式关闭后则改为运行时从 Flash 指定位置FAL_PART_TABLE_FLASH_DEV_NAMEFAL_PART_TABLE_END_OFFSET反向搜索 magic word 并动态加载分区表。四、如何实现读写擦除等操作以 NOR Flash 为例以上完成了设备与分区的登记但操作函数本身才是驱动移植的重头戏。下面以 fal_norflash_port.c 为例完整介绍读写擦除的实现思路。该文件开头声明了需要底层驱动提供的四个外部函数norflash_init、norflash_read、norflash_write_page页写入与norflash_erase_sector扇区擦除并定义了关键参数#define FLASH_SECTOR_SIZE ( 4 * 1024 ) /* 扇区大小 4KB */ #define FLASH_START_ADDR 0U #define FLASH_END_ADDR 0x01000000U /* 16MB */ #define FLASH_PROGRAM_MIN_SIZE 256 /* 最少写入 256 字节 */ #define FLASH_PAGE_SIZE FLASH_PROGRAM_MIN_SIZE4.1 两个对齐宏FAL_ALIGN_UP 与 FAL_ALIGN_DOWN#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)16ALIGN_UP(15,4)16ALIGN_UP(17,4)20ALIGN_DOWN(16,4)16ALIGN_DOWN(15,4)12ALIGN_DOWN(17,4)16不难看出ALIGN_UP是把 size 向上取整到 align 的倍数ALIGN_DOWN则是向下取整到 align 的倍数。它们被用于把任意起止地址映射到扇区/页边界是整个读写实现的地基。4.2 Flash 硬件特性为什么需要对齐与读改写移植前必须理解两类 Flash 的共同特性与差异按块擦除NOR Flash 的块大小一般为 4K单片机内部 Flash 的块大小为 1K、2K、16K 不等最少写入数据限制NOR Flash 按页写入一次最少写 256 个字节数据超过则覆盖起始数据——例如第 257 个数据会覆盖第 1 个数据的位置单片机内部 Flash 一次最少写 2 个字节数据如 STM32F105RC且只能将地址 2 字节对齐写入只写一个字节时给后面的字节补成 FF。正因如此任意长度的写入必须分解为扇区对齐 页对齐的多次底层操作且当写入无法完整覆盖扇区时需要先读出扇区旧数据、合并后再擦写即经典的读-改-写流程。4.3 实现擦除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; }实现要点把起始地址向下对齐、结束地址向上对齐到扇区边界然后逐个扇区调用底层擦除get_sector返回-1表示地址越界参考源码中该函数对FLASH_START_ADDR/FLASH_END_ADDR的范围判断此时返回已擦除的长度。4.4 实现读取/* 这个比较简单直接调用 norflash_read 即可 */ static int read( long offset, uint8_t* buf, size_t size ) { norflash_read( buf, offset FLASH_START_ADDR, size ); return size; }读取没有对齐与擦除的限制直接按物理地址搬运数据并返回长度。4.5 实现写入最关键的一步写入是三个操作中最复杂的部分需要同时处理扇区对齐应对按块擦除与页对齐应对最小写粒度两层问题。/* 写入任意长数据到 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 时才必须擦除。该函数的实现就是扫描缓冲区中是否存在非0xFF的字节见源码static uint32_t judge_whether_erase( uint8_t* sector_buf, uint16_t len ) { uint8_t* p sector_buf; for ( size_t i 0; i len; i ) { if ( p[ i ] ! 0xFF ) return 1; } return 0; }数据合并使用临时缓冲区保存原始数据仅修改需要写入的部分最大限度减少擦除操作擦除会缩短 Flash 寿命能省则省内存管理动态分配扇区大小的缓冲区处理完成后立即释放源码注释提示若不使用内存分配也可定义一个static的FLASH_SECTOR_SIZE长度缓冲区。4.6 补齐最后一块拼图扇区写入函数 write_sector到这里工作似乎做完了但我们还没有写入扇区的函数只有页写入函数norflash_write_page。扇区写入逻辑和任意写入逻辑基本相同区别仅在于对齐粒度从扇区换成了页/* 扇区写入函数处理按页对齐的 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, 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, 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; // 单次最大可写入长度 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 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 { // 整页写入FLASH_PAGE_SIZE 长度 norflash_write_page( buf, cur_addr, FLASH_PAGE_SIZE ); buf FLASH_PAGE_SIZE; // 移动数据指针整页长度 } // 移动到下一页起始地址 cur_addr FLASH_PAGE_SIZE; } return size; // 返回成功写入的总字节数 }至此write的完整调用链就闭环了write()按扇区做对齐与读-改-写合并内部调用write_sector()write_sector()再按页256 字节拆分最终落到norflash_write_page()。两层对齐分别对应按块擦除与最少写入字节数两个硬件约束这种思路对大部分 Flash 驱动来说是通用的。最后将四个操作函数组装成设备对象const struct fal_flash_dev norflash0 { .name norflash0, .addr FLASH_START_ADDR, .len FLASH_END_ADDR - FLASH_START_ADDR, .blk_size FLASH_SECTOR_SIZE, .ops { init, read, write, erase }, .write_gran 1, };注意这里write_gran 1位与 NOR Flash 支持按位写 0、按页编程的特性一致。五、移植完成后的验证与调试移植完成后需要确认 FAL 能正确初始化并找到所有设备与分区编译期检查fal_cfg.h必须被 fal.h 正确引入且定义好FAL_FLASH_DEV_TABLE若开启静态分区表还需定义FAL_PART_TABLE见 fal_flash.c 与 fal_partition.c 中的#error检查。初始化入口在系统初始化流程中调用fal_init()返回值为分区总数内部依次完成设备初始化与分区校验成功后会打印RT-Thread Flash Abstraction Layer initialize success.fal.c。查看分区表开启FAL_USING_DEBUGKconfig 中默认随RT_USING_DEBUG打开后fal_partition_init()会调用fal_show_part_table()打印完整的名称 / Flash 设备 / 偏移 / 长度表格便于核对移植结果fal_partition.c。相关 Kconfig 配置项components/fal/KconfigFAL_USING_SFUD_PORT启用 SFUD 移植文件FAL_USING_NOR_FLASH_DEV_NAME指定其设备名FAL_DEV_NAME_MAX默认 24与FAL_DEV_BLK_MAX默认 6分别控制名称长度上限与设备块数上限设备名较长或设备较多时应相应调大。上层 API 验证初始化成功后即可通过fal_partition_find(app)获取分区再调用fal_partition_read/write/erase或fal_partition_erase_all读写数据接口声明见 fal.h这些接口在分区层也做了地址越界检查addr size part-len时拒绝操作并报错可作为移植正确性的第一道验证。结语FAL 的移植本质上只做三件事写对四个操作函数、登记设备表、划分分区表。其中操作函数的质量取决于对 Flash 硬件特性的理解——扇区擦除、页最小写入、部分写入时的读-改-写合并与擦除必要性判断。本文以components/fal/samples/porting/下的 STM32F2 片内 Flash、SFUD 片外 SPI Flash 与手写 NOR Flash 驱动三个示例为主线从结构定义、宏展开到初始化校验逐层拆解这套设备 → 设备表 → 分区表的移植流程同样适用于其它厂商芯片希望对你手头的 BSP 移植工作有所帮助。赞分享操作系统嵌入式物联网嵌入式OSRTOS【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址https://gitcode.com/gh_mirrors/rt/rt-thread点击查看免费下载相关推荐RT-Thread FAL 完整 API 指南Flash 抽象层的设备查找、分区读写与设备适配RT Thread FAL 完整 API 指南Flash 抽象层的设备查找、分区读写与设备适配 本篇技术指南围绕 RT Thread 开源物联网实时操作系统中操作系统嵌入式物联网嵌入式OSRTOSRT-Thread 设备树 fdt 组件实战fdt_dump / fdt_test 示例程序与 DTB 解析输出详解RT Thread 设备树 fdt 组件实战fdt_dump / fdt_test 示例程序与 DTB 解析输出详解 RT Thread 的 fdt 组件基于操作系统嵌入式物联网嵌入式OSRTOSRT-Thread 内核移植指南libcpu 架构移植与 BSP 板级移植实战以 Cortex-M 为例RT Thread 内核移植指南libcpu 架构移植与 BSP 板级移植实战以 Cortex M 为例 RT Thread 作为一款面向物联网的开源实时操作系统嵌入式物联网嵌入式OSRTOS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门