微波EDA网,见证研发工程师的成长!
首页 > 硬件设计 > MCU和DSP > 八位微控制器的代码优化技巧

八位微控制器的代码优化技巧

时间:01-24 来源:赛普拉斯半导体 点击:

本文将介绍一些优化技术,帮助设计人员节约多达 10% 的代码空间,从而让容量有限的程序存储器支持更多新特性和补丁。

  良好的操作方法

  许多程序员在 32 位处理器上学习编写软件,如 Intel 的 Pentium 处理器或某种 ARM 平台。不过,嵌入式领域的软件编写需要不同的思路。在 32 位 CPU 上,存储比特位的最佳方法通常是使用 32 位变量。对 8 位处理器而言,最好的办法就是采用单字节。像增强型 8051s 等某些处理器可能提供特殊的 1 位变量。

  嵌入式处理器通常会超出标准的哈佛架构将存储器分散到不同的存储器空间中,有的相互重叠,有的又是相互分离。例如,8051 中常见的存储器空间包括 CODE、XDATA、DATA、IDATA、BIT 以及寄存器等。当要决定在何处存放变量时,了解每个存储器空间的优缺点显得非常重要,特别是在各个存储空间的容量都有限时更是如此。例如,IDATA 空间可能只能运行 256 个字节,不过它为间接存取进行了优化。虽然 DATA 空间也只能运行 256 个字节,但它包括了 位可寻址空间和寄存器。尽管 CODE 和 XDATA 只能通过慢速间接存取机制进行访问,但它们的寻址空间却高达 64K。

  许多 8 位 CPU 的编译器包含了很多优化程序,不过,这些优化程序都有其局限性。如果可以,应该尽可能简化表达。例如下面这段代码:

  X = a * CONSTANT1;

  X *= CONSTANT2;

  通常要比下述代码多占空间:

  X = a * CONSTANT1* CONSTANT2;

  因为编译器能将两个常量合并为一个。

  优化--三思而后行

  经验丰富的木匠都知道做事应该事先作好计划,三思而后行。嵌入式固件工程师也应该遵循这一原则。所有嵌入式编译器都提供了一个可给出有用信息映射文件。如图 1 所示,该映射文件提供了本文所用代码示例的有用信息。图中所示的库 (LIB_CODE) 使用的空间超过了 1K,而且启动代码 (c51startup) 使用的代码超过了 140 字节。

  进行优化的另一原因是可以节约时间。在优化之前,衡量程序的性能尤为重要。显而易见,如果源文件过大,肯定会占用大量的存储器空间,但我们很难测定代码的哪些关键部分在消耗宝贵的 MIPS。在此过程中,我们可将程序概要分析 (Profiling) 作为一个重要的工具来加以利用。

  我们可利用未使用的单一输出引脚来进行程序概要分析,不过输出引脚越多,分析也就越容易。我们可创建一个宏来设置程序概要分析输出,如下所示,再将宏放在每个例程的起点和终点处。

  了解支付情况

  在上述的映射文件中,我们了解到库占用了 1K 的宝贵存储器空间。深入查看映射文件,通过 Excel 进行分析后得到了如图 2 所示的结果。我们从图中移出较小的库函数部分。尽管这些函数名称比较晦涩,不过我们可以对照库参考资料逐一了解其含义。首先,ULdiv 是指无符号数的长除法 (long division),而图中第二个则是指长乘法 (long multiplication)。

  .map 文件的交叉参考表明我们很幸运:上述函数只用于一个文件中。.lst 文件显示了长除法函数的两种使用情况以及长乘法函数的一种使用情况

  glNandDevCapacity = CYAN_NAND_DEV_NUMPAGES_BLOCK * CYAN_NAND_UBLKS_PER_ZONE * (uint32_t)glNandNumZones;

  在该特定案例中,我们知道 zone 的数量是一个二进制数,而另两个值为常量。因此,我们可用重复 8 次的左移位 (left shift) 操作替代长乘法:

  尽管这个例程相当大,但它仍能减少库的使用并减小代码的整体大小。

  掌握比编辑器更多的信息

  成熟的 8 位编译器包括代码编写良好、经过优化的库函数。不过,这些函数须考虑到通过对数据的了解可自行处理的一些不常见情况。映射文件中显示的最大库函数就是这样一个很好的例子。调用两次 ULdiv 例程,以获得输入值除以常量后得到的除数和余数:

  zn = (adj_lba / CYAN_NAND_UBLKS_PER_ZONE);

  glNandRelativeBlkAddr = (adj_lba % CYAN_NAND_UBLKS_PER_ZONE);

  由于我们在预期值方面比编译器了解的更多,因此我们可以让编译器不使用庞大的长除法函数,而采用较小的 16 位版本来替代。

  激进的的程序优化者甚至可能实现他们自己的二进制长除法例程。

全局变量更好用

  将参数传递给函数是一个很好的代码经验。在 C 程序中,编译器可绝对确保调用的子程序不会修改参数。编译器可处理存储器管理的问题。不过,这将占用难以承受的大量时间和空间。试考虑下面这段代码:

由于变量在 main() 中已经声明,因此该变量与真正的全局变量之间的真正差别是命名空间 (namespace)。但是,每次调用 foo() 时,编译器都必须在新的位置存储 effectiveGlobal。声明真正的全局变量有助于降低因调用而造成的代码和数

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top