CH32下OTA升级实现

# 前言, 仅记录,初步简单实现串口方式下的json固件包的解析与数据写入,跳转

# 实际上还未做到OTA, 只是预留接口(远程调试还未走通,上位机串口方式走通了)

项目源码:

https://gitee.com/feiniao33/ch32_-ota

一、Flash分区与执行

工程生成.bin文件前提下,观察.bin文件大小即可简单估算空间,合理分配即可。

分区:

app程序起始地址为iap程序跳转地址。注意,MRS下.ld文件:

提供的闪存与ram分配地址起址是逻辑地址,实际执行的地址不是如此的。对于IAP应用,Flash写起始地址一定要注意是物理地址。具体怎么映射查资料即可,不多赘述。

执行:

APP程序跳转到IAP程序,一份实际工程里用到的代码,作为示例:

void OTA::jump_to_bootloader(uint32_t bootloader_addr) {
    // 1. 关闭所有外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, DISABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, DISABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, DISABLE);

    // 2. 禁用所有中断
    __disable_irq();
    SysTick->CTLR = 0;  // 关闭SysTick

    // 3. 清除挂起的中断(使用正确的PFIC寄存器)
    NVIC->IPRR[0] = 0xFFFFFFFF;  // 清除所有中断挂起状态

    // 4. RISC-V架构下的跳转实现
    __asm volatile (
        "mv a0, %[addr]\n"  // 将变量addr的值移动到寄存器a0
        "jr a0"             // 跳转到a0寄存器中的地址
        :                   // 输出操作数列表(此处为空)
        : [addr] "r" (bootloader_addr)  // 输入操作数列表
        : "a0"              // 被破坏的寄存器列表
    );

    // 5. 确保跳转完成
    while(1);
}

二、协议帧简单构建

一)协议:

可触发更新,可通过按下按键 和 发送指令实现。

按下按键,设备会跳到bootloader程序。这个阶段有两个按键可选择,一个取消,一个确认。若取消,则回到原APP.

这里只记录按键触发情形、指令触发也是类似,就不赘述。

设备请求升级:

若确认升级,设备会从e2prom读取信息,然后会发出一帧数据出去:

{

  "type": "request",

  "device_id": "CH32_001",

  "current_fw": "1.0.0",

  "trigger": "button"

} 
服务器起始帧:

然后服务器会返回一个起始帧并等待设备确认:

{

  "type": "start",

  "device_id": "CH32_001"     // 目标设备ID

  "version": "1.0.1",

  "file_size": 32768,       // 固件总大小

  "block_size": 256,       // 每帧数据块大小

}
设备应答帧:

设备会确认device_id是否正确,并记录更新信息(如总共多少帧数据),返回应答帧:

这里1就表示下一帧应该传输数据帧的第一帧。

服务器数据帧:
{

  "type": "ack",

"device_id": "CH32_001",

  "status": "ok",

  "next_seq": 1

}

接着服务器开始发出数据帧:

{

  "type": "data",

"device_id": "CH32_001",

  "seq": 1,                 // 帧序号(从1开始)

  "offset": 0,           // 当前帧在固件中的偏移量

  "data": Base64 编码​的256字节的bin文件块,  // 固件数据

  "check_code": ""0x2E"         // 本帧数据的校验值

}

设备会计算校验和(只用了简单的加法校验),解析并写入固件数据。处理完这一帧后返回应答帧:

{

  "type": "ack",

"device_id": "CH32_001",

  "seq":1,                 // 确认的帧序号

  "status": "ok",           // 或 "error"(需重传)

  "next_seq": 2             // 期望下一帧的序号

}
服务器结束帧:

这样,直到最后一帧传输结束, 服务器发送:

{

  "type": "end",

"device_id": "CH32_001",

  "received_frames": x,    // 实际接收的帧数

  "error_msg": ""           // 失败时的错误信息

}

固件传输结束。

设备固件信息更新:

固件传输完成后,设备通过i2c更新状态信息到e2prom, 格式:

{

"device_id": "CH32_001",

“current_fw": "1.0.0",

“fw_size”: 32468

}

二)E2PROM分区:

0 - 100, 出场信息:
{

  "device_id": "DEVICE_123",

  "current_fw": "1.0.0",

  “fw_size”: x

} 
100 - 150,升级请求信息:
{"UpgradeNeed":"yes"}

 用于标记用户是否点击了升级按键,升级或者取消之后,置为:

{"UpgradeNeed":"no"}

这是决定程序复位后能不能执行app的关键,更新完固件或者取消更新,都会置状态为:{"UpgradeNeed":"no"},方便程序复位就执行app程序。

如果程序要执行iap程序,app程序设计了相应的触发逻辑(按键、指令),会将状态置为{"UpgradeNeed":"yes"},这样iap程序就会进入iap流程而非返回app程序

三、上位机设计(即服务端设计)

一)固件打包:

按照之前的协议标准,将.bin文件处理成一帧一帧的json, 通过上位机传输至MCU。

注意,数据部分应该base64编码,避免传输错误。同样,MCU端base64解码。

python文件与字典、列表操作,不多赘述

本案例生成一个json数据文件,在Qt程序,只需一行行发送至MCU即可。

二)Qt程序:

主要用到 QFileDialog,  pyserial, 和其余基础组件(基于PyQT)。串口读写的方式有多种,定时器,或者多线程均能实现。

重点在于与MCU的对接,以及异常处理。

考虑时间问题,暂时只用的顺序处理

四、设备IAP程序设计

概述

基于裸机的C语言程序程序设计。

使用DMA收发, 空闲中断检测收完,考虑到开发周期,数据解析与烧写目前在串口中断做的。超时机制使用TIM做时钟,base64编码借助开源库,网络资源丰富,不赘述。

解析数据帧用到cJSON,提取校验码除了用到cJSON的提取,还用到C语言strtoul函数将字符串转为数:

对于写入flash, 先解锁,再擦除,再写入,再上锁:

均是"ch32v30x_flash.h"库函数

到APP的跳转:

使用了软中断:

__attribute__((noinline))
void jump_APP(uint32_t  addr)
{
    __asm("jr  a0");
    while(1);
}

void SW_Handler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void SW_Handler() {
    jump_APP(APP_ADDR);
}

通过调用:

    NVIC_EnableIRQ(Software_IRQn);
    NVIC_SetPendingIRQ(Software_IRQn);

执行。

程序架构:

五、测试

测试成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值