找回密码
立即注册
搜索
发回帖 发新帖

6816

积分

0

好友

864

主题
发表于 11 小时前 | 查看: 0| 回复: 0

引言:FSP 生成的代码,为什么总被覆盖?

FSP 自动生成代码后,如何避免用户代码被覆盖?如果你使用过 Renesas FSP(Flexible Software Package,灵活配置软件包),大概率遇到过这样的场景:

  • 为了快速验证功能,直接在 ra_gen 目录中加了几行代码;
  • 为了修改 PWM 周期,手工编辑了 common_data.c;
  • 为了调整 UART 回调,直接修改了生成实例的初始化结构;
  • 当工程配置发生变化,需要再次执行 Generate Project Content 时;
  • 工程还能编译,但之前写的代码不见了。

这不是 FSP 出错,而是工程边界没有划分清楚。

FSP 的本质是:根据图形化配置生成底层工程代码。 只要某个文件可以由 FSP 根据配置重新生成,就应该假设它可能被覆盖。把产品逻辑写入生成文件,重新生成后丢失代码是正常结果。

本文的核心目标只有一个:让 FSP 管配置和生成结果,让用户代码承载产品行为。工程应该可以随时重新生成,而不丢失产品功能。

一、先划清 FSP 的覆盖边界

不同 FSP 版本和工程模板生成的目录结构可能不同,例如 ra_gen、ra_cfg、fsp_cfg 等。通常情况下,由 FSP 根据配置自动生成或维护的文件,都不建议直接加入产品业务代码。

FSP改动类型分类表格:配置改动与行为改动的处理方式对照

一句话判断:删掉该文件后,FSP 能按图形配置重新生成它,就不要把产品逻辑放在里面。

二、推荐工程目录与依赖方向

建议把工程划分为生成层和应用层。ra/、ra_gen/、ra_cfg/ 构成 FSP 生成层,服务于 Smart Configurator 配置、FSP 框架模块及自动生成的初始化代码和实例。src/app/ 为应用层,负责状态机、控制算法和产品流程;src/hal_entry.c 为应用程序入口,负责完成启动初始化并进入应用流程。

推荐的工程目录结构如下:

瑞萨RA MCU FSP工程project目录结构树状图,包含ra、ra_gen、src/app等分层说明

依赖方向应尽量单向:src/app/ 调用 FSP 生成的实例和接口,FSP 生成层不要反向包含产品业务模块。这样即使重新生成导致实例配置变化,影响也主要限制在 ra_gen/ 等生成层文件中。

三、用驱动封装替代直接修改生成文件

假设 FSP 生成 UART 实例 g_uart0。生成层负责定义实例,用户层负责定义产品需要的接口。不要在 r_sci_uart.c、common_data.c 或 hal_data.c 中插入收发状态机。

用户代码可以通过两种方式调用 UART:一种是通过 FSP 生成的实例接口,例如 g_uart0.p_api->write();另一种是在明确使用 SCI UART 驱动时,直接调用 R_SCI_UART_Write()。

在正式项目中,更推荐在用户层再封装 app_uart_write(),让业务代码依赖产品接口,而不是到处依赖具体实例名、控制块或底层驱动函数。示例代码如下:

#include "hal_data.h"
#include "app_uart.h"

fsp_err_t app_uart_open(void)
{
    return g_uart0.p_api->open(g_uart0.p_ctrl, g_uart0.p_cfg);
}

fsp_err_t app_uart_write(uint8_t const * p_data, uint32_t length)
{
    return g_uart0.p_api->write(g_uart0.p_ctrl, p_data, length);
}

或者:

#include "hal_data.h"
#include "app_uart.h"

fsp_err_t app_uart_open(void)
{
    return R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
}

fsp_err_t app_uart_write(uint8_t const * p_data, uint32_t length)
{
    return R_SCI_UART_Write(&g_uart0_ctrl, p_data, length);
}

应用层调用 app_uart_write(),而不是到处访问 g_uart0.p_api 或 R_SCI_UART_Write()。这样做有四个好处:重新生成不会覆盖 app_uart.c;封装层可以统一增加超时和错误处理等;测试时可替换实现;切换通道或实例名称时修改范围可控。

同样模式适用于 ADC、GPT、IIC、SPI、CAN、USB、Flash 和 DMA。FSP 驱动是硬件能力提供者,用户封装才是产品接口的稳定边界。

四、hal_entry.c 只做入口编排

hal_entry.c 通常是用户代码入口,在 No RTOS 配置下,hal_entry() 可用于组织应用启动流程,适合放初始化顺序和主循环调度,但不适合作为所有功能的容器。状态机、通讯协议、控制算法、故障策略、数据处理、参数管理等应拆分到独立模块。

推荐结构如下:

void hal_entry(void)
{
    app_board_init();
    app_uart_init();
    app_fault_init();
    app_main_init();

    while (1)
    {
        app_main_process();
    }
}

在 RTOS 工程中,应用入口和初始化方式取决于工程模板及启动架构。工程可能生成 hal_entry.c,也可能使用其他入口文件或初始化函数。hal_entry.c(如果存在)通常不应作为传统的主循环容器;用户应根据实际启动流程,将初始化代码放在相应的用户入口中,将线程业务逻辑放在线程入口函数中。RTOS 的任务调度和线程间通信应通过线程、队列、事件标志组、信号量、互斥锁和定时器等机制实现。

五、回调函数是连接点,不是业务处理器

在 RA FSP 中,UART、SPI、IIC、ADC、GPT 等模块通常通过 callback 机制通知应用层,用户应避免在 callback 中执行复杂业务逻辑。

volatile bool g_uart_tx_done = false;

void uart0_callback(uart_callback_args_t * p_args)
{
    if (UART_EVENT_TX_COMPLETE == p_args->event)
    {
        g_uart_tx_done = true;
    }
}

回调中建议只执行设置标志位、写入环形缓冲区、复制少量固定长度数据、发送 RTOS 信号量或记录轻量诊断计数。回调中不建议执行 Flash 写入、复杂浮点计算、阻塞等待、长循环、协议全解析等全流程,因为这些操作会影响中断实时性和系统堆栈。

六、Pins、Clocks 和 Stacks 的变更流程

配置变更和产品业务修改混在一起,会让生成后的差异难以审查。每次修改 FSP 配置时,建议使用固定流程:

  1. 提交或暂存当前可工作代码,确保可以回退。
  2. 在 FSP 配置界面修改 Pins、Clocks、BSP 或 Stacks。
  3. 执行 Generate Project Content。
  4. 使用版本控制工具(如 Git)检查生成结果的差异。
  5. 编译工程,确认无 API、回调和资源冲突。
  6. 在目标板执行最小验证,例如串口收发、ADC 采样、PWM 波形或通信握手。
  7. 将配置文件、生成结果和必要的用户层适配修改作为同一逻辑变更提交。

尤其要注意中断优先级、DMA 通道、时钟频率和引脚复用。这些变化可能不导致编译失败,却会改变运行时行为。电机控制、工业通信和低功耗项目不应省略目标板验证。

七、必须修改生成代码时的处理方式

不建议直接修改生成代码,并不代表生成代码不可查看或不可调试。在定位问题、分析驱动行为或处理特殊 workaround 时,可以阅读甚至临时修改,但正式项目中应通过配置、用户封装或补丁机制管理修改。首先确认 FSP 的模块属性、BSP 设置、回调和扩展配置(FSP 为某些模块提供的高级、可选或依赖特定硬件功能的配置项)是否已有对应选项。若确实遇到 FSP 驱动缺陷、芯片勘误问题,优先在用户层封装规避。无法避免时,应将修改维护为独立补丁文件,明确对应 FSP 版本、修改原因、影响范围和重新生成后的应用方式。

临时修改生成代码但没有补丁、说明和重放步骤,本质上等于把风险留给下一次生成。

八、不要依赖用户代码块注释

FSP 没有一个类似其他一些生成器那样“官方统一、到处都能用”的 USER CODE BEGIN/END 机制,RA 工程推荐通过用户文件 + app 分层封装来避免代码被覆盖,而不是依赖保留块。

九、用版本控制把覆盖风险变成可审查差异

Git 不能阻止覆盖,但能让覆盖变成可见、可审查、可恢复的变化。每次生成前后至少执行:

git status
git diff
git diff -- ra_gen ra_cfg fsp_cfg

建议将 configuration.xml、FSP 配置、生成文件、用户代码、补丁文件、FSP 版本信息和构建脚本纳入可追溯管理。

FSP 升级应作为一次受控迁移处理,不要混在功能开发提交里。升级时应记录旧版和目标版本,重新生成并审查差异,处理 API 变化,验证启动、时钟、引脚、通信、ADC、定时器、DMA 和低功耗路径;对关键产品保留波形、通信日志和性能数据。

十、常见错误场景与正确替代方案

FSP嵌入式开发风险场景对照表:调试打印、PWM参数、回调函数、ADC采样等8种错误做法与推荐做法

十一、团队提交前检查清单

  • □ 用户业务逻辑没有写入 ra_gen、ra_cfg、fsp_cfg、common_data 或 hal_data。
  • □ Pins、Clocks、Stacks、IRQ、DMA 和外设参数全部通过 FSP 配置修改。
  • □ 回调函数位于用户文件,且回调内没有阻塞或长耗时工作。
  • □ 新增外设已在用户层中提供稳定封装接口。
  • □ hal_entry.c 只负责入口编排,没有堆叠协议解析、控制算法或长状态机。
  • □ 生成前后已检查 git status 和 git diff。
  • □ 编译通过,并完成与本次变更相关的目标板最小测试。
  • □ 若修改过生成代码,已新增补丁、问题说明和重新应用步骤。
  • □ FSP、IDE、编译器和关键工具版本已记录。
  • □ FSP 升级与功能开发分开提交,升级结果有迁移说明和验证记录。

结束语:让重新生成成为日常动作

自动生成代码不是风险本身;真正的风险是配置、生成结果和产品逻辑混在一起。工程有清晰边界后,FSP 重新生成不再是不可控操作,而是可重复、可审查、可回退的工程动作。

最后记住一句话:配置改在 FSP,行为改在用户代码。 嵌入式工程管理的门道很多,在云栈社区有大量开发者分享过类似的工程化实践与踩坑记录,遇到问题时不妨去找找思路。




上一篇:OP-TEE实战:瑞萨RZ/T2H平台OTP安全访问与PTA驱动开发
下一篇:8种容易被忽视的SQL写法,正在拖慢你的数据库性能
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-11-13 18:01 , Processed in 1.275221 second(s), 47 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表