引导加载程序几乎包含在每个嵌入式系统中,并提供了一种在现场更新应用程序代码的好方法,而无需访问编程端口。与引导加载程序一样重要的是,嵌入式开发人员在尝试从引导加载程序跳转到他们的应用程序代码时经常会被出错。跳跃需要干净利落,但有几个因素会导致问题,例如:
l 一次写入寄存器(例如看门狗寄存器)
l 时钟设置
l 堆栈和程序指针
l 外围设置
开发人员可以通过两种不同的方式从引导加载程序干净地过渡到应用程序代码。第一种方法涉及开发人员仔细匹配他们的应用程序代码和引导程序设置。例如,开发人员将匹配看门狗寄存器、时钟设置,甚至可能匹配UART等外设。引导加载程序将所有这些组件初始化为已知的系统状态,并简单地将程序执行交给应用程序代码。这样做的问题是,应用程序代码中的任何更改也需要在引导装载程序中进行更改。
处理引导加载程序和应用程序代码之间跳转的第二种也是更干净的方法是遵循一个简单的过程,将引导加载程序所涉及的任何设置恢复到它们最初的重置状态。这将包括外设,如GPIO、时钟,甚至修改堆栈和程序计数器。当嵌入式开发人员这样做时,应用程序代码完全不知道内存中正在执行另一个应用程序,唯一需要匹配的设置是只能写入一次的寄存器!
准备从引导程序跳转到应用程序代码的过程很简单,可以在下面找到:
l 确认应用复位向量已经编程
l 验证应用程序校验和及安全凭证
l 对外设进行去初始化,并将其置于复位状态
l 将向量表寄存器设置为应用复位向量(ARM)
l 将堆栈指针寄存器设置为应用程序起始地址
l 将程序计数器设置为复位向量(跳转到应用程序)
让我们简单讨论一下每一步。在引导加载程序做任何事情之前,它需要验证应用程序的完整性。第一步是验证复位向量已经编程,并且不是0xFFFFFFFF或0x00000000。通常,擦除的闪存会设置其所有位,因此如果我们看到这种状态,则引导加载程序知道有问题,应该保持在引导加载程序中,这是一种已知的安全状态。请记住,我们假设两者之间的任何编程值都是正确的,这可能是一个糟糕的假设,但对于今天嵌入式开发人员来说已经足够好了。
接下来,引导装载程序应该对应用程序空间执行校验和计算,以确保应用程序是有效的。同样,如果在更新过程中出现问题,可能会有一个带有复位向量的部分程序,如果没有校验和,就无法知道这一点。最重要的是,引导装载程序还应该检查任何数字签名或安全措施,以确保不仅应用程序是完整的,而且其来源也是正确的,它不是恶意软件或修改过的程序。
一旦引导装载程序验证了应用程序是完整的并且来自正确的来源,就该回到初始状态了。任何被触摸的非一次写入的外设都应该被放回到它们的复位状态。最好的方法是查看所用的驱动器、驱动器初始化和访问的寄存器,然后在数据手册中查找这些寄存器。每个数据手册都显示了上电复位寄存器的值。对于每个被修改的寄存器,它们可以被复位到这些状态。通常,我会避免更改时钟寄存器、看门狗和一次写入寄存器。我只是在应用程序和引导装载程序之间匹配这些状态。
此时,微控制器回到复位状态,并准备运行应用程序代码。在此之前,需要更新向量表寄存器,以指向应用程序的向量表位置,而不是引导加载程序。中断向量表可以位于任何地方,因此引导装载程序和应用程序链接器文件之间需要协调。例如,嵌入式开发人员将编写如下单行代码,其中PROGRAM_FLASH_BASE是应用程序的第一个向量表位置:
SCB _ VTOR =(uint 32 _ t)PROGRAM _ FLASH _ BASE;
一旦完成了这些,就该跳到应用程序了。开发人员可以通过几种不同的方式从引导装载程序跳转到应用程序。一种方法是简单地取消引用应用程序的重置向量位置。这样做的问题是堆栈指针可能不在正确的位置,从而导致奇怪的行为。理想情况下,开发人员会设置堆栈指针,然后设置程序计数器。如何做到这一点将因微控制器而异。几乎总是需要使用内联汇编代码来完成(这是我唯一一次提倡编写汇编代码)。对于ARM微控制器,下面是一个示例代码片段:
void Flash _ start application(uint 32 _ t start address)
{
asm(" ldr SP,[r0,# 0]");
asm("ldr PC,[r0,# 4]");
}
根据所使用的编译器,确切的代码会略有不同。内联汇编不是C标准,所以每个编译器供应商都以不同的方式实现了它,或者在某些情况下根本没有实现!让我们来看看这是怎么回事。
为了最小化汇编语言代码,将汇编语言代码包装在C函数中是至关重要的。原因是当startAddress被传递到Flash_StartApplication函数中时,它会自动存储在寄存器r0中。有了这些知识,就没有理由添加额外的汇编语言指令来将所需的起始地址加载到寄存器中。(是的,它为我们节省了一条汇编指令,但这样做也更容易维护,更灵活)。然后,第一条汇编指令是获取存储在寄存器r0中偏移量为0(# 0)的值,并将其复制到堆栈指针(SP)寄存器。然后,第二条指令告诉处理器将r0中存储的值加上偏移量4 (#4),并将其复制到程序计数器(PC)寄存器中。偏移量4实际上是将r0中存储的值加4。执行的下一条指令将是应用程序代码的复位向量。我们刚刚成功地进入了应用程序!
这就是全部了!按照这个过程,嵌入式开发人员现在可以很容易地从引导加载程序跳转到你的应用程序代码,并确保它将按照你期望的方式运行。
发表评论 取消回复