当前位置:首页 > 工业控制 > 工业控制
[导读]工业网关、现场控制器最怕“改个算法也要整机重启”:重启会丢通信会话、重新对时、重新建链。真正可不停机替换的,不是整包固件,而是把易变业务函数通过跳转表调用,新版本经校验后载入RAM或可写Flash,原子切换函数指针即可。Cortex-M还可借助FPB做指令级重定向,进一步做到不改动原镜像。


工业网关、现场控制器最怕“改个算法也要整机重启”:重启会丢通信会话、重新对时、重新建链。真正可不停机替换的,不是整包固件,而是把易变业务函数通过跳转表调用,新版本经校验后载入RAM或可写Flash,原子切换函数指针即可。Cortex-M还可借助FPB做指令级重定向,进一步做到不改动原镜像。

一、用跳转表隔离“可热更函数”

所有允许运行时替换的函数不直接调用实体,而是走函数指针表:

typedef int (*proc_fn)(const uint8_t *in, uint16_t len, uint8_t *out);


typedef struct {

   proc_fn parse;      // 协议解析

   proc_fn filter;     // 数据滤波

   proc_fn transform;  // 业务变换

} app_api_t;


static int parse_v1(const uint8_t *in, uint16_t len, uint8_t *out){ /* 旧逻辑 */ return 0; }

static int filter_v1(const uint8_t *in, uint16_t len, uint8_t *out){ /* 旧逻辑 */ return 0; }


static app_api_t g_api = { parse_v1, filter_v1, 0 };


int app_process(uint8_t id, const uint8_t *in, uint16_t len, uint8_t *out){

   proc_fn fn = 0;

   if(id==0) fn = g_api.parse;

   else if(id==1) fn = g_api.filter;

   if(!fn) return -1;

   return fn(in, len, out);

}

新增算法只提供parse_v2/filter_v2,不改变调用方,也不改中断和其他驱动。

二、补丁放入可写可执行区

无FPU小MCU可把热更函数放SRAM特定段;资源更宽裕可放SDRAM,通过链接脚本预留热补丁区,启动或从Flash拷贝,运行时也可接收新二进制覆盖。 GNU用section属性:

__attribute__((section(".hotpatch"))) int parse_v2(const uint8_t *in,uint16_t len,uint8_t *out){

   /* 新解析:兼容旧帧头、新扩展字段 */

   return 0;

}

链接脚本预留:

.hotpatch : {

   . = ALIGN(4);

   KEEP(*(.hotpatch))

} > RAM

若新代码通过串口/CAN/OTA下发,先收进临时缓冲,再做校验,最后重定位到hotpatch区。Cortex-M3/M4可用FPB把原地址匹配到补丁入口,适合ROM/Flash原函不便整体搬移的场景。

三、校验+原子切换+回滚

热更最忌“写到一半切指针”。流程:收包→验CRC/签名→写备用区→读回比对→关中断切指针→业务空闲再释放旧版。

extern uint8_t hotbuf[512];   // 接收新函数二进制

extern app_api_t g_api;


int apply_patch(proc_fn new_parse, proc_fn new_filter, uint32_t crc_local){

   if(crc32(hotbuf, sizeof(hotbuf)) != crc_local) return -1;   // 校验失败

   __disable_irq();

   app_api_t old = g_api;            // 备份旧指针,便于回滚

   if(new_parse) g_api.parse = new_parse;

   if(new_filter) g_api.filter = new_filter;

   __ISB(); __enable_irq();

   if(app_self_test() != 0){         // 切换后冒烟测试

       __disable_irq();

       g_api = old;                  // 回滚

       __ISB(); __enable_irq();

       return -2;

   }

   return 0;

}

关中断只包“换指针”两行,重活(收包、CRC、自测)都在外面;多任务系统可改用互斥量和双缓冲,避免长关中断。

四、不同层级怎么选

只换算法/协议分支:跳转表+RAM函数,最轻,亚毫秒切换;

ROM固化、不能改映像:用Cortex-M FPB/Breakpoint做地址重定向到补丁区;

需要换大量外设初始化、中断向量、全局结构体:别热更单函数,用A/B双区整包OTA,新固件写备用区、校验签名、Bootloader切换,失败自动回滚;

写Flash自身时(自编程)必须把擦写例程放RAM,IAR可用__ramfunc,GCC用section+启动拷贝,避免Flash不可读同时写。

五、工程边界

热更函数要做到位置无关或重定位完整,禁止引用旧版static布局;新函数API签名、结构体版本必须向前兼容,改结构体成员就走整包升级。中断服务里调用的热函数要特别谨慎,优先只热更主循环业务,ISR维持稳定版本。所有补丁必须带版本号、CRC/签名、长度上限,接收区静态分配,防止越界覆盖其他热函数。按“跳转表调用—RAM/Flash补丁区—校验后原子换指针—自测失败回滚”这套做,现场改滤波参数、增协议字段、修业务Bug都可不停机完成。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭