王昱顺 发表于 2026-1-22 14:43:57

ISP/OTA升级介绍,A/B分区升级,不同版本芯片不同处理方法

STC, ISP/OTA升级介绍,A/B分区升级,不同版本芯片不同处理方法, 开源参考程序

ISP (In-System Programming) 即“在系统编程”,这是 STC 主导的技术
这是一种对芯片(如单片机、FPGA、存储器)进行程序烧录的技术。这里的“在系统”是指:芯片已经被焊接在电路板(PCB)上之后,不再需要将芯片取下来,而是直接通过预留的接口线(如 JTAG、SWD、UART、SPI 等)对芯片内部的程序进行写入或擦除。

OTA (Over-the-Air) 是一种通过无线通信网络(如 Wi-Fi、蓝牙、4G/5G、LoRa 等)接收数据包,对设备进行远程固件或软件更新的技术,但现在 大量的实际是有线升级的ISP也故意叫OTA。
通常来讲,OTA也会被用在系统升级的一种泛用定义上,因为蓝牙/WIFI等方式,最终还是通过接口线对单片机进行程序的重新烧写,所以在更新单片机程序这件事上来讲,ISP/OTA其实是差不多的定义


那么通常的更新程序手段有哪些呢?
一种是通过官方的下载程序升级的,使用USB转串口,或者USB直接下载,使用官方的ISP软件,走的是官方下载协议,内部程序中,是系统固化的BootLoader来处理,无需用户程序介入
还有一种就是通过蓝牙串口转发,配置好校验位和波特率后,也可以通过官方的ISP软件下载,这种其实也算OTA的一种定义场景


还有就是用户使用自己编写的BootLoader进行程序的更新,这个时候因为是完全由用户控制,所以自由度就比较大了,常见的数据接收手段都能用来更新程序。
例如Wifi/蓝牙/4G/红外线/SPI/IIC/CAN/LIN等众多方式,只要能完成数据通讯就可以进行程序的更新。


而A/B 分区(也称为 Dual-Bank 或双分区机制)是 OTA 升级中一种为了保证安全性和体验而采用的存储架构设计。
[*]核心原理将设备的存储空间(Flash)划分为两个独立的区域,通常称为 A 区(主分区)和 B 区(备份分区)。这两个区域大小相同,都可以存放完整的系统固件。

[*]状态一:设备当前正在运行 A 区的旧系统。
[*]状态二:B 区处于闲置状态。

[*]升级流程(1) 沉默下载:当 OTA 升级开始时,设备继续运行 A 区的系统,用户正常使用不受影响。同时,设备在后台悄悄将下载的新固件写入闲置的 B 区。(2) 校验与激活:写入完成后,系统会校验 B 区数据的完整性。校验通过后,系统修改启动标志位,告诉引导程序(Bootloader)下次从 B 区启动。(3) 重启切换:设备重启。引导程序读取标志位,直接加载 B 区的新系统。此时,原来的 A 区变成了闲置的备份分区,等待下一次升级使用

在普通单片机上的实现差异:由于很多单片机在写入 Flash 时无法同时读取指令(即无法运行程序),所以在单片机上实现 A/B 升级时,通常流程是:先将数据下载到缓存或外部存储,需要写入 Flash 时,程序统一跳转到用户编写的 Bootloader 中(此时 A/B 区都不运行),由 Bootloader 将新固件写入目标分区。如果更新中途出现断电或数据传输失败,Bootloader 会在下次启动时检测,自动跳转到未损坏的那个分区(如保留的 A 区),从而保证设备不“变砖”。


当然由于A/B分区所需空间是双倍大小,而程序有时候会不够用,所以还有一种更新方式是,只保留一个用户BootLoader和程序区。但是更新程序时,如果出现中断或者数据断开,则下次上电或者恢复后,程序仍然保留在BootLoader运行。保证程序区如果不完整则不会跳转过去执行,防止程序死机。
这种方式还可以搭配一个看门狗,发现程序死机的时候,可以通过看门狗纠正回到程序开始,然后进入BootLoader等待重新刷写(异常问题回到等待下载状态,不会继续卡死)




STC单片机如果想要实现普通的ISP/OTA更新,目前已经有了不少参考案例:
深圳国芯人工智能有限公司-在线升级
手机也能对AI8051U, ISP/OTA升级用户程序了,使用用户系统区实现 - ISP下载/做自己的ISP 国芯人工智能技术交流网站 - AI32位8051交流社区

但是如果想要实现A/B分区更新,目前需要分成两种单片机不同的处理方法
支持用户系统区的单片机:AI8051U,STC32G144K246及其后续支持划分用户系统区的单片机
不支持用户系统区的单片机:AI8/AI32系列,STC32G12K128及其不支持划分用户系统区,但是支持全部FLASH可划分为EEPROM操作的单片机

这里主要一个区别是,中断跳转不同和地址映射不同
在支持用户系统区的单片机中,由硬件处理中断跳转地址和程序地址偏移,直接将原有程序下载进去即可,无需更改工程设置,可以完全当成不改动程序,且可以用来切换两套程序的单片机
如果不支持这个特性,那么就需要手动设置BOOT和APP程序不同的程序,要手动处理中断跳转问题(因为中断跳转只能是固定地址,需要使用额外方法来判断究竟是谁要用),要设置中断地址偏移和程序地址偏移,相对来讲稍微麻烦一点


王昱顺 发表于 2026-1-24 18:00:33

这里针对传统 STC32 / STC8 系列,无用户系统区的单片机(但是可以操作全部FLASH空间)

提供一种BOOT和APP之间共享中断的方案,无需重定向中断向量表
且支持多个APP同时使用中断



BOOT程序:
#include <AI8H.H>

int cnt = 0;
int ota_cnt = 0;
void Timer0_Init(void);
unsigned int xdata ISR_Offset _at_ 0x400;         //偏移地址,定义在1k的xdata处
#define LDR_SIZE 0x1800

void All_Isr(void)
{
      cnt++;
}

void Timer0_Isr(void) interrupt 1
{
      ((void (code *)())(ISR_Offset))();
}

void main(void)
{
      P_SW2 |= 0x80;
      ISR_Offset = (unsigned int)All_Isr;//给定中断地址
      P4M0 = 0x00; P4M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
      Timer0_Init();
      EA = 1;
      while(1)
      {
                if(cnt>=200)
                {
                        cnt = 0;
                        P40 = ~P40;
                        P60 = ~P60;
                        ota_cnt++;
                        if(ota_cnt>10)
                        {
                              ota_cnt = 0;
                              EA = 0;//跳转之前记得给中断关了,不然乱蹦跶
                              ((void (code *)())(LDR_SIZE))();
                        }
                }
      }
}


void Timer0_Init(void)                //1毫秒@24.000MHz
{
      AUXR |= 0x80;                        //定时器时钟1T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0x40;                              //设置定时初始值
      TH0 = 0xA2;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
} app程序:
#include <AI8H.H>

int cnt = 0;

void Timer0_Init(void);
unsigned int xdata ISR_Offset _at_ 0x400;         //偏移地址,定义在1k的xdata处

void All_Isr(void)
{
      cnt++;
}

void main(void)
{
      P_SW2 |= 0x80;
      ISR_Offset = (unsigned int)All_Isr;//给定中断地址
      P4M0 = 0x00; P4M1 = 0x00;
P6M0 = 0x00; P6M1 = 0x00;
      Timer0_Init();
      EA = 1;
      while(1)
      {
                if(cnt>=50)
                {
                        cnt = 0;
                        P40 = ~P40;
                        P60 = ~P60;
                }
      }
}

void Timer0_Init(void)                //1毫秒@24.000MHz
{
      AUXR |= 0x80;                        //定时器时钟1T模式
      TMOD &= 0xF0;                        //设置定时器模式
      TL0 = 0x40;                              //设置定时初始值
      TH0 = 0xA2;                              //设置定时初始值
      TF0 = 0;                              //清除TF0标志
      TR0 = 1;                              //定时器0开始计时
      ET0 = 1;                              //使能定时器0中断
}程序主要使用xdata不断电情况下不变的特性,通过一个变量来存储不同的中断函数地址
然后在app内,不使用interrupt定义,而是使用普通函数写完成中断程序后,将此函数的地址赋值给这个共同约定好的变量,使其可以在BOOT的中断函数中被调用一次,以此达到可以不用重定向中断向量,多个APP可用中断函数的作用
APP程序的HEX文件处理方法可以参考:如何把自己的用户区ISP固件和自己的用户区AP固件进行合并供工厂量产使用 - ISP下载/做自己的ISP 国芯人工智能技术交流网站 - AI32位8051交流社区

jwd 发表于 2026-3-13 08:37:56

王工,请问能不能做一个视频讲解一下?
页: [1]
查看完整版本: ISP/OTA升级介绍,A/B分区升级,不同版本芯片不同处理方法