查看: 1700|回复: 0
打印 上一主题 下一主题

Windows CE的电源管理

[复制链接]
跳转到指定楼层
沙发
发表于 2015-3-30 13:00:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

Windows CE的基本电源管理功能

    在所有版本的Windows CE操作系统中,图形、视窗和事件子系统(GWES)在电源管理方面都发挥了关键作用。这是因为早期版本的电源管理功能是由用户的活动所驱动的,而GWES负责处理所有用户的输入,如键盘、鼠标和触摸屏。GWES设置定时器监控用户的活动,当一段时间内用户没有任何输入时,便使系统进入休眠状态。通过注册表可以设置这几个定时器的超时值,它们可以分别被用于电池供电或外部电源供电时。当然,通过注册表也可以禁用GWES的电源管理功能,它在Windows CE.NET以后的版本中是默认被禁用的,这有利于电源管理器的集中管理。


                                   图1 Windows CE基本的电源转换流程

状态及其转换描述
No Power既没有电池也没有外部电源供电.
On所有设备上电的常规运行状态.
Suspend休眠状态,这时大部分设备关闭,仅RAM(自刷新)和外部时钟运行.
Idle空闲状态,这时可停止CPU的运行.
Critical off电池电压过低的状态.
Power-on reset系统清空RAM并初始化文件系统.
Cold bootFirst application of power, for example, when a backup battery is installed.
Warm boot软启(热启动),清空RAM并返回运行(On)状态.
On-to-Idle从全速运行状态到空闲状态的转换.
Idle-to-On处理器从低功耗状态回到全速运行状态.
On-to-Suspend由于某些事件的触发,处理器转换到停止运行状态。并调用设备驱动函数 XXX_PowerDown.
Suspend-to-On由特定的唤醒事件触发,处理器从停止状态返回到全速运行态。并调用设备驱动函数XXX_PowerUp.
On-to-Critical off当电池电压过低时转换到Critical off状态.

                                   图1 Windows CE基本的电源转换流程

    上图是Windows CE系统基本的电源状态转换策略,对应有5种系统电源状态(等级):No Power, On, Suspend, Idle, Critical off。相关描述和转换方式参见上表。  
基本的电源管理功能所采用的节能方法是使系统适时的进入休眠状态,当下面的一种事件发生时,系统将进入休眠状态(SUSPEND):
    l          用户按下On/Off按钮;
    l          监控用户活动的定时器超时;
    l          应用程序调用API,如GwesPowerOffSystem或SetSystemPowerState。

当下面的一种事件发生时,系统将退出休眠状态:
    l          用户再次按下On/Off按钮;
    l          发生某个警告事件,如某个日期或时间定时器的到时提醒;
    l          发生某个唤醒事件,由外设如串口设备或者网卡触发中断来唤醒系统。

虽然通过用户操作、应用程序或者外设都可以使系统进入或者退出休眠状态,但基本的电源管理功能所能控制的粒度过大,对应于CPU只有三种状态:On,Idle和Suspend,对应于所有外设只有两种状态:On和Suspend。而且,当系统进出休眠状态时,应用程序都得不到任何通知。

Windows CE的高级电源管理功能

加入了电源管理组件的Windows CE具有高级的电源管理功能,它允许每个外设具有自己的电源状态,有别于一般的系统电源状态(System Power State),被称作设备电源状态(Device Power State)。现在应用程序有能力设置个别外设的电源状态,比如一个文件传输程序,在保持串口或者蓝牙端口正常通讯时,可以关闭显示屏幕和背光。这就为实现更高级别的动态电源管理提供了可能。

我们可以通过注册表任意设定一组系统电源状态,使其对应于我们设计的状态模型。对于设备电源状态则没有这么大的灵活度,它具有5个设备状态:

D0:Full on;D1:Low on;D2:Standby;D3:Sleep;D4:Off

当定义好系统电源状态,并为每个外设分配了设备电源状态后,通过注册表,我们可以将两者进行映射。在某个系统电源状态下,比如一个电池供电的系统,当电池电量已经少于50%时,显示屏幕和背光可能处于D1状态,而网络设备可以设置为D3状态。也就是说,在同一时刻,不同的外设可能处于不同的设备电源状态中。这样的灵活性意味着每个设备可以最小程度的消耗电池资源。



       图2 Windows CE高级电源管理框架

如图2所示,电源管理器实现为一个名为Pm.dll的动态链接库,电源管理接口分为应用程序和驱动程序两部分。驱动程序通过DeviceIoControl服务例程来处理电源管理器发来的设备电源状态改变请求,另外电源管理器通过消息队列通知应用程序电源相关的事件。

    为了获得高级电源控制的功能,必须通过Platform Builder将电源管理组件编译到内核镜像中。实现的源代码参见{WINCEROOT}/Private/Winceos/Coreos/Device/PMIF/pmif.c,这段代码只是一个封装,它会调用{WINCEROOT}/Public/Common/Oak/Drivers/PM中的组件。参考这个实现OEM可以根据需要设计自己的电源管理策略。

    下一篇将分三个部分来解读WinCE的电源管理,首先从系统平台开发的角度,这部分一般由OEM厂商负责实现,然后介绍电源管理的接口,分别从设备驱动和应用程序的角度介绍如何应用电源管理。


1. OAL的电源管理
OEM Adaptation Layer(OAL)是一层与硬件平台相关的代码,它在电源状态转换中扮演着重要的角色。首先必须实现的是以下几个与硬件相关的函数:

OEMInit:初次上电时(或在冷启后)被调用,一般在这个函数中处理一些重要的初始化工作,如初始化系统内存,建立调试环境,设置系统中断等;

OEMIdle:没有线程可调度运行时被内核调用,这时可将CPU置于低功耗状态,并保证其可以快速返回运行状态;

OEMPowerOff:系统进入休眠(Suspend)状态前被调用,它即是系统进入休眠状态前被调用的最后一个函数,也是在系统被唤醒后所执行的第一个函数(中断处理程序以外);

Interrupt Handler:一些用来唤醒系统的中断处理程序。当中断发生时,内核首先调用中断处理程序,其中一些中断是可以将系统从睡眠状态中唤醒的,但中断时间内能处理的事情非常少,在中断处理程序里大部分API也是不能被调用的。

高级的电源管理功能允许任何设备驱动将其中断作为系统的唤醒源,在版本Windows CE.NET 4.1以前,OAL掌管着所有的中断处理,这就意味着在生成内核镜像以后,我们就无法在系统中添加新的中断处理程序了。现在即使这个设备是在运行时才安装到系统中来的,比如PCMCIA或者CF接口的无线网卡,它可以通过调用API(LoadIntChainHandler和FreeIntChainHandler)实现中断处理程序的安装和卸载。在系统中添加了新的中断处理程序后,它就可以通过内核IOCTL代码(IOCTL_HAL_ENABLE_WAKE)实现其作为系统的唤醒源。

位于操作系统内核层的电源管理策略,也要为设备驱动及上层应用程序提供接口,通过PowerPolicyNotify向电源管理组件发送事件请求,设备及应用程序就可以参与到系统的电源管理策略中来。

2. 设备驱动的电源管理

我们可以为设备驱动添加电源管理功能,首先就是要在驱动程序中添加电源管理的IOCTL代码,然后通知电源管理器该驱动是支持电源管理的,最后在驱动中编码实现该设备支持的几种电源状态。

2.1 建立支持电源管理的设备驱动

为了建立一个能够对设备进行电源管理的驱动程序,我们必须首先建立一个支持non-COM-related设备接口的驱动程序。non-COM-related设备接口标明这个设备是支持电源管理的。可以用以下方式建立这种接口:
    l          可以在注册表中,用激活设备所用的IClass值定义接口;
    l          可以在驱动程序的Init函数中,设置注册表中的IClass值;
    l          可以使用ActivateDeviceEx的参数REGINI设置IClass值;
    l          可以在驱动程序中显示地调用AdvertiseInterface函数。

电源管理器通过IOCTL代码来和驱动通信。通常情况下,当一个驱动程序声明为支持电源管理时,驱动只需要在DeviceIoControl中实现电源的管理即可。下面是电源管理器用来与驱动通信的IOCTL代码:
    l          IOCTL_POWER_CAPABILITIES:代表电源管理器请求设备驱动返回设备支持的电源状态及相关特征;
    l          IOCTL_POWER_SET:请求驱动更新设备的电源状态;
    l          IOCTL_POWER_QUERY:电源管理器询问设备是否准备好进行状态切换;
    l          IOCTL_POWER_GET:请求驱动返回当前设备的电源状态;
    l          IOCTL_REGISTER_POWER_RELATIONSHIP:通知父设备注册所有它所控制的设备。

其中IOCTL_POWER_CAPABILITIES和IOCTL_POWER_SET是支持电源管理的设备驱动必须实现的。

2.2 IOCTL代码的实现

在设备自举的时候,设备驱动必须使设备处于D0状态,当电源管理器通过IOCTL_POWER_CAPABILITIES向设备发出查询时,设备驱动应该尽可能详细的报告该设备的电源管理能力,以便将自己纳入到系统的电源管理策略中去。在自举完成后,电源管理器可以根据管理策略,调用IOCTL_POWER_SET调整设备的电源状态。在设备自我管理电源的情况下,设备应该通过DevicePowerNotify函数请求系统改变它们的电源状态。在实现对IOCTL_POWER_SET支持时,设备驱动开发应该注意以下几点:
    l          设备并不一定具备所有五种设备电源状态,但至少可以工作在D0状态;
    l          电源管理器可能会要求设备进入任何设备电源状态,并不仅仅是设备声明支持的几个。
    l          如果一个设备被要求进入一个它并不支持的电源状态,它就会进入另一个它支持的更高功耗的状态。例如,一个设备并不支持D2,它会被要求进入D1。
    l          电源管理器可能会通过发出IOCTL_POWER_SET,使设备再次进入它已经处于的当前状态。在这种情况下,设备驱动程序简单的返回成功即可。
    l          设备的电源状态不一定与系统的电源状态同步,因为它可能受到应用程序需求的限制。

2.3 休眠和唤醒的处理

支持电源管理的流设备驱动通过XXX_PowerDown和XXX_PowerUp接收系统休眠和唤醒的通知,这些通知在内核调用OEMPowerOff之前发出,并处于中断上下文中。

设备驱动的开发者必须清楚,在系统休眠期间,设备应该处于何种状态。并不是所有的设备在这个时候都应该强制被关闭,比如在音频设备可以不依赖处理器来播放音乐时,在系统休眠期间它的状态就应该交给电源管理器和应用程序来处理。而如果这个音频设备的播放需要处理器频繁的工作,在系统进入休眠状态时,驱动程序应该果断的关闭设备的电源,即使该设备由于应用程序的请求不处于D4状态。

另外,设备的D3状态值得特殊考虑,因为这个状态不仅仅与设备的功耗级别相关,处于D3状态的设备还被允许将系统从休眠状态中唤醒。所以当我们开发支持电源管理的设备驱动时需要注意以下几点:
    l          支持唤醒系统的设备不应该主动通过DevicePowerNotify请求进入D3状态,因为一般情况下,只有当系统打算进入休眠状态时,电源管理器才将设备作为唤醒源启用。从代码的角度也应该避免这一点,因为驱动程序无法区分来自IOCTL_POWER_SET中对D3状态的请求是由它自己还是电源管理器发起的;
    l          如果有必要,能唤醒系统的设备可以定义一样的D2和D3状态,而只有D3具有启动唤醒的功能;
    l          支持D3的设备不一定具有将系统从休眠状态唤醒的功能,不能将系统唤醒的设备,但它又具有低功耗模式,是可以自己主动请求进入D3状态的;   
    l          如果不能唤醒系统的设备处于D3状态,在系统进入休眠状态时,它应该在XXX_PowerDown中将状态转换为D4,并且在XXX_PowerUp中恢复到D3状态;如果无法这么做,它就不应该支持D3状态,而是在请求进入D3时直接进入D4状态;

做到了以上几点,在系统进入休眠状态时,OEM和应用程序开发人员就可以请求将设备进入D3状态,而不必关心这个设备是否支持唤醒系统。

3. 应用程序的电源管理

电源管理组件提供了一组接口供应用程序参与到电源管理的活动中,应用程序可以通过RequestPowerNotifications函数请求电源管理器向其发送电源相关的通知,也可以通过SetPowerRequirement通知电源管理器将设备设置在特殊的电源状态下。这样,指定设备的电源状态就不会随系统电源状态的改变而改变。

3.1 电源通知机制

电源相关的通知通过消息队列传递给应用程序,通常应用程序新建一个消息队列,并通过RequestPowerNotifications将这个消息队列的句柄传递给电源管理器,同时创建一个线程侦听来自这个队列的消息。电源管理器定义了如下几种通知:
    l          PBT_RESUME:当系统从休眠状态被唤醒是产生;
    l          PBT_POWERSTATUSCHANGE:当系统接入或者断开外部电源时产生;
    l          PBT_TRANSITION:当电源管理器执行系统电源状态转换时发生;
    l          PBT_POWERINFOCHANGE:当电池信息更新时发生。

3.2 电源请求机制

电源请求机制为应用程序提供了强大的能力控制电源管理器调整设备的电源等级,与其他所有的电源设置相比,它具有很高的优先级。举例来说,假设有一个条形码阅读器连接在COM1端口,并且COM1只有在最高电源等级(D0)时才能驱动这个条形码阅读器。为了使其正常工作,应用程序将调用SetPowerRequirement把COM1指定D0状态。假设之后串口驱动自身决定降低一个电源等级,驱动调用DevicePowerNotify通知电源管理器它期望的设备电源状态,驱动程序的这个请求将不起作用,直到应用程序调用ReleasePowerRequirement为止。继续这个例子,假设这时的系统电源状态转换为低能耗等级,虽然与之相关的COM1电源等级为D3,由于应用程序的电源请求,COM1将继续维持在D0状态。

在调用SetPowerRequirement函数时,指定POWER_FORCE标志将强制设备不进入休眠状态,即使这时系统已处于休眠状态。

3.3 设置系统电源状态

在某些应用的场合下,应用程序可能需要改变系统的电源状态。OEM通过注册表定义了系统支持的电源状态, 应用程序可以通过GetSystemPowerState返回当前系统电源状态的名称,也可以通过SetSystemPowerState改变系统的电源状态。

本篇将以Windows Mobile为例介绍Windows CE电源管理的实现,大体上,Windows Mobile分为Pocket PC和Smartphone两种版本。这两者之间的主要区别在于触摸屏和电源模型,Smartphone采用的是“Always On”模型。为了说清楚它们的区别,我们就先从系统电源状态说起吧(这里有些系统电源状态是从WM5开始才有的)。

1. Windows Mobile的系统电源状态

On:用户与系统交互时的状态;
BacklightOff:在一段时间内(默认15秒),如果一直没有用户操作(比如按下某个键或者触摸屏幕),就关闭背光,这时其他的设备都没变化。这个timeout值可以通过控制面板进行设置;
UserIdle:这个状态只在Smartphone中被使用。经过一段稍长的时间,如果一直没有用户操作,就关闭背光和LCD。这个timeout值可以通过控制面板进行设置;
ScreenOff:一般由某些程序指定,才进入这个状态。比如音乐播放器程序,当你听音乐时按下某个键可以将屏幕关闭。PocketPC和Smartphone都使用这个状态,它与UserIdle的不同在于,ScreenOff意味着“用户主动关闭了显示,只有当他按下电源键时才重新显示”,而UserIdle意味着“用户有段时间没操作了,那么我们可以关闭屏幕来省电”,所以在UserIdle时,随便按下Smartphone的哪个键都会启动显示;
Suspend:这是PocketPC的睡眠模式,几乎所有设备都被关闭,直到某个硬件设备触发中断才将系统唤醒,这个timeout值可以通过控制面板进行设置(默认为3分钟);
Resuming:这是PocketPC被唤醒后的状态,这时屏幕是关闭的,并启动一个15秒的计时器,在这段时间内决定接下来进入哪个状态,如果计时器超时则重新回到睡眠状态;
Unattended:这个状态只在PocketPC中被使用,用户对其不会有所察觉。有些程序,如ActiveSync每5分钟会唤醒系统进行同步,同步完成后再让系统继续睡眠,这段时间不希望打扰用户,即程序在后台执行。
    可以通过注册表查看系统电源状态对应的具体设备的电源状态,[HLM/System/CurrentControlSet/Control/Power/State]。

    现在我们知道,Smartphone没有真正的睡眠模式,即使它会在一段时间后关闭背光和屏幕,但它并没有睡着,只是休息一下眼睛罢了,它的大脑和四肢仍在正常工作。PocketPC所采用的模型比Smartphone要复杂的多,你可以按下电源键让系统睡眠,在必要时,也可以唤醒系统做一些工作然后再继续睡眠。如果你在Smartphone上运行一个桌面精灵之类的程序,她为了引起你的注意,长时间的蹦啊跳啊,不管白天还是黑夜,可想而知,你的待机时间将......

    你可能会觉得PocketPC的“Sleep”模型比Smartphone的“Always On”模型要省电,其实恰恰相反。因为在系统睡眠的过程中,它需要通知所有的设备驱动,为了让它们保存一些重要的信息并关闭相应的硬件设备,在系统被唤醒时也需要通知它们恢复先前的工作。这个过程不仅耗时还可能会耗更多的电,因为一些设备在频繁的状态转换过程中会消耗比较多的能量。这也就是为什么当你收到一条短信时,睡眠状态的PocketPC要花3到6秒的时间来处理,而Smartphone只需要几个微秒

2. Windows Mobile的电源管理策略

    我们可以用系统电源状态机来简单的描述Windows Mobile的电源管理策略,以PocketPC为例,系统电源状态机如下图所示:

                           

    系统内部的电源管理器负责协调电源状态的转换,电源状态的转换主要由一下几种方式触发:

计时器超时:SuspendTimeout和ResumingSuspendTimeout,分别对应于第一节介绍Suspend和Resuming状态时所提到的计时器。细说起来,它们每个又有两个值,分别对应着电源供电时和电池供电时的超时值,也就是注册表[HLM/System/CurrentControlSet/Control/Power/Timeout]中的ACSuspendTimeout、BattSuspendTimeout、ACResumingSuspendTimeout、BattResumingSuspendTimeout;
系统调用:驱动程序或应用程序通过相应的API,请求进入某种电源状态。这类API在前面的文章中已经有所介绍,如SetSystemPowerState、SetPowerRequirement、DevicePowerNotify等;
平台相关的系统调用:通过PowerPolicyNotify通知电源管理器发生了某个事件,它的实现比较灵活,驱动程序或应用程序可以通过相应的参数与电源管理器进行交互,比如PPN_POWERCHANGE、PPN_SUSPENDKEYPRESSED、PPN_UNATTENDEDMODE等,参见"pmpolicy.h";
直接访问内核对象:事件(Event)作为Windows CE系统的内核对象,可以通过事件名称在进程间共享,因此我们可以访问电源管理器中的两个事件,它们的名字分别是_T("owerManager/ReloadActivityTimeouts")、_T("owerManager/SystemIdleTimerReset")。如果你的程序需要动态修改那几个计时器的时间长度,可以通过第一个事件通知电源管理器重新读取注册表中计时器的值,而第二个事件与SystemIdleTimerReset功能一样,可以阻止系统进入睡眠状态。
3. Windows Mobile电源管理相关API的应用

    最后,通过几个应用场景简单介绍一下常用的电源管理相关的API的使用:

如果你在设计的是媒体播放器程序,不希望在播放电影时,系统自动转入Suspend状态,这时可以每隔30秒调用一次SystemIdleTimerReset,它会帮你重置那个计时器;如果你还想同时保持背光,那么可以调用SetPowerRequirement(TEXT("BKL1:"), D0, POWER_NAME, NULL, 0);如果你提供一个按钮允许用户关闭屏幕,那么调用SetSystemPowerState(NULL, POWER_STATE_IDLE, 0);
如果你在设计的是天气预报程序,需要每天早上6点在线更新天气信息,这时可以调用CeRunAppAtTime,系统到时会被RTC中断唤醒,还记得前面提到的那个15秒的计时器吗,这时你的程序应该在15秒内请求进入Unattended状态,否则系统将重新回到睡眠状态。在处理更新的过程中,还是应该每隔30秒调用一次SystemIdleTimerReset,在处理完更新后,应该再次调用CeRunAppAtTime,并放弃Unattended状态。请注意,在电源管理器的实现代码中,用了一个引用计数的变量(gdwUnattendedModeRequests)统计所有对Unattended状态的请求,所以PowerPolicyNotify(PPN_UNATTENDEDMODE, TRUE);和PowerPolicyNotify(PPN_UNATTENDEDMODE, FALSE);要成对出现,否则系统将无法回到睡眠状态。
如果你要开发一个监控电池状态的程序,首先应该创建一个接收状态通知的线程,在这个线程里调用RequestPowerNotifications,这个函数的第一个参数是一个消息队列的句柄,所以必须先创建一个消息队列(CreateMsgQueue),第二个参数是你希望得到的通知类型,这里要用到的是PBT_POWERSTATUSCHANGE|PBT_POWERINFOCHANGE,然后线程就可以等待通知了(WaitForSingleObject),一旦有通知到来,线程通过ReadMsgQueue读取消息的内容,再做些更新UI的工作。

电源管理

        Windows CE是典型的使用电池供电的系统。这使得正确操作系统十分关键,应用程序大多数时间都不需要关注Windows CE 设备的电源损耗,但是在某些时候,你可能要注意这些损耗。

        当用户关闭了一个使用电池的Windows CE 设备,电源系统不会关闭PC电源,事实上,只是系统被挂起(译者注:这里就像有些PocketPC把关闭电源放在拔SIM卡的位置,拔出SIM卡才真正关闭电源。但是,目前包括Smartphone在内,因为硬件设备,比如CPU无法进入低功耗,所以为了省电,需要做到关闭应用处理器及大部分设备供电,然后需要唤醒时,再通过定时器或无线模块唤醒。所以不关闭电源的情况不是绝对的。)当用户打开设备电源,设备不会像PC一样重新启动,而是被唤醒,返回到与系统挂起前一样的状态。这样导致一个应用程序在唤醒后会像挂起前一样运行。事实上,应用程序根本不知道它被挂起,除非它明确地请求当系统挂起时通知它。从应用程序的角度看,电源管理有三种方式,查询电源状态,改变电源状态,和防止电源状态改变。

查询电源状态

        要查询系统当前的电源状态,你必须调用

DWORD GetSystemPowerStatusEx2 (PSYSTEM_POWER_STATUS_EX2 pSystemPowerStatusEx2, DWORD dwLen, BOOL fUpdate);

        函数带了三个参数:一个指向SYSTEM_POWER_ STATUS_EX2结构的指针,结构的长度,和一个布尔值,表示告诉操作系统是否应该查询电池驱动来得到最后的信息或者直接返回电池缓存中的信息。系统大约每5秒查询一次电池状态,因此,如果第三个差数是FALSE,得到的数据不会太旧。结构SYSTEM_POWER_STATUS_EX2被定义为

typedef struct _SYSTEM_POWER_STATUS_EX2 {

BYTE ACLineStatus;

BYTE BatteryFlag;

BYTE BatteryLifePercent;

BYTE Reserved1;

DWORD BatteryLifeTime;

DWORD BatteryFullLifeTime;

BYTE Reserved2;

BYTE BackupBatteryFlag;

BYTE BackupBatteryLifePercent;

BYTE Reserved3;

DWORD BackupBatteryLifeTime;

DWORD BackupBatteryFullLifeTime;

WORD BatteryVoltage;

DWORD BatteryCurrent;

DWORD BatteryAverageCurrent;

DWORD BatteryAverageInterval;

DWORD BatterymAHourConsumed;

DWORD BatteryTemperature;

DWORD BackupBatteryVoltage;

BYTE BatteryChemistry;

} SYSTEM_POWER_STATUS_EX2;

        在我描述的这个巨大的结构之前,我必须告诫你,这个结构返回的数据精确程度和电池驱动一样。同样的结构被传给电池驱动来查询它的状态。Windows CE不验证电池驱动返回的数据。这个函数返回来的数据依赖于电池驱动,因此不同的系统有不同的变化。举个例子,许多系统在使用AC电源时不报告精确的电源级数;另一些系统则相反。应用程序使用GetSystemPowerStatusEx2来自动预防和检测系统是否可能运行应用程序。

第一个区域,ACLineStatus,包含一个标志,表示系统是否连接到AC 电源。如果值是AC_LINE_OFFLINE,表示系统没有使用AC 电源;AC_LINE_ONLINE,表示系统使用了AC 电源;AC_LINE_BACKUP_POWER和AC_LINE_UNKNOWN,表示备用电源和未知电源。BatteryFlag区域,提供了一个总的标识,表示当前系统的电池状态,可以有以下值:

BATTERY_FLAG_HIGH

电池被充满或接近充满。

BATTERY_FLAG_LOW

电池还有一点剩余。

BATTERY_FLAG_CRITICAL

电池电量处在一个临界状态。

BATTERY_FLAG_CHARGING

电池当前正在充电。

BATTERY_FLAG_NO_BATTERY

系统无电池

BATTERY_FLAG_UNKNOWN

电池状态未知

BatteryLifePercent区域包含估计的电池电量能够维持的百分比。数值可能是0到100之间的一个,或用255表示百分比未知。BatteryLifeTime区域表示电池耗尽之前可以维持的秒数。如果该值不能估计,区域填入BATTERY_LIFE_UNKNOWN。BatteryFullLifeTime区域包含完全充满电池需要的时间。如果该值不能估计,填入BATTERY_LIFE_UNKNOWN。注意,在许多系统中,这些值可能难以测量。大多数OEM 厂商简单地在每个区域内填入BATTERY_LIFE_UNKNOWN。

接下来的第四个区域(不计算保留区域)重复了前面的表述,只不过是对系统备份电池来说。因为这些值大多数难以测量,许多系统简单地返回“unknown”给这些区域。

剩下的区域描述了电池和备用电池的电力状态,因为许多系统缺少测量这些值的能力,这些区域也被简单地默认为“unknown”。最后一个区域,BatteryChemistry,包含一个标志,表示系统中电池的类型。当前已定义的值包括

· BATTERY_CHEMISTRY_ALKALINE

· BATTERY_CHEMISTRY_NICD

· BATTERY_CHEMISTRY_NIMH

· BATTERY_CHEMISTRY_LION

· BATTERY_CHEMISTRY_LIPOLY

· BATTERY_CHEMISTRY_UNKNOWN

改变电源状态

       应用程序能通过一系列的方式改变系统的电源状态。在基于Windows CE.NET系统的较新系统中,首选的方式是使用电源管理程序,在之后的章节将会讨论。可是无论如何,还有大量的基于早期Windows CE版本的系统以及Windows CE.NET不包含电源管理程序版本。对这些系统来说,下面的技术会很方便。

关闭电源

        应用程序可以通过调用一个少有资料的GwesPowerOffSystem函数挂起系统。这个函数可以在大多数版本Windows CE中使用,但是最近才被公开。事实上,大多数SDK没有包含这个函数的原型,你可能要提供原型。这个函数定义为

void GwesPowerOffSystem(void);

GwesPowerOffSystem的使用很简单:简单调用,系统就会挂起。

        如果你想避免使用很少资料的函数,你可以通过简单地模拟用户按关闭按钮来关闭系统。你可以通过使用keybd_event函数很容易地允许你的应用程序挂起系统,如下:

keybd_event (VK_OFF, 0, KEYEVENTF_SILENT, 0);

keybd_event (VK_OFF, 0, KEYEVENTF_SILENT │ KEYEVENTF_KEYUP, 0);

这两个keybd_event调用模拟了按和释放电源按钮,电源按钮的虚拟键值是VK_OFF。执行前面的两行代码将挂起系统。因为虚拟键代码在执行时会由GWES表现,两个函数可能在系统挂起前有一些状态的表现(译者注:屏幕上会有关闭对话框之类的图像,和真实按下按钮的画面一样)。如果你的程序无法在keybd_event函数之前停止工作,添加一个Sleep调用来使应用程序暂停一些毫秒来让GWES真实地挂起系统。

关闭屏幕

如果系统有有色背光显示,主要的电源消耗不是CPU而是背光。在一些环境下,一个应用程序需要运行却不需要显示在屏幕上。一个例子是音乐播放器应用程序,当用户听音乐的时候,不关注屏幕。在这些情形下,有能力关闭背光将意味着提高电池寿命。

当然,当用户想看屏幕时,任何关闭背光应用程序的需要一个简单的用户友好的方式来重新打开屏幕。同样,记得用户典型的想法是屏幕变黑时会认为被关闭了,因此要考虑这点。举个例子,一个用户可能在系统已经运行时试图打开系统电源,并且这样做了,却很意外地发现,设备电源被关闭了。同样,当系统在这种情况下关闭显示,它同时也关闭了触摸屏。这意味着你不能告诉用户敲击屏幕来打开。而是,你需要使用一些其他的事件,比如设置时间,任务完成,或用户按了一个按钮。最后,这里讨论的方式对大多数基于Windows CE 3.0或更新的版本比较有用,并且被Windows CE .NET 4.0中的电源管理程序所替代。对于较新的系统,先看看是否电源管理程序可用,然后通过它来控制屏幕。如果失败了,ExtEscape方式也许能行。

在Windows CE中,显示的控制是通过Ext?Escape函数。这是一个显示和打印机驱动的后门。Windows CE显示驱动支持许多设备转义代码(escape codes),这些被公布在Platform Builder中。对于我们的目的来说,只有两个转义代码被用到:SETPOWERMANAGEMENT来设置显示的电源状态和QUERYESCSUPPORT来查询是否SETPOWERMANAGEMENT被驱动支持。下面的例子打开或关闭系统显示通过显示驱动,并且支持完全的转义代码:

//


// Defines and structures taken from pwingdi.h in the Platform Builder


//


#define QUERYESCSUPPORT 8


#define SETPOWERMANAGEMENT 6147


#define GETPOWERMANAGEMENT 6148



typedef enum _VIDEO_POWER_STATE {


VideoPowerOn = 1,


VideoPowerStandBy,


VideoPowerSuspend,


VideoPowerOff


} VIDEO_POWER_STATE, *PVIDEO_POWER_STATE;



typedef struct _VIDEO_POWER_MANAGEMENT {


ULONG Length;


ULONG DPMSVersion;


ULONG PowerState;


} VIDEO_POWER_MANAGEMENT, *PVIDEO_POWER_MANAGEMENT;



//----------------------------------------------------------------------


// SetVideoPower - Turns on or off the display


//


int SetVideoPower (BOOL fOn) {


VIDEO_POWER_MANAGEMENT vpm;


int rc, fQueryEsc;


HDC hdc;



// Get the display dc.


hdc = GetDC (NULL);


// See if supported.


fQueryEsc = SETPOWERMANAGEMENT;


rc = ExtEscape (hdc, QUERYESCSUPPORT, sizeof (fQueryEsc),


(LPSTR)&fQueryEsc, 0, 0);


if (rc == 0) {


// No support, fail.


ReleaseDC (NULL, hdc);


return -1;


}


// Fill in the power management structure.


vpm.Length = sizeof (vpm);


vpm.DPMSVersion = 1;


if (fOn)


vpm.PowerState = VideoPowerOn;


else


vpm.PowerState = VideoPowerOff;



// Tell the driver to turn on or off the display.


rc = ExtEscape (hdc, SETPOWERMANAGEMENT, sizeof (vpm),


(LPSTR)&vpm, 0, 0);



// Always release what you get.


ReleaseDC (NULL, hdc);


return 0;


}


前面的代码通过调用ExtEscape和QUERYESCSUPPORT命令来查询是否支持转移代码。被查询的命令首先交给输入缓冲,如果SETPOWERMANAGEMENT命令被支持,程序就填充VIDEO_POWER_MANAGEMENT结构并再次调用ExtEscape设置电源状态。

虽然这些转义代码允许应用程序打开或关闭显示,Windows CE没有一个统一的方式来控制背光的亮度。每个系统都有它自己的OEM特有方式来控制背光亮度。如果将来有一种标准的背光亮度控制方式,它将很可能放在ExtEscape函数中。

打开系统电源

当系统被挂起,应用程序将不再运行,因此当系统唤醒时,应用程序看起来没有被控制。然而,有一些方式来唤醒一个挂起的设备。首先,一个应用程序通过给定一个时间,并使用11章提到的消息API(Notification API)做系统被唤醒的计划。在一般情况下,OEM厂商会分配一些中断条件,以便管理系统电源打开,或唤醒。这种方式的一个例子是一个系统当防止了一个同步架(synchronization cradle)时被唤醒。

防止系统关闭电源

相反的情况,防止系统挂起也是一个问题。Windows CE系统通常被设置为当一段时间没有用户输入就自动挂起。要防止自动挂起,一个应用程序可以周期性地调用一下函数:

void WINAPI SystemIdleTimerReset (void);

这个函数重设Windows CE用来监视用户输入的定时器。如果定时器到达预先的没有用户输入的间隔,系统会自动挂起。因为挂起超时值可以被改变,一个应用程序需要知道超时值,这样就要多一点调用SystemIdleTimerReset。系统维护三个超时值,这些都能够使用SystemParametersInfo来查询。传递给SystemParametersInfo的常量的不同表现,显示如下:

SPI_GETBATTERYIDLETIMEOUT

当系统运行在电池电源状态下,离用户最后输入的时间

SPI_GETEXTERNALIDLETIMEOUT

当系统运行在AC电源状态下,离用户最后输入的时间

SPI_GETWAKEUPIDLETIMEOUT

在系统再次挂起时离系统被自动唤醒的时间

要防止电源被自动挂起,你需要查询这三个值,并在最短时间内返回之前调用SystemIdleTimerReset。如果超时值被设置为0,表示超时值被禁止。

电源管理程序

一个新的,独立的电源管理组件在Windows CE .NET 4.0中被引入了。这个电源管理程序替代了许多GWES以前完成的函数。电源管理程序定义了一系列的电源状态,如D0,D1,D2,和D3。这些看起来神秘的名字被对应于一些友好的系统级别名称。

对嵌入式系统来说,OEM厂商定义了系统的电源状态。例如,电源状态可能是打开(On),空闲(Idle)和挂起(Suspend)。其他电源状态也被定义了,像ScreenOff, InCradle, 和 OnBattery。

从应用程序的观点看,新的电源管理程序提供了通知电源状态改变的能力以及通过一系列的函数统一改变电源状态的能力。

系统的电源状态被定义在注册表中,SDK定义了PWRMGR_REG_KEY,以致你不得不知道注册表的字符串,但是当常量没定义的时间,电源管理程序注册数据被保留在HKEY_LOCAL_MACHINESystemCurrentControlSetControlPower。电源状态被定义作为子键,位于Key State。

电源通知

电源管理程序一个十分受欢迎的特点是,可以在系统电源状态改变时通知应用程序。这可以让应用程序从手动检测电源状态中解脱出来。一个应用程序可以通过调用RequestPowerNotifications请求电源管理程序当电源状态改变的时候发送一个通知给应用程序。电源管理程序会通过一个由应用程序前面建立的消息队列发送通知。

RequestPowerNotifications原型如下。

HANDLE RequestPowerNotifications (HANDLE hMsgQ, DWORD Flags);

第一个参数是一个应用程序在之前建立的消息队列的句柄。第二个参数是一系列参数,表示应用程序想接收的通知。

PBT_TRANSITION

接受系统电源状态改变的通知。例如,当系统从On到Suspend。

PBT_RESUME

当系统resume的时候接收通知。

PBT_POWERSTATUSCHANGE

当系统在AC和电池之间切换的时候接收通知。

PBT_POWERINFOCHANGE

当系统电池级数变化时接收通知。

POWER_NOTIFY_ALL

接收所有的通知。


RequestPowerNotifications函数返回一个电源通知的句柄,失败返回NULL。消息队列建立的时候必须使应用程序有读权限,因为应用程序将从消息队列中读取电源通知。

要接收通知,应用程序必须使用WaitForSingleObject来阻塞消息句柄。像第10章所讨论的,当通知被放在队列中时,句柄将被signaled。实际的通知将由结构POWER_BROADCAST表中被接收到。

typedef struct _POWER_BROADCAST {

DWORD Message;

DWORD Flags;

DWORD Length;

WCHAR SystemPowerState[1];

} POWER_BROADCAST, *PPOWER_BROADCAST;



第一个要注意的是,这个结构长度是可变的。最后一个字段,SystemPowerState,是被定义为WCHARs类型,但是可以填上非字符串数据。第一个字段是通知自己的标识,这个字段可以填前面PBT_标志列表之一。Flags区可以包括以下标志,依赖于被接收的通知:

POWER_STATE_ON

系统处于on状态。

POWER_STATE_OFF

系统处于off状态。

POWER_STATE_CRITICAL

系统进入了一个临界off状态。

POWER_STATE_BOOT

系统正在启动。

POWER_STATE_IDLE

系统进入idle状态。

POWER_STATE_SUSPEND

系统被挂起。

POWER_STATE_RESET

系统被复位。

最后两个字段是相互关联的。Length字段是SystemPowerState字段数据的长度。SystemPowerState中包含的数据依赖于被发送的通知。对于PBT_TRANSITION通知来说,SystemPowerState字段包含一个新电源状态的标识字符串。这个字符串是以非0结尾的。为了结束字符串,使用Length字段来指出字符串的长度。注意,Length字段是以字节为单位的,当字符是双字节的Uncode字符时,需要获得字符串字符的长度,就需要用Length字段去除TCHAR的size。

对于PBT_POWERINFOCHANGE通知来说,SystemPowerState字段包含一个PPOWER_BROADCAST_POWER_INFO结构:

typedef struct _POWER_BROADCAST_POWER_INFO {

DWORD dwNumLevels;

DWORD dwBatteryLifeTime;

DWORD dwBatteryFullLifeTime;

DWORD dwBackupBatteryLifeTime;

DWORD dwBackupBatteryFullLifeTime;

BYTE bACLineStatus;

BYTE bBatteryFlag;

BYTE bBatteryLifePercent;

BYTE bBackupBatteryFlag;

BYTE bBackupBatteryLifePercent;

} POWER_BROADCAST_POWER_INFO, *PPOWER_BROADCAST_POWER_INFO;


注意,这里有一些字段的名字和函数十分相似于前面讨论的SYSTEM_POWER_STATUS_EX2结构。


设置电源状态

电源管理程序提供的函数也允许应用程序来控制电源状态。有两个方式来控制电源。第一个方式是应用程序给定一个电源设定。第二个方式是应用程序请求电源状态不要低于给定的级别。

一个应用程序通过调用函数SetSystemPowerState可以请求特定的电源状态。这个函数原型如下。

DWORD SetSystemPowerState (LPCWSTR psState, DWORD StateFlags, DWORD Options);


电源状态可以被请求通过指定前两个参数。如果第一个参数是非零值,它指向一个字符串标识被请求的状态。这个字符串必须和注册表中列出的电源状态之一相匹配。

如果psState 为 NULL,第二个参数StateFlags,定义了请求的电源状态。这个参数是从POWER_STATE_ON直到POWER_STATE_RESET状态其中之一,这些在前面提到的POWER_BROADCAST结构有描述。

比较特别的是POWER_STATE_RESET标志。这个标志请求系统重起,使用SetSystemPowerState的方法重起比通过直接使用IOCTL_HAL_REBOOT命令来调用KernelIoControl的方法更好。调用 SetSystemPowerState 会让系统在重起设备之前任何还在缓冲中的数据保存到文件系统。

调用SetSystemPowerState是一个直接改变电源状态的方法。更巧妙的方法是通过调用SetPowerRequirement来请求系统维持应用程序所需最低限度的电源状态。SetSystemPowerState是假定应用程序知道所需状态,而调用SetPowerRequirement是允许系统对电源设定做优化以满足应用程序的需要。一个使用SetPowerRequirement会比较方便的例子是,一个使用串口的应用程序需要串口在进行通信时保持住电源状态。SetPowerRequirement被定义如下。


HANDLE SetPowerRequirement (PVOID pvDevice,

CEDEVICE_POWER_STATE DeviceState,

ULONG DeviceFlags, PVOID pvSystemState,

ULONG StateFlags);


第一个参数指定了应用程序需要维护电源状态的设备。DeviceState参数定义了设备的电源状态。CEDEVICE_POWER_STATE指定了状态范围是从D0(意味着设备是处于最大功耗状态)到D4(表示设备被关闭)(译者注:其实D0到D4的状态的具体表现,完全是由OEM厂商可自定义的,对应用程序开发者来说,比如是在D1关LCD背光还是在D2,都是不确定的,微软只给出标准定义,而不是实际定义)。DeviceFlags参数由两个标志合并而成:POWER_NAME,表示设备名有效;POWER_FORCE,表示设备应当维持当前状态甚至当系统挂起时。如果pvSystemState不为NULL,它表示只有对于在pvSystemState中已命名的电源请求才是有效的。设备可能无法更改请求的状态。

应用程序应当注销通过调用ReleasePowerRequirement来注销请求,原型如下。


DWORD ReleasePowerRequirement (HANDLE hPowerReq);


这里唯一的参数是从SetPowerRequirement里返回的句柄。

在下一章,我将就Windows CE流设备驱动和服务,继续探讨有关系统的问题。尽管大多数应用程序开发者可能不需要写一些设备驱动或服务,但是知道它们是如何和程序一起工作对我们也是有启发的。让我们一起来看一看吧。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入中科因仑

本版积分规则

快速回复 返回顶部 返回列表