STM32G070串口OTA 升级

STM32G070 串口 OTA 升级

由于没有外挂Flash,内存又比较小,所以在此使用内置Flash作为缓存,

G070CBT6整体flash位128K,为flash规划分区,分区表如下。

image-20230605105405232

从STM32G070寄存器手册可以看到,内部flash是2K对齐总共有64个页,同时写操作flash时要注意地址为4字节对齐。

功能地址占用空间
bootloader0x8000000 - 0x800500020K
config0x8005000 - 0x80058002K
app0x8005800 - 0x801200050K
ota0x8012000 - 0x801E80050K
未使用0x801E800 - 0x801FFFF5K

串口更新协议

YModem协议格式

此部分参考Ymodem协议要点 - Acuity - 博客园 (cnblogs.com)

协议头
简写命令码说明
SOH0x01128字节数据长度,整帧长度133字节
STX0x021024字节数据长度,整帧长度1029字节
EOT0x04文件传输结束
ACK0x06正确接收应答
NAK0x15重传当前数据包
CAN0x18连续发送5条此命令取消传输
CC0x43字符C
握手信息

握手是时首先Client端向Server端发送字符“C”(ASCII码“43”)

Server端接收后发送起始帧,格式如下

帧头包号包号反码文件名称文件大小填充区校验高位校验低位
SOH0x000xffFile name+0x00File size+0x00NULL(0x00)CRC-HCRC-L
数据包

Client端接收起始帧并解码获得文件名称与文件大小后向Server端发送ACK

Server端接收ACK后回复第一帧数据,此时第一帧数据包号为1,数据格式如下

帧头包号包号反码有效数据校验高位校验低位
SOHPNXPN128字节DATACRC-HCRC-L

如果使用STX模式发送,数据则为1024字节。

Client端正确接收后回复ACK,Server端回复第二帧数据。

之后数据包以此类推。

此处有些疑惑,在使用Xshell作为Server端测试STM32的Client端时此处发送ACKCC+ACK均可接收正确回复

包号由于是一字节,所以范围为0-255,若数据包总数超过255则包号到达255后归零重新开始计数。

Client户端接收数据包后CRC校验出错,可向Server端恢复NAK,Server端接收NAK后会重发此次数据包。

在Server端发送至最后一帧数据,若此时数据不足128字节或1024字节,则用0x1A补齐剩余字节数,使数据对齐。

此时Client端在接收完成最后一帧数据后若继续发送ACK,Server端则恢复EOT(文件发送结束)。

结束帧

结束帧为133字节长度,格式如下

帧头包号包号反码数据区校验高位校验低位
SOH0x000xffNULL(0x00)CRC-HCRC-L

此处疑惑,在使用Xshell作为Server测试时,结束帧可以跳过,在Client接收到EOT之后直接连发6个CAN,Xshell也会默认Client文件接收完成。而使用Xshell作为Client时则Client接收EOT帧之后会发送一次NAK再次恢复EOT之后Client会回复一次CC+ACK,此时Server向Client发送结束帧后Client判断接收完成,向Server发送六个CAN

Client接收文件发送结束后连续发送6个CAN至Server表示接收完成,断开接收,Server接收后判断发送完成。

CRC校验

Ymodem协议所用的CRC公式与Xmodem一致,为0x1021,C语言写法如下

data为数据(除去帧头,包号、包号反码、帧尾高低校验位),len为长度

static uint16_t Ymodem_CRC16(const uint8_t* data, uint16_t len) {
    uint16_t crc = 0;

    for (uint16_t i = 0; i < len; i++) {
        crc ^= (uint16_t)data[i] << 8;
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    return crc;
}

取高低位

crc_buf//crc计算结果
(uint8_t)(crc_buf>>8)//CRC_H
(uint8_t)crc_buf//CRC_L
Ymodem接收部分代码

只写了128字节模式,1024字节模式可以自己补充,这里懒得写了🤣

//YMODEM协议处理
void Ymodem(const uint8_t *data) {
    uint8_t data_buf[1030];
    memcpy(data_buf,data,sizeof(data_buf));
    switch(YMODEM_FLAG) {
    case 0: { //处理第0帧数据
        uint8_t *p = &data_buf[3];
        uint8_t num = 0;
        if((data_buf[0] == SOH) && (data_buf[1] == 0)) {
            //CRC校验
            uint16_t crc_buf = Ymodem_CRC16(p,128);
            debug_print("%02X\t%02X\t%04X\n",p[128],p[129],crc_buf);
            if((p[128] != (uint8_t)(crc_buf>>8)) && (p[129] != (uint8_t)crc_buf)) {
                debug_print("CRC ERROR\n");
                HAL_Delay(5);
                Ymodem_Send_Data(NAK);//校验失败重传当前包
                goto ERROR;
            }
            for(uint16_t i = 0; i<128-3; i++) { //获取文件名
                if(data_buf[i] == 0) { //文件名结束,跳出循环
                    break;
                }
                if(i<sizeof(file_name)/sizeof(file_name[0])) { //超出缓存长度进行截断处理
                    file_name[i] = p[i];
                }
                num = i;
            }
            num ++;
            for(uint16_t i = 0; i<128-num; i++) { //获取文件大小
                if(p[num+i] == 0) { //文件大小结束
                    break;
                }
                file_len[i] = p[num+i];
            }
            YMODEM_FLAG = 1;
            Ymodem_Send_Data(ACK);//处理完成发送应答信号
            Ymodem_Send_Data(CC);
            break;
        } else {
            Ymodem_Send_Data(CC);
        }
        break;
    }
    case 1: { //处理数据帧
        if(data_buf[0] == SOH) { //128字节数据类型
            uint8_t *p = &data_buf[3];
            uint8_t data[130] = {0};
            //CRC校验
            uint16_t crc_buf = Ymodem_CRC16(p,128);
            debug_print("%02X\t%02X\t%04X\n",p[128],p[129],crc_buf);
            if((p[128] != (uint8_t)(crc_buf>>8)) && (p[129] != (uint8_t)crc_buf)) {
                debug_print("CRC ERROR\n");
                HAL_Delay(10);
                Ymodem_Send_Data(NAK);//校验失败重传当前包
                goto ERROR;
            }
            for(uint8_t i = 0; i<128; i++) {
                data[i] = p[i];
            }
						//写flash
            Write_Flash_Data(addr,data,128);
						
            HAL_Delay(1);
            Ymodem_Send_Data(ACK);//处理完成发送应答信号
            break;
        } else if(data_buf[0] == STX) { //1024字节数据类型
//            uint8_t *p = &data_buf[3];
//            //CRC校验
//            uint16_t crc_buf = Ymodem_CRC16(p,1024);
//            debug_print("%02X\t%02X\t%04X\n",p[1023],p[1024],crc_buf);
//            if((p[1023] != (uint8_t)(crc_buf>>8)) && (p[1024] != (uint8_t)crc_buf)) {
//                debug_print("CRC ERROR\n");
//                HAL_Delay(5);
//                Ymodem_Send_Data(NAK);//校验失败重传当前包
//                goto ERROR;
//            }
//            //数据处理

//            HAL_Delay(5);
//            Ymodem_Send_Data(ACK);//处理完成发送应答信号
            break;
        } else if(data_buf[0] == CAN) { //主机取消发送
            debug_print("CANCELL\n");
            addr = FLASH_ADDR;
            YMODEM_FLAG = 0;
            break;
        } else if((data_buf[0] == EOT)) {//回复文件接收结束
            for(uint8_t i = 0; i<=5; i++) {
                HAL_Delay(5);
                Ymodem_Send_Data(CAN);
            }
            addr = FLASH_ADDR;
            Write_Config(UPDATE_FLAG);
            printf("[REST START]\r\n");
            HAL_NVIC_SystemReset();
            YMODEM_FLAG = 0;
            break;
        }
        Ymodem_Send_Data(NAK);//校验失败重传当前包
        break;
    }
    default: {

        break;
    }

    }
ERROR:
    return;
}

写Flash

协议接收后每接收一次数据直接写入flash分区进行缓存。全部接收完成后在flash的config分区写入升级标志(此处应该要进行完整性校验的,偷懒没写),之后进行软重启,接下来的搬运分区覆盖掉原app分区交给bootloader进行完成。此篇只写了app内程序,bootloader就很简单了,挖个坑放下篇文章再写🫠

/*
 * @Author: Memory 1619005172@qq.com
 * @Date: 2023-06-06 15:03:55
 * @LastEditors: Memory 1619005172@qq.com
 * @LastEditTime: 2023-06-06 15:05:23
 * @FilePath:
 * @Description:
 */
#include "update.h"
#define TGA "UPDATE"


uint8_t file_name[200] = {0};
uint8_t file_len[50] = {0};

uint32_t addr = FLASH_ADDR;


static FLASH_SATAE_T Erase_Flash(uint32_t addr,uint8_t num);
static uint32_t GetPage(uint32_t Addr);
static uint16_t Ymodem_CRC16(const uint8_t* data, uint16_t len);

FLASH_SATAE_T Update_Init(void) {
    HAL_FLASH_Unlock();//解锁flash
    if(Erase_Flash(FLASH_ADDR,ERASE_LEN/2) != FLASH_OK) { //擦除flash
        goto ERROR;
    }
    HAL_FLASH_Lock();
    return FLASH_OK;
ERROR:
    HAL_FLASH_Lock();
    return FLASH_ERROR;
}

FLASH_SATAE_T Write_Flash_DoubleWord(uint32_t addr,uint64_t data) {
    HAL_FLASH_Unlock();
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,addr,data) != HAL_OK) {
        debug_print("WRITE FLASH ERROR\n");
        goto ERROR;
    }
    HAL_FLASH_Lock();
    return FLASH_OK;
ERROR:
    HAL_FLASH_Lock();
    return FLASH_ERROR;
}

FLASH_SATAE_T Write_Config(uint64_t data) {
    HAL_FLASH_Unlock();//解锁flash
    if(Erase_Flash(CONFIG_ADDR,1) != FLASH_OK) { //擦除flash
        goto ERROR;
    }
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,CONFIG_ADDR,data) != HAL_OK) {
        debug_print("WRITE FLASH ERROR\n");
        goto ERROR;
    }
    debug_print("SUCCESS\n");
ERROR:
    HAL_FLASH_Lock();//上锁
    return FLASH_ERROR;
}

static FLASH_SATAE_T Erase_Flash(uint32_t addr,uint8_t num) {
    uint32_t SectorError=0;

    UPDATE_FLASH.TypeErase = FLASH_TYPEERASE_PAGES;
    UPDATE_FLASH.Page = GetPage(addr);
    UPDATE_FLASH.NbPages = num;
    if(HAL_FLASHEx_Erase(&UPDATE_FLASH,&SectorError) != HAL_OK) {
        debug_print("ERASE ERROR %x\n",SectorError);
        goto ERROR;
    }
    debug_print("ERASE SUCCESS %x\n",SectorError);
    return FLASH_OK;
ERROR:
    return FLASH_ERROR;
}

static uint32_t GetPage(uint32_t Addr)
{
    uint32_t page = 0;
    page = (Addr-FLASH_BASE) / FLASH_PAGE_SIZE;
    return page;
}

FLASH_SATAE_T Write_Flash_Data(uint32_t address, uint8_t *buf, uint32_t length)
{
    uint32_t i = 0;
    uint32_t start_address = address;
    uint64_t data = 0;
    uint32_t data1 = 0, data2 = 0;
    //长度为0时直接返回
    if(length == 0)
    {
        goto ERROR;
    }
    //长度非8字节倍数时则补齐新的8字节
    if(length%8 != 0)
    {
        length = (length/8)*8 + 8;
    }

    for (i=0; i<(length/8); i++)
    {
        //小端格式存放
        data1 = buf[i*8 + 0];
        data1 |= buf[i*8 + 1] << 8;
        data1 |= buf[i*8 + 2] << 16;
        data1 |= buf[i*8 + 3] << 24;
        data2 = buf[i*8 + 4];
        data2 |= buf[i*8 + 5] << 8;
        data2 |= buf[i*8 + 6] << 16;
        data2 |= buf[i*8 + 7] << 24;
        data = (uint64_t)data2*256*256*256*256 + (uint64_t)data1;

        Write_Flash_DoubleWord(start_address, data);
        start_address += 8;
    }
    addr = start_address;
    return FLASH_OK;
ERROR:
    return FLASH_ERROR;
}

update.h

#ifndef __UPDATE_H__
#define __UPDATE_H__

#include "comm.h"

#define JUMP_ADDR 		0x8005800		//跳转地址
#define CONFIG_ADDR		0X8005000		//升级信息存储分区
#define FLASH_ADDR		0x8012000		//Flash读取地址
#define ERASE_LEN			50					//擦除长度
#define UPDATE_FLAG   0x123456		//升级标志

#define YMODEM_UART huart1


enum {
    SOH = 0x01,
    STX = 0x02,
    EOT = 0x04,//文件传输结束
    ACK = 0x06,//正确接收
    NAK = 0x15,//重传当前包
    CAN = 0x18,//取消传输命令
    CC = 0x43,//字符C
};

FLASH_SATAE_T Update_Init(void);
FLASH_SATAE_T Write_Flash(void);
void Ymodem(const uint8_t *data);

END

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kongbai_w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值