OSAL系統(tǒng)框架專(zhuān)題2010-4-15 2:47:00
9. TI協(xié)議棧所用系統(tǒng)框架探討。 51的系統(tǒng)往往不是太大,但是幾十K的程序,也足以讓一個(gè)初學(xué)者望而卻步。我們首先忽略C語(yǔ)言本身的難度,光是系統(tǒng)框架也讓生手讀起來(lái)很吃力,再加上這種到處是API跟"define"的程序,還沒(méi)有正式學(xué)習(xí)協(xié)議部分就已經(jīng)讓人在叢林中“迷路”了。
在接下來(lái)的一段時(shí)間內(nèi),我會(huì)以TI所用的系統(tǒng)框架為主線進(jìn)行學(xué)習(xí),希望大家共同探討。。。
在層層迷霧中摸索了兩天,終于撥云見(jiàn)日,那個(gè)心情啊,怎一個(gè)“爽”字了得~~~
可是怎么能把這么復(fù)雜的一個(gè)問(wèn)題講得清楚呢?嗯。。。還是先上圖吧
注:為了便于直觀,以下涉及到數(shù)據(jù)地址的地方都是由上而下,地址由高變低
第1節(jié)、各個(gè)任務(wù)是如何被調(diào)用到的? 我們還是先從main()函數(shù)開(kāi)始,看看各個(gè)任務(wù)之間是如何協(xié)調(diào)工作的。 插播一句廣告:在一切都看不清的時(shí)候,忽略次要,看主要因素 -- by outman from Zigbeetech
我們直接進(jìn)入主循環(huán)的核心部分,看一下系統(tǒng)中的幾個(gè)主要的任務(wù)是如何被調(diào)用,并開(kāi)始自己的使命的?
看一段程序的時(shí)候,往往要從它的數(shù)據(jù)結(jié)構(gòu)入手。我們先看一下,主循環(huán)中的兩個(gè)關(guān)鍵數(shù)組,*tasksEvents與*tasksArr,從圖一中我們可以看出來(lái),tasksEvents這個(gè)數(shù)組存放的是從序號(hào)為0到tasksCnt,每個(gè)任務(wù)在本次循環(huán)中是否要被運(yùn)行,需要運(yùn)行的任務(wù)其值非0(用橙色表示),否則為0。而tasksArr數(shù)組則存放了對(duì)應(yīng)每個(gè)任務(wù)的入口地址,只有在tasksEvents中記錄的需要運(yùn)行的任務(wù),在本次循環(huán)中才會(huì)被調(diào)用到。--這節(jié)講完了。。。
又有同學(xué)舉手?什么?還沒(méi)明白?恩。。。好像是不能講這么短的。。。 那好吧,把main函數(shù)貼過(guò)來(lái),我們一點(diǎn)一點(diǎn)看 初始化過(guò)程“先不管”,我們先看主循環(huán)(dead loop)
for(;;) // Forever Loop { uint8 idx = 0; Hal_ProcessPoll(); // 先不管1
do { if (tasksEvents[idx]) // 尋找最高優(yōu)先級(jí)的任務(wù)來(lái)運(yùn)行 { break; } } while (++idx < tasksCnt);
if (idx < tasksCnt) { uint16 events; halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState); events = tasksEvents[idx]; tasksEvents[idx] = 0; // 本任務(wù)運(yùn)行完了,要對(duì)其清空,為后面要運(yùn)行的任務(wù)讓路 HAL_EXIT_CRITICAL_SECTION(intState);
events = (tasksArr[idx])( idx, events ); //最關(guān)鍵的一句話,如圖一中,運(yùn)行對(duì)應(yīng)的任務(wù)
HAL_ENTER_CRITICAL_SECTION(intState); tasksEvents[idx] |= events; // 本任務(wù)可能沒(méi)完全完成,如果是這樣,再次設(shè)置標(biāo)志位,在下一次循環(huán)中繼續(xù)執(zhí)行 HAL_EXIT_CRITICAL_SECTION(intState); } }
第2節(jié)、系統(tǒng)時(shí)間
我們知道,每個(gè)操作系統(tǒng)(雖然我不認(rèn)為OSAL是一個(gè)標(biāo)準(zhǔn)的操作系統(tǒng),但我們先這么叫著吧)都有一個(gè)“節(jié)拍”-tick,就像每一個(gè)“活人”都有心跳一樣。那么OSAL的心跳有多快呢?--1ms。當(dāng)然這個(gè)速度是可以設(shè)置的,在osal_timer_activate函數(shù)中開(kāi)啟了系統(tǒng)節(jié)拍,用TICK_TIME來(lái)定義其速度 #define TICK_TIME 1000 // Timer per tick - in micro-sec 注意:這個(gè)1000是micro-sec(微秒),而不是milli-sec(毫秒)!我剛開(kāi)始的時(shí)候就是誤以為是1000ms而耽誤了不少時(shí)間。 那這個(gè)心臟是怎么跳動(dòng)起來(lái)的呢?
這得從“定時(shí)器”說(shuō)起,由于本文的重點(diǎn)不是講單片機(jī)基礎(chǔ)的,如果對(duì)這個(gè)名字還陌生的同學(xué),那還是回去先看看基礎(chǔ)再來(lái)看這個(gè)吧。2430有4個(gè)定時(shí)/計(jì)數(shù)器,其中timer4用來(lái)做系統(tǒng)計(jì)時(shí)。如果認(rèn)為是timer2的同學(xué)請(qǐng)看一下halTimerRemap這個(gè)函數(shù)。在上述osal_timer_activate函數(shù)中,開(kāi)啟了系統(tǒng)計(jì)時(shí),并將timer4的初始設(shè)為TICK_TIME(1000),這樣timer4就開(kāi)始了從1000開(kāi)始的減計(jì)數(shù),減到0以后呢?寄存器TIMIF會(huì)產(chǎn)生一個(gè)溢出標(biāo)志,那么它會(huì)立即產(chǎn)生中斷并進(jìn)入中斷服務(wù)程序嗎?不會(huì)的。
我們看一下第1節(jié)主函數(shù)里的“Hal_ProcessPoll(); // 先不管1”(不管的東西早晚要管的,只是時(shí)間的問(wèn)題而已) 這個(gè)函數(shù)里調(diào)用了HalTimerTick,這個(gè)函數(shù)就是專(zhuān)門(mén)來(lái)檢查是否有硬件定時(shí)器溢出的,如果有的話會(huì)調(diào)用halTimerSendCallBack這個(gè)函數(shù),對(duì)溢出事件做處理。
回過(guò)頭來(lái)說(shuō)系統(tǒng)節(jié)拍,那timer4在計(jì)數(shù)滿1000(即1ms)后做了些什么事呢,那我們看一下halTimerSendCallBack 這個(gè)函數(shù) void halTimerSendCallBack (uint8 timerId, uint8 channel, uint8 channelMode) { uint8 hwtimerid;
hwtimerid = halTimerRemap (timerId);
if (halTimerRecord[hwtimerid].callBackFunc) (halTimerRecord[hwtimerid].callBackFunc) (timerId, channel, channelMode); } 這里面調(diào)用了“callBackFunc”函數(shù),也就是說(shuō)每個(gè)定時(shí)器溢出后都有一個(gè)callBackFunc函數(shù),它在哪里呢,我們?cè)倏匆幌?span lang="EN-US">HalTimerConfig這個(gè)函數(shù),它可以對(duì)每個(gè)定時(shí)器進(jìn)行定義。那什么時(shí)候定義的呢?--InitBoard,即板子上電初始化的時(shí)候就做了這個(gè)定義的。我說(shuō)什么來(lái)?“先不管”的東西,“后要管”的。。。
我們看到timer4的callBackFunc函數(shù)是Onboard_TimerCallBack,最終指向osalTimerUpdate,這個(gè)函數(shù)厲害了~ 從上面的分析中我們知道它是每1ms被調(diào)用一次的,這樣它就為應(yīng)用程序提供了一個(gè)ms計(jì)時(shí)器,應(yīng)用程序所用的定時(shí)往往以ms為單位足夠了,這樣的話就不用另外再占用硬件計(jì)時(shí)器了,畢竟只有4個(gè)嘛。。。同時(shí)這個(gè)函數(shù)還提供了一個(gè)系統(tǒng)時(shí)鐘-osal_systemClock,看看它能計(jì)時(shí)多久吧,它是“uint32”型的,也就是2^32ms=49.7天,怎么樣?你不會(huì)讓這個(gè)系統(tǒng)時(shí)鐘overflow吧:)
第3節(jié)、系統(tǒng)的消息處理機(jī)制
結(jié)合第1、2節(jié)中的內(nèi)容,讓我們一起進(jìn)入到系統(tǒng)最核心的部分-消息處理中來(lái)吧(這個(gè)句型怎么這么耳熟~~~)
第1節(jié)中我們說(shuō)了,tasksEvents數(shù)組存放了一個(gè)任務(wù)是否該被運(yùn)行的序列,但是這個(gè)序列是如何產(chǎn)生的呢?如果了解了這個(gè)問(wèn)題,那也就知道了OSAL系統(tǒng)的運(yùn)作方式。 再插句廣告:在浩如煙海的程序中搜索最重要的東西,就像大浪淘沙,其實(shí)也是蠻享受的一件事情--by outman from zigbeetech
source insight "ctr+/",整個(gè)項(xiàng)目搜索,我們發(fā)現(xiàn)了一個(gè)“osal_set_event”的函數(shù)是專(zhuān)門(mén)來(lái)設(shè)置tasksEvents的,但是似乎并不能幫到我們。繼續(xù)搜!有兩個(gè)很重要的地方引起了我們的注意:osalTimerUpdate和osal_msg_send這兩個(gè)函數(shù)
osalTimerUpdate,這個(gè)稱(chēng)得上“厲害”的函數(shù)還記得吧?它會(huì)去設(shè)置tasksEvents?那不就是說(shuō),它可以讓任務(wù)在主循環(huán)中被運(yùn)行到?答對(duì)了,這就是它“厲害”的地方。。。那看看它運(yùn)行任務(wù)的條件吧? // When timeout, execute the task if ( srchTimer->timeout == 0 ) { osal_set_event( srchTimer->task_id, srchTimer->event_flag ); ... ... 也就是說(shuō),計(jì)時(shí)器溢出--恩。。。不多說(shuō)了,我們埋個(gè)伏筆,先介紹另一個(gè)朋友-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 )
也就是說(shuō),它會(huì)開(kāi)始一個(gè)timeout_value(ms)的計(jì)時(shí)器,當(dāng)這個(gè)計(jì)時(shí)器溢出時(shí),則會(huì)對(duì)taskID這個(gè)task,設(shè)置一個(gè)event_id,讓這個(gè)任務(wù)在后面的主循環(huán)中運(yùn)行到,但是是怎么實(shí)現(xiàn)的呢?還是要請(qǐng)osalTimerUpdate來(lái)幫忙。。。
那位同學(xué)說(shuō)啥?復(fù)雜了,聽(tīng)不懂? 唉,還是上圖吧
還是先從數(shù)據(jù)結(jié)構(gòu)說(shuō)起吧,不知道啥是“數(shù)據(jù)鏈表”的同學(xué),把譚老師的書(shū)拿過(guò)來(lái)再讀幾遍。。。 這個(gè)表就是osalTimerUpdate函數(shù)的“任務(wù)表”,上面不是說(shuō)過(guò)這個(gè)函數(shù)給應(yīng)用程序提供了“軟計(jì)時(shí)”了嗎?就是體現(xiàn)在這里,osal_start_timerEx通過(guò)osalAddTimer向鏈表里添加了“定時(shí)任務(wù)”,由osalTimerUpdate來(lái)以ms為單位對(duì)這些“軟定時(shí)器”減計(jì)數(shù),溢出時(shí),即調(diào)用osal_set_event,實(shí)現(xiàn)主循環(huán)里對(duì)任務(wù)的調(diào)用。
好了,到此講了上面提到的"set event"函數(shù)中的一個(gè)osal_start_timerEx, 還有一個(gè)更厲害的還在外面呢,osal_msg_send,這就漸入佳境,進(jìn)入最重要的消息處理機(jī)制了。。。
-- by outman 2010-4-14 18:00 下班啦,老婆在家等著回去一起做飯哪~~~晚上見(jiàn)~~~
為了更好地說(shuō)明這個(gè)問(wèn)題,還是拿一個(gè)具體的例子來(lái)講比較直觀。不過(guò)在這個(gè)筆記中,我盡量不涉及具體開(kāi)發(fā)板,而講一些通用的知識(shí),因?yàn)檫@樣會(huì)讓更多的人受益。在TI官方zstack 2006中有4個(gè)例子,其中一個(gè)叫GenericApp最基本的通信的例程,如果沒(méi)有安裝zstack的同學(xué)可以到“本站專(zhuān)用下載貼”中下載。當(dāng)然由于講的是些比較通用的東西,所以手頭有開(kāi)發(fā)板的同學(xué)可以用自己的開(kāi)發(fā)板來(lái)試驗(yàn),效果更好。。。
在這樣的通信例程中,一般會(huì)有一個(gè)按鍵觸發(fā),然后會(huì)和相鄰的模塊進(jìn)行通信,當(dāng)然由于這部分是講OSAL的系統(tǒng)框架的,我們先不涉及通信的內(nèi)容,只是看一下按鍵是如何產(chǎn)生的,及如何調(diào)用相應(yīng)的接口程序。
按OSAL的模塊定義,按鍵可能在哪層來(lái)?硬件服務(wù)相關(guān)的,恩。。。是不是在HAL層呢?到Hal_ProcessEvent看看?有個(gè)HalKeyPoll函數(shù)不是?恩,這就是檢測(cè)按鍵的地方~~不過(guò),我可不是像上面這樣這么容易猜出來(lái)的,這幾句話足足用了我大半個(gè)鐘頭呢。。。過(guò)程我不細(xì)說(shuō)了,有興趣的話我可以再補(bǔ)充一下。
在HalKeyPoll函數(shù)中,無(wú)論按鍵是ADC方式,或者是掃描IO口的方式,最后都會(huì)生成一個(gè)鍵值keys, 然后通過(guò)下面的語(yǔ)句來(lái)調(diào)用按鍵服務(wù)程序 /* Invoke Callback if new keys were depressed */ if (keys && (pHalKeyProcess)) { (pHalKeyProcess) (keys, HAL_KEY_STATE_NORMAL); } 這里調(diào)用的服務(wù)程序,在InitBoard中被初始化為OnBoard_KeyCallback,這個(gè)函數(shù)又通過(guò)OnBoard_SendKeys運(yùn)行下面語(yǔ)句 { // 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是如何向上級(jí)應(yīng)用程序發(fā)送消息的。終于要講消息量的數(shù)據(jù)結(jié)構(gòu)了,好像繞得有點(diǎn)遠(yuǎn)。。。。還是先上圖
在理解了消息量的數(shù)據(jù)鏈表后,再來(lái)理解osal_msg_send里的語(yǔ)句就不難了 OSAL_MSG_ID( msg_ptr ) = destination_task;//設(shè)置消息數(shù)據(jù)對(duì)應(yīng)是屬于哪個(gè)任務(wù)的
// 將要發(fā)送的消息數(shù)據(jù)鏈接到以osal_qHead開(kāi)頭的數(shù)據(jù)鏈表中 osal_msg_enqueue( &osal_qHead, msg_ptr );
// 通知主循環(huán)有任務(wù)等待處理 osal_set_event( destination_task, SYS_EVENT_MSG );
這樣用戶任務(wù)GenericApp_ProcessEvent就收到一個(gè)按鍵的處理任務(wù),并通過(guò)GenericApp_HandleKeys來(lái)執(zhí)行相應(yīng)的操作。
好了,現(xiàn)在應(yīng)該對(duì)OSAL的消息處理機(jī)制有個(gè)了解了吧?我們?cè)賮?lái)復(fù)習(xí)一下這個(gè)按鍵的處理過(guò)程:任務(wù)驅(qū)動(dòng)層Hal_ProcessEvent負(fù)責(zé)對(duì)按鍵進(jìn)行持續(xù)掃描,發(fā)現(xiàn)有按鍵事件后OnBoard_KeyCallback函數(shù)向應(yīng)用層GenericApp_ProcessEvent發(fā)送一個(gè)有按鍵需要處理的消息,最終由GenericApp_HandleKeys來(lái)負(fù)責(zé)執(zhí)行具體的操作。
讓我們?cè)倩氐阶畛醯膯?wèn)題,任務(wù)處理表tasksEvents是怎么被改動(dòng)的呢?初始化程序、其他任務(wù)或者本任務(wù)主要通過(guò)下面幾種方式對(duì)其操作: 1、設(shè)置計(jì)時(shí)器,當(dāng)其溢出時(shí),觸發(fā)事件處理 2、直接通過(guò)任務(wù)間的消息傳遞機(jī)制觸發(fā) 3、...(等我想到了再補(bǔ)充)
-- by outman 2010.4.15 00:18 該睡覺(jué)啦~~~
文章來(lái)自:http://www./bbs/viewthread.php?tid=16&extra=page%3D1
加速度 April 15,2010
說(shuō)明:本文轉(zhuǎn)載自http://blog.21ic.com/user1/6121/archives/2010/68309.html
保持了原文的完整性,本人未作任何內(nèi)容上的修改!
本文對(duì)我給予了很多幫助!感謝作者!
|