从硬件角度来说,找到所有可能的消耗电流的回路,一一确定哪些是可以通过软件控制的方式来
优化功耗的,哪些是不可避免的,并给程序的编写人员提供一个所有IO口状态对功耗影响的关系。
说到软件功耗优化,说简单也简单,说复杂也复杂。简单总结过来就是:应用模块化、功能任务
化、任务周期化、功耗自理化、休眠一票否决化。还不够简单?再浓缩就是:能休眠就休眠,怎么休眠
投票选。
1、应用模块化、功能任务化、任务周期化
一个具体的应用,通常由很多子功能,子任务组成。对嵌入式系统软件构架有所了解的人
更能理解:一个应用是由对若干服务(servie)的调用实现的。这里服务可以是硬件服务,比
如AD采样,比如串口通讯,比如外中断触发,比如定时器服务;也可以是软件服务,比如各种
通讯协议栈,FAT文件系统,队列,软件滤波等等。一个服务通常实现一个或多个功能(好的
任务划分不会让一个服务包含多于2种以上不相关的功能)。简单的功能,比如CRC校验这样函
数进去函数出来基本上可以立即获得结果的,我就不说了;复杂的功能最好都使用任务的方式
来实现。说到任务,就要牵涉到操作系统、调度器或者干脆是简单的状态机了。总之,可以将
任务的实现理解为一个流程。既然是流程,那么任务所要做的工作就是周期性的。举例来说,
AD采样任务由至少3个步骤组成:通道选择和启动采样,采样以及等待采样完成,数据的处理。
这样三个步骤共同组成了一个任务周期,当三个步骤完成以后,我们可以认为一个周期结束了。
再举一个例子,I2C通讯,一个完整的数据包的发送通常包含了若干的状态,这一系列状态构成
了一个任务,当最后一个状态(或者某些异常退出状态)结束后,一个任务周期就结束了。
总结来说, 应用模块化,功能任务化,任务周期化的最终目的就是任务周期化。只有实现了
周期化,一件事情才有始有终。有始有终,就可以根据需要发放“工资”,避免浪费。而做到
任务周期化的最常见办法就是通过模块化的服务将功能独立出来从而便于管理,便于找到一件
事情的开头和结尾。
找到不拉马的士兵,是功耗管理的起点。而做到这一点,是需要对嵌入式系统拥有全局的
概念,需要有基于模块化开发,面向服务和接口开发的经验的。经验的积累和全局的概念,是
最复杂的一个部分。
2、功耗自理化、休眠一票否决化
一旦实现了任务周期化,也就等于将整个系统分成了很多周期性工作的小任务,他们可能
看起来是交错的、并行的或者毫无先后关系的,但从本质上说, 每个小任务只要关心自己的起
始和中止就好了。系统的功耗管理最后就化简为每个任务的功耗管理——只要每个任务做到了
功耗最小,那么系统整体在一个有效的协调方式下,就能做到功耗最小。
根据上面的描述,基于任务的功耗管理实际上就被人为的分成两个部分:微观角度 任务
自身的功耗管理和宏观角度 多任务休眠的协调。
我们先从微观来看。一个任务,首先肯定能独立完成自己的功能,这一点看似不起眼,其实<br /> 很关键,他保证了任务中所有的步骤都是确定的,都是“自己说了算的”,对外界来说“都是黑<br /> 盒子”的——简而言之就是“自治”的。在这一基础上,如果要求任务满足低功耗的要求,不外<br /> 乎以下几种情形:<br /> 1)任务执行的过程中,是不允许休眠的,因此任务的开头和结尾处要设置标志——告知协调<br /> 系统,“只要我还没有说OK就不允许休眠”“人在任务在”;<br /> 2)任务执行的过程中,某一些阶段允许休眠,而另外一些步骤是不允许休眠的;如果我们将<br /> “不允许休眠”看作是休眠的最低等级,那么根据功耗的大小,休眠可以由低到高分为若干<br /> 各等级。这种情况下,我们可以修改上面的定义为:任务的执行过程中,不同阶段允许不同<br /> 的休眠等级。<br /> 3)任务执行的过程中,不在乎是否有休眠。
显然,如果这三类任务同时存在于系统中,第三类任务基本上是“空气”可以无视的,而第一
种任务是相当霸道的,只要他在执行,就根本不允许休眠;对于第二类任务,即完成了任务,又兼
顾了休眠,是一个值得表扬的“好同志”。我们在系统任务设计的时候,应该尽可能编写后两类任
务,而避免或者尝试拆分第一类任务。
第一步,查阅数据手册,找到可被唤醒的最大休眠模式,编写一个测试工程——按照数据手册
所说的要求,实现一个纯粹的什么都不做的休眠模式,同时,关闭所有能关闭的功耗——比如掐断
某些外设的时钟,比如正确设置IO口状态。这样我们就获得了一个极限功耗,也就是正常情况下你
通过系统设计可以无限接近的一个功耗。
这个时候,你需要的是,根据硬件设计工程师提供的IO口设置建议配置IO状态,以获得一个
子人为的最低功耗。下载代码到目标电路,测量功耗。如果将电路板上无法优化的固有功耗——比
如某些固定消耗电流的电阻功耗——消除以后,仍然没有达到数据手册上所说的对应休眠模式下的
最大值时,你就要找硬件设计的兄弟喝茶了,两个人一起同时从硬件的角度和软件的角度找原因。
直到我们获得一个满意的可自行唤醒的“纯休眠功耗”。
切忌,做软件的同学不要一口咬定说问题一定出在硬件设计上。举一个例子:我在这个步骤地
时候,始终达不到数据手册上标注的休眠模式下的最大允许功耗,而根据数据手册,我认为我已经将
所有能关断的外设都关闭了。于是我开始联系IC设计部门抱怨。最后的结果是,有一个外设使用了不
同于CPU的独立时钟——也就是可以理解为异步时钟,在这种情况下,CPU对他的寄存器设置需要一定
的时钟周期才能完成同步。而我在系统中简单的设置寄存器将其“关闭”后立即就去休眠了,可想而
知由于没有等待异步始终同步,也就是这个操作还没有生效,我就去休眠了,这个外设还处于开启状
态,功耗自然居高不下。哎……脸红啊……
第二步,确认系统的脉搏。所谓确认系统的脉搏就是要总体审查整个应用的工作方式,找到一个
系统时钟的最大节拍,并根据这一要求确认芯片所使用的唤醒源。也许很多人都有使用定时器溢出中
断或者比较匹配中断产生一个系统毫秒级时钟的习惯,然而,除使用外部手表晶振的RTC或者异步时
钟源的定时器以外,普通定时器的正常工作都需要系统主时钟提供时钟源,这是我们所追求的低功耗
模式所不允许的。有时候仔细想一想,一个毫秒级别的系统时钟真的是必须的么?
在AVR中,在低功耗模式下能提供系统时钟的通常就是看门狗了,通过设置,看门狗能够以一个
固定的时间间隔(16ms / 32ms / 64ms / 128ms)将系统从最大的休眠模式下唤醒。因此在那些必须
要用到系统节拍的应用中,如果16ms是你所需系统节拍周期的约数,你就可以考虑采用看门狗来提供
一个并不是那么准确但是非常稳定的时钟源;否则你就要面对以下的选择:
a. 我的系统工作模式决定了我必须要一个小于16ms的系统时钟;那么整个设计是否允许使用外部
时钟源给异步定时器(Timer2的异步模式)或者某些专门的RTC提供时钟源——这些外设通常支持将系
统从最大的休眠模式下唤醒,就像看门狗做到的那样。
b. 如果我的系统不允许增加外部时钟源,系统是否允许通过外部触发的模式来工作——也就是
通过外中断或者引脚电平变化中断来唤醒系统,并开始一次工作流程,完成后系统再次陷入永久睡眠。
c. 如果以上都不行,你可以考虑换芯片或者修改系统的需求的——至少系统需求在功耗的部分
需要做一些妥协。
一个笼统而完整的描述如下:
1) 对一块目标芯片进行调查和确认:确认其所有的休眠模式,以及对应休眠模式关闭的时钟源,
这些时钟源涉及到的外设——巧妇难为无米之炊,这一步首先搞清楚系统设计的时候手上有
哪些材料。
2)研究具体应用的需求,明确系统的工作模式(以采样类的模式来说就是采样,休息,再采样,
再休息,整个系统是一个状态机并以采样事件作为驱动;采样不仅提供信息,也提供系统的
脉搏。即便这类系统还涉及到LCD刷新或者I2C/串口通讯,由于信息的本源是采样,因此采样 周期本身决定了信息的有效性**,那么LCD的刷新周期,或者通讯的缓冲跟新周期没有理由必须
大于采样的更新周期)。在这一前提下,明确系统对唤醒源以及唤醒模式的需求,由此便确定
了系统的基础休眠模式,进一步说,比较这一基础休眠模式功耗和应用所需的功耗,便可给出
系统设计的一个初步评估结果。有时候甚至能给出一些系统功耗的直接预期数据。
实际上,MCU的功耗分为两个部分,一个与频率有关,一个与工作电压有关,即
E = E(V) + E(f)
当频率翻倍时,影响的只是E(f),而这部分功耗并不会最终导致4倍的功耗,其值往往在2倍
甚至更低。在这种情况下,Sleep+Active的工作模式会带来更低的功耗。