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