在工业控制、电机驱动乃至物联网边缘节点中,固件在线升级(OTA)已成为产品生命周期管理的标配。然而传统 OTA 往往伴随停机、风险与低效。瑞萨电子 MCU 中的 Dual‑Bank 闪存架构为工程师带来了几乎“零感知”的升级体验。本文以 RX26T 为例,拆解无感 OTA 的实现思路、代码框架与实测情况,帮助开发者在自家项目中快速落地。
一、MCU 传统 Flash 架构与 OTA 挑战
1.1 单 Bank Flash 架构
大部分传统 MCU 使用的是单一Bank Flash设计:
- 应用程序存放在一片连续的 Code Flash 区域内
- CPU 取指(Fetch)和 Flash 擦写/编程使用同一个物理通道
这就导致了一个先天矛盾:
- Flash 进入擦除/写入模式时,不能读取指令,这会 CPU 无法正常执行代码,更不要说响应中断请求
传统的解决方案:
- 把代码拷贝到 RAM 区域执行,包括擦写 Flash 的代码以及需要在 Flash 处于擦除和编程期间正常执行的其他代码,如下图所示:
1.2 OTA 中的具体问题
在 OTA 场景下,这种单 Bank 架构会带来一系列挑战:
这种架构下,即使想做 OTA,也必须设计复杂的 Bootloader 流程、校验机制,还要小心翼翼控制电源和升级时机,总体开发成本高、可靠性低。
二、瑞萨的无感 OTA 解决方案
2.1 为什么需要 Dual-Bank?
如果能让 MCU 有两个独立的程序区,一个边跑应用,一个边刷固件,中间互不干扰,升级自然就可以做到:
- 不中断现有业务
- 把新固件写好后,一键切换启动区
- 即使升级失败,也能自动回到老固件
这正是Dual-Bank 闪存架构带来的革命性变化。
2.2 Dual-Bank 方案简介
瑞萨电子在 RX 系列、RL78 系列、RA 系列等 MCU 中,均提供了支持Dual-Bank架构设计的型号,可适配工业、汽车、物联网等多种应用场景。
在本篇中,我们以 RX26T 为例进行详细说明:
- RX26T 内 512 KB Code Flash 被物理划分为 Bank 0/1,各 256 KB
- Bank 0 正常运行应用,Bank 1 在后台擦除/写入新固件
- 升级完成后,通过切换 Bank 方式修改启动设置,软复位切换到新固件
- 如升级中断或失败,可以灵活切换回退到原有 Bank 启动,保障设备持续运行
这一机制让 MCU 升级过程真正做到"无感知",极大提升了系统可维护性和用户体验。
三、RX26T 无感 OTA 实验环境搭建
3.1 硬件环境
- MCK-RX26T 开发套件
- USB 转 UART 工具,用于 PC 传输更新的固件
- 逻辑分析仪抓取关键测试波形
3.2 软件环境
- IDE:e2 studio2023-07(23.7.0)
- 编译器:CC-RX V3.06
- BSP 以及相关外设驱动包:
3.3 Flash 驱动配置
为了正常使用Dual-Bank功能,需要在 Flash 组件中使用以下配置,使用 BGO 模式,并且让自编程库运行在 Code Flash 中,无需拷贝到 RAM 中运行。
3.4 Flash 操作关键代码说明
Dual-Bank架构可以支持 Flash 的操作库在后台运行,可以定义一个回调函数注册,这样当 Flash 完成擦除和写入命令时,就直接进入后台操作,不会阻塞主循环和中断。
- 擦除时--->只需发起一次整体擦除,后台等待标志位完成
- 写入时--->每接收一块数据,发起一次写入,后台等待标志完成,循环直至所有数据写入完毕
主要代码示例如下:
A.Flash 回调函数
voidu_cb_function(void *event)
{
flash_int_cb_args_t *ready_event = event;
switch (ready_event->event)
{
case FLASH_INT_EVENT_ERASE_COMPLETE:
ERASE_COMPLETE_f = 1;
case FLASH_INT_EVENT_WRITE_COMPLETE:
WRITE_COMPLETE_f = 1;
case FLASH_INT_EVENT_TOGGLE_BANK:
break;
default:
break;
}
}
B.Flash 初始化
e_fwup_err_tmy_flash_open_function(void)
{
if (FLASH_SUCCESS != R_FLASH_Open())
{
return (FWUP_ERR_FLASH);
}
#if (FLASH_BGO_MODE == 1)
flash_interrupt_config_t cb_func_info;
cb_func_info.pcallback = u_cb_function;
cb_func_info.int_priority = 1;
if (FLASH_SUCCESS != R_FLASH_Control(FLASH_CMD_SET_BGO_CALLBACK,(void *)&cb_func_info))
{
return (FWUP_ERR_FLASH);
}
#endif/* (FLASH_BGO_MODE == 1) */
return (FWUP_SUCCESS);
}
}
C.Flash 擦除操作
staticvoidfwup_erase_step(void)
{
if (!g_erase_started)
{
ERASE_COMPLETE_f = 0;
R_FLASH_Erase(FLASH_CF_BLOCK_22, 22);
g_erase_started = true;
}
else
{
if (!g_erase_done)
{
if (ERASE_COMPLETE_f == 1)
{
ERASE_COMPLETE_f = 0;
g_erase_done = true;
g_write_addr = BANK_LO_ADDR;
g_write_offset = 0;
g_total_writes_completed = 0;
g_fwup_state = FWUP_STATE_WRITE;
}
}
}
}
D.Flash 写入操作
staticvoidfwup_write_step(void)
{
if (g_write_offset >= TOTAL_DATA_SIZE)
{
g_fwup_state = FWUP_STATE_DONE;
return;
}
if (!g_write_in_progress && FWUP_UART_RTS)
{
uint8_t *buf = malloc(g_write_chunk_size);
if (!buf) return;
uint32_t chunk = (s_flash_buf.cnt >= g_write_chunk_size) ? g_write_chunk_size : s_flash_buf.cnt;
memcpy(buf, s_flash_buf.buf, chunk);
if (chunk < g_write_chunk_size) memset(&buf[chunk], 0xFF, g_write_chunk_size - chunk);
WRITE_COMPLETE_f = 0;
R_FLASH_Write((uint32_t)buf, g_write_addr, g_write_chunk_size);
free(buf);
g_write_in_progress = true;
}
elseif (g_write_in_progress && WRITE_COMPLETE_f)
{
WRITE_COMPLETE_f = 0;
g_write_in_progress = false;
g_total_writes_completed++;
g_write_addr += g_write_chunk_size;
g_write_offset += g_write_chunk_size;
if (s_flash_buf.cnt > g_write_chunk_size)
{
s_flash_buf.cnt -= g_write_chunk_size;
memmove(s_flash_buf.buf, &s_flash_buf.buf[g_write_chunk_size], s_flash_buf.cnt);
}
else s_flash_buf.cnt = 0;
FWUP_UART_RTS = false;
}
}
3.5 无感 OTA 流程示意图
四、RX26T 无感 OTA 实验测试结果
4.1 逻辑分析仪测试波形
加入测试代码
- 在主循环 100us 定时翻转 IO 口
- 中断 10us 定时翻转 IO 口
- 在擦除函数进入前后加入翻转 IO 口
- 在写入函数进入前后加入翻转 IO 口
通过波形可以看到整体对中断基本无影响,主循环在写操作中间偶尔会有短时间的延时,实测 25us 左右,为 Flash 库进入 BGO 操作之前的准备时间。
4.2 电机驱动实测
为了进一步验证实际性能,把电机驱动也加入到代码中来,设计 2 个软件版本,其中:
- 版本 1.1.1 上电后,启动电机,并控制转速到 2000RPM,打印信息
- 版本 6.0.0 上电后,启动电机,并控制转速到 500RPM,打印信息
首先烧录版本 1.1.1,上电后可以看到电机正常运行,打印信息如下:
这时通过 PC 端的 Tere Term 发送版本 6.0.0 版本的 BIN 文件,并进入 OTA 流程,可以观察到电机保持运行,同时持续接收新的固件并写入到 Bank1 的对应地址,当写入完成后,切换 bank 并进行复位,复位过程电机会有几秒钟的时间停下来,当新的软件版本启动后,电机会重新启动加速并控制转速在 500RPM 运行
五、实验总结
RX26T 的 Dualbank+BGO 模式为固件升级提供了一个完美的解决方案。这种方式的优点是:
- 升级过程中不影响当前程序运行
- 防止升级失败导致系统”变砖”
- 升级过程安全可靠,支持断电保护
- 实现真正的”无感”升级体验
这种升级方案特别适合对实时性要求较高的工业控制和电机驱动应用,能够在不影响正常工作的情况下完成固件更新,是工业设备远程维护的理想选择。
六、注意事项
1.BGO 模式使用要点:
- 初始化时必须正确配置回调函数
- 每次操作前检查 Flash 状态
- 合理使用对齐的缓冲区
2.状态机设计原则:
- 状态转换逻辑要清晰
- 单个状态处理时间要短
- 使用标志位跟踪进度
3.调试建议:
- 使用 GPIO 观察关键时序
- 保留关键日志输出
4.拓展说明
- 切换 Bank 进行软件复位的时间非常短,在 1 秒以内即可运行到主函数
- 如果有极限的要求,即核心业务在 OTA 过程中要求不受任何影响,也有解决方案,就是在切换 Bank 之前,把核心业务处理函数以及相关的中断都拷贝到 RAM 中运行,这样就可以不受软件复位的影响,无缝切换到新的固件版本
参考文档和例程
- R01AN6850EJ0203-RX FamilyFirmware Update Module Using Firmware Integration Technology
- r01an7203ej0100-rx-rx26t-ota-dualbank-firmware-update
- R01AN3681EJ0120-RX FamilyFirmware Update Sample Program with Dual Bank Function,and Flash Module and SCI Module Firmware Integration Technology
- R01AN2184EJ0522-RX FamilyFlash Module Using Firmware Integration Technology
- R01AN6894EJ0100-RX FamilyDual Mode Usage Guide
- R01TU0346EJ0100--RL78/I1C(512KB)FOTA solution
END
来源:嵌入式专栏
推荐阅读
- 嵌入式开发之 D-Bus 通信机制
- 零内存泄漏!2KB高效实现事件驱动框架,推荐这款开源事件管理器——LwEVT
- 别再用传统方法解析串口协议啦!单片机高效开发妙招,自定义协议处理效率翻倍(附源码)
- 竟有可以从 AP 直接加载程序启动的 MCU!
欢迎大家点赞留言,更多 Arm 技术文章动态请关注极术社区嵌入式客栈专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。