1.bootloader简介
Bootloader是指系统启动的第一段代码,位于计算机或嵌入式设备的非易失性存储器(如闪存、EPROM等)中。它负责初始化硬件设备、加载操作系统内核,并将控制权传递给内核的入口点,开始系统的正常运行。
Bootloader的主要功能包括以下几个方面:
-
硬件初始化:Bootloader负责初始化计算机或设备的硬件设备,包括处理器、内存、外设等。这些初始化操作确保系统硬件处于正确的状态,并为后续的操作做好准备。
-
引导加载:Bootloader从存储介质(如闪存)中读取操作系统内核的镜像,并将其加载到内存中。这涉及到文件系统的解析和解压缩,确保内核映像正确加载到内存中。
-
参数传递:Bootloader还负责将一些参数传递给操作系统内核,例如启动参数、设备参数等。这些参数可以影响内核的运行方式和配置。
-
可选功能:某些Bootloader还提供额外的功能,例如启动菜单、系统恢复、固件升级等。这些附加功能可以根据需要进行扩展和定制。
Bootloader在计算机和嵌入式系统中起到了非常重要的作用,它是系统启动的关键环节。Bootloader的设计和实现需要考虑硬件平台的特点和要求,以及操作系统的需求。不同平台和设备可能使用不同类型的Bootloader,例如基于BIOS的x86系统(相关技术可以查看BIOS专栏)、基于U-Boot的嵌入式系统等。
总之,Bootloader是系统启动过程中的第一步,它负责初始化硬件、加载内核,并将控制权传递给内核,使系统正常运行。
2.在单片机中实现一个有OTA功能的bootloader
-
地址映射:单片机的存储器中需要划分出一部分空间用于存放Bootloader代码。这个空间通常位于存储器的固定地址处,并设置为可写入和可执行。
-
启动向量设置:将单片机的启动向量设置为Bootloader的起始地址,以便在上电或复位时跳转到Bootloader代码处执行。
-
初始化硬件:Bootloader需要初始化所使用的硬件,包括外设、时钟、中断等。
-
检查更新:Bootloader需要检查是否存在新的固件更新。这可以通过读取特定的存储区域、接收网络数据或其他方式来实现。如果有更新,Bootloader会下载并存储到适当的位置。
-
启动应用程序或内核:如果没有新的固件更新,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 0x08050000typedef 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程序}
}