手把手教你设计一个通用BootLoader

关键技术:函数指针

1 BootLoader

        BootLoader大家应该都听过用过或者自己设计过,进入应正式应用程序前的一个很小的程序,检测应用程序是否需要更新,当需要的时候执行擦除、写入、校验等操作,并跳转到应用程序,一般的做法都如下图所示。就这样看似简单的一个功能,大家在设计过程中有没有遇到这样的问题呢,每做一个项目或者每换一个芯片型号,BootLoader和相应的上位机都要重做一套,重复地苦力搬砖。本文针对这个问题,采用数据操作层与底层脱离的方法,设计了一个相对通用的BootLoader仅供参考,不妥之处,敬请斧正!

2 思路与实现

        BootLoader最主要的功能就是接收数据、存储数据。数据来源、Flash的擦写是跟平台底层紧密相关的,无法通用,而具体去操作数据收发和擦写Flash的方法是可以通用的,这里大家有没有想到什么呢?函数指针!函数指针是可以将操作方法与具体实现脱离的一种设计,设计如下:

        整体分为两部分四个文件,boo.c、boot.h是操作方法,写好后就不需要再改动,而boot_device.c、boot_device.h是跟平台相关的。

        最终实现的效果为:移植时上位机和boo.c、boot.h不需要改动,boot_device.c、boot_device.h只需要实现对应平台的Flash擦写操作,以及定义好Flash存储区域即可。

 

2.1 在boot.h中定义回调函数结构体

        主要包括数据接收、发送、Flash擦除、写入、读取,延时和喂狗根据项目而定,可以为空,这个结构体主要用于初始化时将函数指针与平台具体操作相关联:

typedef struct
{
	//CallBack
	void *(*GetData)(void); //获取数据,返回数据首地址指针
	void (*SendData)(void *data, uint32_t len); //发送数据,主要回复上位机
	
	BootStatus_t (*FlashErase)(uint32_t startAddr, uint32_t len);
	BootStatus_t (*FlashWrite)(uint32_t startAddr, void *data, uint32_t len);
	BootStatus_t (*FlashRead)(uint32_t startAddr, void *data, uint32_t len);
	
	void (*SysDelay)(uint32_t num); //获取系统延时函数
	void (*FeedDog)(void); //喂狗
}BootCallback_TypeDef;

//只提供两个函数供外部调用
void BootInit(BootCallback_TypeDef *boot); //关联回调函数
BootStatus_t BootStart(); //启动Boot,成功或超时后退出

2.2 在boot.c中实现每个方法的操作流程

        1)实现初始化函数 BootInit(),这里考虑到如果系统有sysTick这类的时钟,那就直接调用,可以精确延时,但是有的平台没有提供,所以延时只能在自己内部计数实现,延时需要根据主频调整:

BootCallback_TypeDef *_boot; //函数操作接口
void BootDelay(uint32_t num)
{
	uint32_t i = 0;
	for(i = 0; i < num*1000; i++);
}

void BootInit(BootCallback_TypeDef *boot)
{
	_boot = boot;
	if(_boot->SysDelay == NULL)
	{
		_boot->SysDelay = &BootDelay;
	}
}

        2)初始化完成后即可调用BootStart(),进入Boot中不断检测数据(这里我的数据是一个FIFO类型),进行相应处理,完成或超时后退出:

//启动Boot
BootStatus_t BootStart(void)
{
	//进入boot,完成或超时后返回
	PackResult_TypeDef *res;
	_bootFifo = (FIFO_TypeDef*)(_boot->GetData()); //获取数据接口,相当于把数据托管给Boot自行检测处理
	
	while(1)
	{
		_boot->FeedDog(); //喂狗,没有开看门狗实际函数数为空即可
		BootData_Process(_bootFifo); //查询解析数据
		_boot->SysDelay(2); //适当延时
		if(_nonRecivedTime > 1000) break; //boot成功后主动置nonRecivedTime为一个大于1000的数,退出
		if(_nonRecivedTime++ > 500) goto boottimeout; //若nonRecivedTime 自己累加到500,则1秒钟超时
	}
	res = PackData(CMD_LOG, "Boot Over!!\n", 12); //打包数据发往上位机
	_boot->SendData(res->Pack, res->PackLen);
	return Boot_OK;
	
	boottimeout:
	res = PackData(CMD_LOG, "Boot TimeOut!!\n", 15);
	_boot->SendData(res->Pack, res->PackLen);
	return Boot_TimeOut;
}

        3)在BootData_Process()里面解析处理数据,具体解析方法根据数据类型自己定义:

//查询是否有数据需要处理
void BootData_Process(FIFO_TypeDef *bootFifo)
{
	uint32_t count = FIFO_DataCount(bootFifo);
	uint16_t packLen = 0;
	uint8_t *pData;
	if(count > 4) //当满足一定条件时进行解析
	{
		pData = FIFO_PDataOutPre(bootFifo,count); //预取数据进行判断
		packLen = pData[2] + (pData[3]<<8);
		if(packLen <= count) //接收完成
		{
			if(CheckCrc(pData,packLen)) //校验正确
			{
				Boot_Unpack(pData,packLen); //解析
				FIFO_PDataOut(bootFifo,packLen); //取出已处理的数据
			}
			else
			{
				bootFifo->OutputIndex = bootFifo->InputIndex; //清空数据
				return;
			}
		}
	}
}

//数据包进行解析
void Boot_Unpack(void *src, uint32_t len)
{
	TransPack_TypeDef *pack = (TransPack_TypeDef *)src;
	_nonRecivedTime = 0;
	switch(pack->Cmd)
	{
		case CMD_BOOT_START:
			//todo:boot前准备
			break;

		case CMD_BOOT_EREASE:
			BootEraseFlash(src, len);
			break;
		
		case CMD_BOOT_RECIVE:
			BootSaveData(src, len);
			break;
		
		case CMD_BOOT_STOP:
			BootStop(src, len);
			break;
		
		case CMD_BOOT_RUNAPP:
			break;
		default:
			break;
	}
}

        4)对每一个命令进行具体实现

//擦除Flash,擦除大小由上位机控制
BootStatus_t BootEraseFlash(void *src, uint32_t len)
{
	BootStatus_t status;
	PackResult_TypeDef *res;
	TransPack_TypeDef *pack = (TransPack_TypeDef *)src;
	
	//todo: erase flash
	_bootConfig.AppLen = *(uint32_t*)(&pack->Data[0]); //擦除指定大小flash区域
	status = _boot->FlashErase(BOOTDATAFLASH_ADDR, _bootConfig.AppLen); //通过回调函数调用实际的擦除函数
	
	res = PackData(CMD_BOOT_EREASE, &status,1); //数据打包
	_boot->SendData(res->Pack, res->PackLen); //通过回调函数将处理结果返回上位机
	return status;
}

//保存数据
BootStatus_t BootSaveData(void *src, uint32_t len)
{
	BootStatus_t status = Boot_OK;
	PackResult_TypeDef *res;
	TransPack_TypeDef *pack = (TransPack_TypeDef *)src;
	uint32_t dataLen = pack->Len - 2;

	MemCyp(pack->Data, _bootDataTemp, dataLen); //非必要,防止没有字节对齐
	//todo: save data
	status = _boot->FlashWrite(BOOTDATAFLASH_ADDR + _flashWriteIndex, _bootDataTemp, dataLen); //通过回调写入数据
	_flashWriteIndex += dataLen;
	
	writeEnd:
	_boot->SysDelay(5);
	res = PackData(CMD_BOOT_RECIVE, &status,1);
	_boot->SendData(res->Pack, res->PackLen);
	return status;
}

//boot完成,方法同上,就不贴详细代码了
BootStatus_t BootStop(void *src, uint32_t len)
{
	BootStatus_t status;
	//同上操作
	//校验--->拷贝
// crc = Crc16((uint8_t *)(BOOTDATAFLASH_ADDR), dataLen); //可以用这种方式直接访问Flash
	//向上位机反馈结果
	//若成功,置标志后退出	
	_nonRecivedTime = 10000; //成功,退出boot
	return status;
}

        至此,基本操作函数就己实现完成,下面实现回调函数。

2.3 在boot_device.h中定义平台

//选择平台
#define STM32L4_BOOT
//#define NXP_S32_BOOT

#ifdef STM32L4_BOOT 
//该平台相存储区
#define APPFLASH_ADDR    		((uint32_t)(0x08008000))  //32K后为APP
#define BOOTDATAFLASH_ADDR  	((uint32_t)(0x08012800))  //42K后为back区
#define MAXAPPLEN		 		((uint32_t)(0x0000A800))  //96K

#define PAGE_SIZE   2048
#endif


//其他平台定义
#ifdef xxxxxxx


#endif

2.4 在boot_device.c中实现底层操作

        下面以STM32L4为例,只需要实现该平台的底层Flash操作,移植时仅需要修改#ifdef … #endif 中的底层函数:

void *GetBootData(void);
void SendBootData(void *data, uint32_t len);
BootStatus_t Flash_Erase(uint32_t addr, uint32_t len);
BootStatus_t Flash_Write(uint32_t addr, void *data, uint32_t len);
BootStatus_t Flash_Read(uint32_t startAddr, void *data, uint32_t len);
void SysDelay(uint32_t num);
void FeedDog(void);


//函数指针关联初始化
BootCallback_TypeDef BootLoader =
{
	GetBootData,
	SendBootData,
	Flash_Erase,
	Flash_Write,
	Flash_Read,
	SysDelay, 		//获取系统延时,如果没有则为NULL,boot内部则使用计数延时
	FeedDog,
};

void FeedDog(void)
{
	//开启看门狗时使用,没开看门狗 该函数断为空即可
	HAL_IWDG_Refresh(&hiwdg);
}

void SysDelay(uint32_t num)
{
	//获取系统延时
	HAL_Delay(num);
}


//让bootloader获取数据,注意返回的是指针,是把这个数据区给bootloader,而不是直接返回数据
//我使用的是FIFO数据区
void *GetBootData(void)
{
	//这里我使用的USB boot,直接返回usb数据区,托管给bootloader自行查询
	return &_uart1_FIFO; 
}

void SendBootData(void *data, uint32_t len)
{
	USBSendData(data, len); //通过usb将数据返馈给上位机
}



#ifdef STM32L4_BOOT

typedef  void (*pFunction)(void);
pFunction jump2app;
void (*jump2app)();

void RunApp()
{
   if (((*(__IO uint32_t*)APPFLASH_ADDR) & 0x2FFE0000 ) == 0x20000000)
    {
       __disable_irq();
		//根据情况 DeInit相关外设
		SCB->VTOR = FLASH_BASE | 0x08008000;
       jump2app = (void (*)())*(__IO uint32_t*) (APPFLASH_ADDR + 4);
     __set_MSP(*(__IO uint32_t*) APPFLASH_ADDR);
      jump2app();
   }
 }

 
BootStatus_t Flash_Erase(uint32_t addr, uint32_t len)
{
	uint32_t pageError = 0;
	HAL_StatusTypeDef status;
	FLASH_EraseInitTypeDef flash_erase;
	HAL_FLASH_Unlock();
	flash_erase.TypeErase = FLASH_TYPEERASE_PAGES; //页擦除
	flash_erase.NbPages = len / PAGE_SIZE + 1; //需要擦除的页数
	flash_erase.Page = (addr - 0x08000000) / PAGE_SIZE;   //擦除第xx页
	status = HAL_FLASHEx_Erase(&flash_erase,&pageError);
	HAL_FLASH_Lock();
	if(status == HAL_OK) return Boot_OK;
	else return Boot_EraseFlashError;
}


BootStatus_t Flash_Write(uint32_t addr, void *data, uint32_t len)
{
	uint32_t writeIndex = 0;
	uint8_t *pData = data;
	HAL_StatusTypeDef status;
	HAL_FLASH_Unlock();
	while(writeIndex < len)
	{
		status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + writeIndex, *(uint64_t*)(&pData[writeIndex]));//写入数据
		if(status != HAL_OK) break;
		writeIndex += 8;
	}
	HAL_FLASH_Lock();
	if(status == HAL_OK) return Boot_OK;
	else return Boot_WriteFlashError;
}


BootStatus_t Flash_Read(uint32_t startAddr, void *data, uint32_t len)
{
	//选择性使用,可以直接操作flash读取
}
#endif

2.5 在main中调用

void Main(void)
{
	//先初始化其他外设
	//完成后进行boot初始化
	BootInit(&BootLoader); //参数BootLoader为boot_device.c中的初始化结构体
	if(BootStart() == Boot_OK) //进入boot,在boot中自行查询数据,成功或超时后返回
	{
		//boot成功后的操作
	}
	//超时后的操作
	RunApp();
	
}

好了,现在BootLoader已经设计完成了,再做一个简单的上位机来配合就可以boot一切了!

本文主要讲方法,功能自行扩展!

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值