目录
前言
阅读本篇需要有bootloader和FLASH内存划分的知识,如果不了解可以看上一篇 IAP篇一 —— FLASH内存划分 & Target设置 附Bootloader 或者网上搜索。
一、固件升级概念 及 传输信道
在高级的应用开发中,产品发布后不是一成不变的,需要对产品不断的迭代升级,修正BUG,固件升级就是用来完成这个步骤的。固件升级将新的固件代码下载到设备的闪存中,并替换旧的代码。这个过程可以通过多种方式实现,如串口、USB、网络等。完成升级后,设备会重新启动并以新的固件版本运行。固件升级的传输信道可以有很多种,选择时应选择对外接口连接方便的信道。不要选择需要拆机才能连接的通道。普通的固件升级需要将新程序下载到下载器中,下载器再通过对外接口将程序发送到需要升级的设备。如果有无线信道如WIFI,蓝牙则可以无需下载器,通过APP将更新文件下载到设备中。
本文使用RS485作为传输信道实现固件升级。
二、升级包传输协议
1、协议包
可以自定义,这里给出我的传输协议
固件升级包 | ACK | END | |
---|---|---|---|
0 | 0x91 | 0xAA*6 | 0xFF*6 |
1 | data_len(高位) | ||
2 | data_len(低位) | ||
3 | data | ||
... | ... | ||
data_len+3 | SUM(高位) | ||
data_len+4 | SUM(低位) |
2、协议逻辑
首先由下载器循环发送长度为0的数据包,设备收到数据包就回应ACK,设备进入接收状态,下载器收到ACK就进入发送状态。然后下载器开始读FLASH里的更新后的固件,循环发送给设备。设备每收到一个固件升级包就进行校验,通过检测最后两位是不是前面所有位的校验和,如果校验正确就回应一个ACK,否则不回应。
下载器接收到ACK就继续循环读取更新后的固件并发送,直到固件读取完毕。最后发送END。设备收到END就结束接收,设置APP更新标志位并复位进入BootLoader,BootLoader读取更新标志位得知需要更新APP,就将新的APP复制到APP执行区域运行新APP。
三、代码实现传输协议
因为该信道上还需兼容DMX协议及RDM协议,所以代码比较复杂,这里只给出本协议的代码
1、串口初始化
huart1.Instance = USART1;
huart1.Init.BaudRate = 250000;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_2;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_8;
HAL_UART_Init(&huart1);
2、中断接收
//升级包
else if(rx_buf.buf[0] == 0x91)
{
memcpy((void *)ota_package_buf,(void *)rx_buf.buf,rx_buf.index);
ota_package_ne = 1;
}
else if(rx_buf.buf[0] == 0xAA || rx_buf.buf[0] == 0xFF)
{
rx_buf.index = rx_buf.index > 6 ? 6 : rx_buf.index;
memcpy((void *)ota_package_buf,(void *)rx_buf.buf,rx_buf.index);
ota_package_ne = 1;
}
3、数据包解析
static uint64_t app_buf_page = 0;
void OTA_Package_Prase(void)
{
if(ota_package_ne == 1 && tim_nms == 0)
{
ota_package_ne = 0;
int i = 1;
uint16_t data_len = 0;
uint32_t sum = 0;
switch(ota_package_buf[0])
{
#ifdef OTA_RECV
case 0x91:
//校验
data_len = ota_package_buf[1];
data_len <<= 8;
data_len |= ota_package_buf[2];
//开始传输OTA
if(data_len == 0)
{
OTA_Send_Ack();
return;
}
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;
}
//写入数据到FLASH
IAP_WriteBin(APP_BUFFER_ADDR+app_buf_page,ota_package_buf+3,data_len);
app_buf_page += data_len;
//回应
OTA_Send_Ack();
break;
case 0xFF:
for(i = 1; i < 6; i++)
{
if(ota_package_buf[i] != 0xFF)
break;
}
//收到结束包
if(i == 6)
{
//更新结束,复位
app_buf_page = 0;
//30s内不再更新
MU_Tim_Delay(30000);
//写入更新标志位
IAP_UpdateFlag_Write();
//软件复位
__HAL_RCC_CLEAR_RESET_FLAGS();
HAL_NVIC_SystemReset();
}
break;
#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;
break;
#endif
}
}
}
4、数据包发送
#ifdef OTA_SEND
uint8_t ota_package_buf[6] = {0};
uint8_t ota_package_ne = 0;
uint8_t ota_ack = 0;
//发送固件升级包
void OTA_Send_Package(uint8_t *data,uint16_t data_len)
{
uint8_t* buf = (uint8_t *)malloc(data_len+5);
buf[0] = 0x91;
buf[1] = (data_len >> 8) & 0xFF;
buf[2] = data_len & 0xFF;
memcpy((void *)(buf+3),(const void *)data,data_len);
uint32_t sum = 0;
for(int i = 0; i <= data_len+2; i++)
{
sum += buf[i];
}
buf[data_len+3] = (sum >> 8) & 0xFF;
buf[data_len+4] = sum & 0xFF;
//起始信号
MU_DMX_Change_Mode(DMX_SEND_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
delay_us(88*2);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
delay_us(12*2);
MU_DMX_Change_Mode(DMX_SEND_DATA);
HAL_UART_Transmit(&huart1,buf,data_len+5,HAL_MAX_DELAY);
free(buf);
MU_DMX_Change_Mode(DMX_RECV_DATA);
}
//等待ACK
uint8_t OTA_Wait_Ack(uint32_t wait_time)
{
//最多等5S
while(ota_ack == 0)
{
OTA_Package_Prase();
HAL_Delay(1);
wait_time--;
if(wait_time == 0)
break;
}
if(ota_ack == 1)
{
ota_ack = 0;
return 1;
}
else
return 0;
}
//发送结束包
void OTA_Send_End(void)
{
uint8_t data[6];
memset(data,0xFF,6);
//起始信号
MU_DMX_Change_Mode(DMX_SEND_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
delay_us(88*2);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
delay_us(12*2);
MU_DMX_Change_Mode(DMX_SEND_DATA);
HAL_UART_Transmit(&huart1,data,6,HAL_MAX_DELAY);
MU_DMX_Change_Mode(DMX_RECV_DATA);
}
#endif
#ifdef OTA_RECV
uint8_t ota_package_buf[520] = {0};
uint8_t ota_package_ne = 0;
//发送ACK
void OTA_Send_Ack(void)
{
uint8_t data[6];
memset(data,0xAA,6);
//起始信号
MU_DMX_Change_Mode(DMX_SEND_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
delay_us(88*2);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
delay_us(12*2);
MU_DMX_Change_Mode(DMX_SEND_DATA);
HAL_UART_Transmit(&huart1,data,6,HAL_MAX_DELAY);
MU_DMX_Change_Mode(DMX_RECV_DATA);
}
#endif
5、下载器发送数据代码
while(1)
{
OTA_Send_Package(NULL,0);
//如果有回应就发送固件升级包
if(OTA_Wait_Ack(10) == 1)
{
uint8_t data[512] = {0};
int i;
for(i = 0; i < 640; i++)
{
FLASH_Read(APP_ADDR + 512*i,data,512);
OTA_Send_Package(data,512);
if(OTA_Wait_Ack(1000) == 0)
break;
}
//发送失败
if(i != 640)
{
LED1_ON;
}
//发送完毕
else
{
LED2_ON;
}
OTA_Send_End();
}
}
到此为止接收固件更新的代码已经完成了,接下来就是将数据下载到FLASH指定位置了。
四、下载固件升级包
将接收到的固件升级包下载到APP缓冲区中,再由BootLoader搬运到APP执行区运行新代码,关于分区可以在上一篇找到
1、HAL库FLASH读写相关操作
//读全字
static inline uint32_t FLASH_Read_Word(uint32_t addr)
{
return *(__IO uint32_t *)addr;
}
//读n个字节
//data必须四字节对齐
void FLASH_Read(uint32_t addr,uint8_t *data,uint32_t data_len)
{
uint32_t word = 0;
for(int i = 0; i < data_len; i += 4)
{
word = FLASH_Read_Word(addr);
//小端存
data[i+0] = word & 0xFF;
data[i+1] = (word >> 8) & 0xFF;
data[i+2] = (word >> 16) & 0xFF;
data[i+3] = (word >> 24) & 0xFF;
//四字节偏移
addr += 4;
}
}
//每次传4byte,只能按页传(256BYTE)
static void FLASH_Write(uint32_t addr,uint32_t *data)
{
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_PAGE,addr,data);
HAL_FLASH_Lock();
}
//256BYTE
//擦除页
void FLASH_Erase_Page(uint32_t addr)
{
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit = {
.TypeErase = FLASH_TYPEERASE_PAGEERASE,
.PageAddress = addr,
.NbPages = 1,
};
uint32_t PageError;
HAL_FLASH_Erase(&pEraseInit,&PageError);
HAL_FLASH_Lock();
}
//2K
//擦除扇区
void FLASH_Erase_Sector(uint32_t addr)
{
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit = {
.TypeErase = FLASH_TYPEERASE_SECTORERASE,
.SectorAddress = addr,
.NbSectors = 1,
};
uint32_t SectorError;
HAL_FLASH_Erase(&pEraseInit,&SectorError);
HAL_FLASH_Lock();
}
//32K
//擦除块
void FLASH_Erase_Block(uint32_t addr)
{
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit = {
.TypeErase = FLASH_TYPEERASE_BLOCKERASE,
.BlockAddress = addr,
.NbBlocks = 1,
};
uint32_t PageError;
HAL_FLASH_Erase(&pEraseInit,&PageError);
HAL_FLASH_Lock();
}
//384K
//擦除片
void FLASH_Erase_Mass(uint32_t addr)
{
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef pEraseInit = {
.TypeErase = FLASH_TYPEERASE_MASSERASE,
};
uint32_t MassError;
HAL_FLASH_Erase(&pEraseInit,&MassError);
HAL_FLASH_Lock();
}
2、读写更新标志位
//设置更新标志位
void IAP_UpdateFlag_Write(void)
{
FLASH_Erase_Page(APP_UPDATEFLAG_ADDR);
uint32_t data[64] = {0};
data[0] = APP_UPDATEFLAG;
FLASH_Write(APP_UPDATEFLAG_ADDR,data);
}
//读更新标志位
uint32_t IAP_UpdateFlag_Read(void)
{
uint8_t update_flag_buf[4];
uint32_t update_flag;
FLASH_Read(APP_UPDATEFLAG_ADDR,update_flag_buf,4);
//小段读
update_flag = update_flag_buf[3];
update_flag <<= 8;
update_flag |= update_flag_buf[2];
update_flag <<= 8;
update_flag |= update_flag_buf[1];
update_flag <<= 8;
update_flag |= update_flag_buf[0];
return update_flag;
}
//清更新标志位
void IAP_UpdateFlag_Clear(void)
{
FLASH_Erase_Page(APP_UPDATEFLAG_ADDR);
3、FLASH下载代码
//写入APP缓冲区
void IAP_WriteBin(uint32_t addr,uint8_t *data,uint32_t data_len)
{
uint32_t page = data_len/256;
//不足一页的补齐一页
if(data_len%256 != 0)
page++;
//擦除
uint8_t block_erase = page/128;
uint8_t sector_erase = page%128/16;
uint8_t page_erase = page%128%16;
uint32_t addr_erase = addr;
while(block_erase != 0)
{
FLASH_Erase_Block(addr_erase);
addr_erase += 32768; //32K
block_erase--;
}
while(sector_erase != 0)
{
FLASH_Erase_Sector(addr_erase);
addr_erase += 2048; //2K
sector_erase--;
}
while(page_erase != 0)
{
FLASH_Erase_Page(addr_erase);
addr_erase += 256; //256BYTE
page_erase--;
}
//256BYTE
uint32_t page_data[64];
//每页写
for(int i = 0; i < page; i++)
{
//最后一页
if(i*256+256 > data_len)
{
uint8_t num = 0;
for(int j = i*256; j < data_len; j+=4,num++)
{
//小端存
page_data[num] = data[j+3];
page_data[num] <<= 8;
page_data[num] |= data[j+2];
page_data[num] <<= 8;
page_data[num] |= data[j+1];
page_data[num] <<= 8;
page_data[num] |= data[j+0];
}
//补字节差
//不用补 32位对齐
//补页差
for(int j = data_len; j < i*256+256; j+=4,num++)
{
page_data[num] = 0;
}
}
else
{
uint8_t num = 0;
for(int j = i*256; j < i*256+256; j+=4,num++)
{
//小端存
page_data[num] = data[j+3];
page_data[num] <<= 8;
page_data[num] |= data[j+2];
page_data[num] <<= 8;
page_data[num] |= data[j+1];
page_data[num] <<= 8;
page_data[num] |= data[j+0];
}
}
FLASH_Write(addr,page_data);
addr += 256;
}
}
到此为止就可以将更新的程序下载到指定内存中了,接下来就是跳转到指定内存执行程序,这就是BootLoader需要完成的事了。
五、BootLoader
BootLoaer的相关设置在上一篇也有介绍,这里直接给出BootLoader的代码
uint8_t uid[12] = {0};
int main(void)
{
FLASH_Read(UID_ADDR,uid,12);
/* Reset of all peripherals, Initializes the Systick. */
HAL_Init();
/* System clock configuration */
APP_SystemClockConfig();
//定时器2初始化
MU_Tim2_Init();
//解密
MU_Decrypt();
//判断系统是否初始化
if(IAP_SystemFlag_Read() != APP_SYSTEMFLAG)
{
//初始化
MU_System_Var_Save();
//写入初始化成功标记
IAP_SystemFlag_Write();
}
//检测更新标志
if(IAP_UpdateFlag_Read() == APP_UPDATEFLAG)
{
//清标志位
IAP_UpdateFlag_Clear();
//搬运数据
uint8_t data[256] = {0};
for(int i = 0; i < 640; i++)
{
FLASH_Read(APP_BUFFER_ADDR+256*i,data,256);
IAP_WriteBin(APP_ADDR+256*i,data,256);
}
//执行APP
IapLoadApp(APP_ADDR);
}
//没有就跳转主程序
else
{
IapLoadApp(APP_ADDR);
}
while(1);
}
总结
最后将新固件下载到下载器的FLASH中。下载器就可以给所有设备进行固件更新,后续可以扩展成OTP,将数据用云服务器下发到每个设备中。