IAP固件升级(附代码)-- Bootloader接收BIN文件(带Xmodem和CRC16/32校验)

文章目录

概要

整体架构流程

主框架

API

JumpToApp 

IAP_UpdateProgram 

Xmodem_PacketAnalyze 

IAP_ProgramIntegrityCheck 

IAP_FlashBuffToApplication 

 IAP_SendSignalMode

 IAP_BufferRead

 IAP_FlashProgramdata

 IAP_BufferWrite

 全局变量、宏定义、结构体

技术细节

小结


概要

Bootloader根据不同总线发送到单片机(stm32f407)的Xmodem帧数据进行解析并校验,然后写入FLASH缓存区,接收完整个BIN文件后,将FLASH缓存区部分的代码copy到执行区。设置FALSH缓存区的意义在于防止在更新过程中意外掉电导致更新失败而造成设备变砖。

详细说明请参考我的上一篇文章:IAP固件升级--Bootloader串口接收BIN文件(带Xmodem协议和CRC16/32校验)_如何通过串口接收bin文件数据-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_69014205/article/details/131855107?spm=1001.2014.3001.5502


整体架构流程

1.加载各种驱动配置,包括UART、IIC、TIMER、CAN、GPIO、CRC等等

2.IIC读取更新标志位

3.根据更新标志位的不同,执行不同的阶段流程,共有三个阶段流程:

        3.1        更新:意为需要更新,准备进入 准备接收带有Xmodem协议的BIN程序 的状态

        3.2        缓存完成:意为此前已将更新的程序下载到FLASH缓存区,准备进入 将缓存区的数据copy到执行区域 的状态

        3.3        正常启动: 意为无需更新或已更新完成,准备进入 正常跳转到执行区域 的状态

        例如:当前BL检测到更新标志位为缓存完成,那么会将缓存区的数据copy到执行区域,然后修改更新标志位的值为正常启动,reset重启后检测到更新标志的值为正常启动,接着正常跳转到执行区域。

4.(以正常更新的完全流程描述)总线中断函数接收数据存入IAP接收缓冲区,进入上面3.1的状态后不断的读IAP缓冲区的数据直至读到Xmodem的报头,然后读取报头后的132个字节,加上第一个字节的报头为一帧。

5.将一帧数据解析,判断帧序号及取反值,计算128字节数据的CRC校验比对,无误后写入FLASH缓存区。

6.等待全部程序接收完毕后,最后一帧会接收到上位机发送来的整个程序的CRC32位校验值,然后将这个校验值写入eeprom中。

7.读取缓存区中的数据计算其整体的CRC32校验值并与eeprom中的校验值进行比较,正确则会将缓存区的数据copy到执行区域,否则更新失败跳转执行区域。

8.copy到执行区域的数据会再次进行一次CRC32完整性校验,并读取eeprom中的校验值与之比对,正确则表示更新成功,否则表示copy失败会再次copy一次。


主框架

主要分为main函数及其他API函数,其中API函数我主要贴几个自己写的重要的,像其他的如有需要请评论邮箱。

main函数

//更新过程分为三个阶段:
//1.无需更新/已更新   -- NO_FLAG
//2.缓存完成          -- DOWNLOAD_FLAG
//3.需要更新          -- UPDATE_FLAG
int main()
{
    uint8_t update_flag = 0;
    //加载驱动配置
	BSP_Config();
    //读更新标志位
    I2C_Read(I2C1,ADDR_24LC02,UPDATE_ADDR,&update_flag,1);
    //读更新标志位--要进行更新
    if(update_flag == UPDATE_FLAG)      
    {
        //将标志位写“未更新/无需更新”等更新完毕后才会修改更新标志位,否则判定为更新失败
        I2C_WriteOneByte(I2C1,ADDR_24LC02,UPDATE_ADDR,(uint8_t)NO_FLAG); 

        //开始更新程序,(选择接收方式为CAN总线,此时接收的程序文件下载到flash缓存区部分)
        if( !IAP_UpdateProgram(CAN_Mode))                                               
        {

            //CRC校验程序完整性,(接收程序文件时计算并写入eeprom,再读缓存区中的数据并计算CRC校验值与之比对)
            if(!IAP_ProgramIntegrityCheck((uint32_t)Application_Buff_ADDR))         
            {
                //CRC相同,缓存区下载完成,更新标志位为“缓存完成”
                I2C_WriteOneByte(I2C1, ADDR_24LC02, UPDATE_ADDR, (uint8_t)DOWNLOAD_FLAG); 
     
                 //将缓存区数据拷贝到APP区域,完成后设置标志位为“已更新”
LOOP:           if(!IAP_FlashBuffToApplication((uint32_t)Application_ADDR, (uint32_t)Application_Buff_ADDR))   
                {
                    I2C_WriteOneByte(I2C1,ADDR_24LC02,UPDATE_ADDR,(uint8_t)NO_FLAG);
                    //加入一点延时防止IIC没有写入完成就重启
                    Delay_ms(100);
                    NVIC_SystemReset();
                }
                else
                {
                    goto LOOP;
                }
            }
            else    //CRC不同,更新失败,跳转到APP
            {
                JumpToApp();
            }
        }
        else    //更新程序超时,(此时更新标志位为"未更新/无需更新")
        {
            //跳转到APP
            JumpToApp();
        }
    }
    else if(update_flag == DOWNLOAD_FLAG)  //读更新标志位--已经缓存完成
    {
        //跳转到《将缓存区数据拷贝到APP区域》
        goto LOOP;
    }
    else  //否则跳转APP执行区域
    {
        JumpToApp();
    }
	while(1)
	{
		LED3_ON();
        Delay_ms(200);
        LED3_OFF();
        Delay_ms(200);
	}
}

API

JumpToApp 

 函数名称:static void JumpToApp(void)

功能:跳转到执行区域

参数:无

返回值:无

static void JumpToApp(void)
{
        uint32_t i=0;
        void (*SysMemBootJump)(void);        /* 声明一个函数指针 */
        __IO uint32_t AppAddr = Application_ADDR; /* STM32F4的系统BootLoader地址 */
        
        /* 设置所有时钟到默认状态,使用HSI时钟 */
        RCC_DeInit();

        /* 关闭全局中断 */
        DISABLE_INT(); 

        /* 关闭滴答定时器,复位到默认值 */
        SysTick->CTRL = 0;
        SysTick->LOAD = 0;
        SysTick->VAL = 0;

        /* 关闭所有中断,清除所有中断挂起标志 */
        for (i = 0; i < 8; i++)
        {
                NVIC->ICER[i]=0xFFFFFFFF;
                NVIC->ICPR[i]=0xFFFFFFFF;
        }        

        /* 使能全局中断 */
        ENABLE_INT();

        /* 跳转到系统APP,首地址是MSP,地址+4是复位中断服务程序地址 */
        SysMemBootJump = (void (*)(void)) (*((uint32_t *) (AppAddr + 4)));

        /* 设置主堆栈指针 */
        __set_MSP(*(uint32_t *)AppAddr);
        
//        /* 在RTOS工程,这条语句很重要,设置为特权级模式,使用MSP指针 */
//        __set_CONTROL(0);

        /* 跳转到系统BootLoader */
        SysMemBootJump(); 

        /* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */
        while (1)
        {

        }
}

IAP_UpdateProgram 

 函数名称:uint8_t IAP_UpdateProgram(IAP_Mode_t mode)    

功能:根据不同模式进行数据接收并将数据写入FLASH缓存区

参数:IAP_Mode_t mode  --  不同总线模式

返回值:0 -- 正常结束

              1 -- 接收超时

uint8_t IAP_UpdateProgram(IAP_Mode_t mode)	
{
	uint8_t RtnACK = 0;
	uint8_t data[128] = {0};
	uint16_t pBuffer = 0;
    uint8_t i = 0;
	
	FLASH_Unlock();//flash解锁
    FLASH_DataCacheCmd(DISABLE);//FLASH擦除期间,必须禁止数据缓存
	FLASH_EraseSector(FLASH_Sector_6,VoltageRange_3);       //擦除0x0804 0000
	FLASH_EraseSector(FLASH_Sector_7,VoltageRange_3);       //擦除0x0806 0000
    FLASH_DataCacheCmd(ENABLE);//FLASH擦除结束,开启数据缓存

	TIM_Cmd(TIM2,ENABLE); //使能定时器2
    
    Xmodem.Xmodem_PacketCMP = 1;

    IAP_SendSignalMode(mode,(uint8_t *)START_FRAME);
    Delay_ms(3000);
    IAP_SendSignalMode(mode,(uint8_t *)START_FRAME);

	while(1)
	{
		RtnACK = Xmodem_PacketAnalyze(data);
		if(RtnACK == ACK)
		{
			for(i = 0;i < 64;i++)
			{
				pBuffer= ((uint16_t)(data[i*2])) | ((uint16_t)(data[i*2+1] << 8));
				IAP_FlashProgramdata(pBuffer);
			}
			IAP_SendSignalMode(mode,&RtnACK);			//发送应答信号
			LED2_Toggle();
            Xmodem.Flash_PacketCNT++;       //写入FLASH的Xmodem帧数+1
			memset(data,0,128);
		}
		else if(RtnACK == CRC32)
		{
			RtnACK = ACK;
			IAP_SendSignalMode(mode,&RtnACK);			//发送应答信号
		}
		else if(RtnACK == EOT)
		{
			RtnACK = ACK;
			FLASH_Lock();//flash锁
			TIM_Cmd(TIM2,DISABLE);              //失能定时器2
			IAP_SendSignalMode(mode,&RtnACK);			//发送应答信号
//			printf("User APP Receive Completed!!!\r\n");
//			printf("Code Length:%dBytes\tXmodem_packcnt:%d\tFlash_PacketCNT:%d\r\n",\
//					UART1_RX_CNT,Xmodem.Xmodem_PacketCNT,Xmodem.Flash_PacketCNT);
			return 0;//结束函数
		}
		else if (RtnACK == NAK)
		{
            IAP_SendSignalMode(mode,&RtnACK);			//发送应答信号
		}
		else
            IAP_SendSignalMode(mode,&RtnACK);			//发送应答信号
        
        RtnACK = 0;

		if(rcvTimeout > 10)
		{
			FLASH_Lock();//flash锁
			TIM_Cmd(TIM2,DISABLE);              //失能定时器2
			return 1;	
		}
	}
}

Xmodem_PacketAnalyze 

 函数名称:uint8_t Xmodem_PacketAnalyze(unsigned char *buff)

功能:Xmodem解析函数,读取缓存区中的数据,识别Xmodem报头,解析出128字节数据放入形参中

参数:unsigned char *buff  --  用来传递BIN文件的128字节数据

返回值: EOT 0x04        //发送结束标志
                ACK 0x06        //应答标志
                NAK 0x15        //非应答标志
                CRC32 0x43      //使用CRC校验标志

uint8_t Xmodem_PacketAnalyze(unsigned char *buff)
{
	uint8_t data = 0;
	uint16_t crc_value = 0;
	uint32_t f_size = 0;
    uint8_t i = 0;
    uint8_t crc_buff[4] = {0};
    
	while(!IAP_BufferRead(&data) && (rcvTimeout < 10));
	if(data == SOH)
	{
		Xmodem.Xmodem_Packet[0] = data;
		Xmodem.Xmodem_PacketCNT++;      //接收到的Xmodem帧数++
		for(i = 1; i < 133; i++)
		{
			while(!IAP_BufferRead(&data));
			Xmodem.Xmodem_Packet[i] = data;
		}

		if((Xmodem.Xmodem_Packet[1] == Xmodem.Xmodem_PacketCMP) && (Xmodem.Xmodem_Packet[2] == (uint8_t)~Xmodem.Xmodem_PacketCMP))
		{
			for(i = 0;i < 128;i++)
			{
				buff[i] = Xmodem.Xmodem_Packet[i+3];
			}
			crc_value = calculate(buff,128);        //计算接收到的数据的CRC校验值
			Xmodem.Xmodem_CRC16 = (((uint16_t)Xmodem.Xmodem_Packet[131]) << 8) | ((uint16_t)(Xmodem.Xmodem_Packet[132]));       //获取帧尾CRC校验值
			if(Xmodem.Xmodem_CRC16 == crc_value)
			{
				Xmodem.Xmodem_PacketCMP++;      //Xmodem帧序列++
				memset(Xmodem.Xmodem_Packet,0,133);
				return ACK;
			}
			else            //CRC校验错误
			{
//				printf("\r\n单片机计算的CRC:0x%04x\r\n",crc_value);
				return NAK;
			}
		}
		else if((Xmodem.Xmodem_Packet[1] == (Xmodem.Xmodem_PacketCMP-1)) && (Xmodem.Xmodem_Packet[2] == (uint8_t)~(Xmodem.Xmodem_PacketCMP-1) ))		//上位机没有接收到ACK信号发送过来上一个的重复帧
		{
			return ACK;
		}
		else
		{
//			printf("\r\nXmodem_Packet[1][2]:0x%02x\t0x%02x\r\nXmodem.Xmodem_PacketCMP:0x%02x\t0x%02x\r\n",\
//			Xmodem.Xmodem_Packet[1],Xmodem.Xmodem_Packet[2],Xmodem.Xmodem_PacketCMP,(uint8_t)~Xmodem.Xmodem_PacketCMP);
			return NAK;
		}
	}
	else if(data == EOT)
	{
//		printf("接收到传输完毕信号\r\n");

		f_size = Xmodem.Flash_PacketCNT*128;
    	I2C_Write(I2C1,ADDR_24LC02,PROGRAMSIZE_ADDR,(uint8_t *)f_size,4);        //记录接收到多少个字节
//		printf("共接收到%d个字节\r\n",f_size);
		
		return EOT;
	}
	else if(data == CRC32)
	{
		
		for(i = 0; i < 4; i++)
		{
			while(!IAP_BufferRead(&data));
			crc_buff[i] = data;
		}
		CRC_Compare.Program = (uint32_t)crc_buff[0] | (uint32_t)(crc_buff[1]<<8) | (uint32_t)(crc_buff[2]<<16) | (uint32_t)(crc_buff[3]<<24);
//		printf("CRC From GET Program Vaule : 0x%08x\r\n",CRC_Compare.Program);
		I2C_Write(I2C1,ADDR_24LC02,PROGRAMCRC_ADDR,(uint8_t *)CRC_Compare.Program,4);   //记录整个程序CRC校验值
		return CRC32;
	}
    return 0;
}

IAP_ProgramIntegrityCheck 

 函数名称:uint8_t IAP_ProgramIntegrityCheck(uint32_t addr)

功能:程序完整性校验,首先会获取eeprom中接收程序时计算的CRC值和字节数,接着读取缓存区中的数据并计算CRC,进行比较

参数:uint32_t addr  --  缓存区起始地址

返回值:0  --  CRC相同

              1  --  CRC不同

uint8_t IAP_ProgramIntegrityCheck(uint32_t addr)
{	
	static uint32_t flash_buf[32];
	uint32_t flash_addr = addr;
	uint32_t flash_len = 0x80;          //128
	uint32_t Program_CRC = 0;
	uint32_t packet_cnt = 0;
    uint8_t i = 0;
    
    CRC_ResetDR();				//每次使用CRC寄存器前应当重置一下寄存器,不然会与上次计算值重叠,读寄存器不会自动清除,且重置后会恢复缺省--大端模式
	FLASH_Unlock();
    I2C_Read(I2C1,ADDR_24LC02,PROGRAMSIZE_ADDR,(uint8_t *)packet_cnt,4);
    I2C_Read(I2C1,ADDR_24LC02,PROGRAMCRC_ADDR,(uint8_t *)Program_CRC,4);        //这里注意大小端顺序
//	printf("cnt : 0x%08x \r\n",packet_cnt);
	packet_cnt /= 128;
	while(packet_cnt--)
	{
		memcpy(flash_buf, (uint32_t *)flash_addr, flash_len);       //这里试下能不能读出来
		for(i = 0;i < 32;i++)
		{
			CRC_CalcCRC(flash_buf[i]);
		}
		flash_addr += flash_len;
	}
	CRC_Compare.Flash = CRC_GetCRC();
    FLASH_Lock();
	if(CRC_Compare.Flash == Program_CRC)
		return 0;
	else 
//		printf("********************0x%08x***************\r\n",Program_CRC);
		return 1;
}

IAP_FlashBuffToApplication 

 函数名称:uint8_t IAP_FlashBuffToApplication(uint32_t addr1,uint32_t addr2)

功能:将FLASH缓存区中的数据copy到执行区域

参数:uint32_t addr1  --  执行区起始地址

           uint32_t addr2  --  缓存区起始地址

返回值:0  --  CRC相同

              1  --  CRC不同

uint8_t IAP_FlashBuffToApplication(uint32_t addr1,uint32_t addr2)
{
    uint32_t __app_addr = addr1;
    uint32_t __buf_addr = addr2;
    uint32_t buff = 0;
    uint32_t size = 0;
    uint32_t i = 0;
    I2C_Read(I2C1,ADDR_24LC02,PROGRAMSIZE_ADDR,(uint8_t *)size,4);
    FLASH_Unlock();
    FLASH_EraseSector(FLASH_Sector_2,VoltageRange_3);       //擦除0x0804 0000
    FLASH_EraseSector(FLASH_Sector_3,VoltageRange_3);
    FLASH_EraseSector(FLASH_Sector_4,VoltageRange_3);
    FLASH_EraseSector(FLASH_Sector_5,VoltageRange_3);       //0x0803 FFFF
//    printf("固件缓存烧录中...\r\n");
    for(i = 0;i < size/4 ; i++)
    {
        memcpy(&buff,(uint32_t *)__buf_addr,0x4);
        FLASH_ProgramWord(__app_addr,buff);
        __app_addr += 4;
        __buf_addr += 4;
    }
    FLASH_Lock();         
//    printf("程序完整性校验中...\r\n");
    if(!IAP_ProgramIntegrityCheck((uint32_t)Application_Buff_ADDR))
        return 0;
    else
        return 1;
}

 IAP_SendSignalMode

 函数名称:void IAP_SendSignalMode(IAP_Mode_t mode,uint8_t *signal)

功能:根据不同模式发送不同信号到上位机

参数:IAP_Mode_t mode  --  不同总线

           uint8_t *signal  --  信号数据

返回值:无

这里只举了两个例子,其他总线按需求补充即可

void IAP_SendSignalMode(IAP_Mode_t mode,uint8_t *signal)
{
    switch((uint8_t)mode)
    {
        case UART_Mode:
                UART1_Send_Data(signal,2);
        break;
        
        case IIC_Mode:break;
        case SPI_Mode:break;
        
        case CAN_Mode:
            	CAN1_Send((uint32_t)signal,0);
        
    }
}

 IAP_BufferRead

 函数名称:uint8_t IAP_BufferRead(uint8_t *data)

功能:读IAP接收缓冲区

参数:uint8_t *data  --  存放读到的数据

返回值:0  --  无数据可读

              1  --  有数据可读

uint8_t IAP_BufferRead(uint8_t *data)
{
	if(Update_Rptr == Update_Wptr)//无数据可读
	{
	  return 0;
	}
    *data = ProgramBuffer[Update_Rptr++];//读取缓冲区数据
    Update_Rptr = Update_Rptr % MAXBUFFER;//保证读位置值不溢出
    return 1;
}

 IAP_FlashProgramdata

 函数名称:void IAP_FlashProgramdata(uint16_t data)

功能:向FLASH中写入数据

参数:uint16_t data --  要写入FLASH的数据

返回值:无

void IAP_FlashProgramdata(uint16_t data)
{
	FLASH_ProgramHalfWord(AppBuffer_Addr,data);
	AppBuffer_Addr+=2;
}

 IAP_BufferWrite

 函数名称:void IAP_BufferWrite(uint8_t data)

功能:写IAP接收缓冲区

参数:uint8_t data  --  要写入缓冲区的数据

返回值:无

void IAP_BufferWrite(uint8_t data)
{
	if(Update_Wptr == (Update_Rptr - 1))//缓冲区存满了 当读速度比较慢时,写不能和读同一个地方前一个数据还没读走不能写入。
	{
	  return;//返回
	}
    ProgramBuffer[Update_Wptr++] = data;
	Update_Wptr %= MAXBUFFER;                //保证写位置值不溢出
	rcvTimeout = 0;					
}

 全局变量、宏定义、结构体

//*************update.c**************************
#define MAXBUFFER 512
uint8_t ProgramBuffer[MAXBUFFER];                       //CAN接收数据缓存区
uint16_t Update_Wptr = 0;                                  //缓存区写指针
uint16_t Update_Rptr = 0;	                                //缓存区读指针

uint32_t AppBuffer_Addr = Application_Buff_ADDR;        //用于存放下载缓存区地址
struct Xmodem_t Xmodem;
struct CrcCheck_t CRC_Compare;

extern uint8_t rcvTimeout;

//***************update.h*******************************
#define Application_ADDR            0x08008000      //应用程序地址
#define Application_Buff_ADDR       0x080A0000      //程序缓存地址

#define SOH 0x01        //Xmodem 128字节头标志
#define EOT 0x04        //发送结束标志
#define ACK 0x06        //应答标志
#define NAK 0x15        //非应答标志
#define CRC32 0x43      //使用CRC校验标志


struct Xmodem_t
{
    int Xmodem_PacketCNT;           //接收到的Xmodem帧数量 -- 只要接收到帧起始信号就会自增1
    int Flash_PacketCNT;           //写入Flash中的Xmodem帧数 -- 每一帧校验完CRC才会写入自增1
    uint8_t Xmodem_PacketCMP;       //Xmodem帧序列号比较值 -- 用于比对记录帧是否重复接收,确保帧序正确
    uint8_t Xmodem_Packet[133];
    uint16_t Xmodem_CRC16;
};

struct CrcCheck_t
{
    uint32_t Program;            //接收到BIN文件程序的CRC校验值
    uint32_t Flash;             //写入FLASH后计算的CRC校验值
};

typedef enum Mode_t
{
    UART_Mode,
    IIC_Mode,
    SPI_Mode,
    CAN_Mode
}IAP_Mode_t;

//**********************main.h***************************
/* 开关全局中断的宏 */
#define ENABLE_INT()	__set_PRIMASK(0)	/* 使能全局中断 */
#define DISABLE_INT()	__set_PRIMASK(1)	/* 禁止全局中断 */

//更新指令帧ID
#define UPDATE_FRAME    0x0ACE
#define START_FRAME     0x000C
//更新指令标志位
#define UPDATE_FLAG     0xAA
#define NO_FLAG         0xFF
#define DOWNLOAD_FLAG   0xDD
//AT24C02地址  0 ~ 0x100
#define UPDATE_ADDR         0x50
#define PROGRAMSIZE_ADDR    0x60    //程序大小存放地址
#define PROGRAMCRC_ADDR     0x64    //程序CRC值存放地址

技术细节

如要使用此设计模式,可直接将IAP_BufferWrite(uint8_t data)函数增加到不同总线的中断接收函数中,并将IAP_SendSignalMode(IAP_Mode_t mode,uint8_t *signal)IAP_UpdateProgram(IAP_Mode_t mode)中的形参mode设为对应的总线模式即可。

小结

水平尚浅,变量命名有些乱,凑活看吧,后续会继续研究兼容其他总线,精力应该主要放在USB总线上(真的好难呜哇o(╥﹏╥)o)

  • 29
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,我可以对STM32IAP固件升级设计和U盘升级固件方案进行更详细的介绍。 在STM32中,IAP固件升级设计主要是通过在MCU内部实现自身的固件升级。具体实现方法包括: 1. 在Flash存储器中预留一定的存储空间,用于存放固件升级文件。 2. 在MCU的Bootloader程序中实现升级文件接收和存储功能。 3. 在升级文件传输完成后,MCU会自动重启,并执行新的固件程序。 IAP固件升级设计需要注意以下几点: 1. 升级文件的格式需要符合固件升级设计的要求,在文件传输过程中需要进行校验,确保数据传输的正确性。 2. 在升级过程中需要保证固件程序的完整性,避免升级失败导致MCU无法正常工作。 3. 在实现IAP固件升级设计时,需要考虑到不同型号的MCU之间的差异,确保程序的兼容性和可移植性。 U盘升级固件方案是一种简单、灵活的固件升级方式。具体实现方法包括: 1. 将升级文件存储在U盘中,并将U盘连接到MCU的USB接口。 2. 在MCU中实现USB设备模式,接收U盘中的升级文件,并存储在Flash存储器中。 3. 在升级文件传输完成后,MCU会自动重启,并执行新的固件程序。 U盘升级固件方案需要注意以下几点: 1. 升级文件的格式需要符合固件升级设计的要求,在文件传输过程中需要进行校验,确保数据传输的正确性。 2. 在实现USB设备模式时,需要考虑到不同型号的MCU之间的差异,确保程序的兼容性和可移植性。 3. 在使用U盘升级固件方案时,需要保证U盘的兼容性和可靠性,避免升级失败导致MCU无法正常工作。 以上是对STM32IAP固件升级设计和U盘升级固件方案的详细介绍,希望能够对你有所帮助。如有需要,可以进一步了解相关技术细节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值