嵌入式开发:嵌入式设计中的每一分钱都很重要

更新时间: 2023-01-10 09:57:11来源: 粤嵌教育浏览量:6331

少花钱多办事——这句话抓住了巴克明斯特·富勒(Buckminster Fuller)的短期化概念,在20世纪90年代在嵌入式开发领域中引起了热议,但似乎从未过时。管理者们不断地挤压预算和时间表,以更快、更便宜地交付产品,结果往往是质量受损。与富勒不断提高质量和解决方案的愿景不同,这种方法通常会缩短产品生命周期的测试阶段,以满足积极的计划目标,有时意味着从最终产品中删除功能(可能会在以后的版本更新中添加)。

 

让我们来探索一些技术,这些技术将帮助开发人员更快地发现和修复缺陷,帮助节省构建材料的资金,并可能避免短期化的挑战。虽然主要关注基于Arm的内核,但由于许多嵌入式设备中存在类似的功能,因此许多这些技术可直接应用于其他内核。

 

量化节约的最简单方法之一是BML:成本较低的零件需要公司花费较少的资金来制造产品。在大多数嵌入式设计中,最昂贵的两个部分通常是屏幕(如果设备有一个;大多数物联网设备没有)和处理器。当你向处理器添加更多内存(闪存和RAM)时,处理器的成本会增加。虽然各半导体公司的成本增加的具体程度各不相同,但粗略的经验法则是,每增加一倍内存,处理器的单位成本就会增加一美元左右。

 

更糟糕的是,嵌入式开发工程师通常不太擅长在应用程序的设计阶段预测内存需求。这些对所需内存量的最佳“估计”是处理器选择的关键因素。鉴于每年的生产量都在数十万或数百万台,给BML增加不必要的美元会对公司的底线产生不利影响。

 

结果,无数项目“资源紧张”,这是“我们没有正确预测我们的内存需求”的代码。加剧这一问题的是,在项目开始时,BML经常被分配给高层管理层。一旦发生这种情况,成本就变得不可侵犯。这使得人们争先恐后地减少内存占用,或者依靠采购,通过谈判其他组件的更好价格来保持BML成本与管理层预期的相同。为了减少内存占用,团队通常会使用编译器的优化引擎来减少生成的代码的大小。


提高编译器优化的标准

一些工程师非常不愿意启动优化,因为他们认为优化会给系统带来错误。这种情况很少发生,根据我的经验,大约5%的优化器问题是优化器的问题。

 

当优化级别提高时,编译器会对CC++语言的语义非常挑剔。优化决策基于对语言规则的严格解释。通常,嵌入式开发工程师们并没有完全意识到语言和代码的所有细微差别,这对他们来说是很自然的。

 

例如,如果函数调用是这样编写的:

myFunc(varAvarBvarCvarD)

自然的假设是从左到右读取变量:从内存中读取varA,然后是varB,依此类推。

 

然而,在CC++中并没有说一定要这样。如果有意或无意地将内存布置在varB紧挨着varD的位置,那么高度优化可能会使用索引寄存器来读取连续的内存空间,以节省代码大小和速度。

 

在大多数情况下,这不会对代码产生影响。但是,如果你在从左到右编写变量时依赖于被访问的变量,那么可能会出现这样的情况:代码在较低的优化水平下运行良好,但在较高的优化水平上运行不佳。在这种情况下,工具供应商提供的良好支持结构可以帮助你发现这些类型的问题,并重写代码部分,以更好地进行优化并正确工作,而不依赖于优化设置。

 

此外,如果你的代码可以在高度优化时同样工作,那么它的编写是正确的,并且经过了更好的测试。在嵌入式开发中,如果代码不能在更高的优化下工作,那么很有可能潜在的缺陷正在等待“咬你”。

 

好的工具在设置为大规模优化时可以节省10-40%的代码大小。然而,并非所有优化转换都是任何一段代码的好选择——某些转换实际上可能会增加某些类型代码的代码大小。

 

目前,有一些资源可用于解决“从编译器中获取最少”的问题,这意味着最小的代码和最短的执行时间。节省这么多的代码空间可能是在剥离功能以保持设备大小、由于手动优化代码而错过计划或超出BML预算之间的区别。

 

虽然好的代码可以在任何级别的优化中运行相同的代码,但调试高度优化的代码是非常棘手的。例如,整个代码段可以在完全不同的位置合并到其他代码段中。这就是为什么必须在低优化或无优化的情况下调试代码,并在增加优化以运行全部测试之前验证代码是否正常运行。


BML中的调试成本

让嵌入式调试变得困难的部分原因是,大多数嵌入式开发人员根本不知道他们所有调试工具。它们倾向于默认使用printf语句和代码断点。这些默认值在试图隔离硬故障、查找堆栈溢出发生的位置或查找变量不断被阻塞的原因时没有帮助。好消息是,有一些特殊的工具可以帮助发现这些类型的问题。

 

处理硬件故障

许多现代MCU具有实时指令跟踪功能,允许你跟踪指令流。在基于Arm的设备上,用于实现这一点的技术是嵌入式跟踪宏单元(ETM)。参考手册将显示设备是否支持ETM。如果是这样,请将跟踪引脚连接到调试标头,并使用支持跟踪的调试器,例如IAR I-jet trace,它可以捕获实时指令流并将其显示在调试器窗口中。

 

要查找导致硬件故障的原因,只需滚动跟踪窗口,找到在进入故障处理程序之前执行的指令。如果可以可靠地重现错误,请在故障处理程序处设置断点,并消除跟踪窗口中的所有滚动–罪魁祸首是跟踪窗口中倒数第二条指令。现在原因已经知道了,因此可以在问题上设置断点,并再次运行测试用例,以查看导致异常的代码有什么问题。

 

但如果你没有ETM呢?大多数基于Arm的设备具有串行线输出(SWO),允许采样、低速跟踪。虽然你没有得到每一条指令,但这可以提供足够的跟踪信息来缩小范围并找到问题。此外,在嵌入式开发中,尝试降低MCU时钟和/或调整SWO设置,以便从调试器中获得更精细的跟踪信息,以便了解问题发生的位置。

 

其他设备架构具有与ETMSWO类似的功能。因此,使用高质量的工具可以利用这些信息,快速隔离和消除问题。此外,可用的支持资源有助于提高SWO的性能,以保护更多的跟踪数据。

 

停止堆栈溢出

堆栈溢出或找出变量神秘丢失内容的原因如何?使用相同的技术诊断这两种情况。

Arm世界中,大多数处理器的调试接口中都有一个数据监视点和跟踪(DWT)模块,可用于快速隔离这些类型的问题。在这种情况下,使用一个数据观察点来找出不好的事情发生在哪里。每当接触到一段数据时,这个观察点本质上就是一个断点。

 

将选项配置为仅在数据被读取、写入或同时读取和写入时中断执行。此外,如果数据是具有特定位掩码的特定值,甚至将其限制为仅断开,这在避免每次访问数据时停止时非常方便。

 

在堆栈溢出的情况下,嵌入式开发人员可以在堆栈顶部设置一个数据观察点。读取或写入该值并不重要,因为堆栈在代码中的该点已经损坏。处理器将在堆栈顶部停止执行,提供一个完全保留的调用堆栈,允许查看哪段代码正在刷新堆栈以及到达该点的方式,这是确定如何修复错误的关键。


清理破坏的数据

对于被破坏的数据,我们使用基本相同的技术,只是在该变量经历写入时设置数据观察点。如果它总是被相同的值阻塞,则进一步缩小断点,使其仅在将该值写入变量时才触发。然后,再次运行我们的测试用例,找出导致问题的代码。

 

同样,许多其他架构(如Renesas RL78RX和许多其他芯片供应商的设备)具有类似的功能,可用于实现相同的结果。有了高质量的工具,发现这些类型的问题变得更容易,并增加了满足积极的时间表和截止日期的可能性。

 

让采购了解你的关心

用更少的资源做更多的事情似乎是一个矛盾,但使用正确的工具可以很容易地实现。通过使用编译器优化,你可以将代码压缩到尽可能小的空间,以便为应用程序使用最便宜的设备。

 

优化还可以帮助桌面检查代码,看看它是否在高优化下运行,从而在你将代码签入构建之前找到潜在的代码缺陷(从而根据发布度量计算每个缺陷)。还可以通过使用完整的工具箱更快地发现bug,从而缩短测试和修复周期,更快地完成项目,从而帮助你更高效地进行调试。如果嵌入式开发人员知道工具箱里有什么工具(以及如何正确使用它们),就可以为企业节省每一分钱。

免费预约试听课