STM32 IAP升级(bootLoader)
今天给大家做一个STM32F系列的IAP 升级, 网上有不少例子,我这里字数有点多如果看请耐心看完,如果嫌麻烦可以看其他的帖子
我这边以103为例子,用的人多,不过最近芯片涨价太狠也不知道还有人拿来玩不。当然我做这个不仅仅是可以用在F103上面 F系列/G系列/L系列/H也是可以用的。
废话不多说 先来说明一下这个IAP是个什么,我就简单举个例子 ,大家请看下面:
通常单片机的程序:
加入IAP后单片机的程序:
这个IAP 呢 说白了就是在原来单片机只有一个程序的基础下增加一个程序,这样单片机里面就有两个程序。
重点来了:单片机里面为啥要加个IAP程序呢?写一个就够了还要整两个不是吃饱了没事干吗?
其实不然,如果大家有体验过连续一个星期甚至几个星期都在刷单片机代码就不会觉得了。现实中会有这种情况出现 就是咱做好的电路板写完了程序通常会被一些精美外壳封装好,有些为了防止漏水啊或者什么的 通常都封装的很严实的。要想软件出问题了去拆开得废多大劲。与其这样不如写个能通过通信对单片机进行 程序下载的,就算出现问题了也能通过什么usb口啊 网口啊 进行刷代码,甚至可以连接wifi模块在线远程刷新。
以上为前言啊 ! 说这么多,IAP就是一个能通过其他通信方式对 已经封装好的或者不能通过硬件下载口下载程序的第三方烧录方式。
好接着往下面说,前面大概知道了怎么个东西了,现在我们要开始来实现功能了。怎么实现呢?(如果有什么疑问的请带着疑问往下面看! 很多问题我会写在下面!)
先给大家说一下整体的流程:
上位机发起更新请求 →下位机从用户区域跳转到IAP区域→ 中间传输通过传输协议 →发送到下位机 → 下位机接收文件通过IAP程序更新 →下位机更新完成跳转到用户区域
第一步是 要确定好我们要干啥,首先呢IAP下载到单片机,所以我们需要一个能通信并且内存稍大点的单片机,如果是简单程序呢一个STM32C8T6 就够了
这是我从官方扒下来的图片,这边我只找了103的,有其他型号的大家可以去官网上面看。
单片机选好好过后,IAP传输肯定需要有一个发送方,这里的话我写了C# 的Winform做为发送端,回头我在开一篇文档说一下,然后这个就放一个传送门在这里:👇👇👇
传送门:IAP上位机部分
然后下一步,前面两步有发送端了,有接收端了,剩下的就是要通过一个方式上位机把一个程序传输给单片机。这一部分就是通信协议了
/********************** 以上部分就是我们需要的最基本的东西(上位机,下位机,通信协议) *************************************/
通信协议讲解:
这一部分有很多人都不愿开源出来,当然每一个人写的东西都不一样,一些就是简单的传输,一些就是加了算法校验的保证文件的 准确性和完整性 这边的话我是用的我自己的方式来实现的 ,我里面就不画的太麻烦,简单点大家都懂:
👇👇👇
基础描述
1.协议主要做的就是上面的功能,上位机和下位机要进行更新文件下载,首先呢需要双方都能正常确认通信,一般就是上位机发起请求(请求里面包含下载的信息数据,比如更新文件的版本号,对应的下载包数量之类的)然后让下位机接收并且反馈(下位机初始化),完成一次循环
2.文件传输,握手协议达成之后开始进行数据传输,考虑到一些文件比较大所以这里我们是吧一个bin文件进行拆分来通过多次收发数据来进行烧录文件的发送的。防止一些文件过大导致单片机接收不过来
3.总校验是文件在传输完成后在进行校验一次 防止文件被拆分后拼接出错。
具体数据
上面大概介绍了下,请看下面👇👇👇
1.一条一条分析 首先是握手:
说明:
1.type:“commond” (指令类型 为 命令 固定格式)
2.value:“EFXX10AF” (值 EF数据头 XX 包数量 10AF 数据尾)
3.“response”:“0xEF,0x10,0x00” (反馈为固定校验信息)
上位机发送请求命令:等待下位机进行握手反馈,下位机反馈对应格式反馈上位机,如果上位机多次发送握手申请仍然没有下位机反馈就判定为握手不成功
2.握手完成后:开始数据传输 下发文件请求
说明:
1.type:“commond”,“value”:“EFDAEEEE” (固定格式)
2.response:“0xEF,0xFA,0xFA” (反馈为固定格式)
上位机发送文件下发请求,下位机接收到后开始进行数据下发等待,如果上位机多次发送握手申请仍然没有下位机反馈就判定为请求数据下发失败
3.数据发送:一个大数据包
说明:
1.“type”:“data” (发送类型为数据)
2.“value”:“xxxxxxxxxxxxxxxxxxxxxxxxx” (数据)
3.“response”:“0xCC,0xCC,1” (反馈数据 第三个值为反馈接收的小包数量 其余的为固定值)
4.{“response”:“0xEF,XX,XX”} (反馈数据 0xEF为请求头,第二第三个值为 八个包的CRC校验值)
上位机 发送一大包数据,数据再次分为8次发送,每发送一次数据包,下位机记录反馈一次,当8个数据包发送完成单片机开始进行CRC运算,将校验值反馈给上位机
4.数据校验:上位机验证当前大数据包是否有问题
说明:
1.“type”:“commond” (指令类型 为 命令)
2.“value”:“EFDA3FXX” (EFDA3F为固定格式, XX为发送的大分包数量)
3.“response”:“0xEF,1,0xFA” (0XEF 为数据头 0xFA为数据尾 第二个数据为 大包数)
上位机在上一步接收到下位机传输上来的CRC 校验值之后,开始进行数据分析和对比数据是否相等,如果相等就输出当前步骤语句,发送给单片机,单片机收到数据,反馈当前包接收的包数量
5.数据发送:循环发送所有大数据包
说明:
依次循环执行步骤3 步骤4 每执行一次 发送一个包,直到发送完成
6.总数据校验:所有数据接收完成后进行整个Bin文件校验
说明:
1.type:“commond” (指令类型 为 命令)
2.“value”:“EFDADACC” (固定格式为总CRC校验)
3.“response”:“0xEF,XX,XX” 上传总CRC校验值 第二第三个数据为CRC参数
等待完下位机反馈最后一个数据包,上位机发起所有文件CRC 请求,下位机反馈接收到所有包的CRC 校验总值 然后反馈上来
7.验证单片机:验证单片机 做唯一识别查询时使用
说明:
1.“value”:“EF0B0BEC” (固定格式为 请求CID)
2.cid:“1C003D3439470C39303131” (设备唯一表示符)
此步骤为区分单片机号
8.发送完毕:上位机发送完毕单片机开始执行文件更新
说明:
1.“value”:“EFE0E0EC” (固定格式为 IAP更新完成)
2.response:“0xEF,0x0A,0xFA” (反馈固定格式)
上位机 发送 最终json语句,下位机 执行反馈
以上就是整个文件传输通信协议,主要做的工作就是吧要烧录的文件通过上位机发送到单片机里面。
下面是单片机软件上面的说明:
先看stm32Cubexm
上面就是单片机基础配置, 就用一个串口和TIMER1 ,串口波特率根据自己需求调整,time1 分频72-1 周期100。设置ok
下面是时钟配置:
我这里接的是外部晶振 主频72M,大家可以用HSI 处理
以下是mian.c 函数,主要功能就是在while循环里面进行标志位判定,然后跳转到对应函数, 详情请看下面注释:
int main(void)
{
/* USER CODE BEGIN 1 */
//UseFlag.bits.COMState =0;
//UseFlag.bits.DataState =0;
//UseFlag.bits.WillbeSuccessfull =0;
//UseFlag.bits.TOSuccessfull= 0;
/* USER CODE END 1 */
uint8_t count =0;
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init(); //系统生成不管
SystemClock_Config(); //系统生成不管
MX_GPIO_Init(); //系统生成不管
MX_TIM1_Init(); //系统生成不管
MX_USART1_UART_Init(); //系统生成不管
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //接收中断使能,自己可以判断接收标志,第一种中断方式
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //定义接收空闲使能
HAL_TIM_Base_Start_IT(&htim1); //开始定时
//HAL_UART_Receive_IT(&huart1,&Rxdata,1);
while (1)
{
/* USER CODE END WHILE */
if(system_flag.bits.system_1ms == ON) //system loop 1ms
{
system_flag.bits.system_1ms = OFF;
}
if(system_flag.bits.system_10ms == ON) //system loop 10ms
{
system_flag.bits.system_10ms = OFF;
System_Json_Main(); //处理串口接收到的数据
if(UseFlag.bits.TOSuccessfull==1) //接收完成标志位置位
{
/*以下段都说释放定义了的外设 使能*/
UseFlag.bits.TOSuccessfull =0;
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_15);
HAL_UART_DeInit(&huart1);
HAL_TIM_Base_DeInit(&htim1);
__HAL_TIM_DISABLE_IT(&htim1, TIM_IT_TRIGGER);
HAL_TIM_Base_Stop_IT(&htim1);
system_flag.interface =0; //标志位清空
UseFlag.IAPinterface =0; //标志位清空
IAP_ExecuteApp(APP_START_ADDR); //这句话是区域跳转
}
}
if(system_flag.bits.system_100ms == ON) //system loop 10ms
{
system_flag.bits.system_100ms = OFF;
}
if(system_flag.bits.system_500ms == ON) //system loop 1s
{
system_flag.bits.system_500ms = OFF;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_15); //这个脚接的LED 灯翻转高低电平 闪烁灯
if((++count > 100)&&(UseFlag.bits.UpdateState==0)) //计数达到并且没有更新标志过来
{
/*以下段都说释放定义了的外设 使能*/
count=0;
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_15);
HAL_UART_DeInit(&huart1);
HAL_TIM_Base_Stop_IT(&htim1);
HAL_TIM_Base_DeInit(&htim1);
HAL_NVIC_DisableIRQ(TIM1_UP_IRQn);
__HAL_RCC_TIM1_CLK_DISABLE();
IAP_ExecuteApp(APP_START_ADDR) ; //这句话是区域跳转
}
}
}
}
然后是串口处理接收函数和定时器处理函数 👇👇,主要就是一个计时器供while 里面使用 ,另外一个就是串口接收函数 处理上位机下发得数据
/*******************************************************************************
Name :HAL_TIM_PeriodElapsedCallback
Syntax :void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint8_t system10ms_cnt =0;
static uint8_t system100ms_cnt =0;
static uint16_t system500ms_cnt =0;
if(htim->Instance == TIM1) //系统调度周期定时器 1ms 10ms 1s
{
/*对应定时器到时间 计数对应的值*/
system_flag.bits.system_1ms = ON;
if((++system10ms_cnt) >= 10)
{
system10ms_cnt =0;
system_flag.bits.system_10ms = ON;
}
if((++system100ms_cnt) >= 100)
{
system100ms_cnt =0;
system_flag.bits.system_100ms = ON;
}
if((++system500ms_cnt) >= 500)
{
system500ms_cnt =0;
system_flag.bits.system_500ms = ON;
}
}
}
/*******************************************************************************
Name :HAL_UART_RxCpltCallback
Syntax :void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|********************************************************************************/
uint8_t CMD_Data_Count =0;
uint8_t receive_dat;
void user_uart1IT_ReceiveCallback(void)
{
static uint8_t size_cnt=0;
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) //uart rx flag
{
receive_dat = (uint16_t) READ_REG(huart1.Instance->DR); //从接收buff里面获取数据
USART_RX_BUF[size_cnt] = receive_dat; //数据放到 USART_RX_BUF 数组里面 解析JSON时使用
size_cnt ++; //计数+1
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE); //清除标志位
}
//串口空闲中断
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) //uart idle flag
{
USART1_RX_STA = size_cnt; //获取接收数据
UseFlag.bits.DataACCState = ON; //数据接收完毕
receive_dat = (uint16_t)READ_REG(huart1.Instance->DR); //一定要在接收一次 不然会出问题
size_cnt=0; //清除计数
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除标志位
}
}
然后下面是一些定义,补充上面的标志位说明,需要做一下串口打印重定向,不知道怎么做的可以去看下我之前发的帖子:
传送门:STM32HAL库学习技巧1:基于STM32CubeMX printf重定向
//printf 重定向
#include "stdio.h"
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (unsigned char *)&ch, 1, 0xFFFF);
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET){};
return ch;
}
char USART_RX_BUF[2048]={0};
/************************ SYSTEM TIME TYPE STRUCT *****************************/
typedef union
{
unsigned int interface;
struct
{
unsigned char system_1ms :1;
unsigned char system_10ms :1;
unsigned char system_100ms :1;
unsigned char system_500ms :1;
}bits;
}syst;
/********************** SYSTEM TIME TYPE STRUCT END ***************************/
typedef union{
unsigned int IAPinterface;
struct{
unsigned char COMState :1;
unsigned char DealData :1;
unsigned char DataState :1;
unsigned char DataCRCState :1;
unsigned char DataACCState :1;
unsigned char DataAllowWrite :1;
unsigned char TOSuccessfull :1;
unsigned char UpdateState :3;
unsigned char WillbeSuccessfull :1;
}bits;
}userFlag;
/********************** userFlag TYPE STRUCT END ***************************/
userFlag UseFlag;
syst system_flag; //system flag
上面是一些基础工作:搭建完成就可以开始整个逻辑代码梳理了
首先还是来看文件传输通信部分:就是当我设备已经在IAP 区域了等待和上位机进行通信获取烧录文件的部分 如下图:
下面是JSON 处理主函数,单片机处理JSON 需要外部导入两个库文件
没有的朋友 可以到GIthub上面看一下:CJSON 说明
只用CJSON.C 和CJSON.h 即可。
/*******************************************************************************
Name :System_Json_Main
Syntax :void System_Json_Main(void)
Sync/Async :-
Reentrancy :Non Reentrant
Func :SIP
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
void System_Json_Main(void)
{
System_Json_deal(USART_RX_BUF); //单纯来解析JSON 数据 传入串口接收到的数据
JSON_DATA_TODEAL(); //解析完数据处理
Communication_Deal(); //处理完成与上位机交互
}
以下是对System_Json_Main 函数调用的 函数做一个解剖。
/*******************************************************************************
Name :System_Json_deal
Syntax :uint8_t System_Json_deal(char *getData)
Sync/Async :-
Reentrancy :Non Reentrant
Func :SIP
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
cJSON* cjson_Acc = NULL;
cJSON* cjson_Type = NULL;
cJSON* cjson_Value = NULL;
uint8_t System_Json_deal(char *getData)
{
if(UseFlag.bits.DataACCState == ON) //串口接收完毕状态
{
/* 解析整段数据 */
cjson_Acc = cJSON_Parse(getData);
if(cjson_Acc == 0) //如果不符合json 语法规则 返回0
{
return 0;
}
cjson_Type =cJSON_GetObjectItem(cjson_Acc,"type"); //判断命令还是数据
cjson_Value =cJSON_GetObjectItem(cjson_Acc,"value"); //获取值
if(strcmp1(cjson_Type->valuestring,"commond")) //通过比较函数对比 如果是命令
{
UseFlag.bits.COMState = ON; //命令标志位置位
UseFlag.bits.DataState =OFF; //数据标志位清除
UseFlag.bits.DealData =ON; //数据处理标志位置位
}
if(strcmp1(cjson_Type->valuestring,"data")) //如果通过比较函数对比 是数据
{
UseFlag.bits.COMState = OFF; //命令标志位清除
UseFlag.bits.DataState =ON; //数据标志位置位
UseFlag.bits.DealData =ON; //数据处理标志位置位
}
}
else
{
UseFlag.bits.COMState = OFF;
UseFlag.bits.DataState =OFF;
}
return 1;
}
/*******************************************************************************
Name :JSON_DATA_TODEAL
Syntax :void JSON_DATA_TODEAL(void)
Sync/Async :-
Reentrancy :Non Reentrant
Func :SIP
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
void JSON_DATA_TODEAL(void)
{
if((UseFlag.bits.COMState == ON)&&(UseFlag.bits.DealData == ON)) //接受指令数组
{
// strcpy(getCmdValue,cjson_Value->valuestring); //赋值
Get_add_Array_Value(cjson_Value->valuestring,8,COMMOND_TYPE); //串口获取的为ascii 需要做一个转换 变成hex形式的字符
memset(USART_RX_BUF, 0, sizeof(USART_RX_BUF)); //清除数据
UseFlag.bits.DealData = OFF; //数据处理完成清除标志位
cJSON_Delete(cjson_Acc); //删除jsoN 释放空间
}
else if((UseFlag.bits.DataState == ON)&&(UseFlag.bits.DealData == ON))
{
Get_add_Array_Value(cjson_Value->valuestring,128,DATA_TYPE); //串口获取的为ascii 需要做一个转换 变成hex形式的字符
memset(USART_RX_BUF, 0, sizeof(USART_RX_BUF)); //清除数据
UseFlag.bits.DealData = OFF; //数据处理完成清除标志位
cJSON_Delete(cjson_Acc); //删除jsoN 释放空间
}
else
{
}
}
下面是具体通信协议处理函数,内容可以参照上面通信协议对比查看!
/*******************************************************************************
Name :Communication_Deal
Syntax :void Communication_Deal(void)
Sync/Async :-
Reentrancy :Non Reentrant
Func :SIP
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
uint8_t RealPackSum=1u; //真实计数包
void Communication_Deal(void)
{
static uint16_t CRCCount=0;
if(UseFlag.bits.COMState==ON) //如果是接收到命令
{
UseFlag.bits.COMState = OFF; //处理命令清空标志位
//判断握手
if((getCmdValue[0]==0xEF)&&(getCmdValue[1]!=0x00)&&(getCmdValue[3]==0XAF)&&(UseFlag.bits.UpdateState==0))
{
GetPackSum = getCmdValue[1]; //获取包数量
GetVison = getCmdValue[2]; //获取版本值
memset(getCmdValue, 0, sizeof(getCmdValue)); //清除数据
Usart_respond(0xEF,0x10,0x00); //反馈上位机数据
UseFlag.bits.UpdateState = 1 ; //完成第一步
}
//请求数据传输
else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0xDA)&&(getCmdValue[2]==0xEE)&&(getCmdValue[3]==0xEE)&&(UseFlag.bits.UpdateState == 1))
{
Usart_respond(0xEF,0xFA,0xFA); //反馈上位机数据
memset(getCmdValue, 0, sizeof(getCmdValue)); //清除数据
memset(getAccDatavalue, 0, sizeof(getAccDatavalue));
UseFlag.bits.UpdateState = 2 ; //完成第二步
}
//CRC 转存校验
else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0xDA)&&(getCmdValue[2]==0x3F)&&(getCmdValue[3]!=0x00))
{
if(ON==Transfer_Data()) //转存数据
{
if((RealPackSum%20==0)&&(RealPackSum!=0)) //10k 数据写入flash 一个包512个数据 20*512 = 10240 10k
{
IAP_Write_App_Bin(OldUsLength,ucDataBuf,usLength); //APP_START_ADDR 存储数据 一次性存10k
OldUsLength += usLength; //地址偏移累加
memset(ucDataBuf,0,sizeof(ucDataBuf)); //清除数据
usLength =0;
}
Usart_respond(0xEF,RealPackSum,0xFA); //反馈上位机数据
RealPackSum++;
}
memset(getCmdValue, 0, sizeof(getCmdValue)); //清除数据
}
//传送完成询问 如果完成就 返回校验值
else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0xDA)&&(getCmdValue[2]==0xDA)&&(getCmdValue[3]==0xCC))
{
if(GetPackSum < RealPackSum)
{
CRC16(GetCrcdata,CRCCount); //进行CRC校验
Usart_respond(0xEF,returnCRCValue[0],returnCRCValue[1]); //ALLCRC校验数据
UseFlag.bits.UpdateState = 3 ; //完成第三步
CRCCount=0;
}
else
{
//错误 数据没传输完成
//Usart_respond(0xEF,0xE0,0xE0);
}
memset(getCmdValue, 0, sizeof(getCmdValue)); //清除数据
}
else if((getCmdValue[0]==0xEF)&&(getCmdValue[1]==0x0A)&&(getCmdValue[2]==0xEF)&&(getCmdValue[3]==0xEC))
{
if(GetPackSum%20!=0)
{
IAP_Write_App_Bin(OldUsLength,ucDataBuf,(GetPackSum%20)*512); //APP_START_ADDR 如果最后文件传输没有20个包将剩余的包写入flash
OldUsLength =0; //清空累计长度
memset(ucDataBuf,0,sizeof(ucDataBuf));//清除数据
UseFlag.bits.TOSuccessfull = 1; //数据传输完成
UseFlag.bits.UpdateState = 0 ; //完成第三步
Usart_respond(0xEF,0xEF,0xFA); //向上位机反馈数据
//printf("successfull"); //成功
}
memset(getCmdValue, 0, sizeof(getCmdValue)); //清除数据
}
}
else if(UseFlag.bits.DataState == ON) //如果接收到的是数据
{
UseFlag.bits.DataState = OFF; //清空标志
Data_synthesis(getAccDatavalue,64); //保存512个数据 判定CRC 状态
memset(getAccDatavalue, 0, sizeof(getAccDatavalue)); //清除数据
if(UseFlag.bits.DataCRCState == ON) //有CRC 标志位置位
{
CRC16(getSaveData,512); //计算一个大包的CRC值
GetCrcdata[CRCCount]=returnCRCValue[0]; //得到CRC【0】值
GetCrcdata[CRCCount+1]=returnCRCValue[1]; //得到CRC【1】值
CRCCount += 2;
Usart_respond(0xEF,returnCRCValue[0],returnCRCValue[1]); //发聩CRC校验值
UseFlag.bits.DataCRCState = OFF; //关闭状态标志
}
}
}
对上面的一些没有 的函数做一下补充
/*******************************************************************************
Name :strcmp1
Syntax :int strcmp1(char *source, char *dest)
Sync/Async :-
Reentrancy :Non Reentrant
Func :SIP
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
int strcmp1(char *source, char *dest)
{
while ( (*source != '\0') && (*source == *dest))
{
source++;
dest++;
}
return ((*source) - (*dest) ) ? 0 : 1;
}
/*******************************************************************************
Name :Get_add_Array_Value
Syntax :char Get_add_Array_Value(char array[],uint8_t length)
Sync/Async :Synchronous
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :- :-
|******************************************************************************/
void Get_add_Array_Value(char array[],uint16_t length,uint8_t type){
//char result;
uint16_t count =0;
uint16_t num =0;
uint16_t cnt_num =0;
for(count =0 ;count<length;count++){
switch(array[count]){
case '0':array[count] = 0;break;
case '1':array[count] = 1;break;
case '2':array[count] = 2;break;
case '3':array[count] = 3;break;
case '4':array[count] = 4;break;
case '5':array[count] = 5;break;
case '6':array[count] = 6;break;
case '7':array[count] = 7;break;
case '8':array[count] = 8;break;
case '9':array[count] = 9;break;
case 'a':array[count] = 10;break;
case 'A':array[count] = 10;break;
case 'b':array[count] = 11;break;
case 'B':array[count] = 11;break;
case 'c':array[count] = 12;break;
case 'C':array[count] = 12;break;
case 'd':array[count] = 13;break;
case 'D':array[count] = 13;break;
case 'e':array[count] = 14;break;
case 'E':array[count] = 14;break;
case 'f':array[count] = 15;break;
case 'F':array[count] = 15;break;
}
}
if(type == COMMOND_TYPE)
{
cnt_num =0;
for(num = 0; num < length; num++)
{
if((num+1)%2 ==0)
{
getCmdValue[cnt_num] = array[num-1]*16 + array[num];
cnt_num++;
}
}
//if()
//result = array[0]*16 +array[1];
}
else if(type == DATA_TYPE)
{
cnt_num =0;
for(num = 0; num < length; num++)
{
if((num+1)%2 ==0)
{
getAccDatavalue[cnt_num] = array[num-1]*16 + array[num];
cnt_num++;
}
}
}
}
/*******************************************************************************
Name :CRC16
Syntax :void CRC16(uint8_t p[],uint16_t count)
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
//CRC校验函数 ---------------------------------------------------------
uint8_t returnCRCValue[2];
void CRC16(uint8_t p[],uint16_t count)
{
uint8_t CRC16Lo, CRC16Hi, CL, CH, SaveHi, SaveLo;
uint16_t i, Flag;
CRC16Lo = 0xFF;
CRC16Hi = 0xFF;
CL = 0x86;
CH = 0x68;
//Allsum = sizeof(*p) / sizeof(int);
for(i = 0; i < count; i++)
{
CRC16Lo = CRC16Lo ^ p[i];//每一个数据与CRC寄存器进行异或 167
for (Flag = 0; Flag <= 7; Flag++)
{
SaveHi = CRC16Hi;
SaveLo = CRC16Lo;
CRC16Hi = CRC16Hi >> 1;//高位右移一位 127
CRC16Lo = CRC16Lo >> 1;//低位右移一位 83
if ((SaveHi & 0x01) == 0x01)//如果高位字节最后一位为
{
CRC16Lo =CRC16Lo | 0x80;//则低位字节右移后前面补 否则自动补0
}
if ((SaveLo & 0x01) == 0x01)//如果LSB为1,则与多项式码进行异或
{
CRC16Hi = CRC16Hi ^ CH;
CRC16Lo = CRC16Lo ^ CL;
}
}
}
returnCRCValue[0] = CRC16Hi;//CRC高位 19
returnCRCValue[1] = CRC16Lo;//CRC低位 46
}
/*******************************************************************************
Name :Usart_respond
Syntax :void Usart_respond(uint8_t A,uint8_t B,uint8_t C)
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
//单片机应答函数 ---------------------------------------------------------
void Usart_respond(uint8_t A,uint8_t B,uint8_t C) //JSON 返回
{
printf("{\"response\":\"%d,%d,%d\"}",A,B,C);
}
/*******************************************************************************
Name :Data_synthesis
Syntax :void Data_synthesis(void)
Sync/Async :-
Reentrancy :Non Reentrant
Func :SIP
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/07/17
|******************************************************************************/
uint16_t DATA_SAVE_COUNT =0;
void Data_synthesis(uint8_t data[],uint8_t num)
{
static uint8_t Acc_fragment =0;
uint8_t n =0;
for(n=0;n<num;n++)
{
getSaveData[DATA_SAVE_COUNT] = data[n];
DATA_SAVE_COUNT++;
}
if(Acc_fragment<7) //8个碎片 一个碎片64个字节
{
Usart_respond(0xCC,0xCC,(++Acc_fragment));
}
if(DATA_SAVE_COUNT>=512)
{
UseFlag.bits.DataCRCState = ON; //CRC 状态打开
Acc_fragment = 0;
DATA_SAVE_COUNT =0;
}
}
下面是 FLASH 存储函数,上面有调用我这里把他们集中处理下
/************************** IAP 外部变量********************************/
#define APP_FLASH_LEN 10240u // 定义 APP 固件最大容量55kB=55*1024=56320
#define APP_START_ADDR 0x8005000 //应用程序起始位置
#define STM32_FLASH_SIZE 512 // 所选择的STM32的Flash容量
#define STM32_FLASH_WREN 1 // stm32芯片内容FLASH 写入使能
#define STM_SECTOR_SIZE 2048 // 扇区大小
#define COMMOND_TYPE 1
#define DATA_TYPE 2
// * union --------------------------------------------------------------/
static uint16_t STMFLASH_BUF [ STM_SECTOR_SIZE / 2 ];//最多是2k字节
static FLASH_EraseInitTypeDef EraseInitStruct;
uint8_t ucDataBuf[APP_FLASH_LEN];
//Data 数组长度
uint16_t usLength =0u;
uint32_t OldUsLength = APP_START_ADDR; //默认地址开始写
uint8_t getCmdValue[10]; //接受指令数组
uint8_t GetPackSum=0u; //包数量
uint8_t GetVison =0u; //获取版本值
uint8_t getAccDatavalue[128];
uint8_t getSaveData[512];
uint8_t GetCrcdata[400];
static uint16_t ulBuf_Flash_App[1024];
int strcmp1(char *source, char *dest);
/*******************************************************************************
Name :IAP_Write_App_Bin
Syntax :void IAP_Write_App_Bin(uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength )
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
//向地址写入APP文件 ---------------------------------------------------------
void IAP_Write_App_Bin(uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength )
{
uint16_t us;
uint16_t usCtr =0;
uint16_t usTemp ;
//-----------------------------------------
uint32_t ulAdd_Write = ulStartAddr; //写入当前的地址
uint8_t * pData = pBin_DataBuf; //写入的数据
for ( us = 0; us < ulBufLength; us += 2 ) //循环长度
{
usTemp = ( uint16_t ) pData[1]<<8; //高八位
usTemp += ( uint16_t ) pData[0]; //加上低八位
pData+=2; //偏移两个字节
ulBuf_Flash_App [ usCtr ++ ] = usTemp; //数组保存
if(usCtr==1024) //存满了
{
usCtr = 0; //复位usCtr
STMFLASH_Write( ulAdd_Write, ulBuf_Flash_App, 1024 ); //写入flash 从ulAdd_Write 开始写
ulAdd_Write += 2048;
}
}
if ( usCtr )
STMFLASH_Write ( ulAdd_Write, ulBuf_Flash_App, usCtr );//将最后的一些剩下的写入
}
/*******************************************************************************
Name :STMFLASH_Write
Syntax :void STMFLASH_Write( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
//向地址写入FLASH ---------------------------------------------------------
void STMFLASH_Write( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
{
uint32_t SECTORError = 0; //扇区错误
uint16_t secoff; //扇区内偏移地址(16位计算)
uint16_t secremain; //扇区剩余地址(16位计算)
uint16_t i;
uint32_t secpos; //扇区地址
uint32_t offaddr; //去掉0X08000000后地址
//小于0X08000000 或者大于本来的内存大小
if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
HAL_FLASH_Unlock(); //解锁
offaddr=WriteAddr-FLASH_BASE; //实际的偏移地址 WriteAddr - 0X08000000
secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //扇区内偏移(两个字节为单位)
secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小
if(NumToWrite<=secremain)secremain=NumToWrite;//不大于剩余空间
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//¶Á³öÕû¸öÉÈÇøµÄÄÚÈÝ
for(i=0;i<secremain;i++)//УÑéÊý¾Ý
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//ÐèÒª²Á³ý
}
if(i<secremain)//ÐèÒª²Á³ý
{
//²Á³ýÕâ¸öÉÈÇø
/* Fill EraseInit structure*/
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = secpos*STM_SECTOR_SIZE+FLASH_BASE;
EraseInitStruct.NbPages = 1;
HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
for(i=0;i<secremain;i++)//¸´ÖÆ
{
STMFLASH_BUF[i+secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//дÈëÕû¸öÉÈÇø
}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//дÒѾ²Á³ýÁ˵Ä,Ö±½ÓдÈëÉÈÇøÊ£ÓàÇø¼ä.
if(NumToWrite==secremain)break;//дÈë½áÊøÁË
else//дÈëδ½áÊø
{
secpos++; //ÉÈÇøµØÖ·Ôö1
secoff=0; //Æ«ÒÆλÖÃΪ0
pBuffer+=secremain; //Ö¸ÕëÆ«ÒÆ
WriteAddr+=secremain; //дµØÖ·Æ«ÒÆ
NumToWrite-=secremain; //×Ö½Ú(16λ)ÊýµÝ¼õ
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//ÏÂÒ»¸öÉÈÇø»¹ÊÇд²»Íê
else secremain=NumToWrite;//ÏÂÒ»¸öÉÈÇø¿ÉÒÔдÍêÁË
}
};
HAL_FLASH_Lock();//ÉÏËø
}
/*******************************************************************************
Name :STMFLASH_Write_NoCheck
Syntax :void STMFLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
void STMFLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
{
uint16_t i;
for(i=0;i<NumToWrite;i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
WriteAddr+=2; //µØÖ·Ôö¼Ó2.
}
}
/*******************************************************************************
Name :STMFLASH_ReadHalfWord
Syntax :uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr )
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr )
{
return *(__IO uint16_t*)faddr;
}
/*******************************************************************************
Name :STMFLASH_Read
Syntax :void STMFLASH_Read ( uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead )
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
void STMFLASH_Read ( uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead )
{
uint16_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//¶ÁÈ¡2¸ö×Ö½Ú.
ReadAddr+=2;//Æ«ÒÆ2¸ö×Ö½Ú.
}
}
下面是区域跳转
typedef void ( * pIapFun_TypeDef ) ( void ); //定义一个函数类型的参数
/*******************************************************************************
Name :MSR_MSP
Syntax :__asm void MSR_MSP ( uint32_t ulAddr
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
__asm void MSR_MSP ( uint32_t ulAddr )
{
MSR MSP, r0 //set Main Stack value
BX r14
}
/*******************************************************************************
Name :IAP_ExecuteApp
Syntax :void IAP_ExecuteApp ( uint32_t ulAddr_App )
Sync/Async :-
Reentrancy :Non Reentrant
Parameters(in) :None :
Parameters(out) :None :-
Return value :- :-
Description :- :-
Call By :ZY :-
Date :2020/08/01
|********************************************************************************/
void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
pIapFun_TypeDef pJump2App;
if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 ) //¼ì²éÕ»¶¥µØÖ·ÊÇ·ñºÏ·¨.
{
pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //Óû§´úÂëÇøµÚ¶þ¸ö×ÖΪ³ÌÐò¿ªÊ¼µØÖ·(¸´Î»µØÖ·)
MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //³õʼ»¯APP¶ÑÕ»Ö¸Õë(Óû§´úÂëÇøµÄµÚÒ»¸ö×ÖÓÃÓÚ´æ·ÅÕ»¶¥µØÖ·)
pJump2App (); //Ìøתµ½APP.
}
}
以上就是IAP程序的重要逻辑部分,以下是一些特殊问题处理
1.IAP和用户区是两个区域了,在哪儿可以设置他们文件存储的位置?
IAP区域的地址设定
用户区域的地址设定,如果用户区域地址做了修改 需要在基础文件里面修改他的 中断向量地址
2.怎么设置用户区域应该取什么值?取大了或者取小了会有什么问题?
IAP函数里面有个宏定义 定义的用户区域:
#define APP_START_ADDR 0x8005000 //应用程序起始位置
下图是 stm32f103的flash说明
配置的区域可以选择当前存储页的头位置 如0x8001000、0x8001800等,配置的大小可以根据IAP的大小调整。可以用stvp和STM32CubeProgrammer 或者JFlash等软件测试
可以吧IAP程序下载进去反查 flash 根据实际大小来决定用户区域的的起始值
如图所示 ,IAP程序到0x8003280就结束了 我可以选择0x8004000 或者0x8005000
3.如果我要设置看门狗跳转了后 看门狗还会有效吗? 需不需要重新设置?
注意!!独立看门狗在IAP程序启动了过后,通过跳转语句跳转到用户区域,看门狗还是有效的,但是如果没有在用户区域做看门狗配置,喂狗会失败,而且如果用户区域使用的HAL库,没有配置看门狗好像没有对应生成的库文件。
一句话说明就是如果看门狗配置了 跳转了区域,任然是有效的(iwdg),除非是复位处理
4. 为啥存储的时候数据会丢失单片机会宕机?跳转的时候调用了函数为啥跳转后没有执行函数?
第一个问题: 注意查看对应芯片的数据手册,有的芯片flash 需要做4字节对齐(就是一次性存4字节),有的需要8字节对齐 比如STM32G0系列的
第二个问题: 注意!! 之前开了外设时钟的 或者配置了引脚的 一定要Deinit恢复默认,中断用到了的一定要关掉,系统的可以不管。
算了还是吧工程文件上传吧
链接:https://pan.baidu.com/s/1D-edLrLVEvFx3FSCgq6O6w
提取码:ana3