瑞萨电子MCU无感OTA升级功能介绍

描述

引言

在工业控制、电机驱动乃至物联网边缘节点中,固件在线升级(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处于擦除和编程期间正常执行的其他代码,如下图所示:

FlaSh

1.2.OTA中的具体问题

在OTA场景下,这种单Bank架构会带来一系列挑战:

问题

影响

需要停机升级

业务中断(比如停止电机运行),系统不可用或者性能受限(大部分中断需要间歇关闭)

升级过程遇到意外断电风险

一旦擦写过程中断电,MCU固件受损,有可能会变砖

缺乏回退机制

新固件有BUG,

无法快速恢复旧版本,

只能重刷

这种架构下,即使想做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升级过程真正做到"无感知",极大提升了系统可维护性和用户体验。

FlaSh

三、RX26T无感OTA实验环境搭建

3.1.硬件环境

MCK-RX26T开发套件

USB转UART工具,用于PC传输更新的固件

逻辑分析仪抓取关键测试波形

FlaSh

3.2.软件环境

IDE:E2Studio2023-07(23.7.0)

编译器:CC-RX V3.06

BSP以及相关外设驱动包:

FlaSh

3.3.Flash驱动配置

为了正常使用Dual-Bank功能,需要在Flash组件中使用以下配置,使用BGO模式,并且让自编程库运行在Code Flash中,无需拷贝到RAM中运行。

FlaSh

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流程示意图

FlaSh

四、RX26T无感OTA实验测试结果

4.1.逻辑分析仪测试波形

加入测试代码

在主循环100us定时翻转IO口

中断10us定时翻转IO口

在擦除函数进入前后加入翻转IO口

在写入函数进入前后加入翻转IO口

通过波形可以看到整体对中断基本无影响,主循环在写操作中间偶尔会有短时间的延时,实测25us左右,为Flash库进入BGO操作之前的准备时间。

FlaSh

FlaSh

4.2.电机驱动实测

为了进一步验证实际性能,把电机驱动也加入到代码中来,设计2个软件版本,其中:

版本1.1.1上电后,启动电机,并控制转速到2000RPM,打印信息

版本6.0.0上电后,启动电机,并控制转速到500RPM,打印信息

首先烧录版本1.1.1,上电后可以看到电机正常运行,打印信息如下:

FlaSh

这时通过PC端的Tere Term发送版本6.0.0版本的BIN文件,并进入OTA流程,可以观察到电机保持运行,同时持续接收新的固件并写入到Bank1的对应地址,当写入完成后,切换bank并进行复位,复位过程电机会有几秒钟的时间停下来,当新的软件版本启动后,电机会重新启动加速并控制转速在500RPM运行

FlaSh

五、实验总结

RX26T的Dualbank+BGO模式为固件升级提供了一个完美的解决方案。这种方式的优点是:

升级过程中不影响当前程序运行

防止升级失败导致系统”变砖”

升级过程安全可靠,支持断电保护

实现真正的”无感”升级体验

这种升级方案特别适合对实时性要求较高的工业控制和电机驱动应用,能够在不影响正常工作的情况下完成固件更新,是工业设备远程维护的理想选择。

六、注意事项

1.BGO模式使用要点:

初始化时必须正确配置回调函数

每次操作前检查Flash状态

合理使用对齐的缓冲区

2.状态机设计原则:

状态转换逻辑要清晰

单个状态处理时间要短

使用标志位跟踪进度

3.调试建议:

使用GPIO观察关键时序

保留关键日志输出

4.拓展说明

切换Bank进行软件复位的时间非常短,在1秒以内即可运行到主函数

如果有极限的要求,即核心业务在OTA过程中要求不受任何影响,也有解决方案,就是在切换Bank之前,把核心业务处理函数以及相关的中断都拷贝到RAM中运行,这样就可以不受软件复位的影响,无缝切换到新的固件版本

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分