单片机中实现bootloader功能

1.bootloader简介

Bootloader是指系统启动的第一段代码,位于计算机或嵌入式设备的非易失性存储器(如闪存、EPROM等)中。它负责初始化硬件设备、加载操作系统内核,并将控制权传递给内核的入口点,开始系统的正常运行。

Bootloader的主要功能包括以下几个方面:

  1. 硬件初始化:Bootloader负责初始化计算机或设备的硬件设备,包括处理器、内存、外设等。这些初始化操作确保系统硬件处于正确的状态,并为后续的操作做好准备。

  2. 引导加载:Bootloader从存储介质(如闪存)中读取操作系统内核的镜像,并将其加载到内存中。这涉及到文件系统的解析和解压缩,确保内核映像正确加载到内存中。

  3. 参数传递:Bootloader还负责将一些参数传递给操作系统内核,例如启动参数、设备参数等。这些参数可以影响内核的运行方式和配置。

  4. 可选功能:某些Bootloader还提供额外的功能,例如启动菜单、系统恢复、固件升级等。这些附加功能可以根据需要进行扩展和定制。

Bootloader在计算机和嵌入式系统中起到了非常重要的作用,它是系统启动的关键环节。Bootloader的设计和实现需要考虑硬件平台的特点和要求,以及操作系统的需求。不同平台和设备可能使用不同类型的Bootloader,例如基于BIOS的x86系统(相关技术可以查看BIOS专栏)、基于U-Boot的嵌入式系统等。

总之,Bootloader是系统启动过程中的第一步,它负责初始化硬件、加载内核,并将控制权传递给内核,使系统正常运行。

2.在单片机中实现一个有OTA功能的bootloader

  1. 地址映射:单片机的存储器中需要划分出一部分空间用于存放Bootloader代码。这个空间通常位于存储器的固定地址处,并设置为可写入和可执行。

  2. 启动向量设置:将单片机的启动向量设置为Bootloader的起始地址,以便在上电或复位时跳转到Bootloader代码处执行。

  3. 初始化硬件:Bootloader需要初始化所使用的硬件,包括外设、时钟、中断等。

  4. 检查更新:Bootloader需要检查是否存在新的固件更新。这可以通过读取特定的存储区域、接收网络数据或其他方式来实现。如果有更新,Bootloader会下载并存储到适当的位置。

  5. 启动应用程序或内核:如果没有新的固件更新,Bootloader可以直接跳转到应用程序或操作系统内核的入口点。否则,Bootloader将加载新的固件并跳转到它的入口点。

针对OTA更新,可以设计一个分区用于存储固件更新的数据。这个分区可以是单片机存储器中的一个区域,专门用于存放OTA更新的固件数据。当检测到有固件更新时,Bootloader会通过网络或其他外设接收更新数据,并将其存储到OTA分区中。然后,Bootloader会根据OTA分区中的固件数据进行更新操作,最后跳转到新固件的入口点执行。

3.内存划分的示例

/* 
   使用布局如下:

   0x08000000  -|---------------------  给定一个起始地址
       ~        |  64k   bootloard的程序
   0x08010000  -|--------------------- ApplicationAddress
       ~        |  256k  应用程序1 确保应用程序的大小不会覆盖到下面的数据
       ~        |        确保应用程序的大小不会覆盖到下面的数据
   0x08050000  -|--------------------- UpgradeFileAddress
       ~        |  256k  应用程序2 (也可作为OTA程序区)
       ~        |
   0x08090000  -|-------------------------
       ~        |         数据区
      end      -|
 */

4.bootloader示例1

每次在bootloader启动时都去检查OTA程序区是否存在程序

如果存在则将OTA程序拷贝到应用程序的内存中,运行。然后清空OTA空间中的代码。

这种方法需要在bootloader程序中进行OTA的校验和擦除,但是管控集中,更加安全,不会因为应用程序的出错而造成当前应用程序的出错。

#define ApplicationAddress     0x08010000
#define UpgradeFileAddress     0x08050000
#define FlashEndAddress        0x08090000
#define UpgradeFileMaxLen     (FlashEndAddress - UpgradeFileAddress)

typedef  void (*pFunction)(void);

/* 升级程序消息摘要 */
typedef struct __UpgradeDigest
{
    char MagicNumber[4];     //用与分辨是否是自己人
    uint32_t FileSize;
    uint32_t CRCValue;  
} UpgradeDigest;

void runApplication()
{
    printf("应用程序启动中...\r\n");
    
    if (检查ApplicationAddress地址是否有效 - todo)
    { 
        pFunction Jump_To_Application;
        uint32_t JumpAddress;

        JumpAddress = *(volatile uint32_t*) (ApplicationAddress + 4);
        Jump_To_Application = (pFunction) JumpAddress;

        //设置主堆栈指针的起始地址(ApplicationAddress) todo;

        Jump_To_Application();
    }
}

int main(void)
{
    /* 一些硬件的初始化 */
    ...

    UpgradeDigest *digest = NULL;
    uint32_t appAddress = 0;
    uint32_t gradeAddress = 0;
    uint32_t crc32 = 0;
    uint8_t upgradeFileFound = 0;
    
	/* 1.检查指定区域是否存在'升级程序消息摘要' */    
	digest = (UpgradeDigest *)UpgradeFileAddress;
	
	if( (memcmp(digest->MagicNumber, "xxxx", 4) == 0) &&
		(digest->FileSize <= UpgradeFileMaxLen) )
	{
		/* 存在'升级程序消息摘要',准备校验升级文件  */
		appAddress = ApplicationAddress;
		gradeAddress = UpgradeFileAddress + sizeof(UpgradeDigest);   
		crc32 = getCRC32((uint8_t *)gradeAddress, digest->FileSize);
		if(crc32 == digest->CRCValue)
		{
			/* 检验成功 */
			upgradeFileFound = 1;
		}
		else
		{
			/* 校验失败,清空程序升级区 - todo */
			printf("校验升级文件失败: CRC校验失败! \r\n");
		}
	}
		
	if(upgradeFileFound)
	{
		/* 2.升级文件校验成功,开始复制数据至程序启动位置 */
		printf("开始升级");
		Flash_WriteData(appAddress, (uint32_t *)gradeAddress, digest->FileSize);
		/* 3.复制结束,校验程序CRC */
		crc32 = getCRC32((uint8_t *)ApplicationAddress, digest->FileSize);
		if(crc32 == digest->CRCValue)
		{
			/* 复制成功, 清空升级数据 - todo */
			
			/* 4. 现在可以启动程序 */
			printf("升级成功! \r\r\n");
			runApplication();

		}
		else
		{
			/* 复制失败,清空应用程序区 - todo*/
			printf("升级失败: CRC校验失败,复制过程出错! \r\n");
		}
		
	}
	else
	{
		runApplication(); //不存在ota程序直接运行
	}
}

5.bootloader示例2

这种方法下2个应用程序区都将保留,当应用程序2区被激活时则执行应用程序2,反之则执行应用程序1。

这种方法需要在应用程序中管控更多的变量,但是在程序启动来说会更快。因为2个OTA区都可以在应用程序中修改,造成了一定的风险。

#define ApplicationAddress_1     0x08010000
#define ApplicationAddress_2     0x08050000

typedef  void (*pFunction)(void);

/* OTA消息摘要 */
typedef struct __OTADigest
{
    uint32_t active;     //是否启用
    uint32_t FileSize;
    uint32_t CRCValue;  
} OTADigest;

void runApplication(uint32_t JumpAddress)
{
    printf("应用程序启动中...\r\n");
    
    if (检查JumpAddress地址是否有效 - todo)
    { 
        pFunction Jump_To_Application;

        Jump_To_Application = (pFunction) *(volatile uint32_t*) (JumpAddress + 4);

        //设置主堆栈指针的起始地址(JumpAddress) todo;

        Jump_To_Application();
    }
}

int main(void)
{
    /* 一些硬件的初始化 */
    ...

    OTADigest *ota = NULL;
    uint32_t gradeAddress = 0;
    uint32_t crc32 = 0;
    uint8_t OTA_2_FileFound = 0;
    
	/* 1.检查指ota 2区是否存在程序 */    
	ota = (OTADigest *)ApplicationAddress_2;
	
	if(ota->active  == 1))
	{
		/* ota 1 区 启用,准备校验升级文件  */
		gradeAddress = ApplicationAddress_2 + sizeof(OTADigest);   
		crc32 = getCRC32((uint8_t *)gradeAddress, digest->FileSize);
		if(crc32 == digest->CRCValue)
		{
			/* 检验成功 */
			OTA_2_FileFound = 1;
		}
	}

		
	if(OTA_2_FileFound)
	{
		runApplication(ApplicationAddress_2);
	}
	else
	{
		runApplication(ApplicationAddress_1); //不存在ota 2 程序直接运行ota 1程序
	}
}

你可能感兴趣的:(从单片机到freertos,单片机,bootloarder)