新闻  |   论坛  |   博客  |   在线研讨会
嵌入式系统中低功耗设计的软件策略
lulu888 | 2009-06-24 07:52:45    阅读:1073   发布文章

嵌入式系统中的功耗问题已变得越来越重要。这一情况不仅限于手机、PDA和便携媒体播放器等便携式设备,对一些连接到交流电电源的设备而言,功耗也变得重要起来。条形码扫描器、签名识别设备、外部存储设备和WiFi或无线键盘等通过USB供电的产品日渐增多,而这些低功耗的USB器件所允许的功耗预算只限于8mA到100mA之间。因此,每毫安的电流都弥足珍贵的。

数字逻辑电路中的功耗主要分为两部分:漏电流和开关电流。CMOS逻辑中的漏电流是可以忽略的,当器件采用的是专用于低功耗8位和16位微控制器的更保守的工艺技术时尤其如此。造成功耗的关键是开关瞬态,每当一个数字输出改变状态时,都必须对与其本身相关的多个寄生电容和所有与其相连的电容进行充放电,不论这些电容位于芯片外部还是芯片内部。因此,一条信号线上的每次状态转换都会或多或少地消耗一定能量。因而,降低状态转换的速度(也即降低时钟速度)可以降低功耗。但如果芯片因此而必须运行更长的时间才能得到同样的结果的话,那降低速度就不一定意味着节能了。

因此,真正的挑战是如何降低CPU要完成任务而必须执行的指令条数。这对程序设计提出了多条要求―首先是谨慎设计应用的软件结构,编写紧凑的代码,以及选择合适的开发工具(例如编译器)。编译器本身就可能对执行应用所需的指令条数有很大影响,从而对最后的功耗有很大影响。

微控制器的睡眠模式

大多数微控制器都可以进入某种睡眠或暂停状态,以此降低芯片功耗。在这些状态下,CPU将停止运行,芯片上的许多其它资源也会暂停或关闭。芯片所允许的睡眠状态类型和应用的需要决定了应用中可以采用那些睡眠状态。

软件低功耗的设计目的就是充分利用应用所允许的最深睡眠状态,并确保芯片尽可能长时间地保持在这一状态下。编译器对于微控制器可维持在睡眠模式下的时间长短有着微妙却十分重要的影响。因为采用的编译方法可能会在中断程序上和在带分块存储器(banked memory)的器件的存储器地址定位上浪费很多CPU周期。


图1:调用关系结构图。

中断程序

不论在何种应用中,保持中断程序短小快速都是十分重要的,在速度和/或功耗十分关键的时候尤其如此,因为缩短中断程序能够帮助将中断开销降至最低。

编译器对于中断程序的贡献在于它可以影响一个中断出现时相应需保存的寄存器个数。为避免存储器覆盖,传统的编译器都会将一个中断可能用到的所有寄存器都保存下来。而大多数编译器都无法得知哪些寄存器可能会或不会被某个中断使用,因此只有将所有寄存器全部保存。问题是一个中断所用的CPU周期数直接是它所保存和恢复的寄存器个数的函数。而用于保存和恢复中断时各种寄存器参数的CPU周期越多,消耗的功率也就越多。

例如,微星(Microchip)的PIC16所用的一款编译器就不考虑哪些数据是必需的,而会为每个中断均保存8字节数据。保存8字节数据总共需要42个指令周期(其中23个用于背景保存,19个用于恢复)。看起来好像并不多,但在一个大量包含中断的应用中,CPU“苏醒”时可能就需要多用几千个指令周期,从而不必要地增大了功耗。


图2:指针指向图。

采用全面代码生成(omniscient code generation, OCG)技术的新型编译器则能选择性地只保存每个特定的中断所需的寄存器。这种全面代码生成是通过在编译代码之前,从所有程序模块中收集有关寄存器、堆栈、指针、对象和变量声明的全面数据而实现的。OCG编译器将所有程序模块组合为一个大的程序,然后将其加载到一个调用关系结构图中。根据这个调用图,OCG代码发生器创建一个指针参考图,图中显示出一个变量地址每一次被访问的实例,加上每次一个指针的值被赋给另一个指针的实例(不论是直接赋值、通过函数返回值赋值、函数参数传递赋值或通过其它指针间接赋值)。然后,编译器会识别所有可能被每个指针指向过的对象,再利用这一信息来确定每个指针需要访问的到底有多少内存空间。

OCG编译器确切地知道哪些函数调用了其它函数,或被其它函数调用,哪些变量和寄存器是必需的,哪些指针指向的是哪些存储器块。这些信息就让编译器能够确定程序中每个中断会用到哪些寄存器,从而据此生成代码,最小化代码长度和保存与恢复中断背景所需的指令周期数。

编译器根据代码在编译时的状态来动态确定每个中断所需背景寄存器的多少。在OCG编译器的编译结果中,最少时,一个中断可能只需要17个指令周期――其中10个用于保存背景信息,7个用于恢复。最多时也不过25个指令周期。与传统编译器相比,OCG编译器将中断相关的指令周期数减小了40%到60%。

根据应用的不同,通过编译可节约的指令周期数可能非常大。例如,一个鼠标每秒钟产生300次中断,这些中断会占用处理器所有工作周期的不到1%。而一个靠中断驱动的波特率为480,600bps的串行通信端口每秒则会产生24,000个中断。如果采用传统的每个中断需42条指令周期(即168个时钟周期)来保存和恢复中断背景的编译器,那么每秒中断会占用超过403万零2千个CPU周期,或者说会占用一个20 MHz PIC16 20%的可用周期。而如果采用OCG编译器,每个中断平均只需21个指令周期(即84个时钟周期),那么CPU周期的占用量会降低至201万6千,在保存和恢复中断背景信息上节约一半的时钟周期,从而让CPU得以将其工作周期的10%用于睡眠模式。假设CPU在工作模式下电流为10mA,睡眠模式下为1uA,那么OCG编译器就能将MCU的功耗降低约1mA,也就是10%。

分块式存储器结构也会浪费指令周期

许多8位和16位微控制器中的存储器都是分块式的,不能同时访问。在不同的存储器块之间切换需要执行至少两条块选择指令。因此,若要将一个存储器块内的数据写入另一块,就必须执行块选择指令。显然,将一个函数需要访问的所有变量保存在同一个存储器块中就能减少块选择指令,从而减少应用所需的总指令周期数。但传统编译器无法识别哪些函数调用的是哪些变量,因此也就无法优化这些函数的内存分配。同时,这些编译器也无法得知某个特定的存储器块是否会被代码在任何地方选中。因此,不论一个存储器块是否已被选中,这些编译器都会自动为每一次存储器访问生成块选择指令。

有些编译器厂商已通过提供存储块限定子(bank qualifier)作为C代码的扩展以识别变量地址,解决了这一问题。程序员可以通过使用这些非标准也不可移植的代码,手动将变量分配给存储器块。采用块限定词允许编译器将某个确切的存储器块看作其内部的一个对象,从而减少编译后产生块选择指令的条数。然而,该方法却并不能保证因变量(dependent variable)会被分配到同一个存储器块中。因此,每当需要将一个存储器块中的变量写入另一个存储器块时,仍需执行块选择指令。此外,要想在多个代码模块间追踪所有的内存地址并确保所有指针都拥有正确的地址,这是一个冗长耗时的过程,它本身就可能引发编程错误。

相比而言,由于OCG编译器对所有程序模块中的每个寄存器、堆栈、指针、对象和变量声明都很清楚,所以它能优化所有变量和寄存器的分配,以及编译后的堆栈中所保存的每个指针和对象的大小和范围。OCG编译器无需程序员的任何干涉,就能优化地分配存储器,从而最小化甚至消除应用所需的块选择指令。

OCG编译器在一个可用的非分块式存储器中为全局和静态变量分配固定地址,因而无需任何块选择指令。如果应用中没有非分块式的存储器,那么这些全局和静态变量就会视其大小、相应的特殊限定词和他们被访问的次数而被分配到某个特殊的存储器块中。因变量则会在可能的时候被分配到同一个存储器块中。

函数参数和自变量(auto variable)则被分配给编译堆栈。如果某些变量的函数不是同时被调用,那么链接器会自动允许这些变量共享同一个内存地址,从而将RAM的使用降低多达99%。

通过将常被访问的变量存储在非分块式存储器中,并将所有因变量存储在同一个存储器块中, OCG编译器从根本上减少了CPU需执行的指令周期数,从而也就降低了这些MCU结构中消耗在块选择指令上的功率。由于OCG编译器在代码的任何一点都很清楚被选中的是哪个存储器块,因此当该存储器块已被选中时,它还避免了执行不必要的块选择指令。减少指令条数就减少了CPU需要执行的周期数,从而允许CPU在睡眠模式下保持更长时间。

采用OCG编译器所能节约的总周期数很大程度上取决于它所编译的应用,因而很难量化。但清楚程序当前选择的是哪个存储器块这一特点就有可能将应用所需的总周期数降低30%到50%,而周期数的降低与MCU功耗之间直接是呈线性关系的。


表1:睡眠模式的各种状态。

本文小结

要想尽可能降低功耗,选择低功耗器件并尽可能让MCU保持在睡眠模式是最重要的两种方法。然而,编译器管理中断以及内存使用的方式也会对MCU能够保持在睡眠模式下的时间长短有很大影响。采用全面代码生成技术的新型编译器在节约指令周期和节约功耗方面能够做出相当大的贡献。

作者:Clyde Stubbs

    CEO

    H-TECH 软件公司

[大 中 小][打印]

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客