目录
前言
前两篇介绍了IAP的实现原理和数据包的传输,本篇为本系列的最后一篇,编写自己的下载器将固件升级包发送出去更新数据。
一、固件升级逻辑
主要的逻辑就是下载器将自己FLASH中的新的固件通过自定义的协议发送给设备,设备收到新的固件就下载到FLASH中完成更新。我的自定义的协议在 【IAP】篇二 —— 自定义数据包传输协议实现固件升级 中有介绍。
1、下载器数据包发送逻辑
简化版的逻辑就是这样的,因为我有两个固件需要一起完成升级,所以代码会复杂一点
2、下载器代码
主循环代码,延时1S是为了支持热插拔,等待连接稳定再开始数据包传输。
while(1)
{
//循环判断APP是否在线
OTA_Send_Package(NULL,0,0x91);
if(OTA_Wait_Ack(10) == 1)
{
//如果APP在线就判断控制端在不在线
//给1s判断
for(int i = 0 ; i < 50; i++)
{
OTA_Send_Package(NULL,0,0x92);
//如果有回应就发送固件升级包1然后发2
if(OTA_Wait_Ack(20) == 1)
{
MU_Show_Str(0,56,(uint8_t *)" ",WHITE,DARK_BLUE);
MU_Show_Str(8,56,(uint8_t *)"即将开启传输[ ]",WHITE,DARK_BLUE);
char str[5];
//延时1S等待稳定
for(int i = 100; i > 0; i--)
{
sprintf(str,"%1d.%2d",i/100,i%100);
MU_Show_Str(116,56,(uint8_t *)str,YELLOW,DARK_BLUE);
HAL_Delay(10);
}
OTA_Send_Package1();
}
}
//发送APP升级包
OTA_Send_Package2();
}
}
先是一些无关紧要的显示,然后发送384*512字节大小的数据,即192K大小的固件升级包,这里的192K是固件的最大大小,我偷懒没有加检测固件包大小的逻辑。后续优化可以检测一下固件包大小能少发许多字节。
static void OTA_Send_Package2(void)
{
MU_Show_Str(0,0,(uint8_t *)" ",WHITE,DARK_BLUE);
MU_Show_Str(40,0,(uint8_t *)"APP数据包",WHITE,DARK_BLUE);
int i;
char str[5];
//延时2S等待稳定
uint8_t data[512] = {0};
MU_Show_Str(0,56,(uint8_t *)" ",WHITE,DARK_BLUE);
MU_Show_Str(8,56,(uint8_t *)"数据包传输中[ ]",WHITE,DARK_BLUE);
//192K
for(i = 0; i < 384; i++)
{
sprintf(str,"%3d%%",i*100/384+1);
MU_Show_Str(116,56,(uint8_t *)str,YELLOW,DARK_BLUE);
FLASH_Read(APP1_ADDR + 512*i,data,512);
OTA_Send_Package(data,512,0x91);
if(OTA_Wait_Ack(1000) == 0)
break;
}
MU_Show_Str(0,56,(uint8_t *)" ",WHITE,DARK_BLUE);
//发送失败
if(i != 384)
{
MU_Show_Str(12,56,(uint8_t *)"数据包传输失败...",WHITE,DARK_BLUE);
}
//发送完毕
else
{
MU_Show_Str(12,56,(uint8_t *)"数据包传输成功...",WHITE,DARK_BLUE);
}
OTA_Send_End();
}
下载器的代码就这些了,比较简单,然后看看接收端是怎么处理的。
3、接收端逻辑
首先先明确一下两个固件升级包的整体逻辑,下载器将两个固件升级包传给APP,APP判断是不是自己的升级包,如果是就升级,不是就转发给控制端。控制端就只需要接收升级包并处理即可。
下面是接收端的处理逻辑,复杂的地方主要是BootLoader和主程序之间的跳转。
4、接收端代码
下面是BootLoaer的代码,对应上面的逻辑。tim4_nms用于超时检测。
//判断是否更新完成
if(IAP_UpdateFlag_Read(NULL) == APP_UPDATEFLAG)
{
//清更新标志位
IAP_UpdateFlag_Clear();
//跳转到APP
IapLoadApp(APP1_ADDR);
}
//进行一次更新判断再进入APP
else
{
//超时检测
MU_Tim4_Init();
//RS485
MU_DMX_Init();
//灯
MU_Led_Config();
//内部
MU_InnerCmd_Init();
tim4_nms = 5000;
//40ms内判断是否有升级包传来
for(int i = 0; i < 40; i++)
{
//进入更新
if(ota_package_ne == 1)
{
//循环处理直到收到结束包
while(OTA_Package_Prase() != 0x00);
}
HAL_Delay(1);
}
//去初始化
TIM_HandleTypeDef htim4;
htim4.Instance = TIM4;
HAL_TIM_Base_DeInit(&htim4);
HAL_UART_DeInit(&huart1);
HAL_UART_DeInit(&huart2);
HAL_DMA_DeInit(&hdma_usart1_rx);
HAL_DMA_DeInit(&hdma_usart2_tx);
HAL_DMA_DeInit(&hdma_usart2_rx);
}
//跳转主程序
IapLoadApp(APP1_ADDR);
主要看看接收数据包的解包逻辑,注释很全,可供参考。
//OTA解包
static uint64_t app_buf_page = 0;
uint8_t ota_type = 0;
uint8_t OTA_Package_Prase(void)
{
if(ota_package_ne == 1 && tim4_nms != 0)
{
//重新设置超时时间
tim4_nms = 5000;
ota_package_ne = 0;
int i = 1;
uint16_t data_len = 0;
uint32_t sum = 0;
switch(ota_package_buf[0])
{
#ifdef OTA_RECV
//APP端升级包
case 0x91:
ota_type = 0;
//校验
data_len = ota_package_buf[1];
data_len <<= 8;
data_len |= ota_package_buf[2];
//开始传输OTA
if(data_len == 0)
{
OTA_Send_Ack();
return 0x91;
}
for(i = 0; i < data_len+3; i++)
{
sum += ota_package_buf[i];
}
//校验错误
//& 优先级小于 != 等于 ||
if(((sum>>8)&0xFF) != ota_package_buf[data_len+3] || (sum&0xFF) != ota_package_buf[data_len+4])
{
LED2_ON;
return 0x00;
}
//写入数据到FLASH
IAP_WriteBin(APP1_ADDR+app_buf_page,ota_package_buf+3,data_len);
app_buf_page += data_len;
//回应
OTA_Send_Ack();
LED1_TOGGLE;
return 0x91;
//电机控制端升级包
case 0x92:
ota_type = 1;
data_len = ota_package_buf[1];
data_len <<= 8;
data_len |= ota_package_buf[2];
for(i = 0; i < data_len+3; i++)
{
sum += ota_package_buf[i];
}
//校验错误
//& 优先级小于 != 等于 ||
if(((sum>>8)&0xFF) != ota_package_buf[data_len+3] || (sum&0xFF) != ota_package_buf[data_len+4])
{
LED2_ON;
return 0x00;
}
//发送升级包给电机控制端
InnerCmd_OTA_Send(ota_package_buf+3,data_len);
//等待20ms
if(data_len == 0)
{
//等待电机控制端回应
if(InnerCmd_Ack_Wait(20) == 1)
{
//回应ACK给下载器
OTA_Send_Ack();
}
}
//等待1s
else
{
//等待电机控制端回应
if(InnerCmd_Ack_Wait(1000) == 1)
{
//回应ACK给下载器
OTA_Send_Ack();
}
}
LED2_TOGGLE;
return 0x92;
case 0xFF:
for(i = 1; i < 6; i++)
{
if(ota_package_buf[i] != 0xFF)
break;
}
//收到结束包
if(i == 6)
{
//本设备升级包
if(ota_type == 0)
{
//更新结束,复位
app_buf_page = 0;
//设置更新完成标志位
IAP_UpdateFlag_Write(0xFFFF);
//软件复位
__HAL_RCC_CLEAR_RESET_FLAGS();
HAL_NVIC_SystemReset();
}
//控制端升级
else if(ota_type == 1)
{
//转发
InnerCmd_Send_End();
}
}
return 0xFF;
#endif
#ifdef OTA_SEND
case 0xAA:
for(i = 1; i < 6; i++)
{
if(ota_package_buf[i] != 0xAA)
break;
}
if(i == 6)
ota_ack = 1;
return 0xAA;
#endif
}
}
//超时
if(tim4_nms == 0)
return 0x00;
else
return 0x01;
}
控制端的接收逻辑就更简单了,不用进行数据包转发,上面的看懂就能写出控制端的升级逻辑。
二、使用步骤
首先需要将更新的固件下载到下载器的指定内存地址中。可以看【IAP】篇一 —— FLASH内存划分 & Target设置 附Bootloader的Target设置,设置完成后可以双击打开.map文件
然后往下滑看看偏移地址是否正确,正确就可以下载到下载器中了。
需要注意下载器的FLASH内存划分要和下载的地址一致
#define BOOTLOADER_ADDR 0x08000000 //64K BLOCK0~BLOCK1
#define APP1_ADDR 0x08010000 //192K BLOCK2~BLOCK7
#define APP2_ADDR 0x08040000 //96K BLOCK8~BLOCK10
最后将下载器和设备连接,就可以更新固件了
固件升级
总结
加油少年,完成一个属于自己的下载器吧!