2016-03-08 601 views
2

您好,我想知道將ARM Cortex M0 +深度睡眠的正確方法是什麼。特別是我使用CMSIS-RTOS RTX。CMSIS-RTOS Keil RTX - 進入ARM深度睡眠的正確方法

我的IRQ處理方式是ISR只是設置操作系統信號並清除IRQ。例如:

void ISR_A(){ 
    osSignalSet(ID_Task_Handling_IRQ_A, IRQ_A_SIGNAL_CODE); 
    DisableIRQ_A(); 
} 

然後在我的空閒循環

void os_idle_demon(void) { 
... 
timeToSleep = os_suspend(); // get from OS how long I can sleep and also stop OS scheduling 
LPTMR_Init(timeToSleep,...) // set wakeup timer 
POWER_EnterLLS(void)  // enter deep sleep. Set registers and calls WFI instruction 
// after wakup compute actual slpetTime 
os_resume(sleptTime); // enable OS scheduling 
} 

的問題是,我的ISR沒有完全處理IRQ(它只是設置信號OS和一些線程將根據優先級處理,並調度 - 我想保持這種方式)。但是當IRQ進入os_suspend()__wfi()指令之間時,則清除IRQ,但不能調度任務(因爲os_suspend())。當CPU進入WFI時,它會進入休眠狀態,因此處理來自ISR的信號的OS線程將不會執行。但是CPU也沒有被(pad)IRQ喚醒,因爲這已經被處理了。

問題是如何自動做檢查,沒有任務掛起並啓動WFI。

喜歡的東西

if(! OS_Signal_Is_rised) { 
    // only do it atomically because what if IRQ would come here? 
    wfi; 
} 

回答

1

所以我有時間在芯片MKL17Z256VFT4的ARM M0 +上做一些測試。使用CMSIS-RTOS RTX(v 4.75)。

它的工作原理是這樣的:

void os_idle_demon(void) { // task with lowest priority - scheduled by 
    //system when there is no action to do 
    for (;;) { 
    timeToSleep = os_suspend(); // stop OS from switching tasks and get maximum allowed sleep time 
    __disable_irq(); 
    LPTMR_Init(timeToSleep...); // set Low Power sleep timer 
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;//set DeepSleep 
    GPIO(pin=0,val=1); // signalize on GPIO pad that CPU is (almost) in sleep 
    __enable_irq(); 
    __wfi(); // go to DeepSleep 
    GPIO(pin=0,val=0); // signalize on GPIO pad that CPU just wakeup 
    sleptTime = LPTMR_GetCounterValue(); // get sleepTime after wakeup 
    os_resume(sleptTime); // set system to schedule tasks and give os info about sleep time 
    } 

我確實觀察到,當我刺激中斷在代碼執行的不同的地方發生了什麼。我使用NVIC_SetPendingIRQ(PORTCD_IRQn);來實施IRQ。我通過觀察GPIO引腳觀察了邏輯分析儀正在運行哪個任務。

情況1)容易的情況是:在調用ISR之前觸發IRQ,並在ISR系統信令中使用osSignalSet(ID_Thread1, SIGNAL_X)。由於每個線程的優先級都高於os_idle_demon,因此正在等待event = osSignalWait(ANY_SIGNAL, osWaitForever);的線程ID_Thread1被切換到(通過RTOS)並處理信號。之後線程開始再次等待任何信號,並計劃os_idle_demon任務,ARM進入睡眠狀態。

案例2)另一種情況是:IRQ設置在os_suspend()__disable_irq()之間。我發現當在__disable_irq()之前調用IRQ時,ARM處理IRQ的速度不夠快,而實際上__disable_irq()首先被執行。所以IRQ一直等到__enable_irq()被調用。所有這些都涉及另一個案例。

情況3)在__enable_irq()之前設置IRQ。在啓用IRQ之後,在CPU執行DeepsSleep(__wfi();)之前,ISR被執行。信號已設置。但是系統不能切換線程(我們稱之爲os_suspend())。但顯然WFI在某種程度上神奇地(我仍在研究規範爲什麼)沒有執行。深度睡眠不是敵人,代碼繼續到os_resume()。然後OS切換任務和信號被正確處理。

所以,唯一的車的情況是,當你把指令之間的東西:

__enable_irq(); 
// do not put anything here 
__wfi(); 

如果你把有什麼情況,然後將3這樣的反應:ISR已經__enable_irq()後立即執行。 ISR設置操作系統信號但沒有安排信號任務(因爲我們之前稱之爲os_suspend())。然後通過__wfi()進入深度睡眠。系統然後永久休眠或直到LPTMR。但這是錯誤的,因爲有信號應該儘快處理,而不是!

所以結論是,看起來序列中描繪的序列是安全的。只要你沒有在__enable_irq();__wfi();之間放置任何指令。您也不得在中間放置任何指示:os_suspend();__disable_irq();。這至少對於MKL17Z256VFT4有效。不知道其他芯片。但是你可以通過執行該IRQ標記功能測試自己NVIC_SetPendingIRQ()

---編輯---

所以,我的朋友給我看了也01​​其中寫入,即使你關閉中斷CPSID的ARM喚醒功能WFI。因此,也許更安全的序列是

__wfi(); // go to DeepSleep 
// optionally enable peripherals that might been disabled 
__enable_irq(); 

不要忘了最新的你叫os_resume(sleptTime);之前,否則我的芯片上,我得到HardFault打電話__enable_irq();

--- EDIT 2 ---

而且我發現,我們可以使用__WFE();指令,以確保沒有賽車條件。 WFE正在等待事件。它將CPU置於與WFI相同的睡眠模式。但它也會檢查「事件記錄」。該寄存器在每個ISR(最後)被設置。當WFE之前有IRQ時,WFE將不會進入睡眠狀態。您可以通過調用指令__SEV();來選擇設置「事件寄存器」。 SW無法訪問「事件寄存器」。如果你想確保清除它,你可以撥打電話

__SEV(); // set event register if it was not set 
__WFE(); // clear event register and don't goto sleep because we set event register just before 

但請注意,該指令與WFI的行爲略有不同。例如,如果設置了SEVONPEND,WFE也可以在掛起的IRQ上喚醒(請參閱WFE spec)。 (如果中斷的優先級低於Base Priority Mask Register中的配置,則說明中斷處於掛起狀態)另請參閱Entering Sleep Mode。這裏也是非常的nice table about difference WFI vs WFE

1

我推薦以下兩種方法之一。當我在操作系統上下文切換中使用wfi()時,我啓用了SysTick中斷,因此在極少數情況下,中斷在os_suspend()和wfi()之間到達,系統只會在SysTick中斷,然後喚醒以檢查操作系統狀態。這種方法適用於大多數情況。 2)如果您有嚴格的實時要求,可以使用此處記錄的睡眠退出功能:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/BABHHGEB.html。這可能會更復雜的實現,但使用中斷優先級,你可以保證os_suspend()和進入睡眠之間的原子操作。

+0

謝謝你的評論,但在情況下1)我買不起信號要處理很長一段時間。設定很短的時間意味着我不會睡得太久(我的應用程序從電池運行,所以睡眠是至關重要的)。廣告2)我有RTOS。所以我不能在ISR中只運行代碼,在沒有ISR的情況下睡眠。 –