【IAP】篇二 —— 自定义数据包传输协议实现固件升级


前言

        阅读本篇需要有bootloader和FLASH内存划分的知识,如果不了解可以看上一篇 IAP篇一 —— FLASH内存划分 & Target设置 附Bootloader 或者网上搜索。


一、固件升级概念 及 传输信道

        在高级的应用开发中,产品发布后不是一成不变的,需要对产品不断的迭代升级,修正BUG,固件升级就是用来完成这个步骤的。固件升级将新的固件代码下载到设备的闪存中,并替换旧的代码。这个过程可以通过多种方式实现,如串口、USB、网络等。完成升级后,设备会重新启动并以新的固件版本运行。固件升级的传输信道可以有很多种,选择时应选择对外接口连接方便的信道。不要选择需要拆机才能连接的通道。普通的固件升级需要将新程序下载到下载器中,下载器再通过对外接口将程序发送到需要升级的设备。如果有无线信道如WIFI,蓝牙则可以无需下载器,通过APP将更新文件下载到设备中。

        本文使用RS485作为传输信道实现固件升级。

二、升级包传输协议

1、协议包

        可以自定义,这里给出我的传输协议

固件升级包ACKEND
00x910xAA*60xFF*6
1data_len(高位)
2data_len(低位)
3data
......
data_len+3SUM(高位)
data_len+4SUM(低位)

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,将数据用云服务器下发到每个设备中。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值