OSAL系統架構專題
9. TI協議棧所用系統架構探討。
51的系統往往不是太大,但是幾十K的程式,也足以讓一個初學者望而卻步。我們首先忽略C語言本身的難度,光是系統架構也讓生手讀起來很吃力,再加上這種到處是API跟"define"的程式,還沒有正式學習協議部分就已經讓人在叢林中“迷路”了。
在接下來的一段時間內,我會以TI所用的系統架構為主線進行學習,希望大家共同探討。。。
在層層迷霧中摸索了兩天,終於撥雲見日,那個心情啊,怎一個“爽”字了得~~~
可是怎麼能把這麼複雜的一個問題講得清楚呢?嗯。。。還是先吧
註:為了便於直觀,以下涉及到資料地址的地方都是由上而下,地址由高變低
第1節、各個任務是如何被調用到的?
我們還是先從main()函數開始,看看各個任務之間是如何協調工作的。
插播一句廣告:在一切都看不清的時候,忽略次要,看主要因素 -- by outman from Zigbeetech
我們直接進入主迴圈的核心部分,看一下系統中的幾個主要的任務是如何被調用,並開始自己的使命的?
看一段程式的時候,往往要從它的資料結構入手。我們先看一下,主迴圈中的兩個關鍵數組,*tasksEvents與*tasksArr,從圖一中我們可以看出來,tasksEvents這個數組存放的是從序號為0到tasksCnt,每個任務在本次迴圈中是否要被運行,需要啟動並執行任務其值非0(用橙色表示),否則為0。而tasksArr數組則存放了對應每個任務的入口地址,只有在tasksEvents中記錄的需要啟動並執行任務,在本次迴圈中才會被調用到。--這節講完了。。。
又有同學舉手?什嗎?還沒明白?恩。。。好像是不能講這麼短的。。。
那好吧,把main函數貼過來,我們一點一點看
初始化過程“先不管”,我們先看主迴圈(dead loop)
for(;;) // Forever Loop
{
uint8 idx = 0;
Hal_ProcessPoll(); // 先不管1
do {
if (tasksEvents[idx]) // 尋找最高優先順序的任務來運行
{
break;
}
} while (++idx < tasksCnt);
if (idx < tasksCnt)
{
uint16 events;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState);
events = tasksEvents[idx];
tasksEvents[idx] = 0; // 本任務運行完了,要對其清空,為後面要啟動並執行任務讓路
HAL_EXIT_CRITICAL_SECTION(intState);
events = (tasksArr[idx])( idx, events ); //最關鍵的一句話,一中,運行對應的任務
HAL_ENTER_CRITICAL_SECTION(intState);
tasksEvents[idx] |= events; // 本任務可能沒完全完成,如果是這樣,再次設定標誌位,在下一次迴圈中繼續執行
HAL_EXIT_CRITICAL_SECTION(intState);
}
}
第2節、系統時間
我們知道,每個作業系統(雖然我不認為OSAL是一個標準的作業系統,但我們先這麼叫著吧)都有一個“節拍”-tick,就像每一個“活人”都有心跳一樣。那麼OSAL的心跳有多快呢?--1ms。當然這個速度是可以設定的,在osal_timer_activate函數中開啟了系統節拍,用TICK_TIME來定義其速度
#define TICK_TIME 1000 // Timer per tick - in micro-sec
注意:這個1000是micro-sec(微秒),而不是milli-sec(毫秒)!我剛開始的時候就是誤以為是1000ms而耽誤了不少時間。
那這個心臟是怎麼跳動起來的呢?
這得從“定時器”說起,由於本文的重點不是講單片機基礎的,如果對這個名字還陌生的同學,那還是回去先看看基礎再來看這個吧。2430有4個定時/計數器,其中timer4用來做系統計時。如果認為是timer2的同學請看一下halTimerRemap這個函數。在上述osal_timer_activate函數中,開啟了系統計時,並將timer4的初始設為TICK_TIME(1000),這樣timer4就開始了從1000開始的減計數,減到0以後呢?寄存器TIMIF會產生一個溢出標誌,那麼它會立即產生中斷並進入中斷服務程式嗎?不會的。
我們看一下第1節主函數裡的“Hal_ProcessPoll(); //
先不管1”(不管的東西早晚要管的,只是時間的問題而已)
這個函數裡調用了HalTimerTick,這個函數就是專門來檢查是否有硬體定時器溢出的,如果有的話會調用halTimerSendCallBack這個函數,對溢出事件做處理。
回過頭來說系統節拍,那timer4在計數滿1000(即1ms)後做了些什麼事呢,那我們看一下halTimerSendCallBack
這個函數
void halTimerSendCallBack (uint8 timerId, uint8 channel, uint8 channelMode)
{
uint8 hwtimerid;
hwtimerid = halTimerRemap (timerId);
if (halTimerRecord[hwtimerid].callBackFunc)
(halTimerRecord[hwtimerid].callBackFunc) (timerId, channel, channelMode);
}
這裡面調用了“callBackFunc”函數,也就是說每個定時器溢出後都有一個callBackFunc函數,它在哪裡呢,我們再看一下HalTimerConfig這個函數,它可以對每個定時器進行定義。那什麼時候定義的呢?--InitBoard,即板子上電初始化的時候就做了這個定義的。我說什麼來?“先不管”的東西,“後要管”的。。。
我們看到timer4的callBackFunc函數是Onboard_TimerCallBack,最終指向osalTimerUpdate,這個函數厲害了~
從上面的分析中我們知道它是每1ms被調用一次的,這樣它就為應用程式提供了一個ms計時器,應用程式所用的定時往往以ms為單位足夠了,這樣的話就不用另外再佔用硬體計時器了,畢竟只有4個嘛。。。同時這個函數還提供了一個系統時鐘-osal_systemClock,看看它能計時多久吧,它是“uint32”型的,也就是2^32ms=49.7天,怎麼樣?你不會讓這個系統時鐘overflow吧:)
第3節、系統的訊息處理機制
結合第1、2節中的內容,讓我們一起進入到系統最核心的部分-訊息處理中來吧(這個句型怎麼這麼耳熟~~~)
第1節中我們說了,tasksEvents數組存放了一個任務是否該被啟動並執行序列,但是這個序列是如何產生的呢?如果瞭解了這個問題,那也就知道了OSAL系統的運作方式。
再插句廣告:在浩如煙海的程式中搜尋最重要的東西,就像大浪淘沙,其實也是蠻享受的一件事情--by outman from zigbeetech
source insight "ctr+/",整個項目搜尋,我們發現了一個“osal_set_event”的函數是專門來設定tasksEvents的,但是似乎並不能幫到我們。繼續搜!有兩個很重要的地方引起了我們的注意:osalTimerUpdate和osal_msg_send這兩個函數
osalTimerUpdate,這個稱得上“厲害”的函數還記得吧?它會去設定tasksEvents?那不就是說,它可以讓任務在主迴圈中被運行到?答對了,這就是它“厲害”的地方。。。那看看它運行任務的條件吧?
// When timeout, execute the task
if ( srchTimer->timeout == 0 )
{
osal_set_event( srchTimer->task_id, srchTimer->event_flag );
... ...
也就是說,計時器溢出--恩。。。不多說了,我們埋個伏筆,先介紹另一個朋友-osal_start_timerEx,先看下它的自我介紹
/*********************************************************************
* @fn osal_start_timerEx
*
* @brief
*
* This is called to start a timer to expire in n mSecs.
* When the timer expires, the calling task will get the specified event.
*
* @param byte taskID - task id to set timer for
* @param UINT16 event_id - event to be notified with
* @param UNINT16 timeout_value - in milliseconds.
*
* @return ZSUCCESS, or NO_TIMER_AVAIL.
*/
byte osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value )
也就是說,它會開始一個timeout_value(ms)的計時器,當這個計時器溢出時,則會對taskID這個task,設定一個event_id,讓這個任務在後面的主迴圈中運行到,但是是怎麼實現的呢?還是要請osalTimerUpdate來幫忙。。。
那位同學說啥?複雜了,聽不懂?
唉,還是吧
還是先從資料結構說起吧,不知道啥是“資料鏈表”的同學,把譚老師的書拿過來再讀幾遍。。。
這個表就是osalTimerUpdate函數的“任務表”,上面不是說過這個函數給應用程式提供了“軟計時”了嗎?就是體現在這裡,osal_start_timerEx通過osalAddTimer向鏈表裡添加了“定時任務”,由osalTimerUpdate來以ms為單位對這些“軟定時器”減計數,溢出時,即調用osal_set_event,實現主迴圈裡對任務的調用。
好了,到此講了上面提到的"set event"函數中的一個osal_start_timerEx, 還有一個更厲害的還在外面呢,osal_msg_send,這就漸入佳境,進入最重要的訊息處理機制了。。。
-- by outman 2010-4-14 18:00 下班啦,老婆在家等著回去一起做飯哪~~~晚上見~~~
為了更好地說明這個問題,還是拿一個具體的例子來講比較直觀。不過在這個筆記中,我盡量不涉及具體開發板,而講一些通用的知識,因為這樣會讓更多的人受益。在TI官方zstack 2006中有4個例子,其中一個叫GenericApp最基本的通訊的常式,如果沒有安裝zstack的同學可以到“本站專用下載貼”中下載。當然由於講的是些比較通用的東西,所以手頭有開發板的同學可以用自己的開發板來實驗,效果更好。。。
在這樣的通訊常式中,一般會有一個按鍵觸發,然後會和相鄰的模組進行通訊,當然由於這部分是講OSAL的系統架構的,我們先不涉及通訊的內容,只是看一下按鍵是如何產生的,及如何調用相應的介面程式。
按OSAL的模組定義,按鍵可能在哪層來?硬體服務相關的,恩。。。是不是在HAL層呢?到Hal_ProcessEvent看看?有個HalKeyPoll函數不是?恩,這就是檢測按鍵的地方~~不過,我可不是像上面這樣這麼容易猜出來的,這幾句話足足用了我大半個鐘頭呢。。。過程我不細說了,有興趣的話我可以再補充一下。
在HalKeyPoll函數中,無論按鍵是ADC方式,或者是掃描IO口的方式,最後都會產生一個索引值keys,
然後通過下面的語句來調用按鍵服務程式
/* Invoke Callback if new keys were depressed */
if (keys && (pHalKeyProcess))
{
(pHalKeyProcess) (keys, HAL_KEY_STATE_NORMAL);
}
這裡調用的服務程式,在InitBoard中被初始化為OnBoard_KeyCallback,這個函數又通過OnBoard_SendKeys運行下面語句
{
// Send the address to the task
msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );
if ( msgPtr )
{
msgPtr->hdr.event = KEY_CHANGE;
msgPtr->state = state;
msgPtr->keys = keys;
osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
}
return ( ZSuccess );
}
下面我們就看下osal_msg_send是如何向上級應用程式發送訊息的。終於要講訊息量的資料結構了,好像繞得有點遠。。。。還是先
在理解了訊息量的資料鏈表後,再來理解osal_msg_send裡的語句就不難了
OSAL_MSG_ID( msg_ptr ) = destination_task;//設定訊息資料對應是屬於哪個任務的
// 將要發送的訊息資料連結到以osal_qHead開頭的資料鏈表中
osal_msg_enqueue( &osal_qHead, msg_ptr );
// 通知主迴圈有任務等待處理
osal_set_event( destination_task, SYS_EVENT_MSG );
這樣使用者任務GenericApp_ProcessEvent就收到一個按鍵的處理任務,並通過GenericApp_HandleKeys來執行相應的操作。
好了,現在應該對OSAL的訊息處理機制有個瞭解了吧?我們再來複習一下這個按鍵的處理過程:任務驅動層Hal_ProcessEvent負責對按鍵進行持續掃描,發現有按鍵事件後OnBoard_KeyCallback函數嚮應用層GenericApp_ProcessEvent發送一個有按鍵需要處理的訊息,最終由GenericApp_HandleKeys來負責執行具體的操作。
讓我們再回到最初的問題,任務處理表tasksEvents是怎麼被改動的呢?初始化程式、其他任務或者本任務主要通過下面幾種方式對其操作:
1、設定計時器,當其溢出時,觸發事件處理
2、直接通過任務間的訊息傳遞機制觸發
3、...(等我想到了再補充)
-- by outman 2010.4.15 00:18 該睡覺啦~~~
文章來自:http://www.zigbeetech.com/bbs/viewthread.php?tid=16&extra=page%3D1
加速度 April 15,2010