杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概览
    • 快速开始与开发环境
  • 应用与运行时

    • 应用入口与主循环
    • 按键驱动与用户消息处理
    • 消息系统
  • 固件升级

    • 双备份升级机制与状态机
    • UART 升级传输
    • 升级校验、启动信息与复位流程
  • 芯片与硬件支持

    • AC63 系列芯片 BSP 结构
    • 外设接口与驱动
    • 低功耗、RTC 与时基唤醒
  • 构建与工具

    • 构建系统与工作区
    • 烧写与量产工具
  • 参考资源

    • 数据手册与原理图
    • 双备份升级文档

UART 升级传输

UART 升级传输是杰理 AC63 系列 MCU(AC632N/AC635N/AC636N/AC638N)在双备份(Dual Bank)固件升级架构下,通过 UART 串口通道完成固件升级包传输的完整机制。本文档基于 sdk/bsp/dual_bank_update_uart.c 及其头文件,详细说明协议帧格式、命令集、接收状态机、重传机制以及与应用主循环的集成方式。

Purpose and Scope

本文档覆盖以下内容:

  • UART 升级传输的协议层设计:帧同步头、长度字段、CRC16 校验的完整帧格式。
  • 命令集:设备端与升级上位机(Host)之间交互的全部命令定义。
  • 接收状态机:uart_loop_rx_handler 如何逐字节解析数据流并完成校验。
  • 升级流程:从启动初始化、波特率切换、数据块请求重传,到升级完成后的复位。
  • 文件操作抽象:update_op_api_t 接口(f_open/f_read/f_seek/f_stop)如何将 UART 通道伪装成升级引擎眼中的"文件"。
  • 应用集成:main.c 中 user_main 与 user_msg_handler 对升级模块的挂载方式。

以下内容属于其他目录页的范畴,本文档不展开:

  • 双备份 Flash 擦写、启动信息(boot_info)管理的核心实现,请参阅 "Dual Bank 升级引擎"(sdk/bsp/dual_bank_update.c、dual_bank_update_loop.h、dual_bank_updata_api.h)。
  • UART 底层驱动(寄存器配置、DMA、波特率设置),请参阅 BSP 的 uart.c / debug_uart.c。

Overview

在双备份升级架构中,设备侧固件(Boot 区)需要从某个"通道"读取升级包数据并写入非活动备份区。通道可以是 USB、SD 卡、BLE 或 UART。UART 升级传输模块即为其中的 UART 通道实现:它把一条串口链路封装为"只读文件"(f_read/f_seek),供升级引擎 active_update() 以统一的方式逐块拉取固件数据。

其核心设计思路是**"设备主动拉取 + CRC 校验重传"**:

  1. 上位机(Host,通常是 PC 工具或另一颗 MCU)先通过 UART 发送同步帧握手。
  2. 设备收到握手后切换波特率,并启动升级引擎。
  3. 升级引擎通过 uart_f_read() 发出 CMD_UPDATE_READ(携带地址 addr 和长度 len),Host 收到后回传对应数据块。
  4. 设备对每个数据块做 CRC16 校验,失败则重发请求,最多重试 4 次(RETRY_TIME)。
  5. 全部数据写入并校验完成后,升级引擎回调 uart_update_state_cbk,设备关闭 UART 并执行 cpu_reset() 重启进入新固件。

Architecture

flowchart TD
    subgraph sg_Host["上位机 (Host / 升级工具)"]
        Host["PC 工具或主控 MCU<br/>发送固件数据块"]
    end

    subgraph sg_App["应用层 (sdk/apps/main.c)"]
        UserMain["user_main()"]
        MsgHandler["user_msg_handler()"]
    end

    subgraph sg_UartCh["UART 升级传输通道 (dual_bank_update_uart.c)"]
        UartInit["dual_bank_update_init()<br/>uart_init + RX IRQ 注册"]
        RxHandler["uart_loop_rx_handler()<br/>帧同步 / CRC16 校验"]
        Dispatch["update_download_opt()<br/>命令分发"]
        CmdProc["uart_update_recv()<br/>CMD_UPDATE_START 波特率切换"]
        UartOp["uart_ch_update_op<br/>(f_open / f_read / f_seek / f_stop)"]
        StateCbk["uart_update_state_cbk()<br/>升级结束处理"]
    end

    subgraph sg_Engine["双备份升级引擎 (dual_bank_update.c)"]
        ActiveUpdate["active_update()<br/>状态机 START/DATA/VERIFY"]
        WriteFlash["写入非活动备份区 + 校验"]
    end

    Host -->|"同步帧 0xAA 0x55 + CRC16"| RxHandler
    UserMain -->|"DUAL_BANK_UPDATE_BY_UFW 使能时调用"| UartInit
    UartInit -->|"注册 RX IRQ"| RxHandler
    MsgHandler -->|"轮询调用"| Dispatch
    RxHandler -->|"整帧校验通过后置 RECV_DATA 标志"| Dispatch
    Dispatch -->|"CMD_UART_UPDATE_READY / START"| CmdProc
    CmdProc -->|"active_update(info)"| ActiveUpdate
    ActiveUpdate -->|"f_read / f_seek"| UartOp
    UartOp -->|"CMD_UPDATE_READ 请求 + 等待响应"| Host
    Host -->|"数据块 (带 CRC16)"| UartOp
    ActiveUpdate -->|"UPDATE_CH_EXIT 回调"| StateCbk
    StateCbk -->|"成功则 cpu_reset()"| UserMain
    ActiveUpdate --> WriteFlash

架构要点说明:

  • dual_bank_update_init() 是传输通道的入口,负责打开 UART 设备并将 uart_loop_rx_handler 注册为接收中断回调(dual_bank_update_uart.c#L283-L293)。
  • uart_ch_update_op 是 update_op_api_t 接口的具体实现(dual_bank_update_uart.c#L221-L227),升级引擎只依赖这组回调,不关心底层通道是 UART 还是其他介质——这是典型的策略模式解耦。
  • update_download_opt() 在应用主循环中轮询执行,只有当接收标志为 CMD_UART_UPDATE_FLAG_RECV_DATA 时才分发命令,因此握手帧的处理是"非阻塞、事件驱动"的。

协议帧格式

UART 升级协议采用自定义的定长数据帧 + CRC16 校验结构。帧定义见头文件中的 protocal_frame_t 联合体(dual_bank_update_uart.h#L13-L26):

#define PROTOCAL_SIZE       528
#define SYNC_SIZE           6
#define SYNC_MARK0          0xAA
#define SYNC_MARK1          0x55

typedef union {
    u8 raw_data[PROTOCAL_SIZE + SYNC_SIZE];
    struct {
        u8 mark0;
        u8 mark1;
        u16 length;
        u8 data[PROTOCAL_SIZE + 2]; // 最后CRC16
    } data;
} protocal_frame_t;

Source: dual_bank_update_uart.h

帧布局

偏移字段长度说明
0mark01 字节同步头 1,固定 0xAA
1mark11 字节同步头 2,固定 0x55
2length2 字节(小端)数据域长度(不含同步头,不含 CRC)
4data[]length 字节负载:1 字节命令 + 命令参数
4 + lengthCRC162 字节对 同步头 + length + data 整体计算的 CRC16

单帧最大负载为 PROTOCAL_SIZE(528 字节),因此整帧最大为 534 字节(PROTOCAL_SIZE + SYNC_SIZE)。发送端封帧逻辑如下(dual_bank_update_uart.c#L45-L62):

static bool uart_send_packet(u8 *buf, u16 length)
{
    bool ret = TRUE;
    u16 crc;
    u8 *buffer;

    buffer = (u8 *)&protocal_frame;
    protocal_frame.data.mark0 = SYNC_MARK0;
    protocal_frame.data.mark1 = SYNC_MARK1;
    protocal_frame.data.length = length;
    memcpy((char *)&buffer[4], buf, length);
    crc = CRC16(buffer, length + SYNC_SIZE - 2);
    memcpy(buffer + 4 + length, &crc, 2);
    log_debug("tx(%d)", length + SYNC_SIZE);
    log_debug_hexdump((u8 *)&protocal_frame, length + SYNC_SIZE);
    uart_tx_buf(dual_uart_config.id, (u8 *)&protocal_frame, length + SYNC_SIZE);
    return ret;
}

Source: dual_bank_update_uart.c

CRC 计算范围覆盖同步头与长度字段(length + SYNC_SIZE - 2 字节),即除 CRC 自身外的所有字节,发送时通过 uart_tx_buf 一次性发出,保证帧在链路层不被拆分。

命令集

设备端(发送方)命令

设备向上位机发出的命令定义在实现文件顶部(dual_bank_update_uart.c#L5-L10):

// 命令
#define CMD_UPDATE_START    0x01
#define CMD_UPDATE_READ     0x02
#define CMD_UPDATE_END      0x03
#define CMD_SEND_UPDATE_LEN 0x04
#define CMD_KEEP_ALIVE      0x05

Source: dual_bank_update_uart.c

命令值方向载荷用途
CMD_UPDATE_START0x01设备→Host4 字节波特率(u32)应答握手 / 通知 Host 已切换波特率
CMD_UPDATE_READ0x02设备→Hoststruct file_info(cmd+addr+len,10 字节)请求指定偏移、指定长度的固件数据块
CMD_UPDATE_END0x03设备→Host1 字节错误码升级中止,携带失败原因
CMD_SEND_UPDATE_LEN0x04设备→Host—请求 Host 上报固件总长度(预留)
CMD_KEEP_ALIVE0x05设备→Host—保活探测(预留)

上位机(接收方)命令

设备侧解析 Host 发来的命令,枚举定义于 dual_bank_update_uart.c#L29-L36:

enum {
    CMD_UART_UPDATE_START = 0x1,
    CMD_UART_UPDATE_READ,
    CMD_UART_UPDATE_END,
    CMD_UART_UPDATE_UPDATE_LEN,
    CMD_UART_JEEP_ALIVE,
    CMD_UART_UPDATE_READY,
};

Source: dual_bank_update_uart.c

命令值方向载荷用途
CMD_UART_UPDATE_START0x1Host→设备4 字节目标波特率发起升级并通知设备切换波特率
CMD_UART_UPDATE_READ0x2Host→设备—应答 CMD_UPDATE_READ 的数据帧(数据紧随命令字节)
CMD_UART_UPDATE_END0x3Host→设备—通知升级结束(预留)
CMD_UART_UPDATE_UPDATE_LEN0x4Host→设备—上报固件总长度(预留)
CMD_UART_JEEP_ALIVE0x5Host→设备—保活探测(预留)
CMD_UART_UPDATE_READY0x6Host→设备—握手就绪:设备收到后回 CMD_UPDATE_START

接收状态标志

dual_bank_update_flag 是连接中断处理与主循环的桥梁(dual_bank_update_uart.c#L38-L43):

enum {
    CMD_UART_UPDATE_FLAG_NONE,
    CMD_UART_UPDATE_FLAG_RECV_DATA,
    CMD_UART_UPDATE_FLAG_RETRY,
    CMD_UART_UPDATE_FLAG_INIT_ERR = 0xff,
};

Source: dual_bank_update_uart.c

  • CMD_UART_UPDATE_FLAG_NONE(0):空闲,无待处理帧。
  • CMD_UART_UPDATE_FLAG_RECV_DATA(1):已收到完整且 CRC 正确的帧,主循环可分发。
  • CMD_UART_UPDATE_FLAG_RETRY(2):收到的帧 CRC 错误,需重发请求。
  • CMD_UART_UPDATE_FLAG_INIT_ERR(0xff):UART 初始化失败,此后丢弃所有接收数据。

核心流程

接收状态机(字节级)

uart_loop_rx_handler 是 UART 接收中断回调,实现了一个逐字节推进的帧同步状态机(dual_bank_update_uart.c#L79-L118):

static void uart_loop_rx_handler(u8 *buf, u16 len)
{
    u16 crc, crc0, i, ch;
    for (i = 0; i < len; i++) {
        ch = buf[i];
__recheck:
        if (rx_cnt == 0) {
            if (ch == SYNC_MARK0)   { protocal_frame.raw_data[rx_cnt++] = ch; }
        } else if (rx_cnt == 1) {
            protocal_frame.raw_data[rx_cnt++] = ch;
            if (ch != SYNC_MARK1) { rx_cnt = 0; goto __recheck; }
        } else if (rx_cnt < 4) {
            protocal_frame.raw_data[rx_cnt++] = ch;
        } else {
            protocal_frame.raw_data[rx_cnt++] = ch;
            if (rx_cnt == (protocal_frame.data.length + SYNC_SIZE)) {
                log_debug("rx(%d)", rx_cnt);
                log_debug_hexdump((u8 *)&protocal_frame, rx_cnt);
                rx_cnt = 0;
                crc = CRC16(protocal_frame.raw_data, protocal_frame.data.length + SYNC_SIZE - 2);
                memcpy(&crc0, &protocal_frame.raw_data[protocal_frame.data.length + SYNC_SIZE - 2], 2);
                if (CMD_UART_UPDATE_FLAG_INIT_ERR != dual_bank_update_flag) {
                    if (crc0 == crc) {
                        dual_bank_update_flag = CMD_UART_UPDATE_FLAG_RECV_DATA;
                    } else {
                        dual_bank_update_flag = CMD_UART_UPDATE_FLAG_RETRY;
                    }
                }
                if (uart_update_resume_hdl && (CMD_UART_UPDATE_READ == protocal_frame.data.data[0])) {
                    uart_update_resume_hdl(NULL);
                }
            }
        }
    }
}

Source: dual_bank_update_uart.c

状态机四个阶段:

  1. rx_cnt == 0(等同步头 1):只有遇到 0xAA 才进入下一阶段,其余字节全部丢弃——实现帧头捕获。
  2. rx_cnt == 1(校验同步头 2):若第二个字节不是 0x55,回退到阶段 1 并对当前字节重新检查(goto __recheck),可处理 0xAA 0xAA 0x55 这类连续同步头场景。
  3. rx_cnt < 4(收长度):累计 length 字段(2 字节小端),据此推算整帧应接收的字节数。
  4. rx_cnt >= 4(收数据 + CRC):持续累积直至 rx_cnt == length + SYNC_SIZE,随后计算 CRC16 并与帧尾携带值比对。校验通过置 RECV_DATA,失败置 RETRY;若初始化失败(INIT_ERR)则直接丢弃。

值得注意的是,当收到的命令是 CMD_UART_UPDATE_READ(即 Host 对设备数据请求的应答)时,会立即调用 uart_update_resume_hdl(NULL) 唤醒阻塞在 uart_dev_receive_data 中的升级引擎——这是中断上下文与升级任务之间的信号量式唤醒。

升级全流程时序

sequenceDiagram
    participant H as 上位机 (Host)
    participant U as UART 模块 (dual_bank_update_uart.c)
    participant E as 升级引擎 (active_update)
    participant F as Flash 写接口

    Note over H,U: 阶段一:握手与波特率切换
    H->>U: CMD_UART_UPDATE_READY (0xAA 0x55 ...)
    U->>U: CRC 校验通过,置 RECV_DATA
    U->>H: CMD_UPDATE_START (波特率=9600)
    H->>U: CMD_UART_UPDATE_START + 新波特率(如 115200)
    U->>U: uart_update_set_baud() 切换波特率
    U->>E: active_update(info) 启动升级引擎

    Note over U,E: 阶段二:数据拉取与写入
    loop 每个数据块
        E->>U: f_read() / uart_dev_receive_data()
        U->>H: CMD_UPDATE_READ (addr + len)
        H-->>U: 数据帧 (带 CRC16)
        U->>U: CRC 校验
        alt CRC 正确
            U->>E: 返回数据长度,offset 递增
            E->>F: 写入非活动备份区
        else CRC 错误
            U->>H: 重发 CMD_UPDATE_READ (最多 4 次)
        end
    end

    Note over U,E: 阶段三:收尾
    E->>F: 整包校验 (VERIFY)
    E->>U: UPDATE_CH_EXIT 状态回调
    U->>U: uart_close() 关闭 UART
    alt 升级成功
        U->>U: cpu_reset() 重启进入新固件
    else 失败
        U->>H: CMD_UPDATE_END + 错误码
    end

数据块请求与重传

uart_dev_receive_data 实现"请求—应答—校验—重试"的核心循环(dual_bank_update_uart.c#L152-L189):

static u32 uart_dev_receive_data(void *buf, u32 relen, u32 addr)
{
    u8 i;
    struct file_info file_cmd;
    for (i = 0; i < RETRY_TIME; i++) {
        if (i > 0) { putchar('r'); }
        file_cmd.cmd = CMD_UPDATE_READ;
        file_cmd.addr = addr;
        file_cmd.len = relen;
        uart_send_packet((u8 *)&file_cmd, sizeof(file_cmd));
        if (uart_update_sleep_hdl) {
            if (uart_update_sleep_hdl(NULL)) {
                log_error("uart_time_out\n");
                return -1;
            }
            if (CMD_UART_UPDATE_FLAG_RETRY == dual_bank_update_flag) {
                // crc错误,重发
                dual_bank_update_flag = CMD_UART_UPDATE_FLAG_NONE;
                continue;
            }
        }
        memcpy(&file_cmd, protocal_frame.data.data, sizeof(file_cmd));
        if ((file_cmd.cmd != CMD_UPDATE_READ) || (file_cmd.addr != addr) || (file_cmd.len != relen)) {
            continue;
        }
        memcpy(buf, &protocal_frame.data.data[sizeof(file_cmd)], protocal_frame.data.length - sizeof(file_cmd));
        return (protocal_frame.data.length - sizeof(file_cmd));
    }

    if (i == RETRY_TIME) {
        log_error("receive data is err\n");
        relen = -1;
    }
    putchar('R');
    return relen;
}

Source: dual_bank_update_uart.c

设计要点:

  • 应答校验三重比对:除 CRC 外,还校验应答帧中的 cmd、addr、len 是否与请求一致,防止错位数据块被误写入 Flash。
  • 超时机制:uart_update_sleep_hdl 是外部注入的等待函数(内部基于 time_out_msec),返回非零表示超时,立即返回 -1 让升级引擎中止。
  • CRC 错误重发:RETRY 标志被清除后 continue 重新发起请求;重试 4 次仍失败则打印错误并以 -1 返回。
  • 串口调试时可用 putchar('r') / putchar('R') 观察重传行为(小写 r = 单次重试,大写 R = 放弃)。

文件操作抽象

升级引擎通过 update_op_api_t 与通道解耦,UART 通道注册的回调表如下(dual_bank_update_uart.c#L221-L227):

static const update_op_api_t uart_ch_update_op = {
    .ch_init = uart_update_hdl_register,
    .f_open  = uart_f_open,
    .f_read  = uart_f_read,
    .f_seek  = uart_f_seek,
    .f_stop  = uart_f_stop,
};

Source: dual_bank_update_uart.c

回调实现语义
ch_inituart_update_hdl_register注入 resume/sleep 钩子,供数据请求阻塞与唤醒
f_openuart_f_open恒返回 1(UART 无需打开语义,通道已由 dual_bank_update_init 初始化)
f_readuart_f_read按当前 uart_file_offset 请求一块数据,成功则推进偏移
f_seekuart_f_seek支持 SEEK_SET / SEEK_CUR,用于随机访问固件数据
f_stopuart_f_stop发送 CMD_UPDATE_END 并携带错误码,复位波特率为 9600

f_read / f_seek 的实现(dual_bank_update_uart.c#L191-L212):

static u16 uart_f_read(void *handle, void *buf, u32 relen)
{
    u32 len;
    log_info("%s\n", __func__);
    len = uart_dev_receive_data(buf, relen, uart_file_offset);
    if ((u32) - 1 == len) {
        log_error("%s err\n", __func__);
        return -1;
    }
    uart_file_offset += len;
    return 0;
}

static int uart_f_seek(void *fp, u8 type, u32 offset)
{
    if (type == SEEK_SET) {
        uart_file_offset = offset;
    } else if (type == SEEK_CUR) {
        uart_file_offset += offset;
    }
    return 0;
}

Source: dual_bank_update_uart.c

uart_file_offset 是 volatile u32 全局变量,记录当前"文件"读取位置;SEEK_END 分支未实现(UART 通道无法预知固件总长),固件长度信息由升级引擎通过其他途径获取。

应用集成

UART 升级传输模块由应用主程序挂载,开关为头文件中的宏 DUAL_BANK_UPDATE_BY_UFW(dual_bank_update_uart.h#L6):

#define DUAL_BANK_UPDATE_BY_UFW    0//双备份模块宏

Source: dual_bank_update_uart.h

默认关闭(0);需要启用时由构建系统或上层配置覆盖为 1。启用后,main.c 中的两处集成生效(main.c#L8-L12、main.c#L51-L53):

static void user_msg_handler()
{
#if (DUAL_BANK_UPDATE_BY_UFW)
    update_download_opt();
#endif
    ...
}

int user_main()
{
    ...
#if (DUAL_BANK_UPDATE_BY_UFW)
    dual_bank_update_init();
#endif

    while (1) {
        wdt_clear();
        user_msg_handler();
        __asm__ volatile("idle");
    }
}

Source: main.c

集成方式的两个关键设计:

  1. 启动时初始化:user_main() 在系统启动早期调用 dual_bank_update_init(),打开 UART 并注册接收中断,此后上位机随时可发起升级握手。
  2. 主循环轮询分发:update_download_opt() 挂在 user_msg_handler() 中,与按键等消息处理共用同一主循环。由于接收中断只负责"收帧 + 置标志",命令解析与升级启动都在主循环上下文完成,避免了在中断里调用重量级流程(如 active_update())。

配置选项

选项类型默认值说明
DUAL_BANK_UPDATE_BY_UFW宏 (int)0双备份模块总开关;置 1 启用 UART 升级传输
RETRY_TIME宏 (int)4单个数据块请求的最大重试次数
PROTOCAL_SIZE宏 (int)528单帧数据域最大长度(字节)
SYNC_SIZE宏 (int)6帧头开销:2 同步头 + 2 长度 + 2 CRC
SYNC_MARK0 / SYNC_MARK1宏 (u8)0xAA / 0x55帧同步头字节
update_baudratestatic u329600当前升级波特率;CMD_UPDATE_START 握手时上报
dual_uart_configconst struct uart_platform_dataBSP 板级配置UART 通道的端口号、默认波特率、引脚等(extern 于头文件,实现在板级文件)
MSG_UART_UPDATE_READY/START/START_RSP/READ_RSP宏 (u8)0x1–0x4头文件中预留的消息号定义(当前版本未在传输通道内使用)

波特率切换通过 uart_update_set_baud(int id, u32 baudrate) 完成,切换后设备回发 CMD_UPDATE_START 确认(dual_bank_update_uart.c#L231-L256):

static void uart_update_recv(u8 cmd, u8 *buf, u32 len)
{
    u32 baudrate = 9600;
    switch (cmd) {
    case CMD_UPDATE_START:
        memcpy(&baudrate, buf, 4);
        log_info("CMD_UPDATE_START:%d\n", baudrate);
        if (update_baudrate != baudrate) {
            update_baudrate = baudrate;
            uart_update_set_baud(dual_uart_config.id, baudrate);
            time_out_msec(500, NULL);
            uart_update_cmd(CMD_UPDATE_START, (u8 *)&update_baudrate, 4);
        } else {
            update_mode_info_t info = {
                .type = UART_UPDATA,
                .state_cbk = uart_update_state_cbk,
                .p_op_api = &uart_ch_update_op,
                .en = 1,
            };
            if (active_update(&info)) {
                dual_bank_update_init();
            }
        }
        break;
    }
}

Source: dual_bank_update_uart.c

波特率协商逻辑:若 Host 上报的波特率与当前不同,设备先切换波特率、延时 500ms 让链路稳定、再回发 CMD_UPDATE_START(携带新波特率)确认;若波特率一致(或已完成切换后的第二次握手),则直接以 UART_UPDATA 类型、uart_ch_update_op 回调集和 uart_update_state_cbk 状态回调构造 update_mode_info_t 并调用 active_update() 启动升级引擎。若 active_update() 失败,会重新 dual_bank_update_init() 以便再次接收握手。

API 参考

void dual_bank_update_init(void)

初始化升级 UART 通道:打开 dual_uart_config 配置的串口,并将 uart_loop_rx_handler 注册为 RX 中断回调;初始化失败时置 CMD_UART_UPDATE_FLAG_INIT_ERR(dual_bank_update_uart.c#L283-L293)。

调用时机:user_main() 启动阶段;active_update() 失败后的重新初始化。

void update_download_opt(void)

主循环轮询入口:当 dual_bank_update_flag == CMD_UART_UPDATE_FLAG_RECV_DATA 时按帧首字节分发命令,处理完毕清除标志(dual_bank_update_uart.c#L258-L281)。未知命令打印 unknow cmd...。

分发行为:

  • CMD_UART_UPDATE_START → uart_update_recv() 处理波特率协商与升级启动。
  • CMD_UART_UPDATE_READY → 回发 CMD_UPDATE_START(空载荷),进入握手。
  • CMD_UART_UPDATE_READ / END / UPDATE_LEN / JEEP_ALIVE → 当前版本为空分支(数据块应答由 RX 中断内的 resume_hdl 直接唤醒处理)。

static void uart_update_state_cbk(int type, u32 state, void *priv)

升级引擎状态回调(dual_bank_update_uart.c#L120-L139):

  • 处理 UPDATE_CH_EXIT:清零 uart_file_offset、恢复默认波特率、uart_close() 关闭串口;若 ret_code->stu == 0 && ret_code->err_code == 0(升级成功)则 cpu_reset() 重启。
  • priv 指向 update_ret_code_t,包含 stu(状态)与 err_code(错误码)。

static void uart_loop_rx_handler(u8 *buf, u16 len)

UART 接收中断回调(帧解析状态机,详见"核心流程"章节)。在中断上下文执行,因此只做字节累积、CRC 校验和标志置位,不做协议处理。

static void uart_update_hdl_register(int (*resume_hdl)(void *priv), int (*sleep_hdl)(void *priv))

注入两个钩子(ch_init 回调):

  • resume_hdl:收到 CMD_UART_UPDATE_READ 应答时被 RX 中断调用,唤醒阻塞中的数据请求。
  • sleep_hdl:数据请求等待应答时调用,内部提供超时控制;返回非零表示超时。

全局状态变量

变量类型作用
protocal_frameprotocal_frame_t(4 字节对齐)收发共用的帧缓冲区(单缓冲设计,收/发串行使用)
uart_file_offsetvolatile u32固件"虚拟文件"当前读取偏移
dual_bank_update_flagvolatile u8中断与主循环之间的帧状态标志
rx_cntvolatile u16接收状态机的字节计数器
update_baudrateu32当前协商的升级波特率,默认 9600

故障模式、边界情况与并发

帧错误与重传

  • CRC 校验失败:RX 中断置 CMD_UART_UPDATE_FLAG_RETRY,uart_dev_receive_data 在下一次循环中清除该标志并重新发送 CMD_UPDATE_READ。连续失败达到 RETRY_TIME(4)次后返回 -1,升级引擎中止,uart_f_stop 发送 CMD_UPDATE_END 上报错误码。
  • 应答错位:即使 CRC 正确,若应答帧的 cmd/addr/len 与请求不一致(例如 Host 乱序回包),同样视为失败并重试,防止数据块错位写入 Flash。
  • 超时:uart_update_sleep_hdl 返回非零即判定超时,打印 uart_time_out 并返回 -1。超时上限由注入的 sleep 钩子内部决定(模块内使用 time_out_msec(500, NULL) 作为波特率切换后的链路稳定延时)。

初始化失败

dual_bank_update_init() 中 uart_init() 返回的端口号与 dual_uart_config.id 不一致时,置 CMD_UART_UPDATE_FLAG_INIT_ERR = 0xff。此后 RX 中断不再更新 RECV_DATA/RETRY 标志,即整条升级链路静默关闭——这是防止在硬件异常时反复进入升级流程的保护措施。

单缓冲并发约束

protocal_frame 是收发共用的单帧缓冲区。发送(uart_send_packet)与接收(RX 中断累积)不同时发生:数据请求流程中设备先发 CMD_UPDATE_READ 再阻塞等待应答,此时不会发起新的发送;握手帧处理也在主循环串行执行。因此不存在收发竞争,但不支持全双工并发收发——新增发送逻辑时必须保证与 RX 中断互斥。

中断上下文约束

uart_loop_rx_handler 在中断上下文运行,直接访问 protocal_frame、dual_bank_update_flag、rx_cnt 等共享状态;主循环通过 volatile 标志轮询消费。不得在中断内调用 active_update()、uart_close() 等耗时操作——当前实现将协议处理全部放在主循环(update_download_opt),这是正确性的关键。

波特率切换窗口

CMD_UPDATE_START 处理中切换波特率后延时 500ms 再回发确认,且切换后 time_out_msec 是忙等实现——该窗口内设备不响应其他命令。Host 侧必须等待收到 CMD_UPDATE_START 确认后再用新波特率发送数据。

升级失败后的恢复

  • active_update() 返回失败:重新 dual_bank_update_init(),链路复位,可再次握手重试。
  • 升级完成但校验失败:uart_update_state_cbk 中 ret_code 非零则不复位,调用 uart_f_stop 发送错误码,设备留在旧固件(双备份架构的核心价值:失败不损坏当前运行固件)。

性能与运维注意事项

  • 单帧大小:PROTOCAL_SIZE = 528 字节是数据吞吐的关键参数。扣除命令头(struct file_info 10 字节),单帧净荷约 518 字节;若需提升升级速度,可在上位机与设备同步修改该宏(注意帧缓冲区与 CRC 计算范围需一致)。
  • 默认波特率 9600:这是握手阶段的低速链路,CMD_UPDATE_START 协商后切换至高速(如 115200 以上)进行数据传输。握手阶段只交换命令帧,数据阶段才走大块帧,兼顾可靠性与速度。
  • 重传代价:每次 CRC 失败都重新发送完整请求帧并重新等待,重试 4 次的最坏耗时约为正常传输的 4 倍。上位机应优先保证传输质量(流控、降噪)以减少重传。
  • 调试观测:串口日志 log_debug("tx(%d)") / log_debug("rx(%d)") 及 hexdump 可观察帧收发;putchar('r')(小写,重试中)与 putchar('R')(大写,放弃)是轻量级重传指示,便于在无日志系统时用示波器/串口助手快速判断链路质量。

扩展点

  1. 更换传输介质:实现另一组 update_op_api_t 回调(类似 uart_ch_update_op)即可复用 active_update() 升级引擎,例如 USB 或 SPI 通道。
  2. 自定义握手流程:update_download_opt() 的分支中 CMD_UART_UPDATE_READ、CMD_UART_UPDATE_UPDATE_LEN、CMD_UART_JEEP_ALIVE 目前是空分支,可在此补充固件总长度校验(CMD_SEND_UPDATE_LEN 对端)、保活超时踢出等增强逻辑。
  3. 注入超时/唤醒钩子:uart_update_hdl_register 的 resume/sleep 钩子由外部传入,可在不修改协议的前提下替换超时策略(如改为带看门狗喂狗的可抢占等待)。
  4. 波特率策略:update_baudrate 与 uart_update_set_baud 构成可配置的协商点,可扩展多级速率握手(如 9600 → 460800 分级确认)。

相关链接

  • dual_bank_update_uart.c(UART 升级传输实现)
  • dual_bank_update_uart.h(协议定义与接口)
  • main.c(应用集成点)
  • dual_bank_update.c(双备份升级引擎,升级状态机与 Flash 写入)
  • UART 底层驱动:sdk/bsp/AC632N/src/uart.c 等各芯片型号的 uart.c / debug_uart.c
  • 升级模式相关结构(update_op_api_t、update_mode_info_t、update_ret_code_t)定义于 dual_bank_updata_api.h,请参阅 "Dual Bank 升级引擎" 目录页
Prev
双备份升级机制与状态机
Next
升级校验、启动信息与复位流程