Windows CE电源管理

 2012-11-12          [nemail]    
[lablebox]

        对于手持式、便携式等移动设备来讲,为了让设备有更长的待机和工作时间,系统必需具备完善的电源管理功能。英创公司针对手持式、电池供电设备应用而推出的嵌入式核心主板EM9283,预装了Windows CE6.0操作系统,提供了完善的电源管理支持。在与EM9283配套的通用开发评估底板上测得的EM9283在各个电源状态下的功耗指标为:

          系统正常运行:110mA

          系统挂起:8mA。通过PSwitch键,可在1S内恢复到正常运行状态

          系统关机:<15uA。系统仅维持实时时钟的供电,系统重新启动时间小于10S

 

        本文将以EM9283为例,介绍用户应用程序如何实现电源管理,以减少整机功耗,延长设备工作时间。在这之前,有必要先对Windows CE系统的电源管理功能做一个了解。所以我们将先介绍WinCE系统的电源状态,然后是设备的电源状态,接着才是应用程序电源管理。文档的最后是一个实际的电源管理例子:LCD背光的电源管理。

 

1、系统电源状态

 

        上面提到了EM9283的运行,挂起和关机三种直观电源状态下的功耗指示,而至Windows CE.NET 4.2版本以后的系统具有的高级电源管理组件:WinCE电源管理器,为系统定义的电源状态如表1:

 


系统电源状态

简要描述

  On  用户与系统交互的常规运行状态
  UserIdle  用户与设备停止交互,但仍可能使用设备
  SystemIdle  经过一段时间UserIdle后进入此状态,但驱动和系统仍然活动
  SystemIdle  挂起状态,系统大部分外设关闭,仅RAM外于自刷新状态


 

        WinCE电源管理器维护了一个监控用户活动的定时器,通过监控这个定时器的超时来使系统进入用户空闲模式、系统空闲模式,最终挂起系统。基本的电源管理功能所采用的节能方法是使系统适时的进入挂起状态,此时系统RAM处于自刷新状态,整机功耗降到了较
低的水平,同时又能快速恢复到正常运行状态。下面的几种方法都可以让系统进入Suspend状态:

 

        1、监控的用户活动定时器超时。通过控制面板里的电源属性项设置超时时间。如图1

 

 

        图1中设置的意图是:在电池供电情况下,当用户停止操作1分钟后,系统电源管理器将控制系统进入用户空闲模式,如果2分钟后用户仍然没有操作,系统进入系统空闲状态,系统空闲3分钟后还是没有用户操作,系统挂起。

 

        在用户的最终产品中,由于种种原因,可能不能通过控制面板来直接设置系统超时,或者用户希望自己设计设置系统超时的界面,此时用户应用程序可以通过修改系统注册表来实现与通过控制面板设置一样的功能。具体的做法是修改[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Power\Timeouts]下面的超时时间。通过控制面板设置后的注册表如图2所示。

 

 

        可以看到,在注册中超时时间的单位是秒。应用程序要利用系统的电源管理器,除了在控制面板中做了上述设置外,还需要在应用程序启动时设置系统的'PowerManager/ReloadActivityTimeouts'自动事件为有信号状态,电源管理器才会正常工作。当在应用程序中直接修改了对应的Timeouts注册表项后,也需要打开(或创建)'PowerManager/ReloadActivityTimeouts'的自动重置事件,并SetEvent置这个事件为有信号状态,这样可以使电源管理器再次从注册表中读取状态切换定时器的设置。

 

  HANDLE hReloadActivityTimeouts;
  hReloadActivityTimeouts = OpenEvent(EVENT_ALL_ACCESS, FALSE, _T('PowerManager/ReloadActivityTimeouts'));
  if(hReloadActivityTimeouts)
  {
    SetEvent(hReloadActivityTimeouts);
    CloseHandle(hReloadActivityTimeouts);
  }

 

        2、应用程序调用API。除了通过超时让系统自动进入挂起状态外,应用程序还可以调用API函数让系统挂起:SetSystemPowerState(NULL,POWER_STATE_SUSPEND,0);

 

        3、EM9283提供了一键开关机PSwitch键。所以最直接的方法是通过PSwitch按键让系统在挂起状态和运行状态之间切换。

 

        通过上面的三种方法让可以让系统挂起,而当下面的一种事件发生时,系统将退出挂起状态:

        1、用户再次按下PSwitch键。

        2、发生某个警告事件,如时间定时器到时提醒。

        3、发生某个唤醒事件。

 

2、设备电源状态

 

        虽然通过用户操作、应用程序或者外设都可以使系统进入或退出挂起状态,但基本的电源管理功能所能控制的粒度过大,整个系统只有三种状态,On,Idle和Suspend,对应于所有外设只有On和Suspend两种状态。加入了电源管理组件的Windows CE具有高级的电源管理功能,它允许每个外设具有自己的电源状态——设备电源状态。应用程序有能力单独设置外设的电源状态,比如在保持串口通讯时,关闭显示和背光。Wince通常对设备电源状态的描述如表2:

 


设备电源状态

注册表键值

描述

Full on

D0

此状态表示设备已开启或正在运行,设备将以系统允许的最在功耗及最高性能运行
Low on

D1

此状态表示设备已开启或正在运行,但以低于D0状态的功耗及性能运行。D1状态适用于设备已经被使用,但以较低的性能运行即可
Standby

D2

此状态表示设备被部份供电
Sleep

D3

睡眠状态。保证唤醒的最小供电,在需要时能自动唤醒并初始化
off

D4

关闭状态,不供电


 

        物理设备并不需要支持上面所有的设备电源状态,但所有设备必需要支持D0电源状态,EM9283上的各种外设,如背光、显示等至少支持D0和D4两种状态,也就是说应用程序在能力决定是否单独给某个设备供电,以尽可能降低整机功耗。

 

3、应用程序电源管理

 

        上面所提的系统电源状态和设备电源状态都是系统或驱动一级对电源管理的支持,对使用英创公司工控主板的用户来讲,这些功能都是由英创公司直接提供并维护的,用户如果知道如何利用系统提供的这些功能,就能轻松实现适用于自己设备的电源管理策略。

 

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

 

        3.1  电源通知机制

        电源相关的通知通过消息队列传递给应用程序,通常应用程序新建一个消息队列,并通过RequestPowerNotifications将这个消息队列的句柄传递给电源管理器,同时创建一个线程侦听来自这个队列的消息。电源管理器定义了如下几种通知:

          PBT_RESUME:当系统从休眠状态被唤醒是产生;

          PBT_POWERSTATUSCHANGE:当系统接入或者断开外部电源时产生; 

          PBT_TRANSITION:当电源管理器执行系统电源状态转换时发生; 

          PBT_POWERINFOCHANGE:当电池信息更新时发生。

 

        下面的代码演示了应用程序如何侦听电源管理器发出的通知。

 

        // 1.新建一个消息队列
        ghPowerNotifications= CreateMsgQueue(NULL, &msgOptions);
        if (!ghPowerNotifications) { 
                DWORD dwErr = GetLastError();
                RETAILMSG(1, (TEXT('%s:CreateMessageQueue ERROR:%d\n'), pszFname, dwErr));
                goto _Exit;
        }

 

        // 2.请求得到电源通知
        hNotifications = RequestPowerNotifications(ghPowerNotifications, POWER_NOTIFY_ALL);
        if (!hNotifications) {
                DWORD dwErr = GetLastError();
                RETAILMSG(TRUE, (TEXT('%s:RequestPowerNotifications ERROR:%d\n'), pszFname, dwErr));
                goto _Exit;
        }

 

        // 3.创建侦听线程
        ht = CreateThread(NULL, 0, ProcessPowerNotification, NULL, 0, NULL);

 

        // 4.电源通知消息侦听线程
        void ProcessPowerNotification(HANDLE hMsgQ)
        {
                ……
                if (ReadMsgQueue(hMsgQ, buf, QUEUE_SIZE, (LPDWORD)&dwSize, 0, &dwStatus)) 
                {……} // 5.电源通知消息处理

 

        3.2  电源请求机制

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

 

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

 

        3.3  设置电源状态

        在某些应用的场合下,应用程序可能需要改变系统的电源状态。 应用程序可以通过GetSystemPowerState返回当前系统电源状态的名称,也可以通过SetSystemPowerState改变系统的电源状态。同样的,如果应用程序需要改变某个设备的电源状态,可以通过GetDevicePower返回设备当前的电源状态,也可以通过SetDevicePower改变设备的电源状态。

 

4、电源管理实例——LCD背光控制

 

        上面依次描述了系统电源状态,设备电源状态以及电源管理器的应用程序接口,但对于没有此类经验的用户来讲,可能仍不清楚如何在自己的设备中实现所谓的电源管理,本节将以控制LCD背光为例,介绍实现对背光进行电源管理的方法。

 

        4.1  背光控制方法一

        对背光的电源控制有很多方法,设备电源状态的定义是在驱动程序中实现的,电源管理器通过DeviceIoCtrol与设备驱动程序交互,实现对设备的电源管理,同样,应用程序也可以通过DeviceIoCtrol实现对设备电源直接的控制。

 

        // 1.打开背光句柄
        HANDLE hBLK = CreateFile(L'BKL1:', // name of device
        GENERIC_READ|GENERIC_WRITE, // desired access
        FILE_SHARE_READ|FILE_SHARE_WRITE, // sharing mode
        NULL, // security attributes (ignored)
        OPEN_EXISTING, // creation disposition
        FILE_FLAG_RANDOM_ACCESS, // flags/attributes
        NULL);

 

        // 2.通过DeviceIoCtrol关闭LCD背光
        CEDEVICE_POWER_STATE stPower;
        stPower = D4;
        DWORD dwReturn;
        bResult = DeviceIoControl(hBLK,
        IOCTL_POWER_SET,
        NULL,
        0,
        & stPower,
        sizeof(CEDEVICE_POWER_STATE),
        &dwReturn,
        NULL);

 

        // 3.通过DeviceIoCtrol打开LCD背光
        stPower = D0;
        bResult = DeviceIoControl(hBLK,
        IOCTL_POWER_SET,
        NULL,
        0,
        & stPower,
        sizeof(CEDEVICE_POWER_STATE),
        &dwReturn,
        NULL);

 

        4.2  背光控制方法二

        方法一仅适用于系统没有电源管理器的情况下使用,如果使能了系统的电源管理功能,则应禁止使用上面的方法来控制背光。用户可以将第2节提到的设备电源状态和第1节的系统电源状态做一个映射,当系统在不同的电源状态间切换时,设备电源状态随之改变。系统电源状态与设备电源状态的映射是通过系统注册表来实现的。如用户希望在系统On状态时,背光最亮,系统电源状态切换到UserIdel时,背光变暗一点,系统处于SystemIdel和Suspend时,背光灭,则注册表配置如下:

 

        [HKEY_LOCAL_MACHINE\System\ CurrentControlSet \Power\State\On]
        “Default” =dword:0 ;D0 当系统为On状态时,所有设备默认在D0状态

 

        [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Power\State\UserIdle]
        “Default” =dword:1 ;D1所有设备默认在D1状态,包括LCD背光

 

        [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Power\State\SystemIdle]
        “Default” =dword:0 ;D2所有设备默认在D2状态
        “bkl1:”=dword:4 ;D4 背光处于D4关闭状态

 

        [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Power\State\Suspend]
        “Default” =dword:3 ;D3所有设备默认在D3状态,如果设备不支持D3,自动进入D4状态

 

        上面的第1、2、4条注册表使用的是系统默认值,所以实际上只需要在SystemIdle状态下增加一项就可以了,如图3:

 

 

        这里必需使用小写字母的设备名称,电源管理器才能识别。注册表设置好后,用户再通过控制面板\电源属性设置好系统超时,背光就能随系统电源状态的变化而自动切换了。

 

        如果系统使能了电源管理器,而应用程序又想直接修改背光的电源状态应该怎样做呢?

 

        首先肯定不能使用DeviceIoCtrol的方法,也不推荐使用SetDevicePower函数,而应调用较为温和的DevicePowerNotify。DevicePowerNotify会与系统电源管理器商量改变背光的电源状态,电源管理器最终决定并实施背光状态的改变。

 

        4.3  背光控制方法三

        实际上,在手持设备或便携式等移动设备中,背光功耗在整个设备功耗中占很大一部份,对背光电源的灵活控制十分必要,EM9283的控制面板\显示属性中有专门对背光进行管理的选项,如图4。

 

 

        背景光的高级选项可以分别设置EM9283在电池供电或适配器供电情况下LCD背光的亮度,如图5。EM9283 LCD背光的亮度调节范围是5%~100%。

 

 

        与通过控制面板设置系统超时一样,如果应用程序希望直接设置背光的点亮时间和背光亮度,可以通过修改注册表来实现。与图4,图5对应的注册表设置如下:

 

        [HKEY_CURRENT_USER\ControlPanel\BackLight]
        “UserExt” =dword:1 ;允许在适配器供电情况下对背光进行控制
        “UseBattery:”=dword:1 ;允许在电池供电情况下对背光进行控制
        “ACTimeout”=dword:60 ;适配器供电情况下用户停止操作60秒后关闭背光 
        “BatteryTimout”=dword:15 ;电池供电情况下用户停止操作15秒后关闭背光

 

 

        如果应用程序修改了背光亮度相关的注册表项,需要创建名为BackLightLevelChangeEvent的事件,并置为有信号状态,相应的驱动程序就会自动更新刚刚的设置,使设置生效。

        Handel hBackLightLevelChange=
                CreateEvent(NULL, FALSE, FALSE, L'BackLightLevelChangeEvent' );
                SetEvent(hBackLightLevelChange);

 

        同样,如果应用程序修改了背光点亮时间,需要创建名为BackLightChangeEvent的事件,并置为有信号状态使设置生效。
        Handel hBackLightChange=
                CreateEvent(NULL, FALSE, FALSE, L'BackLightChangeEvent' ) ;
                SetEvent(hBackLightChange);

 

        在控制面板系统电源属性设置和背景光设置中,都讲到当用户停止操作某某长时间后,系统会怎样怎样。Windows CE和桌面Windows系统一样,将没有鼠标和键盘输入定义为用户停止操作,但用户停止操作并不意味着用户已经停止使用设备。比如用户正打开一个串口,查看串口收到的数据,查看过程中用户没有使用键盘,也没有移动鼠标,系统认为用户已经停止操作设备,并在一段时间后关闭了LCD背光,显示这不是用户所希望的。在这种情况下,用户只能周期性的移动鼠标或使用一下键盘来提醒设备,我还在使用你。

 

        如果应用程序能够确定当自己被打开时,用户需要屏幕和背光一直点亮(比如动画播放程序),那么应用程序可以自己来提醒操作系统的电源管理:用户还在使用设备,而不需要用户去频繁的移动鼠标。应用程序可以创建或打开名为PowerManager/ActivityTimer/UserActivity事件,并以合适的时间,周期性的置这个事件有信号来达到提供电源管理器的目的。
        Handle hUserActivity = OpenEvent( EVENT_ALL_ACCESS ,
                FALSE,
                L'PowerManager/ActivityTimer/UserActivity');
                SetEvent( hUserActivity );

 

        背光控制的方法三,对用户来讲是独立于电源管理器的,也就是说无论用户是否通过控制面板使能了系统电源超时切换功能,都可以通过此方法单独控制系统背景光。

 

5、电源管理实例——系统屏保

 

  上述三种方法能实现对系统背光的灵活控制,当系统关闭背光时,EM9283会将背光控制引脚LCD_BKLIGHT置低,控制外部电路关闭背光,但实际上这时候EM9283的显示信号仍在输出,如果EM9283外接的是VGA显示或其它独立背光的显示设备,就会看到显示界面,而我们所期望的是系统空闲一段时间后,LCD屏的背光和显示都关闭,即通常说的屏保。

 

  以VGA显示器为例,当没有检测到输入信号时,显示器会自动关闭背光并进入低功耗模式,此时EM9283控制LCD_BKLIGHT是不行的,而需要将输出的显示信号关闭,我们同样可以利用WinCE的电源管理器来实现屏保功能。比如让系统空闲10分钟后屏保的设置如下:

 

设备系统10分钟后关闭显示器

 

  系统空闲10分钟后会关闭背光和显示输出,控制原理已经在第1节中进行了描述,同时需要注意对系统事件'PowerManager/ReloadActivityTimeouts'进行合适的设置,屏保功能才会正常启用。

 

6、结束语

 

        本文主要从应用的角度出发,结合英创工控主板EM9283,介绍了WinCE系统电源管理的实现过程。从背光的电源管理可以看到,有多种方法对系统设备的电源进行控制管理,但应该注意,尽量避免使用不同的方法同时管理一个设备的电源,以免造成用户甚至应用程序开发者本身的混乱。

 

        本文中涉及到的API函数、事件、注册表等,用户可以参考微软的MSDN,对Windows CE电源管理更全面、更权威的描述,也请参考MSDN。对于文中使用的程序片段,感兴趣的用户可以联系英创索要完整的测试代码。

[lablebox]