示例代码为STM32H7系列串口升级,如果在代码中发现问题欢迎联系本人(加本人微信或者私信)
后续会免费发布上位机应用和源代码。
IAP.c
/*
* @Author: ZDJ
* @Date: 2024-10-12 21:28:45
* @LastEditTime: 2024-10-13 22:15:46
* @LastEditors: ZDJ
* @Description: STM32H7系列串口IAP升级
* @FilePath: \u-bootc:\Users\zdj\Desktop\DMA_IDLE\MDK-ARM\IAP.c
* @联系方式:18567222619
*/
#include "IAP.h"
//类型定义
#define Write_Type 4 /* 写入的是半字还是字,2是半字,4是字 */
#define RxData_Size 131 //2个接收标志,128个数据,1个校验和
#define BLOCK_SIZE 16 // 解码数组个数
uint8_t xorkey[BLOCK_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};//异或解码数组
uint32_t ulPage_DataBuf[STM32_FlASH_Page_SIZE / Write_Type]; //FLASH页面数据缓存
uint8_t Uart_RxData_Buf[RxData_Size]; //串口接收数组
UART_HandleTypeDef IAP_UART;
DMA_HandleTypeDef IAP_DMA_uart_rx;
typedef void (*pIapFun_TypeDef)(void); //定义一个函数类型的参数
void IAP_Download_Init(UART_HandleTypeDef *huart, DMA_HandleTypeDef *hdma_uart_rx);
void IAP_Download(void);
uint8_t IAP_Jump_Flag(void);
static void Write_App_Bin (uint32_t StartAddr, uint8_t * pBin_DataBuf, uint32_t BufLength);
static void Error_Handler(void);
static uint32_t GetSector(uint32_t Address);
static void STM32_FLASH_Erase(uint32_t WriteAddr, uint32_t NumToWrite);
static void STM32_FLASH_Write(uint32_t WriteAddr, uint32_t * pBuffer, uint32_t NumToWrite);
static void STM32_FLASH_Read(uint32_t ReadAddr, uint32_t *pBuffer, uint32_t NumToRead);
static void STM32_FLASH_Copy(uint32_t ReadAddr, uint32_t WriteAddr, uint32_t size);
static void xor_encrypt_decrypt(uint8_t *datas, uint32_t datalen, uint8_t *key);
static void ExecuteApp(uint32_t ulAddr_App);
static void System_Reset(void);
static uint8_t CRC_SUM_CHECK(uint8_t *data);
typedef enum
{
FALSE = 0U,
TRUE = !FALSE
} FlagStatus_t;
typedef struct
{
uint8_t rx_flag;
uint8_t ucAppBuf[APP_MAX_LEN]; //APP应用程序接收缓存
uint32_t usAPPLength; //APP应用程序接收长度,在固件接收完成后需要将该值赋给Write_AppInfo.size
uint32_t FLASH_Page_Buf[STM32_FlASH_Page_SIZE / Write_Type]; //最大STM32_FlASH_Page_SIZE
void (*STM32_FLASH_Write)(uint32_t, uint32_t *, uint32_t); //从指定地址开始写入指定长度的数据
void (*xor_encrypt_decrypt)(uint8_t *datas, uint32_t datalen, uint8_t *key);
void (*System_Reset)(void);
uint32_t (*GetSector)(uint32_t Address);
uint8_t (*CRC_SUM_CHECK)(uint8_t *data);
void (*Write_App_Bin)(uint32_t, uint8_t *, uint32_t); //写入APP的bin文件
void (*ExecuteApp)(uint32_t);
void (*STM32_FLASH_Copy)(uint32_t ReadAddr, uint32_t WriteAddr, uint32_t size);
void (*STM32_FLASH_Read)(uint32_t ReadAddr, uint32_t *pBuffer, uint32_t NumToRead);
void (*STM32_FLASH_Erase)(uint32_t WriteAddr, uint32_t NumToWrite);
void (*Error_Handler)(void);
} STM32_IAP_INNER;
STM32_IAP_INNER inner =
{
0,
{0},
0,
{0},
STM32_FLASH_Write,
xor_encrypt_decrypt,
System_Reset,
GetSector,
CRC_SUM_CHECK,
Write_App_Bin,
ExecuteApp,
STM32_FLASH_Copy,
STM32_FLASH_Read,
STM32_FLASH_Erase,
Error_Handler,
};
IAP_t IAP =
{
#if BootLoder
IAP_Jump_Flag,
#endif
IAP_Download_Init,
IAP_Download,
};
AppInfo_t AppInfo;
/**
* @name: IAP_Download_Init(UART_HandleTypeDef *huart, DMA_HandleTypeDef *hdma_uart_rx)
* @description: CubeMX初始化只需配置中断和接收DMA功能,然后将生成的串口和DMA写入下面函数
* @param {UART_HandleTypeDef} *huart 下载使用的串口
* @param {DMA_HandleTypeDef} *hdma_uart_rx 下载使用的DMA
* @return {*}
*/
void IAP_Download_Init(UART_HandleTypeDef *huart, DMA_HandleTypeDef *hdma_uart_rx)
{
IAP_UART = *huart;
IAP_DMA_uart_rx = *hdma_uart_rx;
__HAL_UART_ENABLE_IT(&IAP_UART, UART_FLAG_IDLE);
HAL_UART_Receive_DMA(&IAP_UART, Uart_RxData_Buf, sizeof(Uart_RxData_Buf));
}
/**
* @name: IAP_Download()
* @description: 需要周期性或在while(1)中不断调用
* @return {*}
*/
void IAP_Download()
{
if(inner.rx_flag == 2) //整个固件包接收完成
{
#if APP
//Write_AppInfo.size=IAP.usAPPLength;
//需要调用外部存储器写函数将Write_AppInfo写入
#endif
inner.Write_App_Bin(FLASH_BOOTDownload_ADDR, inner.ucAppBuf, inner.usAPPLength);//整个固件包接收完成,并将其写入固件存放地址
printf("APP固件刷写完成!");
printf("3s后程序自动跳转!");
inner.System_Reset();//程序软复位
}
}
#if BootLoder
/**
* @name: uint8_t IAP_Jump_Flag(void)
* @description: 判断是否需要将固件写入APP代码区,或者直接跳转到APP固件区
* 正常情况下该函数不会运行到最终返回值,如果最终返回1,则在固件区和APP区不存在代码
* @return {*}
*/
uint8_t IAP_Jump_Flag(void)
{
AppInfo_t APP_Info;
uint8_t flag = 1;
inner.STM32_FLASH_Read(FLASH_Variate_ADDR, (uint32_t*)&APP_Info, sizeof(APP_Info)); //读取STM32内部变量区的版本信息
if(AppInfo.Version != APP_Info.Version || AppInfo.BuildDate != APP_Info.BuildDate || AppInfo.BuildTime != APP_Info.BuildTime)
{
//对比STM32内部变量区与外部存储芯片中存储的版本信息
flag = 2; //需要更新APP代码
}
if(flag == 2)
{
inner.STM32_FLASH_Write(FLASH_Variate_ADDR, (uint32_t*)&AppInfo, sizeof(AppInfo)); //将版本信息写入变量区
inner.STM32_FLASH_Copy(FLASH_BOOTDownload_ADDR, FLASH_APP1_ADDR, AppInfo.size); //将固件区代码copy到APP区
inner.ExecuteApp(FLASH_APP1_ADDR); //跳转到APP区执行
}
if(AppInfo.Version[0] != 'S' && AppInfo.Version[1] != 'T'&AppInfo.Version[2] != 'M')
{
//外部存储芯片中不存在版本信息,故不存在APP区和固件区不存在代码
return flag;
}
else
{
//外部存储芯片中存在版本信息,且APP区和固件区代码一致,直接跳转到APP区
inner.ExecuteApp(FLASH_APP1_ADDR);
}
return 0;
}
#endif
/**
* @name: void IAP_UART_IRQHandler(void)
* @description: 串口数据接收中断
* @return {*}
*/
void IAP_UART_IRQHandler(void)
{
uint16_t i = 0;
uint16_t rx_cnt = 0;
if(__HAL_UART_GET_FLAG(&IAP_UART, UART_FLAG_IDLE) == SET)
{
__HAL_UART_CLEAR_FLAG(&IAP_UART, UART_FLAG_IDLE);
HAL_UART_DMAStop(&IAP_UART);
rx_cnt = RxData_Size - __HAL_DMA_GET_COUNTER(&IAP_DMA_uart_rx);
if(rx_cnt >= 3 && inner.rx_flag == 0)
{
inner.rx_flag = 1; /* 读取接收到的数据*/
}
}
inner.xor_encrypt_decrypt(Uart_RxData_Buf, RxData_Size, xorkey);//数据解码
if(Uart_RxData_Buf[0] == 0x55 && Uart_RxData_Buf[1] == 0X66 && inner.rx_flag == 1)//数据帧头正确才会进行接收
{
if(inner.CRC_SUM_CHECK(Uart_RxData_Buf) == Uart_RxData_Buf[RxData_Size - 1])
{
for(i = 2; i < RxData_Size - 1; i++)
{
//将接受到的数据放入APP应用程序接收缓存
if(inner.usAPPLength < APP_MAX_LEN)
{
inner.ucAppBuf[inner.usAPPLength++] = Uart_RxData_Buf[i];
}
else
{
printf("错误:APP固件长度超过了最大值!");
}
}
HAL_UART_Transmit(&IAP_UART, &Uart_RxData_Buf[RxData_Size - 1], sizeof(uint8_t), 10); //将最后一位解码后的校验和返回给上位机
}
inner.rx_flag = 0;
}
if(Uart_RxData_Buf[0] == 0xAA && Uart_RxData_Buf[1] == 0XBB && inner.rx_flag == 1) //bin文件传输完成
{
inner.rx_flag = 2;
}
//继续串口接收(中断模式,130个字节)
HAL_UART_Receive_DMA(&IAP_UART, Uart_RxData_Buf, sizeof(Uart_RxData_Buf));
}
/*
* @name Write_App_Bin
* @brief 通过IAP写入应用程序BIN文件
* @param StartAddr :起始地址(起始地址必须与Page页面地址对齐)
* pBin_DataBuf:数据指针
* BufLength :应用程序长度(写入的32位数据的个数)
* @retval None
*/
static void Write_App_Bin (uint32_t StartAddr, uint8_t * pBin_DataBuf, uint32_t BufLength)
{
uint16_t usCnt = 0; //计数
uint32_t ulIndex, ulAppWriteAddr = StartAddr; //索引,APP数据写入地址
uint8_t* pBinData = pBin_DataBuf; //APP数据指针
//起始地址与Page页面地址对齐校验
if(((StartAddr - FLASH_BASE) % STM32_FlASH_Page_SIZE) != 0)
{
printf("错误:FLASH写入初始地址没有与Page页面地址对齐!");
inner.Error_Handler();
}
//按页写入FLASH
for(ulIndex = 0; ulIndex < BufLength; ulIndex += Write_Type)
{
ulPage_DataBuf[usCnt++] = (uint32_t)(*(pBinData + 3) << 24) + (uint32_t)(*(pBinData + 2) << 16) + (uint32_t)(*(pBinData + 1) << 8) + (uint32_t)(*pBinData); //数据处理,按半字发送
pBinData += Write_Type; //指针偏移4个字节
//数据达到1页
if(usCnt == STM32_FlASH_Page_SIZE / Write_Type)
{
usCnt = 0; //计数清零
inner.STM32_FLASH_Write(ulAppWriteAddr, ulPage_DataBuf, STM32_FlASH_Page_SIZE / Write_Type); //发送一页
ulAppWriteAddr += STM32_FlASH_Page_SIZE; //写入地址偏移一页
}
}
//写入最后不到一页的内容
if(usCnt > 0)
{
inner.STM32_FLASH_Write(ulAppWriteAddr, ulPage_DataBuf, usCnt);
}
}
/**
* @name: void STM32_FLASH_Erase(uint32_t WriteAddr, uint32_t NumToWrite)
* @description:
* @param {uint32_t} WriteAddr 擦除首地址
* @param {uint32_t} NumToWrite 需要擦除的32位字的个数
* @return {*}
*/
static void STM32_FLASH_Erase(uint32_t WriteAddr, uint32_t NumToWrite)
{
//擦除变量
uint32_t PageError = 0;
uint32_t FirstSector = 0, NbOfSectors = 0;
FirstSector = GetSector(WriteAddr);
/* Get the number of sector to erase from 1st sector*/
NbOfSectors = GetSector(NumToWrite) - FirstSector + 1;
FLASH_EraseInitTypeDef FLASH_EraseInit ;/* 这里是用的H743,如果要移植F4,这里不一样 */
FLASH_EraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
FLASH_EraseInit.Banks = FLASH_BANK_1;
FLASH_EraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;/* 这里是用的H743,如果要移植F4,这里不一样,此时是选择的32位 */
FLASH_EraseInit.Sector = FirstSector; /* 这里是用的H743,如果要移植F4,这里不一样,H743S是扇区编号,F4是页地址 */
FLASH_EraseInit.NbSectors = NbOfSectors;
//擦除页数据,并进行校验
if(HAL_FLASHEx_Erase(&FLASH_EraseInit, &PageError) != HAL_OK)
{
printf("错误:FLASH擦除失败!");
inner.Error_Handler();
}
printf("PageError = 0x%.8x\r\n", PageError);
while(PageError != 0xFFFFFFFF)
{
inner.Error_Handler();
}
}
/*
* @name STM32_FLASH_Write
* @brief 从指定地址开始写入指定长度的数据
* @param WriteAddr :起始地址(起始地址必须与Page页面地址对齐)
* pBuffer :数据指针
* NumToWrite:字数(写入的32位数据的个数)
* @retval None
*/
static void STM32_FLASH_Write(uint32_t WriteAddr, uint32_t * pBuffer, uint32_t NumToWrite)
{
uint32_t i;
uint8_t Cnt = 0; //擦除与写入时,错误计数
FlagStatus_t Check_Flag; //写入时校验标志位
HAL_FLASH_Unlock(); //解锁
inner.STM32_FLASH_Erase( WriteAddr, NumToWrite);
//写入地址合法校验
if((WriteAddr < FLASH_BASE) || (WriteAddr > FLASH_APP1_END_ADDR))
/*FLASH_BANK2_BASE这里可能也得改,这里在H743有两个BASE,所以移植到F4要改 ,一定要在BASE内 */
{
printf("错误:FLASH写入地址非法!");
inner.Error_Handler();
}
printf("写入%u字节\r\n", NumToWrite * Write_Type); //写入页数据,并进行校验
do
{
//写入数据
for(i = 0; i < NumToWrite; i++)
{
/*FLASH_TYPEPROGRAM_FLASHWORD这里要和FLASH_EraseInitTypeDef 中的电压一致,H743存在,F4可能不存在,F4得是半字 */
HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, WriteAddr, pBuffer[i]);
WriteAddr += Write_Type; //地址增加4
}
inner.STM32_FLASH_Read(WriteAddr - NumToWrite * Write_Type, inner.FLASH_Page_Buf, NumToWrite); //读出数据
Check_Flag = TRUE; //校验数据
for(i = 0; i < NumToWrite ; i++)
{
if(pBuffer[i] != inner.FLASH_Page_Buf[i])
{
Check_Flag = FALSE;
if((Cnt++) == 10)
{
printf("页面地址:0x%.8x写入错误达到10次\r\n", WriteAddr);
inner.Error_Handler();
}
}
}
}
while(Check_Flag != TRUE);
HAL_FLASH_Lock(); //上锁
}
/*
* @name STM32_FLASH_Read
* @brief 从指定地址开始读出指定长度的数据
* @param ReadAddr:起始地址
* pBuffer:数据指针
* NumToRead:字(32位)数
* @retval None
*/
static void STM32_FLASH_Read(uint32_t ReadAddr, uint32_t *pBuffer, uint32_t NumToRead)
{
uint16_t i;
//地址校验
if((ReadAddr % Write_Type) != 0)
{
printf("错误:FLASH读出地址必须为4的整数倍!");
inner.Error_Handler();
}
for(i = 0; i < NumToRead; i++)
{
pBuffer[i] = ReadAddr; //读取4个字节.
ReadAddr += Write_Type;//偏移4个字节.
}
}
/**
* @name: void STM32_FLASH_Copy(uint32_t ReadAddr, uint32_t WriteAddr, uint32_t size)
* @description: 用于将固件区的代码copy到APP区
* @param {uint32_t} ReadAddr 读取的数据首地址
* @param {uint32_t} WriteAddr 写入的数据首地址
* @param {uint32_t} size copy的32位字的个数
* @return {*}
*/
static void STM32_FLASH_Copy(uint32_t ReadAddr, uint32_t WriteAddr, uint32_t size)
{
uint32_t *Copy_Buff;
inner.STM32_FLASH_Read(ReadAddr, Copy_Buff, size);
inner.STM32_FLASH_Write(WriteAddr, Copy_Buff, size);
}
/**
* @name: static void xor_encrypt_decrypt(uint8_t *datas, uint32_t datalen, uint8_t *key)
* @description: 数据解码
* @param {uint8_t} *datas 需要解码的数据
* @param {uint32_t} datalen 需要解码数据的长度
* @param {uint8_t} *key 解码秘钥
* @return {*}
*/
static void xor_encrypt_decrypt(uint8_t *datas, uint32_t datalen, uint8_t *key)
{
int i = 0;
for (i = 0; i < datalen; ++i)
{
datas[i] = datas[i] ^ key[i % BLOCK_SIZE];//即xorkey[BLOCK_SIZE]
}
}
/**
* @name: static uint8_t CRC_SUM_CHECK(uint8_t *data)
* @description: 数据校验和
* @param {uint8_t} *data 需要校验的数据
* @return {*}校验和
*/
static uint8_t CRC_SUM_CHECK(uint8_t *data)
{
int i = 0;
uint8_t CRC_SUM = 0;
for(i = 2; i < RxData_Size - 1; i++)
{
CRC_SUM += data[i];
}
return CRC_SUM;
}
__asm void MSR_MSP ( uint32_t ulAddr )
{
MSR MSP, r0 //set Main Stack value
BX r14
}
/*
* @name ExecuteApp
* @brief 跳转到应用程序段
* @param ulAddr_App: 应用程序段起始地址
* @retval None
*/
static void ExecuteApp(uint32_t ulAddr_App)
{
pIapFun_TypeDef pJump2App;
//检查栈顶地址是否合法
if(((*(__IO uint32_t*)ulAddr_App) & 0x2FFE0000) == 0x20000000)
{
printf("栈顶合法,运行APP\r\n");
pJump2App = (pIapFun_TypeDef) * (__IO uint32_t *)(ulAddr_App + 4); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(__IO uint32_t *)ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
pJump2App (); //跳转到APP
}
else
{
printf("错误:栈顶地址不合法!");
HAL_ResumeTick();
inner.Error_Handler();
}
}
/**
* @name: static uint32_t GetSector(uint32_t Address)
* @description: 得到扇区编号
* @param {uint32_t} Address
* @return {*}
*/
static uint32_t GetSector(uint32_t Address)
{
return (Address - FLASH_BASE) / FLASH_SECTOR_SIZE;
}
/*软件复位函数*/
static void System_Reset(void)
{
__set_FAULTMASK(1); /*关闭所有中断*/
HAL_NVIC_SystemReset(); /*进行软件复位*/
}
static void Error_Handler(void)
{
__disable_irq();
while (1)
{}
}
/********************************************************
End Of File
********************************************************/
IAP.h文件
#ifndef __IAP_H__
#define __IAP_H__
#include "main.h"
#include "usart.h"
#define APP 0
#define BootLoder 1
#define FLASH_Variate_ADDR (FLASH_BASE + (FLASH_SECTOR_SIZE * 1)) //变量存放地址,用于判断是否程序已更新,更新则直接跳转
#define FLASH_BOOTDownload_ADDR (FLASH_BASE + (FLASH_SECTOR_SIZE * 2)) //固件存放地址,上电初始化和应用程序运行过程中下载固件时存放地址
#define FLASH_APP1_ADDR (FLASH_BASE + (FLASH_SECTOR_SIZE * 4)) //第一个应用程序起始地址(存放在FLASH)
#define FLASH_APP1_END_ADDR (FLASH_BASE + FLASH_BANK_SIZE - 1) /*APP运行地址到FLASH末尾 */
#define IAP_UART_IRQHandler USART1_IRQHandler
//应用程序最大长度(实战板的RAM只有64k)
#define APP_MAX_LEN (FLASH_APP1_ADDR-FLASH_BOOTDownload_ADDR) //50kB=50*1024=51200
//#define STM32_FlASH_Total_SIZE 512 //FLASH容量大小(单位为K)
#define STM32_FlASH_Page_SIZE 2048 //页面尺寸 单位字节
typedef struct
{
char Version[32]; // 软件版本
char BuildDate[32]; // 程序编译日期
char BuildTime[32]; // 程序编译时间
uint32_t size; //程序的大小,将APP应用程序接收长度 赋予该值
} AppInfo_t;
#if APP
AppInfo_t Write_AppInfo = //在APP应用程序中需要将该结构体写到外部存储区,与AppInfo地址一样
{
"STM32_V0.1.5",
__DATE__,
__TIME__,
0 //程序的大小,将APP应用程序接收长度 赋予该值
};
#endif
#if BootLoder
extern AppInfo_t AppInfo ; //仅在bootloder中需要上电后从外部存储器读取AppInfo信息,与Write_AppInfo地址一样
#endif
typedef struct
{
#if BootLoder
uint8_t (*IAP_Jump_Flag)(); //跳转APP应用程序,仅需要再bootloder中调用
#endif
void (*IAP_Download_Init)(UART_HandleTypeDef *huart, DMA_HandleTypeDef *hdma_uart_rx);//IAP初始化函数
void (*IAP_Download)(); //需要周期性或在while(1)中不断调用
} IAP_t;
extern IAP_t IAP;
#endif