许多嵌入式系统部署在人类操作员很难或无法访问的地方。 对于物联网应用程序来说尤其如此,物联网应用程序通常数量较大,电池寿命有限。 一些例子是监视人或机器健康状况的嵌入式系统。 这些挑战,再加上快速的软件生命周期,导致许多系统需要对OTA更新提供支持。
OTA更新以新的软件替代了嵌入式系统中单片机或微处理器上的软件。 虽然许多人非常熟悉他们的移动设备上的 OTA 更新,但是在资源受限的系统上的设计和实现会带来许多不同的挑战。
在IoT固/软件更新及开源选项一文中,学习了一些开源的技术,在这里,将描述几种不同的OTA更新软件设计,并讨论它们的利弊,并将了解两个超低功耗微控制器的硬件特性如何在 OTA更新软件中得到的利用。
构建基础
嵌入式系统中的CS架构
OTA升级用新的软件取代了设备上现有的软件,新的软件通过无线网络下载。 在嵌入式系统中,运行这个软件的设备通常是一个微控制器。 微控制器是一种小型计算设备,具有有限的存储器,速度和功耗。 微控制器通常包含一个微处理器(核心)以及用于特定操作(外围设备)的数字硬件。 超低功耗微控制器通常在主动模式下消耗30 微安/Mhz到40微安/Mhz,是这种类型应用的理想选择。
在这些微控制器上使用特定的硬件外设,并将其设置为低功耗模式,是 OTA 更新软件设计的重要组成部分。 图1显示了一个可能需要 OTA 更新的嵌入式系统示例。 这里的一个微控制器连接着一个无线模块和传感器,它可以用在物联网应用中,通过传感器收集环境数据,并定期用无线模块报告。 系统的这一部分称为边缘节点或客户端,是 OTA 更新的目标。 系统的另一部分称为云或服务器,是新软件的提供者。 服务器和客户端通过使用收发信机(无线电)进行通信。
图1 嵌入式系统中的客户机/服务器体系结构
OTA 的软件本质
OTA更新流程的大部分工作是将新软件从服务器转移到客户端。 当软件从源代码格式转换为二进制格式后,软件以字节序列的形式传输。 转换过程包括编译源代码文件(例如 c、 cpp) ,将它们链接到一个可执行文件(例如 exe、 elf)中,然后将可执行文件转换为可移植的二进制文件格式(例如 bin、 hex)。这些文件格式包含一个字节序列,属于微控制器内存的特定地址。
通常,通过无线链路发送的信息概念化为数据,例如更改系统状态或系统收集的传感器数据的命令。 在 OTA 更新的情况下,数据是二进制格式的新软件。 在许多情况下,二进制文件太大,无法将一次传输从服务器发送到客户机,这意味着需要将二进制文件放入单独的数据包中,这个过程被称为打包。 为了更好地将这一过程可视化,图2演示了不同版本的软件如何生成不同的二进制文件,从而在 OTA 更新期间发送不同的数据包。 在这个简单的示例中,每个数据包包含8个字节的数据,前4个字节表示客户机内存中的地址,用于存储后4个字节的数据。
图2 软件应用的二进制转换和打包过程
OTA的主要挑战
基于这种对 OTA 更新流程的描述,OTA 更新解决方案必须解决三大挑战。
第一个挑战与内存有关。 软件解决方案必须将新的软件应用程序组织到客户端设备的易失性或非易失性存储器中,以便在更新过程完成时执行。 解决方案必须确保以前的软件版本在新软件出现问题时作为后备应用程序保留下来。 此外,必须保留客户端设备的状态之间的重置和电源周期,如软件的版本,已经目前正在运行在内存中的位置。
第二个挑战是通信。新软件必须以离散数据包的形式从服务器发送到客户机,每个数据包针对客户机内存中的特定地址。 软件设计中必须考虑数据包的分组方案、分组结构和传输协议。
最后一个主要挑战是安全问题。 随着新的软件从服务器无线发送到客户端,必须确保服务器是可信的。 这种安全挑战称为身份验证,还必须确保新软件对任何观察者进行模糊处理,因为它可能包含敏感信息。这种安全挑战称为保密性。 安全的最后一个要素是完整性,确保新软件在空中发送时不会损坏。
引导加载程序
理解启动顺序
主引导加载程序是永久驻留在微控制器只读内存上的软件应用程序。 主引导加载程序驻留的内存区域称为信息空间,用户有时无法访问该区域。 这个应用程序在每次重置时执行,通常执行一些必要的硬件初始化,并可能加载用户软件到内存中。
但是,如果单片机包含片内非易失性内存,如闪存,启动加载程序不需要做任何加载,只需将控制权转移到闪存中的程序。 如果主引导加载程序没有对 OTA 更新的任何支持,则有必要使用第二阶段引导加载程序(SSBL)。 与主引导加载程序一样,SSBL 将在每次reset时运行,但将实现OTA更新过程的一部分。 引导顺序如图3所示。 在这里将学习为什么需要第二阶段引导加载程序,以及如何指定此应用程序的角色是一个关键的设计权衡。
图3 用SSBL实现内存映射和引导流的示例
不使用SSBL的问题
从概念上讲,省略 SSBL 将所有的OTA更新功能放到用户应用程序中似乎更简单,因为它将允许现有的软件框架、操作系统和设备驱动程序无缝地用于OTA过程。 选择这种方法的系统内存映射和引导顺序如图4所示。
图4 没有SSBL的内存映射和启动流
应用程序A是原来的应用,是部署在微控制器的控制域。 此应用程序包含与 OTA 更新相关的软件,当服务器请求时,将利用该软件下载应用程序 B。 在完成下载并验证了应用程序B 之后,应用程序A将通过向应用程序B执行reset指令将控制转移到应用程序B。 reset处理程序是一小段代码,它是软件应用程序的入口点,并在重置时运行。 在这种情况下,通过执行分支(相当于函数调用)来模仿reset。
这种做法有两个主要问题:
- 许多嵌入式软件应用程序使用实时操作系统(RTOS) ,它允许将软件分解成并发任务,每个任务在系统中有不同的职责。 例如,图1所示的应用程序可能具有读取传感器、在传感器数据上运行算法以及与无线模块连接的 RTOS 任务。 RTOS本身总是处于活动状态,负责基于异步事件或特定的时间延迟在这些任务之间切换。 因为其他任务将保持在后台运行,所以从 RTOS 任务分支到一个新的程序是不安全的, 唯一安全的方法是通过通过重置来终止一个程序与实时操作系统。
- 基于图4,解决上一个问题的办法是将主引导加载程序切换到应用程序B,而不是应用程序A。然而,在一些微控制器上,主引导加载程序总是运行中断向量表(IVT)的程序,IVT 是应用程序中描述中断处理函数的关键部分,位于地址0。 这意味着需要某种形式的IVT重新定位到应用程序B的重置映射。 如果电源重启在 IVT 重新定位过程中发生,则可能使系统处于永久性的破坏状态。
如图3所示,通过在地址0处设置 SSBL 可以减轻这些问题。 由于SSBL是一个非RTOS程序,它可以安全地切换到一个新的应用程序。无需对电源重启关注,因为 IVT 的 SSBL 在地址0是从来没有重置的。
SSBL 的功能
了解了 SSBL 及其与应用软件的关系,那这个 SSBL 做什么呢? 最起码,程序必须确定当前应用程序是什么(开始的位置) ,然后再切换到该地址。 如图3所示,微控制器内存中各种应用程序的位置通常保存在一个目录(ToC)中。 这是持久内存的一个共享区域,SSBL和应用程序软件都使用它们来相互通信。
当 OTA 更新过程完成时,ToC 将使用新的应用程序信息进行更新。 OTA更新功能的一部分也可以推送到SSBL。在开发 OTA 更新软件时,“确定哪些部分”是一个重要的设计决策。 上面描述的最小 SSBL 将是非常简单的,易于验证,并且很可能不需要在应用程序的生命周期中进行修改。
但是,这意味着每个应用程序必须负责下载并验证下一个应用程序。 这可能导致无线堆栈、设备固件和 OTA 更新软件方面的代码重复。 另一方面,可以选择将整个 OTA 更新过程推送到 SSBL。 在这种情况下,应用程序只需在 ToC 中设置一个标志来请求更新,然后执行复位。 SSBL然后执行下载序列和验证过程。 这将最大限度地减少代码重复,并简化应用程序特定的软件。
这带来了一个新的挑战,可能需要更新 SSBL 本身(即升级更新代码)。 最后,决定在 SSBL 中放置什么功能将取决于客户端设备的内存约束、下载应用程序之间的相似性以及 OTA 更新软件的可移植性。
设计权衡: 缓存和压缩
OTA更新软件中的另一个关键设计决策是在 OTA 更新过程中如何在内存中组织收到的应用程序。 微控制器中两种典型的存储器是非易失性存储器(例如,闪存)和易失性存储器(例如,SRAM)。 闪存将用于存储程序代码和应用程序的只读数据,以及其他系统级数据,如 ToC 和事件日志。 SRAM将用于存储软件应用程序的可修改部分,例如非常量全局变量和堆栈。 图2所示的软件应用程序二进制代码只包含程序在非易失性存储器中的部分。 在启动例程期间,应用程序将初始化属于可变内存中的部分。
在 OTA 更新过程中,每当客户端设备从服务器接收到一个包含部分二进制的数据包时,它将被存储在 SRAM 中。 这个数据包可以是压缩的,也可以是未压缩的。 压缩应用程序二进制文件的好处是它的体积更小,允许发送的数据包更少,在下载过程中 SRAM 中存储它们所需的空间更少。 这种方法的缺点是压缩和解压缩会给更新过程增加额外的处理时间,而且必须在 OTA 更新软件中捆绑相关代码。
由于新的应用软件在升级过程中位于闪存中,但是在升级过程中却进入了 SRAM,所以 OTA 的升级软件在升级过程中需要对闪存进行写操作。 在 SRAM 中临时存储新应用程序称为缓存。 在高层,OTA 更新软件可以采取三种不同的方法进行缓存。
- 禁用高速缓存: 每当包含一部分新应用程序的数据包到达时,将其写到闪存中的目标位置。 此方案非常简单,可以最小化 OTA 更新软件的逻辑量,但是它要求新应用程序的闪存区域被完全擦除。 这种方法削弱了闪存,增加了开销。
- 部分缓存: 保留一个 SRAM 区域用于缓存,当新数据包到达时将它们存储在 SRAM 的区域中。 当区域填满时,通过将数据写入快闪存储器来清空它。 如果数据包无序到达,或者在新的应用程序二进制文件中存在间隙,这可能会变得很复杂,因为需要一种将 SRAM 地址映射到闪存地址的方法。 一种策略是将高速缓存作为闪存的一部分镜像。 闪存分为小区域的页面,这是写操作的最小划分。 由于这种自然的划分,一个好的方法是在 SRAM 中缓存一页闪存,当它填满或者下一个数据包属于不同的页面时,通过写该页面的闪存来刷新缓存。
- 完全缓存: 在 OTA 更新过程中,将整个新应用程序存储在 SRAM 中,并只在从服务器完全下载后将其写入闪存。 这种方法通过减少对闪存的写入次数,避免了 OTA 更新软件复杂的缓存逻辑,克服了以往方法的缺点。 但是,这将限制下载新应用程序的大小,因为系统上可用 SRAM 的数量通常远小于可用闪存的数量。
图5 利用 SRAM 实现一页高速缓存
在 OTA 更新过程中使用部分缓存的第二种方案如图5所示,其中图3和图4中应用程序 a 的闪存部分被放大,而 SSBL 的 SRAM 功能存储器映射图则如图所示。 显示了一个2 kB 大小的 flash 页面示例。最终,这个设计决策将根据新应用程序的大小和允许的 OTA 更新软件的复杂性来确定。
安全及通信
设计权衡: 软件 vs 协议
OTA更新解决方案还必须解决安全性和通信问题。 许多类似于图1所示的系统将具有在硬件和软件中实现的通信协议,用于正常的(非OTA更新相关的)系统行为,如交换传感器数据。 这意味着在服务器和客户机之间已经建立了一种(可能是安全的)无线通信方法。 像图1这样的嵌入式系统可能使用的通信协议,例如,BLE或6LoWPAN。 有时这些协议支持安全性和数据交换,OTA更新软件可以更新过程中利用这些安全性和数据交换。
必须构建 OTA 更新软件中的通信功能,但最终将取决于现有通信协议提供了多少抽象。 现有的通信协议是在服务器和客户机之间发送和接收文件的工具,OTA 更新软件可以简单地利用这些工具进行下载。 然而,如果通信协议比较原始,并且只能发送原始数据,则 OTA 更新软件可能需要执行分包,并随新的应用程序二进制文件提供元数据。 这也适用于安全挑战。 如果通信协议不支持,则 OTA 更新软件可能负责解密通过空中发送的用于保密的字节。
总之,构建诸如自定义包结构、服务器/客户端同步、加密和密钥交换功能,并把它们房到 OTA 更新软件中的工具将根据系统的通信协议提供的内容以及对安全性和可靠性的要求来确定。
解决安全挑战
安全解决方案需要对通过空中发送的新应用程序保密,检测新应用程序中的任何损坏,并验证新应用程序是从受信任的服务器发送的,而不是从恶意方发送的。 这些挑战可以通过使用加密(crypto)操作来解决。
具体来说,可以在安全解决方案中使用两种称为加密和哈希的加密操作。 加密技术将在客户端和服务器之间使用一个共享的密钥(密码)来混淆无线传输的数据。 微控制器的密码硬件加速器可能支持的一种特殊类型的加密被称为 AES-128或 AES-256,这取决于密钥的大小。 与加密的数据一起,服务器可以发送摘要,以确保没有损坏。 这个摘要是通过散列数据包(一个生成唯一代码的不可逆数学函数)生成的。 如果消息或摘要的任何部分在服务器创建它们之后被修改,比如在无线通信期间有一个位被翻转,当客户端对数据包执行相同的哈希函数并比较摘要时,它会注意到这个修改。
微控制器的加密硬件加速器可能支持的一种特定类型哈希方式是 SHA-256。 图6显示了微控制器中的加密硬件外设的框图,OTA 更新软件位于 Cortex-M4应用层。 此图还显示了对外围设备中的受保护密钥存储的支持,可以在 OTA 更新软件解决方案中利用该支持来安全存储客户端的密钥。
图6 基于 ADuCM4050的密码加速器硬件框图
使用非对称加密是解决身份验证最后挑战的一种常用技术。 对于此操作,服务器生成一个公私密钥对。只有服务器知道私钥,而客户机知道公钥。 使用私钥,服务器可以生成给定数据块的签名——就像将通过无线方式发送的包的摘要一样。 签名被发送到客户端,客户端可以使用公钥验证签名。 这使客户机能够确认消息是从服务器发出的,而不是由流氓第三方发出的。 这个序列如图7所示,用实箭头表示函数的输入 / 输出,用虚箭头表示通过空中发送的信息。
图7 使用非对称加密对消息进行身份验证
大多数微控制器没有硬件加速器来实现这些非对称加密操作,但可以使用诸如 Micro-ECC 等软件库来实现,这些软件库专门针对资源受限的设备。库需要一个用户定义的随机数母函数,这可以实现使用真随机数发生器的硬件外围的微控制器。
尽管这些非对称加密操作解决了 OTA 更新过程中的信任问题,但它们在处理时间方面代价高昂,而且需要将签名与数据一起发送,这增加了数据包的大小。 可以在下载结束时执行一次检查,使用最终包的摘要或整个新软件应用程序的摘要,但这将允许第三方下载不受信任的软件到客户端,这并不理想。 理想情况下,希望验证所收到的每个数据包都是来自受信任的服务器,而且每次都不需要签名的开销。 这可以通过哈希链来实现。
哈希链将这里讨论的加密概念合并为一系列数据包,从而实现数学上的连接。 如图8所示,第一个包(数字0)包含下一个包的摘要。 与实际的软件应用程序数据不同,第一个数据包的负载是签名。 第二个包(编号1)有效负载包含二进制的一部分和第三个包的摘要(编号2)。 客户端验证第一个数据包中的签名,并缓存摘要 H0,以供以后使用。 当第二个数据包到达时,客户端哈希负载并将其与 H0进行比较。 如果它们匹配,客户端可以确定这个后续数据包来自受信任的服务器,而不需要进行签名检查。 生成这个链的代价高昂的任务留给了服务器,当每个数据包到达时,客户机必须简单地缓存和哈希,以确保数据包到达时没有损坏,具有完整性,并经过验证。
图8 将哈希链应用于包序列
实验验证
本文中提到的解决存储器、通信和安全设计难题的超低功耗微控制器是 ADuCM3029和 ADuCM4050。 这些微控制器包含为 OTA 更新讨论的硬件外设,如闪存、 SRAM、加密加速器和真正的随机数发生器。 用于这些微控制器的设备家族包(DFP)为在这些设备上构建 OTA 更新解决方案提供软件支持。 DFP包含外围驱动程序,为硬件提供了简单、灵活的接口。
硬件配置
为了验证这里讨论的概念,使用 ADuCM4050创建了一个 OTA update 软件参考设计。 对于客户端,ADuCM4050 EZ-KIT 通过使用无线收发器连接到 ADF7242。 客户机设备如图9所示。 对于服务器,开发了一个在 Windows PC 上运行的 Python 应用程序。 Python 应用程序通过串行端口与另一个 ADuCM4050 EZ-KIT 进行通信,后者也有 ADF7242与客户端相同的设置。 然而,图9中右边的 EZ-KIT 不执行 OTA 更新逻辑,只是将从ADF7242收到的数据包转发给 Python 应用程序。
图9 实验性硬件设置
软件组件
如图3所示的软件参考设计对客户端设备的闪存进行分区。 主要的客户端应用程序被设计为可移植和可配置的,这样就可以在其他配置或其他硬件平台上使用。 图10显示了客户端设备的软件架构。请注意,虽然有时将整个应用程序称为 SSBL,但是在图10中以及从现在开始,从逻辑上将真正的 SSBL 部分(蓝色)与 OTA 更新部分(红色)分开,因为后者不一定需要像前面讨论的那样完全在同一个应用程序中实现。 图10所示的硬件抽象层层保持了 OTA 客户端软件的可移植性,并且独立于任何底层库(如橙色所示)。
图10 客户端软件体系架构
软件应用程序实现了图3中的引导序列,一个从服务器下载新应用程序的简单通信协议,以及哈希链。 通信协议中的每个数据包都有12字节的元数据头、64字节的有效负载和32字节的摘要。此外,它还具有以下特点:
- 缓存: 支持不缓存或缓存一页闪存,具体取决于用户配置。
- 目录: ToC 被设计用于只保存两个应用程序,并且新的应用程序总是被下载到最老的位置,以保留一个回退应用程序。 这就是所谓的 A/B更新方案。
- 消息传递: 根据用户配置,对消息传递的 ADF7242或 UART 提供支持。 使用 UART 进行消息传递消除了图9中左边的 EZ-KIT,使得右边的部分留给了客户端。 这种在线更新方案对于初始化系统和调试非常有用。
实验结果
除了满足功能需求和通过各种测试之外,软件的性能也是决定项目成功与否的关键。 两个通常用来衡量嵌入式软件性能的指标是占用空间和指令周期。 占用空间是指软件应用程序在SRAM和Flash中占用的空间。 指令周期是指软件用来执行特定任务的微处理器时钟周期数。 虽然它类似于软件运行时,但它解释了这样一个事实,即在进行 OTA 更新时,软件可能会进入低功耗模式,因为在那里微处理器是非活动的,并且没有消耗周期。 虽然软件参考设计没有针对这两个度量标准进行优化,但是它们对于测试程序和比较设计权衡都是有用的。
图11和图12显示了 OTA 更新软件参考设计在 ADuCM4050上实现的内存大小,不使用缓存。 这些数字是根据图10所示的组件进行分区的。 如图11所示,整个应用程序使用约15kb 的闪存。 考虑到 ADuCM4050包含512kb 的闪存,这个数据太小了。 真正的应用软件(为OTA更新过程开发的软件)只需要1.5 kB 左右,其余部分用于 DFP、 Micro-ECC 和 ADF7242堆栈等库。 这些结果有助于说明 SSBL 在系统中应该扮演的角色的设计权衡。 大多数15 kB 的内存占用用于更新过程。 SSBL本身只占用大约500个字节的内存空间,另外还有1到2 kB 的 DFP 代码用于设备访问,比如 Flash 驱动程序。
图11 闪存占用空间(字节)
图12 SRAM占用空间(字节)
为了评估软件的开销,在每次收到数据包时执行指令周期计数,然后查看每个包所消耗的平均指令周期数。 每个数据包都需要 AES-128解密、 SHA-256散列、对闪存的写入以及一些包的元数据验证。 在数据包有效负载为64字节且没有缓存的情况下,处理单个数据包的开销为7409周期。 使用26Mhz时钟,处理时间约为285微秒。 该值是使用位于 ADuCM4050 DFP (未调整周期)的循环计数驱动程序计算的,是100kb 二进制下载(大约1500个数据包)期间的平均值。
每个包的最小开销可归因于 DFP 中的驱动程序在执行总线事务时利用 ADuCM4050上的直接内存访问(DMA)硬件外设,以及驱动程序在每个事务期间将处理器置于低功耗睡眠状态。 如果禁用 DFP 中的低功耗休眠,并将总线事务改为不使用 DMA,那么每个数据包的开销将增加到17,297个周期。 这说明了设备驱动程序的有效使用对嵌入式软件应用程序的影响。 虽然每个数据包的数据字节数很少,因此开销也很低,但是每个数据包的数据字节数增加一倍,达到128,只会在循环中产生一个小的增长,因此对于同样的实验,需要8,362个指令周期。
指令周期和占用空间还说明了前面讨论的缓存包数据而不是每次写入闪存的权衡。 启用一页闪存缓存后,每个数据包的开销从7,409减少到5,904个周期。 这20% 的减少是由于对大多数包跳过了Flash 写操作,并且只在缓存满的时候执行一次Flash 写操作。 这种减少是以SRAM为代价的。 如果没有缓存,HAL只需要336个字节的 SRAM,如图12所示。 然而,当使用缓存时,必须保留相当于一整页闪存的空间,这将 SRAM 的利用率增加到2,388个字节。 HAL 的闪存利用率也略有提高,因为需要额外的代码来决定什么时候必须刷新缓存。
这些结果证明了设计决策对软件性能的切实影响。 没有放之四海而皆准的解决方案ーー每个系统都有不同的需求和限制,而且 OTA 的更新软件需要进行调整以解决这些问题。 希望本文对设计、实现和验证 OTA 更新软件解决方案时遇到的一些常见问题和解决方案提供一些帮助,真正弄懂如何实现物联网设备的OTA。
参考资料:
Nilsson, Dennis Kengo and Ulf E. Larson. “Secure Firmware Updates over the Air in Intelligent Vehicles.” ICC Workshops—2008 IEEE International Conference on Communications Workshops, May 2008.
(本文 编译自 http://www.embedded-computing.com/guest-blogs/over-the-air-ota-updates-in-embedded-microcontroller-applications-design-trade-offs-and-lessons-learned)