GD32H757 OTA升级

GD32H757 OTA升级

一.整体介绍

1.什么是BootLoader

BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序.。换言之, BootLoader是一个程序, App也是一个程序, BootLoader程序是用于启动App程序的。

对于CPU来说都是0地址来执行程序。

2.GD32中的程序在哪儿

正常情况下, 我们写的程序都是放在GD片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码。

3.OTA

所谓的OTA升级程序,其实程序分成两部分,一部分是BootLoader,另一部分是APP。BootLoader的程序就引导程序,他分成两个功能,一个更新App,另一个是往Flash里面写数据其实跟仿真器下载程序到指定地址的程序是一个原理。

4.使用串口以及SD卡来实现OTA

5.设计思路

  1. 设计单片机的Flash分区,一部分放置BootLoader,另一部分是APP。
  2. 这个分区的标准具体以编译后BootLoader的程序2进制文件的大小来分区。
  3. 在这儿我将前1M(这个可以根据具体的BootLoader程序的大小来设计)区域的地方用来存储BootLoader。后面的地址空间用来存储APP。
  4. 到这里就清晰了,所以程序就需要实现两部分代码。一个BootLoader,另一个就是APP。
  5. BootLoader的大小可以通过map文件来查看具体的大小。

image-20250626090901034

本人GD32F4的OTA链接:https://blog.csdn.net/lcl113480/article/details/142063560

二.GD32H757 FMC内部Flash读写

1.简介

闪存控制器(FMC),提供了片上闪存需要的所有功能。包括扇区擦除,整片擦除,以及编程
操作等。

2.主要特性

  1. 高达3840KB字节的片上闪存可用于存储指令或数据;
  2. 支持64位双字、32位整字、16位半字或字节读操作;
  3. 支持64位双字、32位整字编程,扇区擦除和整片擦除操作;
  4. 选项字节会在每次系统复位时装载到选项字节控制寄存器;
  5. 具有安全保护状态,可阻止对代码或数据的非法读访问;
  6. 具有擦除和编程保护状态,可阻止意外写操作;
  7. 具有仅执行的专用代码读保护(DCRP)区域;
  8. 具有仅能在安全模式下访问的安全用户区域;

3.闪存结构

  • FMC 支持用以访问代码或数据的 64 位 AXI 接口以及用以访问寄存器的 32 位 AHB 从机接口。FMC 框图显示了 FMC 的架构。

    image-20250515162918658

  • FMC的AXI接口可以同时支持读写操作。FMC的AXI从机接口支持以下访问类型:

  • 支持数据宽度为1、2、4、8字节的单一读操作;

  • 支持数据宽度为1、2、4、8字节的增量突发读操作,突发传输数据长度最多可达128字节。

  • 支持数据宽度为8字节的单一、2拍、4拍、8拍的回卷突发读操作;

  • 支持数据宽度为4字节、8字节的单一编程操作;

  • 支持数据宽度为8字节的最多32拍的突发编程操作,且4拍的突发编程操作数据传输效率最高;

  • 使用数据宽度为8字节的非单拍的突发编程操作时,则只能使用MDMA而不能使用DMA,否则会产生不可预期的错误;

  • 所有编程地址必须与数据宽度对齐;

  • 在一次突发读/写操作中,地址不能跨越4K边界;

  • 以上描述以外的其他所有操作将产生总线错误,且读操作将返回零,编程操作将被忽略。AHB从机接口支持32位整字访问。

  • 闪存包括3840KB字节主闪存,分为960个扇区,扇区大小为4KB,和64KB用于引导加载程序的信息块。主存储闪存的每个扇区都可以单独擦除。

image-20250515163243368

有关Flash擦写操作均需要先解锁Flash,然后进行擦写操作,擦写完成后再进行锁Flash,注意Flash特性只能由1写0,也就是Flash需要先擦除才能写入新的数据,如果确保写入地址的数据为全0xFF,也可以直接写入。读取Flash数据可以采取直接寻址的方式进行读取。

4.FMC中断

image-20250516083915564

5.FMC主要函数介绍

库函数名称库函数描述
fmc_unlock解锁FMC_CTL寄存器
fmc_lock锁定FMC_CTL寄存器
fmc_sector_erase擦除扇区
fmc_typical_mass_erase标准整片擦除
fmc_protection_removed_mass_erase带清除保护的整片擦除
fmc_word_program在主编程块相应地址全字编程
fmc_doubleword_program在主编程块相应地址双字编程
fmc_check_programming_area_enable使能编程区域检查
fmc_check_programming_area_disable失能编程区域检查

6.读写实验

实验说明:FlashDrvTest调用测试函数,将进行读写最后一块扇区来进行测试。先写后读判断数据有效性。

/*********************************************************************************************************
 * 模块名称:Flash.c
 * 摘    要:Flash模块
 * 当前版本:1.0.0
 */
/*********************************************************************************************************
 *                                              包含头文件
 *********************************************************************************************************/
#include "Flash.h"
#include "gd32H7xx_fmc.h"
#include "stdio.h"
#include "IAP.h"

/*********************************************************************************************************
 *                                              宏定义
 *********************************************************************************************************/

/*********************************************************************************************************
 *                                              内部变量
 *********************************************************************************************************/
static u32 s_arrFlashBuf[FLASH_SECTOR_SIZE / 4]; // Flash写入缓冲区,大小为32K字节

/*********************************************************************************************************
 *                                              内部函数声明
 *********************************************************************************************************/

/*********************************************************************************************************
 *                                              内部函数实现
 *********************************************************************************************************/

/*********************************************************************************************************
 *                                              API函数实现
 *********************************************************************************************************/

/*********************************************************************************************************
 * 函数名称:FlashWriteWord
 * 函数功能:向Flash中写入字
 * 输入参数:startAddr-起始地址,pBuf-待写入数据指针,len-写入数据的数量
 * 输出参数:void
 * 返 回 值:void
 * 注    意:输入数据一定是uint32_t的指针,即数据一定是按照4字节对齐写入的
 *          写入数据时为保证之前写入的数据不被擦除,Flash读写以扇区为单位
 *********************************************************************************************************/
void FlashWriteWord(uint32_t startAddr, uint32_t *pBuf, uint32_t len)
{
  u32 i;          // 循环变量
  u32 sectorAddr; // 扇区地址,即起始地址startAddr所在的扇区地址
  u32 sectorOff;  // 扇区内偏移地址(32位计算),即起始地址startAddr所在的扇区的偏移地址
  u32 rwAddr;     // 读写地址
  u32 dataCnt;    // 已写入数据量

  // 计算扇区地址和扇区内偏移量
  sectorAddr = startAddr;
  sectorOff = 0;

  while (0 != (sectorAddr % FLASH_SECTOR_SIZE))
  {
    sectorAddr = sectorAddr - 4;
    sectorOff++;
  }

  // 解锁Flash,准备写入
  fmc_unlock();

  // 已写入数据量清零
  dataCnt = 0;

  // 写入Flash时需要先读取整扇区的内容到缓冲区,然后擦除一整扇区,将缓冲区修改后再写回Flash
  while (1)
  {
    // 读取一整扇区的数据到缓冲区
    rwAddr = sectorAddr;
    for (i = 0; i < FLASH_SECTOR_SIZE / 4; i++)
    {
      s_arrFlashBuf[i] = *(u32 *)rwAddr;
      rwAddr = rwAddr + 4;
    }
    // 根据扇区地址计算扇区号
    uint32_t sectorNum = (sectorAddr - FLASH_BASE) / FLASH_SECTOR_SIZE;

    // 执行扇区擦除
    fmc_sector_erase(FLASH_BASE + (sectorNum * FLASH_SECTOR_SIZE));
    ClearFmcFlag();              /* 清除FMC标志位  */
    SCB_CleanInvalidateDCache(); /* 清除无效的D-Cache */

    // 修改缓冲区内的内容
    while (sectorOff < (FLASH_SECTOR_SIZE / 4))
    {

      // 将数据保存到缓冲区
      s_arrFlashBuf[sectorOff] = pBuf[dataCnt];

      // 已写入数据加一
      dataCnt++;

      // 扇区内偏移量加一
      sectorOff++;

      // 写入完成
      if (dataCnt >= len)
      {
        break;
      }
    }

    // 扇区内偏移量清零
    sectorOff = 0;

    // 将修改后的缓冲区内容写回Flash
    rwAddr = sectorAddr;
    for (i = 0; i < FLASH_SECTOR_SIZE / 4; i++)
    {
      fmc_word_program(rwAddr, s_arrFlashBuf[i]);
      rwAddr = rwAddr + 4;
      ClearFmcFlag();
    }

    // 更新到下一扇区首地址
    sectorAddr = sectorAddr + FLASH_SECTOR_SIZE;

    // 写入完成
    if (dataCnt >= len)
    {
      break;
    }
  }
  SCB_CleanInvalidateDCache(); /* 清除无效的D-Cache */
  // Flash上锁
  fmc_lock();
}

/*********************************************************************************************************
 * 函数名称:FlashReadWord
 * 函数功能:从Flash读取字
 * 输入参数:startAddr-起始地址(由于是字(32位)读取,因此,此地址必须为4的倍数);pBuf-数据指针
 *           len-字(32位)数,即要读取的32位数据的个数
 * 输出参数:void
 * 返 回 值:void
 * 创建日期:2021年07月01日
 * 注    意:
 *********************************************************************************************************/
void FlashReadWord(uint32_t startAddr, uint32_t *pBuf, uint32_t len)
{
  u32 addr;
  u32 i;

  addr = startAddr;
  for (i = 0; i < len; i++)
  {
    pBuf[i] = *(u32 *)addr;
    addr = addr + 4;
  }
}
/*********************************************************************************************************
 * 函数名称:ClearFmcFlag
 * 函数功能:清除标志
 * 输入参数:void
 * 输出参数:void
 * 返 回 值:void
 *********************************************************************************************************/
void ClearFmcFlag(void)
{
  fmc_flag_clear(FMC_FLAG_WPERR);
  fmc_flag_clear(FMC_FLAG_PGSERR);
  fmc_flag_clear(FMC_FLAG_RPERR);
  fmc_flag_clear(FMC_FLAG_RSERR);
  fmc_flag_clear(FMC_FLAG_ECCCOR);
  fmc_flag_clear(FMC_FLAG_ECCDET);
  fmc_flag_clear(FMC_FLAG_OBMERR);
}
/*********************************************************************************************************
 * 函数名称:EraseAppVersion
 * 函数功能:写入版本号
 * 输出参数:void
 * 返 回 值:void
 *********************************************************************************************************/
void EraseAppVersion(void)
{
  uint32_t sectorAddr = FLASH_START_ADDR - FLASH_SECTOR_SIZE;
  fmc_unlock(); // 解锁Flash,准备写入
  ClearFmcFlag();
  // 根据扇区地址计算扇区号
  uint32_t sectorNum = ((sectorAddr - FLASH_BASE) / FLASH_SECTOR_SIZE) - FLASH_SECTOR_SIZE;
  // 执行扇区擦除
  fmc_state_enum status = fmc_sector_erase(FLASH_BASE + (sectorNum * FLASH_SECTOR_SIZE));
  switch (status)
  {
  case FMC_READY:
    // printf("the operation has been completed\r\n");
    break;
  case FMC_BUSY:
    printf("the operation is in progress\r\n");
    break;
  case FMC_WPERR:
    printf("erase/program protection error\r\n");
    break;
  case FMC_PGSERR:
    printf("program sequence error\r\n");
    break;
  case FMC_RPERR:
    printf("read protection error\r\n");
    break;
  case FMC_RSERR:
    printf("read secure error\r\n");
    break;
  case FMC_ECCCOR:
    printf("one bit correct error\r\n");
    break;
  case FMC_ECCDET:
    printf("two bits detect error\r\n");
    break;
  case FMC_OBMERR:
    printf("option byte modify errorr\r\n");
    break;
  case FMC_TOERR:
    printf("timeout error\r\n");
    break;
  default:
    printf("Unknown error: %u\r\n", status);
  }
  fmc_lock(); // Flash上锁
}
/**
*******************************************************************
* @function 擦除从eraseAddr开始到eraseAddr + numToErase的页
* @param    eraseAddr,地址
* @param    numToErase,对应写入数据时的个数
* @return
*******************************************************************
*/
bool FlashErase(uint32_t eraseAddr, uint32_t numToErase)
{
  if (numToErase == 0 || (eraseAddr + numToErase) > FLASH_ENDADDR)
  {
    return false;
  }

  uint8_t pageNum;
  uint8_t addrOffset = eraseAddr % FLASH_SECTOR_SIZE; // mod运算求余在一页内的偏移,若eraseAddr是FLASH_PAGE_SIZE整数倍,运算结果为0

  fmc_state_enum fmcState = FMC_READY;
  fmc_unlock();

  if (numToErase > (FLASH_SECTOR_SIZE - addrOffset)) // 跨页
  {
    ClearFmcFlag();
    fmcState = fmc_sector_erase(eraseAddr); // 擦本页
    if (fmcState != FMC_READY)
    {
      goto erase_err;
    }

    eraseAddr += FLASH_SECTOR_SIZE - addrOffset; // 对齐到页地址
    numToErase -= FLASH_SECTOR_SIZE - addrOffset;
    pageNum = numToErase / FLASH_SECTOR_SIZE;

    while (pageNum--)
    {
      ClearFmcFlag();
      fmcState = fmc_sector_erase(eraseAddr);
      if (fmcState != FMC_READY)
      {
        goto erase_err;
      }
      eraseAddr += FLASH_SECTOR_SIZE;
    }
    if (numToErase % FLASH_SECTOR_SIZE != 0)
    {
      ClearFmcFlag();
      fmcState = fmc_sector_erase(eraseAddr);
      if (fmcState != FMC_READY)
      {
        goto erase_err;
      }
    }
  }
  else // 没有跨页
  {
    ClearFmcFlag();
    fmcState = fmc_sector_erase(eraseAddr);
    if (fmcState != FMC_READY)
    {
      goto erase_err;
    }
  }
  /* lock the main FMC after the erase operation */
  fmc_lock();
  return true;

erase_err:
  /* lock the main FMC after the erase operation */
  fmc_lock();
  return false;
}

/**
*******************************************************************
* @function 指定地址开始写入指定个数的数据
* @param    writeAddr,写入地址
* @param    pBuffer,数组首地址
* @param    numToWrite,要写入的数据个数
* @return
*******************************************************************
*/
bool FlashWrite(uint32_t writeAddr, uint8_t *pBuffer, uint32_t numToWrite)
{
  if ((writeAddr + numToWrite) > FLASH_ENDADDR || (writeAddr % 4 != 0))
  {
    printf("Invalid write address\r\n");
    return false;
  }

  fmc_state_enum fmcState = FMC_READY;
  fmc_unlock();

  for (uint32_t i = 0; i < numToWrite; i += 4)
  {
    uint32_t data = *((uint32_t *)(pBuffer + i));
    fmcState = fmc_word_program(writeAddr + i, data);
    if (fmcState != FMC_READY)
    {
      fmc_lock();
      printf("Write error\r\n");
      return false;
    }
    ClearFmcFlag();
  }

  fmc_lock();
  return true;
}
#define BUFFER_SIZE                   3000
#define FLASH_TEST_ADDRESS            0x083BF000//第959扇区起始地址开始写
void FlashDrvTest(void)
{
    uint8_t bufferWrite[BUFFER_SIZE];
    uint8_t bufferRead[BUFFER_SIZE];

    printf("flash writing data:\n");
    for (uint16_t i = 0; i < BUFFER_SIZE; i++)
    { 
        bufferWrite[i] = i + 1;
        printf("0x%02X ", bufferWrite[i]);
    }
    printf("\n开始写入\n");

    if (!FlashErase(FLASH_TEST_ADDRESS, BUFFER_SIZE))
    {
        printf("Flash写数据故障,请排查!\n");
        return;
    }

    if (!FlashWrite(FLASH_TEST_ADDRESS, bufferWrite, BUFFER_SIZE))
    {
        printf("Flash写数据故障,请排查!\n");
        return;
    }

    printf("开始读取\n");
    if (!FlashRead(FLASH_TEST_ADDRESS, bufferRead, BUFFER_SIZE))
    {
        printf("Flash读数据故障,请排查!\n");
        return;
    }
    for (uint16_t i = 0; i < BUFFER_SIZE; i++)
    {
        if (bufferRead[i] != bufferWrite[i]){
            printf("0x%02X ", bufferRead[i]);
            printf("Flash测试故障,请排查!\n");
            return;
        }
        printf("0x%02X ", bufferRead[i]);

    }
    printf("\nFlash测试通过!\n");
}

/*********************************************************************************************************
 * 模块名称:Flash.h
 * 摘    要:Flash模块
 * 当前版本:1.0.0
 * 内    容:
 * 注    意:
 *********************************************************************************************************/
#ifndef _FLASH_H_
#define _FLASH_H_

/*********************************************************************************************************
 *                                              包含头文件
 *********************************************************************************************************/
#include "DataType.h"
#include <stdint.h>
#include <stdbool.h>
/*********************************************************************************************************
 *                                              宏定义
 *********************************************************************************************************/
#define FLASH_SECTOR_SIZE ((uint32_t)0x0001000)     // 扇区大小
#define FLASH_START_ADDR ((uint32_t)0x0800A000) // 用户Flash起始地址,为扇区5
#define FLASH_ENDADDR (0x083C0000)              // 用户Flash结束地址

/*********************************************************************************************************
 *                                              结构体定义
*********************************************************************************************************/
/*********************************************************************************************************
 *                                              API函数声明
*********************************************************************************************************/
void FlashWriteWord(uint32_t startAddr, uint32_t *pBuf, uint32_t len);// 向Flash中写入字
bool FlashWrite(uint32_t writeAddr, uint8_t *pBuffer, uint32_t numToWrite); // 向Flash中写入数据
void FlashReadWord(uint32_t startAddr, uint32_t *pBuf, uint32_t len);// 从Flash读取字
void EraseAppVersion(void);// 擦除APP版本号
bool FlashErase(uint32_t eraseAddr, uint32_t numToErase);/ 擦除Flash
void ClearFmcFlag(void);// 清除FMC标志
#endif

三.BootLoader

1.BootLoader跳转到APP实现

typedef void (*pFunction)(void);
#define RAM_START_ADDRESS    0x20000000
#define RAM_SIZE             0x100000    //1M----不同的单片机RAM不同需要查一下

static void BootToApp(void)
{
    uint32_t stackTopAddr = *(volatile uint32_t*)APP_ADDR_IN_FLASH; //获取栈顶地址
    //判断栈顶地址是否在合法范围内
    if (stackTopAddr > RAM_START_ADDRESS && stackTopAddr < (RAM_START_ADDRESS + RAM_SIZE)) 
    {
        __disable_irq();//关闭中断
        __set_MSP(stackTopAddr);//设置栈顶地址,就是这个地址保存到CPU的SP寄存器里面。
        //获取复位函数的地址
        uint32_t resetHandlerAddr = *(volatile uint32_t*) (APP_ADDR_IN_FLASH + 4);
        /* Jump to user application */ // int *p = (int *)0x8003145
        pFunction Jump_To_Application = (pFunction) resetHandlerAddr; 
        /* Initialize user application's Stack Pointer */
        Jump_To_Application();
    }
    NVIC_SystemReset();//进入系统复位
}

2.update.c 更新文件

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "delay.h"
#include "flash.h"
#include "update.h"
#include "systick.h"
#include "usart.h"
#include "IAP.h"

#define IS_AF(c) ((c >= 'A') && (c <= 'F'))
#define IS_af(c) ((c >= 'a') && (c <= 'f'))
#define IS_09(c) ((c >= '0') && (c <= '9'))
#define ISVALIDHEX(c) (IS_AF(c) || IS_af(c) || IS_09(c))
#define ISVALIDDEC(c) IS_09(c)
#define CONVERTDEC(c) (c - '0')

#define CONVERTHEX_alpha(c) (IS_AF(c) ? (c - 'A' + 10) : (c - 'a' + 10))
#define CONVERTHEX(c) (IS_09(c) ? (c - '0') : CONVERTHEX_alpha(c))

/**
 * @brief  Convert an Integer to a string
 * @param  str: The string
 * @param  intnum: The intger to be converted
 * @retval None
 */
void Int2Str(uint8_t *str, int32_t intnum)
{
    uint32_t i, div = 1000000000, j = 0, Status = 0;

    for (i = 0; i < 10; i++)
    {
        str[j++] = (intnum / div) + 0x30;
        intnum = intnum % div;
        div /= 10;
        if ((str[j - 1] == '0') & (Status == 0))
        {
            j = 0;
        }
        else
        {
            Status++;
        }
    }
}

/**
 * @brief  Convert a string to an integer
 * @param  inputstr: The string to be converted
 * @param  intnum: The intger value
 * @retval 1: Correct
 *         0: Error
 */
uint32_t Str2Int(uint8_t *inputstr, int32_t *intnum)
{
    uint32_t i = 0, res = 0;
    uint32_t val = 0;

    if (inputstr[0] == '0' && (inputstr[1] == 'x' || inputstr[1] == 'X'))
    {
        if (inputstr[2] == '\0')
        {
            return 0;
        }
        for (i = 2; i < 11; i++)
        {
            if (inputstr[i] == '\0')
            {
                *intnum = val;
                /* return 1; */
                res = 1;
                break;
            }
            if (ISVALIDHEX(inputstr[i]))
            {
                val = (val << 4) + CONVERTHEX(inputstr[i]);
            }
            else
            {
                /* return 0, Invalid input */
                res = 0;
                break;
            }
        }
        /* over 8 digit hex --invalid */
        if (i >= 11)
        {
            res = 0;
        }
    }
    else /* max 10-digit decimal input */
    {
        for (i = 0; i < 11; i++)
        {
            if (inputstr[i] == '\0')
            {
                *intnum = val;
                /* return 1 */
                res = 1;
                break;
            }
            else if ((inputstr[i] == 'k' || inputstr[i] == 'K') && (i > 0))
            {
                val = val << 10;
                *intnum = val;
                res = 1;
                break;
            }
            else if ((inputstr[i] == 'm' || inputstr[i] == 'M') && (i > 0))
            {
                val = val << 20;
                *intnum = val;
                res = 1;
                break;
            }
            else if (ISVALIDDEC(inputstr[i]))
            {
                val = val * 10 + CONVERTDEC(inputstr[i]);
            }
            else
            {
                /* return 0, Invalid input */
                res = 0;
                break;
            }
        }
        /* Over 10 digit decimal --invalid */
        if (i >= 11)
        {
            res = 0;
        }
    }

    return res;
}

/******************************************************************************
 * Name:    CRC-16/XMODEM       x16+x12+x5+1
 * Poly:    0x1021
 * Init:    0x0000
 * Refin:   False
 * Refout:  False
 * Xorout:  0x0000
 * Alias:   CRC-16/ZMODEM,CRC-16/ACORN
 *****************************************************************************/
uint16_t Crc16Ymodem(uint8_t *data, uint16_t length)
{
    uint8_t i;
    uint16_t crc = 0; // Initial value
    while (length--)
    {
        crc ^= (uint16_t)(*data++) << 8;
        for (i = 0; i < 8; ++i)
        {
            if (crc & 0x8000)
                crc = (crc << 1) ^ 0x1021;
            else
                crc <<= 1;
        }
    }
    return crc;
}

#define PACKET_SEQNO_INDEX (1)
#define PACKET_SEQNO_COMP_INDEX (2)

#define PACKET_HEADER (3)  // 数据帧前面的3个字节
#define PACKET_TRAILER (2) // 后面2个CRC校验和字节
#define PACKET_OVERHEAD (PACKET_HEADER + PACKET_TRAILER)
#define PACKET_SIZE (128)
#define PACKET_1K_SIZE (1024)

#define SOH (0x01)	// 128字节数据包开始
#define STX (0x02)	// 1024字节的数据包开始
#define EOT (0x04)	// 结束传输
#define ACK (0x06)	// 回应
#define NAK (0x15)	// 没回应
#define CA (0x18)	// 这两个相继中止转移
#define CREQ (0x43) //'C' == 0x43, 请求数据

#define ABORT1 (0x41) //'A' == 0x41, 用户终止
#define ABORT2 (0x61) //'a' == 0x61, 用户终止

#define NAK_TIMEOUT (5000)
#define MAX_ERRORS (5)

#define YMODEM_PACKET_LENGTH 1024
static uint8_t g_packetBuffer[YMODEM_PACKET_LENGTH];

#define FILE_NAME_LENGTH 256
#define FILE_SIZE_LENGTH 16

static char g_imageName[FILE_NAME_LENGTH];
#define FLASH_WRITE_UNIT 4 // GD32H7要求4字节对齐
#define MAX_EOT_RETRY 2	   // EOT最大重试次数


// 接收一包包的数据
static int32_t ReceivePacket(uint8_t *data, int32_t *length, uint32_t timeout)
{
    uint16_t i, packetSize;
    uint8_t c;
    *length = 0;
    if (ReceiveByteTimeout(&c, timeout) != 0)
    {
        return -1;
    }
    switch (c)
    {
            // 判断数据帧头
        case SOH:
            packetSize = PACKET_SIZE; // 128
            break;
        case STX:
            packetSize = PACKET_1K_SIZE; // 1024
            break;
        case EOT:
            return 0;
        case CA:
            if ((ReceiveByteTimeout(&c, timeout) == 0) && (c == CA))
            {
                *length = -1;
                return 0;
            }
            else
            {
                return -1;
            }
        case ABORT1:
        case ABORT2:
            return 1;
        default:
            return -1;
    }
    *data = c;
    for (i = 1; i < (packetSize + PACKET_OVERHEAD); i++) // 等待接收完一整包数据
    {
        if (ReceiveByteTimeout(data + i, timeout) != 0)
        {
            return -1;
        }
    }
    if ((data[PACKET_SEQNO_INDEX] | data[PACKET_SEQNO_COMP_INDEX]) != 0xff)
    {
        return -1;
    }

    uint16_t crc16 = Crc16Ymodem(&data[3], packetSize); // 计算CRC
    uint16_t raw_crc16 = (uint16_t)(data[packetSize + PACKET_OVERHEAD - 2] << 8) | data[packetSize + PACKET_OVERHEAD - 1];
    if (crc16 != raw_crc16)
    {
        return -1;
    }

    *length = packetSize;
    return 0;
}

/**
 * @brief  Receive a file using the ymodem protocol
 * @param  buf: Address of the first byte
 * @retval The size of the file, or negative error code
 */
int32_t YmodemReceive(uint8_t *buf)
{
    uint8_t packetData[PACKET_1K_SIZE + PACKET_OVERHEAD];
    uint8_t fileSize[FILE_SIZE_LENGTH], *filePtr, *bufPtr;
    int32_t i, packetLength, sessionDone, fileDone, packetsReceived, errors, sessionBegin, size = 0;

    uint32_t baseFlashAddress = APP_ADDR_IN_FLASH;
    uint32_t flashDestination = baseFlashAddress;
    uint32_t remainingBytes = 0;
    uint32_t fileActualSize = 0;
    uint32_t requiredSpace = 0;
    uint8_t retryCount = 0;

    for (sessionDone = 0, errors = 0, sessionBegin = 0;;)
    {
        flashDestination = baseFlashAddress;
        size = 0;
        fileDone = 0;
        remainingBytes = 0;
        fileActualSize = 0;
        retryCount = 0;

        for (packetsReceived = 0, fileDone = 0, bufPtr = buf;;)
        {
            int receiveStatus = ReceivePacket(packetData, &packetLength, NAK_TIMEOUT);

            switch (receiveStatus)
            {
                case 0: // 正常接收
                    errors = 0;
                    switch (packetLength)
                    {
                        case -1: // 发送方中止
                            SendByte(ACK);
                            return 0;

                        case 0: // EOT结束传输
                            if (retryCount == 0)
                            {
                                SendByte(NAK); // 第一次EOT回复NAK
                                retryCount++;
                            }
                            else
                            {
                                SendByte(ACK); // 第二次EOT回复ACK
                                fileDone = 1;
                            }
                            break;

                        default:					  // 数据包
                            if (packetsReceived == 0) // 文件名包
                            {
                                if (packetData[PACKET_HEADER] != 0)
                                {
                                    // 解析文件名
                                    for (i = 0, filePtr = packetData + PACKET_HEADER;
                                         (*filePtr != 0) && (i < FILE_NAME_LENGTH);)
                                        g_imageName[i++] = *filePtr++;
                                    g_imageName[i] = '\0';

                                    // 解析文件大小
                                    for (i = 0, filePtr++; (*filePtr != ' ') && (i < FILE_SIZE_LENGTH);)
                                        fileSize[i++] = *filePtr++;
                                    fileSize[i] = '\0';
                                    Str2Int(fileSize, &size);

                                    //printf("\r\nFile: %s, Size: %lu bytes\r\n", g_imageName, size);
                                    //printf("Flash range: 0x%08X - 0x%08X\r\n", baseFlashAddress, baseFlashAddress + FLASH_APP_SIZE - 1);

                                    // 检查文件大小
                                    if (size > FLASH_APP_SIZE)
                                    {
                                        //printf("\r\nFile too large (%lu > %lu)\r\n", size, FLASH_APP_SIZE);
                                        SendByte(CA);
                                        SendByte(CA);
                                        return -1;
                                    }

                                    // 计算对齐后的空间需求[2](@ref)
                                    requiredSpace = size;
                                    if (size % FLASH_WRITE_UNIT != 0)
                                    {
                                        requiredSpace += FLASH_WRITE_UNIT - (size % FLASH_WRITE_UNIT);
                                    }

                                    if (requiredSpace > FLASH_APP_SIZE)
                                    {
                                        printf("\r\nRequired space %d > available %d\r\n",requiredSpace, FLASH_APP_SIZE);
                                        SendByte(CA);
                                        SendByte(CA);
                                        return -1;
                                    }

                                    // 擦除Flash区域
                                    if (FlashErase(baseFlashAddress, requiredSpace) != true)
                                    {
                                        printf("\r\nFlash erase error!\r\n");
                                        SendByte(CA);
                                        SendByte(CA);
                                        return -4;
                                    }
                                    remainingBytes = size;

                                    SendByte(ACK);
                                    SendByte(CREQ);
                                }
                                else // 空文件名包,结束会话
                                {
                                    SendByte(ACK);
                                    fileDone = 1;
                                    sessionDone = 1;
                                    break;
                                }
                            }
                            else // 数据包
                            {
                                uint32_t writeLength = packetLength;

                                // 处理最后不完整的数据块
                                if (remainingBytes < (uint32_t)packetLength)
                                {
                                    writeLength = remainingBytes;
                                    writeLength = (writeLength + 3) & ~0x03;
                                }

                                // 边界检查
                                if ((flashDestination + writeLength) > (baseFlashAddress + FLASH_APP_SIZE))
                                {
                                    printf("\r\n!!! Address overflow (0x%08X > 0x%08X) !!!\r\n", flashDestination + writeLength, baseFlashAddress + FLASH_APP_SIZE);
                                    SendByte(CA);
                                    SendByte(CA);
                                    return -2;
                                }

                                // 复制数据到缓冲区
                                memcpy(bufPtr, packetData + PACKET_HEADER, writeLength);

                                //printf("\r\nWriting %lu/%d bytes at 0x%08X (remaining: %lu)\r\n",writeLength, packetLength, flashDestination, remainingBytes);

                                // 写入Flash
                                if (FlashWrite(flashDestination, bufPtr, writeLength) != true)
                                {
                                    printf("\r\n-----Flash write error at 0x%08X------\r\n", flashDestination);
                                    SendByte(CA);
                                    SendByte(CA);
                                    return -4;
                                }

                                // 更新指针和计数器
                                flashDestination += writeLength;
                                fileActualSize += writeLength;
                                remainingBytes = (remainingBytes > writeLength) ? (remainingBytes - writeLength) : 0;

                                // 检查是否完成
                                if (remainingBytes == 0)
                                {
                                    fileDone = 1;
                                }

                                SendByte(ACK);
                            }
                            packetsReceived++;
                            sessionBegin = 1;
                            break;
                    }
                    break;

                case 1: // 接收错误
                    printf("\r\nPacket receive error!\r\n");
                    SendByte(CA);
                    SendByte(CA);
                    return -3;

                default: // 超时或其他错误
                    if (sessionBegin > 0)
                    {
                        if (++errors > MAX_ERRORS)
                        {
                            printf("\r\nToo many errors, aborting!\r\n");
                            SendByte(CA);
                            SendByte(CA);
                            return 0;
                        }
                        printf("\r\nTimeout, retrying... (%d/%d)\r\n", errors, MAX_ERRORS);
                    }
                    SendByte(CREQ);
                    break;
            }

            // 文件传输完成检查
            if (fileDone)
            {
                // 验证实际写入大小
                if (fileActualSize != size)
                {
                    printf("\r\nWarning: File size mismatch (actual %d vs declared %d)\r\n",fileActualSize, size);
                }
                break;
            }
        }

        // 会话结束检查
        if (sessionDone)
        {
            break;
        }
    }

    printf("\r\nFile received successfully. Total size: %d bytes\r\n", fileActualSize);
    printf("Last address: 0x%08X\r\n", flashDestination);

    // 返回实际写入大小(转换为int32_t)
    return (int32_t)fileActualSize;
}


bool VerifyFirmware(uint32_t addr, uint8_t *data, uint32_t len)
{
    for (uint32_t i = 0; i < len; i += 4)
    {
        uint32_t flashData = *(uint32_t *)(addr + i);
        uint32_t bufData = *(uint32_t *)(data + i);

        if (flashData != bufData)
        {
            printf("Verify fail @ 0x%08X: 0x%08X vs 0x%08X\n",
                   addr + i, flashData, bufData);
            return false;
        }
    }
    return true;
}
void UpdateApp(void)
{

    //uint8_t strBuffer[10] = "";
    int32_t imageSize = 0;
    // 在不调试阶段为了降低设备的启动速度,将一些延时的操作避免。
    printf("Waiting for the file to be sent ... (press 'a' to abort)\n\r");
    imageSize = YmodemReceive(g_packetBuffer);
    #if 1 // 调试阶段

    // if (imageSize > 0)
    // {
    // 	printf("\n\n\r Programming Completed Successfully!\n\r\r\n");
    // 	printf("[ Name: %s", g_imageName);
    // 	Int2Str(strBuffer, imageSize);
    // 	printf(",imageSize: ");
    // 	printf("%s", strBuffer);
    // 	printf(" Bytes]\r\n");
    // }
    if (imageSize > 0)
    {
        printf("\nProgramming Successful!\n");
        printf("[Name: %s, Size: %d bytes]\n", g_imageName, imageSize);

        // 添加固件验证步骤
        if (VerifyFirmware(APP_ADDR_IN_FLASH, g_packetBuffer, imageSize))
        {
            printf("Verification PASSED\n");
        }
        else
        {
            printf("Verification FAILED!\n");
            imageSize = -2; // 标记为验证失败
        }
    }
    else if (imageSize == -1)
    {
        printf("\n\n\rThe image size is higher than the allowed space memory!\n\r");
    }
    else if (imageSize == -2)
    {
        printf("\n\n\rVerification failed!\n\r");
    }
    else if (imageSize == -3)
    {
        printf("\r\n\nAborted by user.\n\r");
    }
    else
    {
        printf("\n\rFailed to receive the file!\n\r");
    }
    #endif
}

3.update.h 更新文件

#ifndef _UPDATE_H_
#define _UPDATE_H_

void UpdateApp(void);

#endif

4.串口

#include "usart.h"
#include "gd32H7xx_it.h"

/*--------------------------------全局变量定义-------------------------*/

USART_CFG_TABLE_S Usart1Cfg = {USART1, Usart1GpioInitFunc};
USART_CFG_TABLE_S Usart2Cfg = {USART2, Usart2GpioInitFunc};


/*
	摘要:串口5初始化Gpio复用回调函数
	\param[in]  none
	\retval     none
*/
static void Usart1GpioInitFunc(void)
{
	/*  使能串口0中断 */
  nvic_irq_enable(USART1_IRQn, 0, 0);
  /*  使能GPIOA时钟 */
  rcu_periph_clock_enable(RCU_GPIOD);
  /*  使能串口0时钟 */
  rcu_periph_clock_enable(RCU_USART1);

  /*  设置GPIOA9复用为USART0  */
  gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_5);
  /*  设置GPIOA10复用为USART0  */
  gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_6);

  /*  设置GPIOA9为复用推挽输出  */
  gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_5);
  /* 设置GPIOA9输出类型为推挽,推挽速度50MHz    */
  gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_5);

  /*  设置GPIOA10为复用推挽输出,上拉  */
  gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6);
  /*  设置GPIOA10输出类型为推挽,推挽速度50MHz  */
  gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_6);
}

/*
  摘要:串口2初始化Gpio复用回调函数
  \param[in]  none
  \retval     nones
*/
static void Usart2GpioInitFunc(void)
{
  nvic_irq_enable(USART2_IRQn, 0, 2);

  rcu_periph_clock_enable(RCU_GPIOB);
  rcu_periph_clock_enable(RCU_USART2);

  gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_10);
  gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_11);

  gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10);
  gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_10);

  gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_11);
  gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_11);
}
/*
	摘要:所有串口初始化函数
	\param[in]  none
	\retval     none
*/
void AllUsartInit(void)
{

	UsartInit(&Usart1Cfg, 115200);
	UsartInit(&Usart2Cfg, 115200);
}

/*
	摘要:串口初始化函数
	\param[in]  pUsartCfg: 串口结构体
				BaudRate:  波特率
	\retval     none
*/
static void UsartInit(USART_CFG_TABLE_S *pUsartCfg, uint32_t BaudRate)
{
	pUsartCfg->UsartGpioInitFunc();

	/* USART configure */
	usart_deinit(pUsartCfg->UsartPeriph);
	usart_baudrate_set(pUsartCfg->UsartPeriph, BaudRate);
	usart_word_length_set(pUsartCfg->UsartPeriph, USART_WL_8BIT);
	usart_stop_bit_set(pUsartCfg->UsartPeriph, USART_STB_1BIT);

  /* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
	usart_transmit_config(pUsartCfg->UsartPeriph, USART_TRANSMIT_ENABLE);
	/* 在USART_CTL0寄存器中设置TEN位,使能接收功能;*/
	usart_receive_config(pUsartCfg->UsartPeriph, USART_RECEIVE_ENABLE);
	usart_enable(pUsartCfg->UsartPeriph);
}



//串口数据接收,接收到数据返回真否则假
static bool ReceiveByte(uint8_t *key)
{
	if (usart_flag_get(Usart1Cfg.UsartPeriph, USART_FLAG_RBNE) != RESET)
	{
		*key = (uint8_t) usart_data_receive(Usart1Cfg.UsartPeriph);
		return true;
	}
	else
	{
		return false;
	}
}

//接收数据超时
int32_t ReceiveByteTimeout(uint8_t *c, uint32_t timeout)
{
	while (timeout-- > 0)
	{
		if (ReceiveByte(c))
		{
			return 0;
		}
	}
	return -1;
}

/**
  * @brief  Test to see if a key has been pressed on the HyperTerminal
  * @param  key: The key pressed
  * @retval 1: Correct
  *         0: Error
  */
bool GetKeyPressed(uint8_t *key)
{
	return ReceiveByte(key);
}


/**
  * @brief  Print a character on the HyperTerminal
  * @param  c: The character to be printed
  * @retval None
  */
static void SerialPutChar(uint8_t c)
{
    usart_data_transmit(Usart1Cfg.UsartPeriph, (uint8_t)c);
    while(RESET == usart_flag_get(Usart1Cfg.UsartPeriph, USART_FLAG_TC));
}

void SendByte(uint8_t c)
{
	SerialPutChar(c);
}

/**
  * @brief  写入串口数据 
  * @param  pBuf:指针   len:长度
  * @retval None
  */
u32  WriteUART1(u8 *pBuf, u32 len)
{
	uint32_t i = 0;
  for (i = 0; i < len; i++)
  {
    while (usart_flag_get(Usart1Cfg.UsartPeriph, USART_FLAG_TBE) == RESET);
    usart_data_transmit(Usart1Cfg.UsartPeriph, pBuf[i]);
    while (usart_flag_get(Usart1Cfg.UsartPeriph, USART_FLAG_TC) == RESET);
  }
  return len;  
}
/**
  * @brief  写入串口数据 
  * @param  pBuf:指针   len:长度
  * @retval None
  */
u32  WriteUART2(u8 *pBuf, u32 len)
{
	uint32_t i = 0;
  for (i = 0; i < len; i++)
  {
    while (usart_flag_get(Usart2Cfg.UsartPeriph, USART_FLAG_TBE) == RESET);
    usart_data_transmit(Usart2Cfg.UsartPeriph, pBuf[i]);
    while (usart_flag_get(Usart2Cfg.UsartPeriph, USART_FLAG_TC) == RESET);
  }
  return len;  
}


#ifndef USART_H
#define USART_H
#include "stdint.h"
#include <stdbool.h>
#include <DataType.h>

/*--------------------------宏定义----------------------------*/
/*--------------------------结构体定义------------------------*/

typedef struct 
{
    uint32_t UsartPeriph;                   /* 串口号 */
    void (*UsartGpioInitFunc)(void);        /* 串口初始化Gpio复用回调函数 */
}USART_CFG_TABLE_S;

/*----------------------------函数声明----------------------------*/
static void UsartInit(USART_CFG_TABLE_S *pUsartCfg, uint32_t BaudRate);//串口初始化
static bool ReceiveByte(uint8_t *key);
static void SerialPutChar(uint8_t c);
static void Usart1GpioInitFunc(void);
static void Usart2GpioInitFunc(void);
/*----------------------------外部接口----------------------------*/
extern int32_t ReceiveByteTimeout(uint8_t *c, uint32_t timeout);//接收数据超时
extern void SendByte(uint8_t c);
extern bool GetKeyPressed(uint8_t *key);
extern void AllUsartInit(void);//所有串口初始化函数
u32  WriteUART1(u8 *pBuf, u32 len);
u32  WriteUART2(u8 *pBuf, u32 len);
#endif

4.具体实现

1. 初始化时钟、延时、串口等。
2. 等待上位机按键C。
3. 50ms以内按下则开启串口传输bin文件,否则超时直接退出(我这边是由于还会检测SD卡升级故故超时直接退出,无其他升级检测超时后直接跳转到APP)。

void UartOTA(void)
{
	uint8_t serialKey = 0;
	uint32_t nowTime, lastTime;
	lastTime = (uint32_t)GetRuningTime();
	uint8_t buf[2] = {"1\n"};
	WriteUART1(buf, 2);
	DelayNms(10);
	while (!GetKeyPressed(&serialKey))
	{
		nowTime = (uint32_t)GetRuningTime();
		if ((nowTime - lastTime) >= BOOT_DELAY_COUNT) // 超时没有按键未按键
		{
			printf("超时没有按键按下,请重新启动\r\n");
			return;
		}
	}
	if (serialKey == DOWNLOAD_KEY_VALUE)
	{
		printf("开始更新App程序\r\n");
		UpdateApp();
		GotoApp(APP_BEGIN_ADDR);
	}
}

四.APP实现

image-20250626092605689

中断向量表的偏移,以及开启中断

void InitIrqAfterBoot(void)
{
	SCB->VTOR = FLASH_BASE | 0xA000;
	sys_intx_enable();
}

void UartOTA(void)
{
	uint8_t serialKey = 0;
	uint32_t nowTime, lastTime;
	lastTime = (uint32_t)GetRuningTime();
	uint8_t buf[2] = {"1\n"};
	WriteUART1(buf, 2);
	DelayNms(10);
	while (!GetKeyPressed(&serialKey))
	{
		nowTime = (uint32_t)GetRuningTime();
		if ((nowTime - lastTime) >= BOOT_DELAY_COUNT) // 超时没有按键未按键
		{
			printf("超时没有按键按下,请重新启动\r\n");
			return;
		}
	}
	if (serialKey == DOWNLOAD_KEY_VALUE)
	{
		printf("开始更新App程序\r\n");
		UpdateApp();
		GotoApp(APP_BEGIN_ADDR);
	}
}

四.APP实现

中断向量表的偏移,以及开启中断

void InitIrqAfterBoot(void)
{
	SCB->VTOR = FLASH_BASE | 0xA000;
	sys_intx_enable();
}
### GD32以太网接口实现远程固件更新 #### 实现原理 GD32设备可以通过其内置的以太网控制器连接到互联网,利用IAP (In Application Programming)技术,在应用程序运行期间通过网络下载并安装新的固件版本。这种方式不仅提高了系统的灵活性和可维护性,还减少了停机时间[^1]。 #### 准备工作 为了成功实施这一过程,需准备如下组件: - 支持以太网功能的GD32微控制器开发板; - 可靠稳定的局域网环境; - 配置好DHCP服务器或静态IP地址分配方案以便于通信; - 编写用于接收HTTP请求并将数据传递给IAP模块的应用层协议栈; #### 软件设计要点 针对GD32系列MCU的特点,软件架构应考虑以下几个方面: ##### 初始化阶段 初始化过程中要确保硬件资源被正确配置,特别是涉及到以太网MAC/PHY设置的部分。这一步骤通常由HAL库中的函数自动处理,开发者只需调用相应的API即可完成基本设定。 ##### 数据传输机制 采用TCP/IP协议族作为底层通讯手段,上层可以选用HTTP(S)/FTP等常见文件传输方式来获取待刷写的二进制映像。对于安全性有较高要求的应用场景,则建议启用SSL/TLS加密通道防止中间人攻击或其他形式的数据篡改行为发生。 ##### IAP流程控制 当接收到完整的固件包之后,便进入至关重要的在线编程环节——即执行真正的Firmware Over-The-Air(OTA)操作。此时应当暂停当前正在执行的任务(如果有必要的话),接着按照特定格式解析输入流,并将其烧录至指定存储区域中去。最后重启系统使更改生效之前还需验证新镜像的有效性和完整性。 ```c // 示例代码片段展示了一个简单的基于HTTP GET方法从远端服务器拉取最新版固件的过程。 #include "gd32fxxx.h" /* ... */ void http_get_firmware(const char *url){ struct espconn conn; /* 建立ESPCONN对象 */ espconn_create(&conn); /* 设置目标URL */ strcpy(conn.proto.tcp->remote_ip, url); /* 发送GET请求 */ espconn_regist_connectcb(&conn, on_connected); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值