Windows CE 作為一個廣泛應用于移動便攜裝置上的作業系統,提供了完善的電源管理的功能。其中,休眠喚醒便是一個重要的功能。那麼,休眠喚醒是什麼原理呢,這首先要從硬體說起。這裡呢,我就拿用自己得最熟練的三星平台的2440 CPU為例來和大家探讨一下。
首先看2440 Datasheet 裡關于休眠部分的描述
SLEEP Mode
The block disconnects the internal power. So, there occurs no power consumption due to CPU and the internal logic except the wake-up logic in this mode. Activating the SLEEP mode requires two independent power sources. One ofthe two power sources supplies the power for the wake-up logic. The other one supplies other internal logics ,including CPU, and should be controlled for power on/off. In the SLEEP mode, the second power supply source forthe CPU and internal logics will be turned off. The wakeup from SLEEP mode can be issued by the EINT[15:0] or by RTC alarm interrupt.
上面這段話大意就是,當CPU進入休眠模式後,整個CPU系統會進入低功耗模式,隻有當外部中斷0-15中任意一個中斷被觸發,或者實時時鐘中斷被觸發時,系統才會被喚醒。我們結合2440 wince5.0 BSP中的相關部分來詳細分析。
(這裡說句題外話,現在市面流傳着2種類型的wince5.0的BSP,一種是基于三星官方釋出的PQOAL結構的,另一種,是從4.2的BSP更新上來的,也就是把原來4.2下的 BSP經過修改,使得能夠在PB5.0下編譯通過。下面我要分析的就是後者,也就是從 wince4.2下更新過來的5.0 BSP.他的結構和4.2下面的基本相同。
按我的觀點,這兩種BSP 無所謂好壞,隻要能實作産品功能的,就是好的BSP.不過從長遠來看,微軟主推的是PQOAL結構的BSP,以後官方釋出的5.0和6.0的BSP,基本都是采用的這種結構。)
使得系統進入休眠的方法有很多,比如在WINCE的桌面上,點左下角的圖示,然後選擇 “挂起”。或者是在應用程式或驅動中調用SetSystemPowerState函數,都可以讓系統進入休眠狀态。實際上,這兩種方法殊途同歸,最終都是要去調一個 OEM層的函數 : OEMPowerOff
.
這個函數的具體實作在
WINCE500"PLATFORM"SMDK2410"KERNEL"HAL"power.c中
,如下
VOID OEMPowerOff(void)
{
volatile IOPreg *s2410IOP = (IOPreg *)IOP_BASE;
volatile INTreg *s2410INT = (INTreg *)INT_BASE;
volatile LCDreg *s2410LCD = (LCDreg *)LCD_BASE;
CPUSaveRegs(CPUBackupRegs);
CPULCDOff();
ConfigStopGPIO();
ConfigMiscReg();
CPUPowerOff();
CPULoadRegs(CPUBackupRegs);
s2410IOP->rGSTATUS2 = s2410IOP->rGSTATUS2;
s2410IOP->rEINTPEND = s2410IOP->rEINTPEND;
s2410LCD->rLCDSRCPND = s2410LCD->rLCDSRCPND;
s2410LCD->rLCDINTPND = s2410LCD->rLCDINTPND;
s2410INT->rSUBSRCPND = s2410INT->rSUBSRCPND;
s2410INT->rSRCPND = s2410INT->rSRCPND;
s2410INT->rINTPND = s2410INT->rINTPND;
OEMInitDebugSerial();
CPUClearCS8900();
RETAILMSG(1,(TEXT("-- Exit OEMPOWER."r"n")));
RETAILMSG(1,(TEXT("s2410INT->rINTMOD = 0x%x "r"n"), s2410INT->rINTMOD));
RETAILMSG(1,(TEXT("s2410INT->rINTMSK = 0x%x "r"n"), s2410INT->rINTMSK));
}
我們可以看到,這裡面依次做了以下工作:
調用 CPULCDOff函數,關閉背光。
調用 ConfigStopGPIO,設定各IO休眠後的狀态
調用 ConfigMiscReg,設定 CPU上的 Misc寄存器。
接下來,調用 CPUPowerOff。。。。。。。
注意看程式裡的注釋:Actual Power-Off Mode Entry
也就是說,在這個函數的内部,才會真正使得CPU進入休眠模式,那麼我們接下來看看這個函數都幹了什麼工作吧。搜尋,怎麼搜不到函數的實作?????
其實,這個函數的具體實作是用彙編語句來寫的,是以在搜尋的時候,檔案的類型得選擇 *.*,而不是 .c,.cpp,*.h等,
我們終于在 WINCE500"PLATFORM"SMDK2410"KERNEL"HAL"ARM"fw.s中找到了這個函數的實作,當然,都是彙編寫得 :(
(未完待續)
Windows CE 休眠喚醒全面解析(基于2440平台)(2)
LEAF_ENTRY CPUPowerOff
; 1. Save register state and return address on the stack.
;
stmdb sp!, {r4-r12}
stmdb sp!, {lr}
; 2. Save MMU & CPU Registers to RAM.
;。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
這裡我就不把代碼貼出來了,大家自己去BSP包裡看。
這寫部分代碼的功能就是儲存目前系統的狀态,把CPU上一些寄存器裡的資料儲存到RAM裡去,然後禁止RAM自重新整理的功能。而且當CPU進入Sleep狀态時,RAM是不會掉電的,這樣RAM裡得資料就不會丢失,當CPU被喚醒後再用RAM裡的資料來恢複系統。
注意這一句
; 6. Set external wake-up interrupts (EINT0-2: power-button and keyboard).
。。。。。。。。。。。。。
。。。。。。。。。。。。
也就是說在這行話下面,你就得加入設定喚醒中斷源的程式了
如果你在這裡成功設定了某個IO 作為中斷功能的話,那麼系統在休眠後就可以通過人為觸發這個中斷來實作喚醒CPU(注意,是喚醒CPU,而不是喚醒Wince 系統).這裡教大家個小竅門,我們完全可以不在這個語句下面來寫彙編語句來實作設定外部喚醒中斷的功能(誰讓咱是彙編菜鳥呢)。而是在之前的 ConfigStopGPIO裡,寫C的程式來完成同樣的功能。當然,你得保證你設定的那個IO的狀态在進入休眠前沒有被改變 :)
接下來,程式走到這裡
。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。。。
ldr r4, =vCLKCON
ldr r5, =0x7fff8 ; Power Off Mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Sometimes it is not working in cache mode. So I modify to jump to ROM area.
ldr r6, =0x92000000 ; make address to 0x9200 0020
add r6, r6, #0x20 ;
mov pc, r6 ; jump to Power off code in ROM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
b SelfRefreshAndPowerOff
ALIGN 32 ; for I-Cache Line(32Byte, 8 Word)
SelfRefreshAndPowerOff ; run with Instruction Cache's code
str r1, [r0] ; Enable SDRAM self-refresh
str r3, [r2] ; MISCCR Setting
str r5, [r4] ; Power Off !!
b .
LTORG
這段代碼的意義,就是把 0x7fff8 這個32位數送到CLKCON寄存器裡,這樣就使得CPU進入了休眠的模式
不過在實際編譯運作過程中,我發現,如果是在4.2的bsp中,這樣的代碼是沒問題的,系統能夠正常進入休眠,但是在更新到5.0後,在進入休眠之前,系統會發生異常錯誤,還沒執行進入休眠的語句,程式就跑飛了。經過一段排查,發現把
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Sometimes it is not working in cache mode. So I modify to jump to ROM area.
ldr r6, =0x92000000 ; make address to 0x9200 0020
add r6, r6, #0x20 ;
mov pc, r6 ; jump to Power off code in ROM
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
這段代碼注釋掉,就解決了上面的問題(具體原理是什麼還在摸索中)
至此,無論是4.2的BSP還是5.0的BSP,都可以進入休眠狀态了。拿儀器測了測,恩,果然這時候系統中消耗的電流大幅度降低。那麼接下來,就是另一個艱巨的任務了:系統喚醒。
正如之前我們提到的,必須在系統進入休眠前,正确設定外部喚醒中斷,才能夠喚醒CPU.一般來說,正确設定喚醒中斷源,有三個要點。
1 把對應的GPIO設定為中斷功能
2 明确外部中斷觸發條件,比如我們把這個喚醒用的中斷源所對應的IO接到一個按鍵上,希望通過按下按鍵來實作喚醒。那麼就得明确,當按下這個按鍵時,IO口上的電平會發生什麼樣的變化。
3 設定EXTINTn寄存器,按照按鍵按下時IO電平的變化條件來設定。比如當按下按鍵時,IO口上的電平會發生從高到低的變化,那麼我們就設定對應的EXTINTn,使得中斷觸發條件為Falling edge trigeerde,即下降沿觸發。
這三點都注意了,那麼你會發現,當系統休眠後,按下這個按鍵,CPU就會被喚醒,消耗電流一下子就變大了。。。但是,這僅僅是喚醒了CPU,還沒有使得WINCE系統恢複起來,那麼要恢複WINCE 系統,要怎麼做呢??
首先,通過看CPU 的Data Sheet
由上圖可以看出,當系統發生由SLEEP到NORMAL的切換時,中間要經過一個 RESET的過程,這個過程稱之為 Power On Reset,在2440 CPU的寄存器中,專門有一個用來判斷發生Reset原因的寄存器GSTATUS2
也就是說,當CPU Reset後,這個寄存器裡的值是會保留的。那麼,就可以通過讀取這個寄存器裡的值,來判斷究竟是什麼原因發生的CPU Reset了。
說了這麼多,無非是為了明确一點,當CPU被外部中斷喚醒時,相當于發生了Power Reset的過程。那麼喚醒CPU,就類似于給CPU做了一個 硬體複位,不過GSTATUS2裡會儲存一個數值,來表明Reset的原因。另外,Power On Reset後,在之前Sleep過程中儲存下來的RAM裡的系統資料是不會丢失的,我們要做的喚醒系統,就是把這些數值恢複到他原來的位址裡去。
(未完待續)
那麼當CPU 喚醒之後,它運作的第一段程式是什麼呢?這點很重要,因為喚醒=Power Reset,那麼Reset CPU之後,運作第一段程式自然就是Bootloader了,那麼我們看看,Bootloader裡是怎麼處理的。
這裡我以常用的三星提供的Nboot為例。Bootloader的入口點是
ENTRY
;1)The code, which converts to Big-endian, should be in little endian code.
;2)The following little endian code will be compiled in Big-Endian mode.
; The code byte order should be changed as the memory bus width.
;3)The pseudo instruction,DCD can't be used here because the linker generates error.
ASSERT :DEF:ENDIAN_CHANGE
b ResetHandler ; 0x00 Reset
b . ; 0x04 Undefined
b . ; 0x08 Supervisor
b . ; 0x0c Prefetch Abort
b . ; 0x10 Data Abort
b . ; 0x14 Reserved
b . ; 0x18 IRQ
b . ; 0x1c FIQ
在這裡,如果是Reset複位,那麼就會跳轉到0位址,也就是ResetHandler 去執行。
ResetHandler
ldr r0, = GPFCON
ldr r1, = 0x55aa
str r1, [r0]
ldr r0,=WTCON ;watch dog disable
ldr r1,=0x0
str r1,[r0]
ldr r0,=INTMSK
ldr r1,=0xffffffff ;all interrupt disable
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7ff ;all sub interrupt disable
str r1,[r0]
ldr r0, = INTMOD
mov r1, #0x0 ; set all interrupt as IRQ (not FIQ)
str r1, [r0]
; configure GPIO pins
bl Port_Init
; CLKDIVN
ldr r0,=CLKDIVN
ldr r1,=0x7 ; 0x0 = 1:1:1 , 0x1 = 1:1:2 , 0x2 = 1:2:2 , 0x3 = 1:2:4, 0x4 = 1:4:4, 0x5 = 1:4:8, 0x6 = 1:3:3, 0x7 = 1:3:6
str r1,[r0]
; delay
mov r0, #DELAY
5 subs r0, r0, #1
bne %B5
; MMU_SetAsyncBusMode FCLK:HCLK= 1:2
ands r1, r1, #0x2
beq %F1
mrc p15,0,r0,c1,c0,0
orr r0,r0,#R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
1
;To reduce PLL lock time, adjust the LOCKTIME register.
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
; delay
mov r0, #DELAY
5 subs r0, r0, #1
bne %B5
;Configure MPLL
ldr r0,=MPLLCON
ldr r1,=((110<<12)+(3<<4)+1) ;Fin=16MHz,Fout=399MHz
; ldr r1,=((0xf6<<12)+(0xd<<4)+0x0) ;Fin=12MHz,Fout=200MHz
str r1,[r0]
; delay
mov r0, #DELAY
5 subs r0, r0, #1
bne %B5
;Configure UPLL
ldr r0, =UPLLCON
ldr r1, =((60<<12)+(4<<4)+2) ;Fin=16MHz, Fout=48MHz
; ldr r1, =((0x48<<12)+(0x3<<4)+0x2) ;Fin=12MHz, Fout=48MHz
str r1, [r0]
; delay
mov r0, #0x200
5 subs r0, r0, #1
bne %B5
以上部分無論是HardReset還是PowerReset都要執行,主要是做的初始化CPU時鐘的工作,接下來
; :::::::::::::::::::::::::::::::::::::::::::::
; BEGIN: Power Management
; - - - - - - - - - - - - - - - - - - - - - - -
ldr r1, =GSTATUS2 ; Determine Booting Mode
ldr r10, [r1]
tst r10, #0x2
beq %F2 ; if not wakeup from PowerOffmode Skip
; MISCCR setting
LED_ON 0xc
str r10, [r1] ; Clear Test
; B .
ldr r1, =MISCCR ; MISCCR's Bit 17, 18, 19 -> 0
ldr r0, [r1] ; I don't know why, Just fallow Sample Code.
bic r0, r0, #(3 << 17) ; SCLK0:0->SCLK, SCLK1:0->SCLK, SCKE:L->H
str r0, [r1]
; Set memory control registers
add r0, pc, #SMRDATA - (. + 8)
ldr r1, =BWSCON ; BWSCON Address
add r2, r0, #52 ; End address of SMRDATA
loop10
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne loop10
mov r1, #256
loop11
subs r1, r1, #1 ; wait until the SelfRefresh is released.
bne loop11
ldr r2, =0x201000 ; offset into the RAM
add r2, r2, #0x30000000 ; add physical base
mov pc, r2 ; & jump to StartUp address
nop
nop
nop
b .
b %F3 ; if wakeup from PowerOff mode
; goto Power-up code.
; Watchdog reset
2
tst r10, #0x4 ; In case of the wake-up from Watchdog reset,
; go to SDRAM start address(0x3000_0000)
b %F4 ; If not wakeup from Watchdog reset,
; beq %F4 ; If not wakeup from Watchdog reset,
; goto Normal Mode.
mov r0, #4
str r0, [r1] ; Clear the GSTATUS2. Because same code is located in memory address.
; Set memory control registers
ldr r0, =SMRDATA
ldr r1, =BWSCON ; BWSCON Address
add r2, r0, #52 ; End address of SMRDATA
loop0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne loop0
mov r1, #256
loop1
subs r1, r1, #1 ; wait until the SelfRefresh is released.
bne loop1
ldr r2, =0x201000 ; offset into the RAM
add r2, r2, #0x30000000 ; add physical base
mov pc, r2 ; & jump to StartUp address
b .
; Case of Power off reset
3
ldr r1, =MISCCR ; MISCCR's Bit 17, 18, 19 -> 0
ldr r0, [r1] ; I don't know why, Just fallow Sample Code.
bic r0, r0, #(3 << 17) ; SCLK0:0->SCLK, SCLK1:0->SCLK, SCKE:L->H
str r0, [r1]
; - - - - - - - - - - - - - - - - - - - - - - -
; END: Power Management
; :::::::::::::::::::::::::::::::::::::::::::::
4
; Configure memory controller
;ldr r0,=SMRDATA
add r0, pc, #SMRDATA - (. + 8)
ldr r1,=BWSCON ;BWSCON Address
add r2, r0, #52 ;End address of SMRDATA
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0
BringUpWinCE
……..
……..
……..
…….
這段代碼,首先通過讀取 GSTATUS2 寄存器裡的數值,來判斷Reset的原因,我們之前提到過,如果這個值是0x2,那麼就是喚醒引起的PowerReset.
ldr r1, =GSTATUS2 ; Determine Booting Mode
ldr r10, [r1]
tst r10, #0x2
beq %F2 ; if not wakeup from PowerOffmode Skip
; MISCCR setting
判斷GSTATUS2 裡的數值是否為0x2,如果是的話,繼續向下執行喚醒的恢複操作,否則就跳轉到标簽為2的程式段去執行。
标簽2的程式中,判斷GSTATUS2裡的數值是否為0x4,如果是的話,說明發生了看門狗reset,那麼要執行看門狗reset的恢複過程。
如果二者都不是的話,那麼就認為是發生了Hard Reset,那麼就按照正常的步驟,去加載Wince系統。
好,我們接着看看,假如GSTATUS2裡的數值為2的話。那麼就不會跳轉到标簽2的程式段中,而是向下執行這段程式。
LED_ON 0xc
str r10, [r1] ; Clear Test
; B .
ldr r1, =MISCCR ; MISCCR's Bit 17, 18, 19 -> 0
ldr r0, [r1] ; I don't know why, Just fallow Sample Code.
bic r0, r0, #(3 << 17) ; SCLK0:0->SCLK, SCLK1:0->SCLK, SCKE:L->H
str r0, [r1]
; Set memory control registers
add r0, pc, #SMRDATA - (. + 8)
ldr r1, =BWSCON ; BWSCON Address
add r2, r0, #52 ; End address of SMRDATA
loop10
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne loop10
mov r1, #256
loop11
subs r1, r1, #1 ; wait until the SelfRefresh is released.
bne loop11
ldr r2, =0x201000 ; offset into the RAM
add r2, r2, #0x30000000 ; add physical base
mov pc, r2 ; & jump to StartUp address
nop
nop
nop
b .
這段程式的意義,就是恢複CPU的時鐘,開啟RAM的自重新整理然後跳轉到 RAM中的一個位址去執行,這個位址是0x32001000。那麼熟悉2440 WINCE啟動的朋友們應該明白了,這個位址就是Bootloader把NandFlash裡的資料裝載完畢後,跳轉執行的位址。那麼在這裡,跳轉到0x30201000這個位址後,WINCE系統就會被裝載了,也就是說WINCE的作業系統被喚醒了。(全文完)
http://www.cnblogs.com/zq2002/archive/2008/07/24/1250624.html