傻孩子--说说低功耗开发的那些事儿(未完)

来源:互联网 发布:mac windows徽标键 编辑:程序博客网 时间:2024/06/02 19:57
傻孩子

不知从什么时候开始,随便做个什么电子产品,至少是电池供电的,都要求低功耗特性了。好在市面上随便什么芯片都敢在自己的数据手册的第一页赫然写着低功耗。究竟怎样算低功耗?小于5mA?小于1mA?小于100uA?离开了应用场合,似乎数值也失去了单纯的意义,总之越小越好。但感觉上,能用水果点亮的应用应该就是低功耗了吧。认真说来,有点怀念当年随便一个应用500mA,芯片微微发烫,用手一摸只要还能放得住就大手一挥“没问题”的时代了。最近总是和uA打交道,超过100A,周围的人脸色就不好看了,好容易达到了传说中的20uA以内,也会觉得沾沾自喜,哎……uA啊……情何以堪啊,伤不起啊……久病成医,渐渐的也就有了一些心得,似乎低功耗开发的过程也可以一板一眼,按部就班,似乎不单纯是一些零散的“看情况而定”“只可意会”的东西了。于是忍不住,将这些似是而非的步骤记录下来,以箪初来者。

事半功倍还是事倍功半——思路决定成败

“肚子饿的时候,睡着了也就不觉得饿了……于是乎,难得的双休日宅在家中补觉,往往也就一天只吃一餐饭了”——技术宅人_大体如此。
     应该没有人能在梦游的时候干活吧?所以,平常工作的时候,饭还是要吃的。休眠和干活应该是一对矛盾体。于是乎,芯片数据手册上那些“小的出奇”的休眠功耗,似乎大部分时候只是用来摆设的;而工作功耗才是实实在在的东西。有时候,为了体现所谓的低功耗,还要在应用中设计一种所谓的低功耗模式——当系统确认没有事情可做一段时间以后就干脆回家睡觉了——这大体就是现在市面上常见的低功耗应用的某种程度上的现状吧。于是乎,降低工作频率这种“马儿跑,马儿不吃草”的逻辑,就成为降低正常工作模式下系统功耗的常规选择。苦啊……多少人在工作频率和功耗间纠结……又有多少功能实现的本身对对频率拥有最低要求……苦啊——我说的是写代码的程序员。
     
     说起来,降低功耗似乎是一个软件和硬件协同工作才能解决的问题。比如AD采样时候的分压电阻,如果直接接了地,那么就会一直消耗电流,如果通过一个IO口来控制其接地的方式,只在需要采样的时候接地,采样完成以后就悬浮或者拉高,就可以将这部分开销降低的最小。
     
     显然,将低功耗完全化作硬件设计的工作或者软件设计的工作都是不合适的。

       从硬件角度来说,找到所有可能的消耗电流的回路,一一确定哪些是可以通过软件控制的方式来优化功耗的,哪些是不可避免的,并给程序的编写人员提供一个所有IO口状态对功耗影响的关系(通常用简单的表格说明一下高电平会怎样,低电平会怎样,悬浮会怎样就足够了,并不需要精细到具体的数值。)做到这一点,基本上硬件的工作就告完成,剩下的就是软件开发人员的发挥空间。而基于软件的功耗降低策略,正是本文所要讨论的重点。
      说到软件功耗优化,说简单也简单,说复杂也复杂。简单总结过来就是:应用模块化、功能任务化、任务周期化、功耗自理化、休眠一票否决化。还不够简单?再浓缩就是:能休眠就休眠,怎么休眠投票选。呵呵……估计简单过头了,失去了信息量,下面将这几个方面一一展开:

1、应用模块化、功能任务化、任务周期化
一个具体的应用,通常由很多子功能,子任务组成。对嵌入式系统软件构架有所了解的人更能理解:一个应用是由对若干服务(servie)的调用实现的。这里服务可以是硬件服务,比如AD采样,比如串口通讯,比如外中断触发,比如定时器服务;也可以是软件服务,比如各种通讯协议栈,FAT文件系统,队列,软件滤波等等。一个服务通常实现一个或多个功能(好的任务划分不会让一个服务包含多于2种以上不相关的功能)。简单的功能,比如CRC校验这样函数进去函数出来基本上可以立即获得结果的,我就不说了;复杂的功能最好都使用任务的方式来实现。说到任务,就要牵涉到操作系统、调度器或者干脆是简单的状态机了。总之,可以将任务的实现理解为一个流程。既然是流程,那么任务所要做的工作就是周期性的。举例来说,AD采样任务由至少3个步骤组成:通道选择和启动采样,采样以及等待采样完成,数据的处理。这样三个步骤共同组成了一个任务周期,当三个步骤完成以后,我们可以认为一个周期结束了。再举一个例子,I2C通讯,一个完整的数据包的发送通常包含了若干的状态,这一系列状态构成了一个任务,当最后一个状态(或者某些异常退出状态)结束后,一个任务周期就结束了。
      总结来说,应用模块化,功能任务化,任务周期化的最终目的就是任务周期化。只有实现了周期化,一件事情才有始有终。有始有终,就可以根据需要发放“工资”,避免浪费。而做到任务周期化的最常见办法就是通过模块化的服务将功能独立出来从而便于管理,便于找到一件事情的开头和结尾。找到不拉马的士兵,是功耗管理的起点。而做到这一点,是需要对嵌入式系统拥有全局的概念,需要有基于模块化开发,面向服务和接口开发的经验的。经验的积累和全局的概念,是最复杂的一个部分。

2、功耗自理化、休眠一票否决化
       一旦实现了任务周期化,也就等于将整个系统分成了很多周期性工作的小任务,他们可能看起来是交错的、并行的或者毫无先后关系的,但从本质上说,每个小任务只要关心自己的起始和中止就好了。系统的功耗管理最后就化简为每个任务的功耗管理——只要每个任务做到了功耗最小,那么系统整体在一个有效的协调方式下,就能做到功耗最小。
       根据上面的描述,基于任务的功耗管理实际上就被人为的分成两个部分:微观角度任务自身的功耗管理和宏观角度多任务休眠的协调。
       我们先从微观来看。一个任务,首先肯定能独立完成自己的功能,这一点看似不起眼,其实很关键,他保证了任务中所有的步骤都是确定的,都是“自己说了算的”,对外界来说“都是黑盒子”的——简而言之就是“自治”的。

在这一基础上,如果要求任务满足低功耗的要求,不外乎以下几种情形:
1)任务执行的过程中,是不允许休眠的,因此任务的开头和结尾处要设置标志——告知协调系统,“只要我还没有说OK就不允许休眠”“人在任务在”
2)任务执行的过程中,某一些阶段允许休眠,而另外一些步骤是不允许休眠的;如果我们将“不允许休眠”看作是休眠的最低等级,那么根据功耗的大小,休眠可以由低到高分为若干各等级。这种情况下,我们可以修改上面的定义为:任务的执行过程中,不同阶段允许不同的休眠等级。
3)任务执行的过程中,不在乎是否有休眠。
       显然,如果这三类任务同时存在于系统中,第三类任务基本上是“空气”可以无视的,而第一种任务是相当霸道的,只要他在执行,就根本不允许休眠;对于第二类任务,即完成了任务,又兼顾了休眠,是一个值得表扬的“好同志”。我们在系统任务设计的时候,应该尽可能编写后两类任务,而避免或者尝试拆分第一类任务。

     从宏观角度来看,任意时刻,可能有多个任务同时在执行,因此每个任务对休眠的需求都是不同的。如果要设立一个协调机制,该怎么办呢?难道这个协调机制需要了解每个任务的细节,然后“智能”的找到合适的时间点使用合适的等级来休眠么?太繁杂了吧?其实很简单,让每个任务都选派一个代表来开会好了——每次这个协调机构想休眠了,就召集所有的代表投票,大家每个人提供一个自己觉得能够容忍的最高休眠等级,最后会议的仲裁者从这些投票中找到一个最小的休眠等级——也就是水桶的最矮一环作为“会议共识”,然后进入相应的休眠等级。显然,如果有人投了“不休眠”得票,仲裁就只能无奈的选择放弃休眠。所以,每一个“任务”都应该是一个负责的任务,而不能因为一己私利,为了保证自己任务的执行就草率的选择自己在执行的期间“不允许休眠”。正确的做法就是,每个任务都应该根据自己的不同步骤及时的更新自己对休眠的容忍度,从而保证开会的时候,能够达成有意义的结果。

        总结来说,如果每一个做事的人都很负责任,任务的划分又很合理,那么通过这种协商机制,系统自然就不存在“不拉马的士兵”,也能做到“能休眠时就休眠”了。这种情况下,芯片数据手册上那些休眠的功耗数字对你就有了实实在在的意义。怎么样?如果你已经领悟到了,就偷着乐吧。如果你还不知道如何具体去实现这些步骤,别着急,随后的章节我们一一为您展开。

低功耗设计第一步:自底向上,顺藤摸瓜

        假想一下,现在有一个新的项目放在你的手上,具体的项目需求包含了AD多通道数据采样,AD采样后数据的处理(较为复杂),同时还要支持I2C通讯,I2C的通讯协议较为复杂。这样一个设备必须做到功耗最低。应对这样一个需求,如果已经有一个建议的芯片,比如AVR Mega或者Tiny我们应该怎么做呢?
 
     第一步,查阅数据手册,找到可被唤醒的最大休眠模式,编写一个测试工程——按照数据手册所说的要求,实现一个纯粹的什么都不做的休眠模式,同时,关闭所有能关闭的功耗——比如掐断某些外设的时钟,比如正确设置IO口状态。这样我们就获得了一个极限功耗,也就是正常情况下你通过系统设计可以无限接近的一个功耗。
     这个时候,你需要的是,根据硬件设计工程师提供的IO口设置建议配置IO状态,以获得一个子人为的最低功耗。下载代码到目标电路,测量功耗。如果将电路板上无法优化的固有功耗——比如某些固定消耗电流的电阻功耗——消除以后,仍然没有达到数据手册上所说的对应休眠模式下的最大值时,你就要找硬件设计的兄弟喝茶了,两个人一起同时从硬件的角度和软件的角度找原因。直到我们获得一个满意的可自行唤醒的“纯休眠功耗”。
     这个过程非常关键,他直接决定了日后你能达到的最好效果。多花费一点时间是值得的,因为在这一过程中,你能非常细微的“初步”了解到哪些外设和配置影响功耗,如何影响,有多大的影响。千里之行始于足下,多花点时间,值!

     切忌,做软件的同学不要一口咬定说问题一定出在硬件设计上。举一个例子:我在这个步骤地时候,始终达不到数据手册上标注的休眠模式下的最大允许功耗,而根据数据手册,我认为我已经将所有能关断的外设都关闭了。于是我开始联系IC设计部门抱怨。最后的结果是,有一个外设使用了不同于CPU的独立时钟——也就是可以理解为异步时钟,在这种情况下,CPU对他的寄存器设置需要一定的时钟周期才能完成同步。而我在系统中简单的设置寄存器将其“关闭”后立即就去休眠了,可想而知由于没有等待异步始终同步,也就是这个操作还没有生效,我就去休眠了,这个外设还处于开启状态,功耗自然居高不下。哎……脸红啊……


  第二步,确认系统的脉搏。所谓确认系统的脉搏就是要总体审查整个应用的工作方式,找到一个系统时钟的最大节拍,并根据这一要求确认芯片所使用的唤醒源。也许很多人都有使用定时器溢出中断或者比较匹配中断产生一个系统毫秒级时钟的习惯,然而,除使用外部手表晶振的RTC或者异步时钟源的定时器以外,普通定时器的正常工作都需要系统主时钟提供时钟源,这是我们所追求的低功耗模式所不允许的。有时候仔细想一想,一个毫秒级别的系统时钟真的是必须的么?
  在AVR中,在低功耗模式下能提供系统时钟的通常就是看门狗了,通过设置,看门狗能够以一个固定的时间间隔(16ms / 32ms / 64ms / 128ms)将系统从最大的休眠模式下唤醒。因此在那些必须要用到系统节拍的应用中,如果16ms是你所需系统节拍周期的约数,你就可以考虑采用看门狗来提供一个并不是那么准确但是非常稳定的时钟源;否则你就要面对以下的选择:
  a. 我的系统工作模式决定了我必须要一个小于16ms的系统时钟;那么整个设计是否允许使用外部时钟源给异步定时器(Timer2的异步模式)或者某些专门的RTC提供时钟源——这些外设通常支持将系统从最大的休眠模式下唤醒,就像看门狗做到的那样。
  b. 如果我的系统不允许增加外部时钟源,系统是否允许通过外部触发的模式来工作——也就是通过外中断或者引脚电平变化中断来唤醒系统,并开始一次工作流程,完成后系统再次陷入永久睡眠。
  c. 如果以上都不行,你可以考虑换芯片或者修改系统的需求的——至少系统需求在功耗的部分需要做一些妥协。
  以上“确认系统脉搏”的步骤实际上是一个“例子”——系统设计的例子,或者说系统工作模式设计的例子,这是一个系统构架师或者说像我这样“自觉的”系统构架师应该要认真学会并经常实践的工作。一个笼统而完整的描述如下:
  1) 对一块目标芯片进行调查和确认:确认其所有的休眠模式,以及对应休眠模式关闭的时钟源,这些时钟源涉及到的外设——巧妇难为无米之炊,这一步首先搞清楚系统设计的时候手上有哪些材料。
  2)研究具体应用的需求,明确系统的工作模式(以采样类的模式来说就是采样,休息,再采样,再休息,整个系统是一个状态机并以采样事件作为驱动;采样不仅提供信息,也提供系统的脉搏。即便这类系统还涉及到LCD刷新或者I2C/串口通讯,由于信息的本源是采样,因此采样周期本身决定了信息的有效性,那么LCD的刷新周期,或者通讯的缓冲跟新周期没有理由必须大于采样的更新周期)。在这一前提下,明确系统对唤醒源以及唤醒模式的需求,由此便确定了系统的基础休眠模式,进一步说,比较这一基础休眠模式功耗和应用所需的功耗,便可给出系统设计的一个初步评估结果。有时候甚至能给出一些系统功耗的直接预期数据。
  3)根据研究报告,讨论系统的可行性。如果不可行,则根据已经明确的系统工作模式,对应的唤醒源要求重新选择芯片,并返回步骤1)。如果可行,则可以进行后续的设计。
  这种系统设计模式看似有点本末倒置——还没有搞清楚需求就已经把芯片定了——实际上,这种方式非常符合我们通常的开发模式:先有了一个初步的概念或者备选芯片方案,这些方案可能来自一个已有的方案,一个已有方案的兼容方案,一个老板或者老员工/经验者提出的方案——总之有了一个基础或者说原形,我们开始调查和研究具体低功耗的可行性,并形成了一个研究报告,这一报告将直接指导下一步的行为:由于需求非常明确,我们可以决定是否更换芯片或者直接进入下一步的开发。上面三个步骤实际上形成了一个LOOP,这一loop其实来自于“快速原型法”这一古老的“敏捷”开发模式自然,规范,高效。
  完成了以上两个步骤,可以说这一阶段就结束了,至此我们对系统的关键性能数据已经成竹在胸:
  系统最低能实现多大的功耗;外设某些敏感的参数设置将如何影响功耗——当我们需要在外设性能和功耗之间做妥协和权衡的时候这些信息就将发挥巨大的作用;我们甚至对系统外来如何工作,或者说系统最基本的问题:“时间问题,整个复杂的多米诺骨牌是从哪里被什么推倒第一张牌的”大致有了了解。我们甚至知道,如果不出意外系统将会达到怎样的功耗。
  简单说,我们已经知道系统是能够实现的,功耗会在什么范围也是大体有所概念的。万事俱备,只欠东风。


  本小结的一个附录:试探功耗底限的系统配置方式——分享一些实实在在的经验A. 对AVR来说,不用的引脚怎么办?
  >> 如果这个引脚属于ADC采样引脚i) 通过DIDRn寄存器关闭对应引脚的数字输入,这个时候PINX对应位将永远读取到0ii)通过DDRx和PORTx寄存器将对应的引脚设置为输入,“关闭”上拉电阻>> 如果这个引脚是普通的GPIO官方推荐的方式是给这个引脚一个确定的电平,比如:
  i) 通过DDRx和PORTx寄存器将对应的引脚设置为输入状态,并“开启”上拉电阻ii)通过DDRx和PORTx寄存器将对应的引脚设置为输出状态,并输出低电平,PCB设计上,将该引脚接地。
  >> 如果这个引脚是RESET引脚当电路要求保证稳定的情况下尽可能简化,同时,VCC电压在上电时刻电压升高速度不会很缓慢,则接外部上拉电阻到VCC,这样虽然对功耗影响不大,但是可以适当提高一点抗干扰性。
  >> 如果这个引脚是扩展的ADC引脚基本可以不管,或者接地。
  B. 对AVR来说,需要使用的引脚怎么办?
  >> 如果这个引脚是开漏输出/线与输入的引脚,比如TWI,同时需要与外部设备连接,而这一连接是允许拔插的i) 如果逻辑上允许,接下拉电阻。
  ii)如果是外中断引脚或者因脚电平变化中断引脚,避免悬浮,相比上拉来说,尽可能选择下拉。这样设置的目的是避免不确定电平经常将系统唤醒。选择下拉的原因是将高电平的选择权力交给外部设备,同时更倾向于盗电(*^_^*)。
  >> 对于ADC引脚i) 如果永远不会用于数字信号的输入用途,请参考空闲引脚的处理方式。
  ii)如果需要用作数字信号的输入用途,请在通过PINx读取电平前,通过DIDRn寄存器打开对应引脚的数字输入,并插入两个NOP后读取电平,完成读取后,立即关闭数字输入功能。
  >> 对于控制信号引脚i) 直接驱动LED总是悲剧的开始,别忘记加入限流电阻,尽可能使用高亮LED。
  ii)如果非要输出电平的控制信号,请认真考量有没有漏出电流的可能,如果有,请尽可能在不需要输出控制信号的情况下,将IO口处理为无电流漏出的状态(或最小小电流漏出的状态,关于出还是入的语言文字问题,你懂的),具体状态图应该由硬件设计人员给建议。总原则就是按需分配。



原创粉丝点击