# U2A8-FBL **Repository Path**: hackxt/u2a8-fbl ## Basic Information - **Project Name**: U2A8-FBL - **Description**: RH850-U2A8芯片FBL开发笔记 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-09-16 - **Last Updated**: 2025-09-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RH850-U2A8芯片FBL开发笔记 ## 总体架构 * 整个 FBL 分为 BootManger(BM)、PrimaryBootloader(PBL) 和 SecondaryBootloader(SBL) 三个部分。 * BootManger(BM) 负责跳转功能 * PrimaryBootloader(PBL) 负责升级刷写功能 * SecondaryBootloader(SBL) 为 Flash 驱动 * FBL 架构如下图所示: ![](./images/FBL架构.png) ## 地址规划 * 查找芯片手册,其中 U2A8 芯片的 Code Flash 地址规划如下: ![](./images/FlashAddress.png) * 可以看出在 Double Map 模式下 U2A8 芯片有 8M 的 Code Flash,其中 4M 处于激活状态,4M 处于未激活状态。 * 也就是说,如果在开启 SWAP 交换功能情况下,程序只有 4M 的 Code Flash 可用。 * FBL 的 Code Flash 分区如下: | 分区 | 范围 | |---|---| | BM - 128K | 0x00000000 - 0x0001FFFF 或 0x02000000 - 0x0201FFFF | | PBL - 384K | 0x00020000 - 0x0007FFFF 或 0x02020000 - 0x0207FFFF | | APP - 3200K | 0x00080000 - 0x0039FFFF 或 0x02080000 - 0x0239FFFF | | HSM - 384K | 0x003A0000 - 0x003FFFFF 或 0x023A0000 - 0x023FFFFF | * 注:HSM 占用 Code Flash 地址空间是供应商决定的,在开发 FBL 升级功能前可以先预留。 * 创建一个 "Boot_Cfg.h" 头文件,关于 FBL 配置可以放在该文件中。比如地址空间分配 ```c /* * Total - 4M * BM - 128K * PBL - 384K * APP - 3200K * HSM - 384K */ #define BOOT_BM_ADDRESS (0x00000000U) #define BOOT_BM_LENGTH (0x00020000U) #define BOOT_PBL_ADDRESS (0x00020000U) #define BOOT_PBL_LENGTH (0x00060000U) #define BOOT_APP_ADDRESS (0x00080000U) #define BOOT_APP_LENGTH (0x00320000U) #define BOOT_HSM_ADDRESS (0x003A0000U) #define BOOT_HSM_LENGTH (0x00060000U) ``` ## Flash Driver 移植 * 从 RH850 官网中查找当前芯片 U2A8 的 Flash 驱动代码,不过 Falsh 驱动不是以代码的形式直接提供,而是一个名为 "RENESAS_RFD28F_RH850_V1.00.02_Free.exe" 的 exe 文件,当前很多芯片的示例代码也是以 exe 形式提供的,直接双击安装后就会解压出来示例代码。注意安装路径,不然有可能找不到解压后的源码位置。 * 移植 Flash Driver 的过程这里省略,注意移植完 Flash Driver 代码后编译链接时会出现一系列类似如下的警告: ``` [elxr] (warning #283) section .R_RFD_CODE_COMMON from r_rfd_common_api.o isn't included by the section map; appending after last section. add to section map or use -append to append without warning ``` * 这是因为链接脚本 "*.ld" 中没有 ".R_RFD_XXXXXX" 等自定义段。复制示例代码中链接脚本中相关的自定义段粘贴到自己工程的链接脚本中即可。 * 注:无论是 Data Flash 还是 Code Flash,擦写都是以块为单位的,不能直接操作单个任意地址。具体的擦除块大小与写入块大小需要查询芯片手册。 ## 升级标志读写接口实现 * 升级过程中会有一些标志参数需要断电保存,一般芯片内部会提供一片 Data Flash 用于存储数据,U2A8 Data Flash 所在地址范围如下: ![](./images/DataFlash.png) * 查找芯片手册可知 Data Flash 擦除最小单位是 4K。写入数据单位只能是 4、8、16、32、64、128 这几个字节。我们在调用 Data Flash 擦除接口函数时,注意函数传参的起始地址最好要 4K 对齐,并且长度是 4K 的整数倍。否则有可能操作失败。 ![](./images/FlashOptSize.png) * 从上图中可以看出 Code Flash 擦除时前面 8 个 Block(共 16*8=128K)擦除单位是 16K,后面的是擦除单位是 64K,写入数据单位是 512 字节。 * 定义一个共用体类型数据结构的变量用作升级标志读写的 buffer 数据区域,其中 ProgRequestFlag 表示升级请求标志)、PblValidFlag 表示 PBL 有效性标志)、AppValidFlag 表示 APP 有效性标志),Magic 用于初次烧录时该片区域没有初始数据,通过检测该标志作为判断条件,从而给其它标志进行一些初始化赋值操作等。 ```c typedef union UPInfoType { uint8 upInfoArr[512]; struct upInfo { uint32 Magic; uint32 ProgRequestFlag; uint32 PblValidFlag; uint32 AppValidFlag; }p; } UPInfoType; static UPInfoType __attribute__((aligned(4))) UP; ``` * 我们可以通过指针的方式直接读取到 Flash 中的数据,于是在初始化时可以将升级标志数据从 Flash 中读到 UP.upInfoArr[] 数组中,另外可以通过判断 Magic 值来确定该区域是否是第一次使用,如果从未被使用,可以做一些设置初始值的操作。 ```c Std_ReturnType Boot_DataM_Init(void) { uint32 i = 0; // flash read uint8* upInfoPtr = (uint8 *)(BOOT_UPINFO_SAVE_ADDR); if(MAGIC != *(uint32*)BOOT_UPINFO_SAVE_ADDR) { R_RFD_ShiftToPEMode(R_RFD_FACI1, R_RFD_MODE_DFPE); DISABLE_INTERRUPT(); WaitUntilFaciNotBusy(R_RFD_FACI1); R_RFD_EraseDFRequest((T_u4_RfdAddress)BOOT_UPINFO_SAVE_ADDR); // Erase 4K // 设置初始值 // ... UP.p.Magic = MAGIC; WaitUntilFaciNotBusy(R_RFD_FACI1); R_RFD_WriteDFRequest((T_u4_RfdAddress)BOOT_UPINFO_SAVE_ADDR, (T_pu4_RfdBuffer)&(UP.upInfoArr[0])); // Write 128 Bytes once. WaitUntilFaciNotBusy(R_RFD_FACI1); ENABLE_INTERRUPT(); } for(i = 0; i < sizeof(UPInfoType); i++) { UP.upInfoArr[i] = upInfoPtr[i]; } return E_OK; } ``` * 注意:擦写前要调用 R_RFD_ShiftToPEMode 设置 PE 模式,否则 Flash 无法擦写成功。 * 给这些标志封装读写接口以供外部使用。 ```c Std_ReturnType Boot_ReadProgRequestFlag(uint32* flag) { *flag = UP.p.ProgRequestFlag; return E_OK; } // ...... Std_ReturnType Boot_WriteProgRequestFlag(uint32 flag) { if(MAGIC != UP.p.Magic || flag == UP.p.ProgRequestFlag) return E_NOT_OK; UP.p.ProgRequestFlag = flag; R_RFD_ShiftToPEMode(R_RFD_FACI1, R_RFD_MODE_DFPE); DISABLE_INTERRUPT(); WaitUntilFaciNotBusy(R_RFD_FACI1); R_RFD_EraseDFRequest((T_u4_RfdAddress)BOOT_UPINFO_SAVE_ADDR); // Erase 4K WaitUntilFaciNotBusy(R_RFD_FACI1); R_RFD_WriteDFRequest((T_u4_RfdAddress)BOOT_UPINFO_SAVE_ADDR, (T_pu4_RfdBuffer)&(UP.upInfoArr[0])); // 128Bytes WaitUntilFaciNotBusy(R_RFD_FACI1); ENABLE_INTERRUPT(); return E_OK; } ``` ## FLASH 注意事项 * **注意 1:Flash 擦除和写入都必须遵循指定的大小,不是任意字节都能擦写成功。** * **注意 2:Flash 读写操作的 buf 缓冲区最好是 4 字节对齐。** * **注意 3:Flash 擦写操作都需要一定的时间,擦写操作前后注意要给予足够的等待时间。** * **注意 4:Flash 擦写操作时建议关闭中断。** * **注意 5:Flash 擦写两个不同的物理 BANK 时最好分开独立操作。** ## 发现问题 * 依据芯片手册中 Data Flash 地址空间划分,尝试把升级参数存储到 0xFF28F000-0xFF28FFFF(Data Flash 最后面 4k)处。发现无法写入。 * 分析原因:上面的 Data Flash 地址范围可能是 U2A 这个系列芯片的顶配,当前我们使用的 U2A8 芯片只有其中一部分。 * 在芯片手册里找到芯片 U2A8 的 Data Flash 地址范围 0xFF200000-0xFF24FFFF. ![](./images/U2A8DFlash.png) * 该范围参数与程序中定义的不一致,追查代码发现程序是支持 U2A 全系列芯片的,默认是匹配 U2A16,需要更改 "r_rfd_config.h" 这个配置文件,在文件开头添加如下定义,选择 U2A8 芯片 ```c #define __RH850_U2A8__ ``` * 不过即便如此,也只有 Data Area 0 和 Data Area 1 可以擦写,即 0xFF200000-0xFF23FFFF 有效,Data Area 2 始终无法擦写,暂时未找到原因,可能这块内存被保护起来了。 * 借助 UDE 调试界面发现 DFLASH_2 也是无法使能的,而 DFLASH_0 和 DFLASH_1 可以使能。 ![](./images/DataArea2无法Enable.png) * 目前暂时将升级参数放到 0xFF23F000 处。 ```c #define BOOT_UPINFO_SAVE_ADDR (0xFF23F000U) ``` * **询问了一下 HSM 供应商的技术人员,Data Area 2 原来是被 HSM 使用了,并且被保护了起来。而他们提供的相关文档中并没有这方面的描述。** * 另外从手册中还查到查到 Data Area0/1/2 分别由 FACI0/1/2 控制,Code Flash 由 FACI0 控制,这点在编程时需要注意。 ## 将参数存入 CFlash * 参数一般情况下会存在 Data Flash,但是 APP 层会使用 NvM 来管理并使用 Data Flash,考虑到跟 APP 程序独立开来,尽量减少两者之间的关联,可以将升级相关参数存入 Code Flash 中,现将其规划到 0x0001C000 地址处。修改 "Boot_Cfg.h" 文件,增加升级参数分区。 ```c #define BOOT_INFO_ADDRESS (0x0001C000U) #define BOOT_INFO_LENGTH (0x00004000U) ``` * 根据手册,0x0001C000 地址擦除 block 大小是 16K,写入是 512 字节,Code Flash 只由 R_RFD_FACI0 控制。可参考如下代码: ```c R_RFD_ShiftToPEMode(R_RFD_FACI0, R_RFD_MODE_CFPE); DISABLE_INTERRUPT(); WaitUntilFaciNotBusy(R_RFD_FACI0); R_RFD_EraseCFRequest((T_u4_RfdAddress)BOOT_UPINFO_SAVE_ADDR); // Erase 16K WaitUntilFaciNotBusy(R_RFD_FACI0); UP.p.ProgRequestFlag = flag; R_RFD_WriteCFRequest((T_u4_RfdAddress)BOOT_UPINFO_SAVE_ADDR, (T_pu4_RfdBuffer)&(UP.upInfoArr[0])); // 512Bytes WaitUntilFaciNotBusy(R_RFD_FACI0); ENABLE_INTERRUPT(); ``` * **注意:注意有的芯片的 Code Flash 只允许操作 Inactive Bank,不允许操作 Active Bank。要根据实际情况而定。** ## BM 跳转逻辑实现 * BootManger 主要的功能就是根据各种标志判断跳转至 PBL 或 APP 又或者是留在 BM。 * 逻辑见下表所示: | ProgRequestFlag | PblValidFlag | AppValidFlag | Jump | |---|---|---|---| | 0 | 0 | 0 | StayInBm | | 0 | 0 | 1 | JumpToApp | | 0 | 1 | 0 | JumpToPbl | | 0 | 1 | 1 | JumpToApp | | 1 | 0 | 0 | StayInBm | | 1 | 0 | 1 | StayInBm | | 1 | 1 | 0 | JumpToPbl | | 1 | 1 | 1 | JumpToPbl | * 用 C 语言实现即定义一个如下数组 ```c static const JumpCommandElementType JumpTbl[] = { {0, 0, 0, StayInBm}, {0, 0, 1, JumpToApp}, {0, 1, 0, JumpToPbl}, {0, 1, 1, JumpToApp}, {1, 0, 0, StayInBm}, {1, 0, 1, StayInBm}, {1, 1, 0, JumpToPbl}, {1, 1, 1, JumpToPbl} }; ``` * 该数组的元素类型定义如下: ```c typedef void (*JumpExec)(void); typedef struct { boolean ProgRequestFlag; boolean PblValidFlag; boolean AppValidFlag; JumpExec Exec; }JumpCommandElementType; ``` * 接下来封装一个函数,该函数功能是遍历 JumpTbl[] 上面定义数组,匹配前面几个参数值,匹配成功后执行对应的函数。 ```c static void Boot_InspectBootStateInputs(uint32 ProgRequestFlag, uint32 PblValidFlag, uint32 AppValidFlag) { ProgRequestFlag = (ProgRequestFlag == BOOT_PROG_REQUEST_FLAG) ? 1u : 0u; PblValidFlag = (PblValidFlag == BOOT_PBL_VALID_FLAG) ? 1u : 0u; AppValidFlag = (AppValidFlag == BOOT_APP_VALID_FLAG) ? 1u : 0u; for(uint32 index = 0; index < (sizeof(JumpTbl) / sizeof(JumpTbl[0])); index++) { if((ProgRequestFlag == JumpTbl[index].ProgRequestFlag) && (PblValidFlag == JumpTbl[index].PblValidFlag) && (AppValidFlag == JumpTbl[index].AppValidFlag)) { JumpTbl[index].Exec(); } } } ``` ## 关闭核 1 * 从 FBL 跳转到 APP 运行,会经历多次初始化,即有可能重复开启其它核心,有的芯片重复使能其他核心时会出现死机问题,为了避免这个风险,在 FBL 中最好只保留核 0,关闭其他核。 * 其他核心启动一般在启动汇编中实现,当前 BM 工程关闭其他核心的方法如下: ```c -- enable PEX by PE0 #define ENABLE_PE1_BY_PE0 FLAG_OFF // kjw ``` ## PBL 程序添加诊断栈 * 移植 CanIf、PduR、CanTp、Dcm 这几个诊断栈关联模块代码 * 关于代码的调试或者移植细节在这里不做详细描述。不过有以下几个必须要关注的地方。 * 修改 "CanIf_PBcfg.c" 文件,主要是 UDS 诊断的物理地址和功能地址,以及 CAN 发送的句柄。(0x700, 0x7DF, 0x708) * 由于在 CanIf 层,UDS 物理寻址被配置为通道 0,UDS 功能寻址被配置为通道 1,而底层 CAN 驱动是移植 APP 过来的代码,所以可能通道号不匹配,在 can 驱动层调用 CanIf_RxIndication 函数时简单做如下处理: ```c if(LstMailbox.CanId == 0x700 || LstMailbox.CanId == 0x40000700) { LstMailbox.Hoh = 0; LstMailbox.ControllerId = 0; CanIf_RxIndication(&LstMailbox, &LstPduInfo); } else if(LstMailbox.CanId == 0x7df || LstMailbox.CanId == 0x400007DF) { LstMailbox.Hoh = 1; LstMailbox.ControllerId = 0; CanIf_RxIndication(&LstMailbox, &LstPduInfo); } else { ; } ``` * 如果不想打补丁就需要重新配置 MCAL Can 驱动。 * 由于没有移植管理模块代码,默认状态下 CanIf、Dcm 收发数据通道是不通的,需要调用下面接口函数开启: ```c CanIf_SetControllerMode(0, CANIF_CS_STARTED); CanIf_SetPduMode(0, CANIF_ONLINE); Dcm_ComM_FullComModeEntered(0); ``` * CanIf 层的 CanIf_SetControllerMode 函数实际上是调用底层 CAN 驱动层接口函数 Can_SetControllerMode 的,底层 can 驱动设置成功后再调用 CanIf 层的 CanIf_ControllerModeIndication 函数通知 CanIf 层改变 mode,由于 CanIf 层与驱动层的 can 通道不匹配,这条路不一定执行正确,手动修改 CanIf_SetControllerMode 函数直接改变 mode。这个模式不改变,接收的 can 消息会被 CanIf 层过滤掉。 ![](./images/修改CanIf_SetControllerMode.png) ## 修改 PBL 程序地址 * 通过修改 "*.ld" 链接脚本,重定位 PBL 程序地址,如下所示: ![](./images/重定位代码段.png) * 可以使用二进制工具查看是否成功 ![](./images/查看代码段定位是否成功.png) ## 从 BM 跳转到 PBL * 从 BM 跳转到 PBL,需要明确 PBL 程序的起始地址,一般在启动汇编文件中实现,当前 U2A8 芯片工程的启动汇编文件后缀时 "*.850"。其中 "_RESET" 函数就是起始代码,程序从此处开始运行。 ![](./images/启动汇编.png) * 在编程生成的 "*.map" 文件中查找如下所示: ![](./images/map中_RESET.png) * 一般情况下,链接脚本会将中断向量表放到生成的 hex 或二进制程序的最头部,然后是启动汇编,再然后是其他代码,不过当前的 PBL 工程显然做的不够好,启动汇编根本没有紧挨着中断向量表放置,而是像普通代码文件一样编译链接,这也就意味着修改代码后该地址可能会跟着变化。 * 如果 "_RESET" 放在中断向量表后面,那么该地址就是固定的,我们可以直接跳转到这个地址运行,但是该地址明显不是固定的,在 BM 程序中肯定无法定位到 PBL 中的 "_RESET"。 * 此时就只能去中断向量表中找到复位芯片的中断偏移。从 U2A8 的芯片手册中找到复位中断处在向量表中的第一个位置。 ![](./images/中断向量表.png) * 中断向量表中放到是一个个跳转指令,复位中断就是跳转到 "_RESET",虽然不懂二进制的机器指令,但是也能看个大概,比如地址有一致性 ![](./images/跳转指令.png) * 补全 BM 程序中跳转函数,采用函数指针的形式进行跳转,这里的偏移量 BOOT_START_OFFSET 值为 0. ```c static void JumpToApp(void) { ((void (*)(void))(BOOT_APP_ADDRESS+BOOT_START_OFFSET))(); } static void JumpToPbl(void) { ((void (*)(void))(BOOT_PBL_ADDRESS+BOOT_START_OFFSET))(); } ``` * **注:BOOT_START_OFFSET 偏移量要根据实际的复位中断偏移而定,有的芯片复位中断并不在中断向量表中的第一个。** ## APP 下载 * APP 下载主要涉及到 UDS 诊断 31、34、36 服务,本质上就是 Flash 擦写工作,这里不详细描述。参考上面的 “FLASH 注意事项” ## SBL 制作 * 考虑到安全等原因,我们把 Flash Driver 从 Code Flash 中转移到 RAM 中,查找芯片手册,U2A8 RAM 地址范围如下图所示: ![](./images/U2A8_RAM.png) * 将 0xFE4F0000-0xFE4FFFFF(64K) 这片 RAM 空间划分给 SBL。更改链接脚本 "*.ld", 注意,原 RAM 空间大小要减去 64K。 ```c MEMORY { ... cRAM_2_SBL : ORIGIN = 0xFE4F0000, LENGTH = 64k /* kjw - SBL */ ... } ``` * RAM 空间重新划分完成后,接下来要想办法让 Flash Driver 从代码段重定位到 RAM 中上面刚分配的区域。原程序程序中 Flash Driver 的代码包括 ".R_RFD_CODE_CF"、".R_RFD_CODE_CF_RAM_NO_BGO" 和 ".R_RFD_CODE_COMMON" 段,只需要将他们重定位到 cRAM_2_SBL 中即可。 ``` SECTIONS { ... // kjw .FLASH_DRIVER ALIGN(4) MAX_SIZE(0x10000): { *(.flash_driver_header) *(.R_RFD_CODE_CF) *(.R_RFD_CODE_COMMON) *(.R_RFD_CODE_CF_RAM_NO_BGO) } > cRAM_2_SBL =0xFFFFFFFF ... } ``` * 如果 Flash Driver 没有定义代码段的话那自己重定义代码段。然后放到上面的 cRAM_2_SBL 中即可。 * Flash Driver 相关的代码段被连接到 cRAM_2_SBL 处后,为了能够找到我们想要的 Flash 驱动接口函数,于是自定义一个 ".flash_driver_header" 段,放在 cRAM_2_SBL 最开始处。".flash_driver_header" 段中只定义一个 const 数据结构,其中存放要用到 Flash 驱动接口函数。 ```c #pragma ghs section rodata= ".flash_driver_header" const FlashHeaderType FlashHeader = { 0xAAAA5555, Flash_Init, WaitUntilFaciNotBusy, R_RFD_ShiftToPEMode, R_RFD_EraseCFRequest, R_RFD_WriteCFRequest, }; #pragma ghs section rodata=default ``` * FlashHeaderType 类型定义如下: ```c typedef struct { uint32 magic; void (*pFunc_Flash_Init)(void); T_u4_RFDReturn (*pFunc_WaitUntilFaciNotBusy)(T_u2_FACI i_u2_TargetFACI); T_u4_RFDReturn (*pFunc_R_RFD_ShiftToPEMode)(T_u2_FACI i_u2_TargetFACI, T_en_FACIMode i_en_FACIMode); void (*pFunc_R_RFD_EraseCFRequest)(T_u4_RfdAddress i_u4_Start); void (*pFunc_R_RFD_WriteCFRequest)(T_u4_RfdAddress i_u4_Start, T_pu4_RfdBuffer i_pu4_Data); }FlashHeaderType; ``` * SECTIONS 会在编译连接后生成 hex 中体现。如下图所示。 ![](./images/SBL.png) * 利用工具软件将 0xFE4F0000-0xFE4FFFFF 剪切出来就是我们想要的 SBL 了。 ## SBL 下载与使用 * 由于 SBL 所处地址是在 RAM 中,上位机通过 UDS 诊断下载 SBL 时,可以利用指针的方式搬运数据到指定的 SBL 区域,不需要像 Flash 一样需要提前擦除。 * 定义一个全局变量指针 ```c FlashHeaderType* pFlashHeader = NULL_PTR; ``` * 当通过诊断服务下载并校验 SBL 成功后,初始化该指针 ``` pFlashHeader = (FlashHeaderType *)0xFE4F0000; ``` * 之后就可以通过如下的方式调用 Flash Driver 接口函数了。 ```c pFlashHeader->pFunc_Flash_Init() ``` ## SWAP 功能 * 啦啦啦,我被剔出项目啦,开发成果也没人要啦,既然没人要那我就整理一下笔记开源啦,不能白干。 * 单 Bank 升级功能已经开发完成,还剩下 SWAP 双区备份功能没有开发。懒得实现了,这里就说个大概吧。 * 必须要封装三个功能接口函数: * 获取当前 Active Bank 功能,一般直接读取某个寄存器的值即可。 * 使能 SWAP 功能,该配置字参数一般是存储在芯片 Data Flash 中,不过是特殊的 Data Flash 中,不在通用的 Data Flash 范围内,该范围内数据的修改限制更加严格。 * 切换 Active Bank 功能,一般跟使能 SWAP 功能的配置字参数一样在特殊 Data Flash 中。 * 封装完以上三个接口后,接下来逻辑功能实现就非常简单了,首先是初始化时开启 SWAP 功能,然后就是在擦写的时候判断一下当前的 Active Bank,如果当前 Bank A 处于活跃状态,那就擦写 Bank B 区域,反之则擦写 Bank A 区域。 * 注意: * 开启 SWAP 功能后,程序的跳转地址不需要改变,因为程序运行时采用的是逻辑地址。但是在升级过程中调用 Flash 擦写接口函数时,传入的地址参数应是物理地址。 * 切换 Active Bank 后,需要复位芯片方可生效,有时候 Reset 软复位可能由于复位深度不够导致无法切换 Active Bank,此时可以调整复位深度,或者采用硬件复位或者看门狗复位。 * 若开启 SWAP 功能,在升级前必须要将 Active Bank 和 Inactive Bank 都烧录好程序。