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.设计思路
- 设计单片机的Flash分区,一部分放置BootLoader,另一部分是APP。
- 这个分区的标准具体以编译后BootLoader的程序2进制文件的大小来分区。
- 在这儿我将前1M(这个可以根据具体的BootLoader程序的大小来设计)区域的地方用来存储BootLoader。后面的地址空间用来存储APP。
- 到这里就清晰了,所以程序就需要实现两部分代码。一个BootLoader,另一个就是APP。
- BootLoader的大小可以通过map文件来查看具体的大小。
本人GD32F4的OTA链接:https://blog.csdn.net/lcl113480/article/details/142063560
二.GD32H757 FMC内部Flash读写
1.简介
闪存控制器(FMC),提供了片上闪存需要的所有功能。包括扇区擦除,整片擦除,以及编程
操作等。
2.主要特性
- 高达3840KB字节的片上闪存可用于存储指令或数据;
- 支持64位双字、32位整字、16位半字或字节读操作;
- 支持64位双字、32位整字编程,扇区擦除和整片擦除操作;
- 选项字节会在每次系统复位时装载到选项字节控制寄存器;
- 具有安全保护状态,可阻止对代码或数据的非法读访问;
- 具有擦除和编程保护状态,可阻止意外写操作;
- 具有仅执行的专用代码读保护(DCRP)区域;
- 具有仅能在安全模式下访问的安全用户区域;
3.闪存结构
-
FMC 支持用以访问代码或数据的 64 位 AXI 接口以及用以访问寄存器的 32 位 AHB 从机接口。FMC 框图显示了 FMC 的架构。
-
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用于引导加载程序的信息块。主存储闪存的每个扇区都可以单独擦除。
有关Flash擦写操作均需要先解锁Flash,然后进行擦写操作,擦写完成后再进行锁Flash,注意Flash特性只能由1写0,也就是Flash需要先擦除才能写入新的数据,如果确保写入地址的数据为全0xFF,也可以直接写入。读取Flash数据可以采取直接寻址的方式进行读取。
4.FMC中断
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实现
中断向量表的偏移,以及开启中断
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();
}