使用Nucleus SE实时操作系统
使用Nucleus SE實時操作系統
Using the Nucleus SE real-time operating system
到目前為止,在本系列文章中,我們詳細介紹了Nucleus SE提供的所有設施?,F在是時候看看如何在一個真正的嵌入式軟件應用程序中使用它。
什么是核SE?
我們知道Nucleus SE是一個實時內核,但是了解它如何與應用程序的其余部分相適應是很重要的。“適應”正是它所做的,因為與桌面操作系統(如Windows)不同,您并不真正在Nucleus SE上運行應用程序;內核只是運行在嵌入式設備上的應用程序軟件的一部分。這種情況在實時操作系統中非常常見。
在最高層次上,傳統的嵌入式應用程序是一些代碼,當CPU復位時運行。這將初始化硬件和軟件環境,然后調用main()函數,這是應用程序代碼的開始。不同的是,在使用Nucleus SE(或許多其他類似的內核)時,main()函數是作為內核代碼的一部分提供的。此函數只需初始化所有內核數據結構,然后調用調度程序,從而運行應用程序代碼(任務)。用戶可能希望向main()函數添加任何特定于應用程序的初始化。
Nucleus SE還包括一系列函數——應用程序接口(API)——它們提供了一系列功能,如任務間通信和同步、定時、內存分配等。本系列前面已經介紹了所有API函數。
所有Nucleus SE軟件都是作為(主要是C語言)源代碼提供的。條件編譯用于根據特定應用程序的要求配置代碼。這將在本文后面的配置中詳細描述。
當代碼被編譯后,生成的Nucleus SE對象模塊與應用程序代碼的對象模塊相鏈接,以生成一個二進制圖像,該圖像通常被放置在嵌入式設備的閃存中。這樣一個靜態鏈接的結果是,所有的符號信息都可能保持可用——無論是來自應用程序代碼還是來自內核。這對調試是一個有用的幫助,但是需要注意避免誤用Nucleus SE數據。
CPU和工具支持
由于Nucleus SE是在源代碼中提供的,其目的是使其具有可移植性。然而,在如此低的級別上工作的代碼(即,除了運行到完成之外,需要上下文切換的調度程序)不可能完全脫離匯編語言。但我已經將這一點降到了最低限度,移植到一個新的CPU上只需要很少的低級代碼。使用新的開發工具包(編譯器、匯編程序、鏈接器等)也可能引起可移植性問題。
配置Nucleus SE應用程序
有效使用Nucleus SE的關鍵是正確配置。這可能看起來很復雜,但實際上相當合乎邏輯,只是需要系統地處理。幾乎所有的配置都是通過編輯兩個文件來完成的:nuse_config.h和nuse_config.c。
設置nuse_config.h
這個文件只是一個#define符號的列表,這些符號被設置為適當的值來指定所需的內核配置。在默認的nuse_config.h文件中,所有符號都存在,但設置為最小配置。
對象計數
每個內核對象類型的數量是通過為NUSE_SEMAPHORE_number形式的符號賦值來設置的。對于大多數對象,該值可以是0到15。任務是例外,因為必須至少有一個任務。信號本身并不是真正的對象,因為它們與任務相關,并通過將NUSE_SIGNAL_SUPPORT設置為TRUE來啟用。
API函數啟用
每個Nucleus SE API函數可以通過將與函數同名的符號(例如NUSE_PIPE_JAM)設置為TRUE來單獨啟用。這將導致函數的代碼包含在應用程序中。
調度程序選擇和設置
Nucleus SE支持四種類型的調度程序,如前一篇文章中詳細描述的那樣。要用于應用程序的調度程序類型可以通過將NUSE_scheduler_type設置為以下值之一來指定:NUSE_RUN_to_COMPLETION_scheduler、NUSE_TIME_SLICE_scheduler、NUSE_ROUND__scheduler或NUSE_PRIORITY_scheduler。
還可以設置調度器的其他方面:
NUSE_TIME_SLICE_TICKS指定時間片調度程序每個插槽的計時點數。如果正在使用另一個計劃程序,則必須將其設置為0。
可將“任務調度程序”設置為“啟用U計數”或“禁用U計數”機制。
使用SUSPEND_ENABLE可以方便地支持任務掛起。如果設置為FALSE,則任務永遠不會掛起,并且始終準備好運行。如果使用優先級計劃程序,則必須將此選項設置為TRUE。
NUSE_BLOCKING_ENABLE在許多API函數上啟用任務阻塞(掛起)。這意味著對這樣一個函數的調用可能會導致調用任務暫停,等待資源的可用性。選擇此選項要求NUSE_SUSPEND_ENABLE也設置為TRUE。
其他選項
可以將其他一些選項設置為TRUE或FALSE以啟用/禁用其他內核功能:
NUSE_API_PARAMETER_CHECKING允許包含代碼來驗證API函數調用參數,通常在調試期間設置。
NUSE_INITIAL_TASK_STATE_SUPPORT可將所有任務的初始狀態指定為NUSE_READY或NUSE_PURE_SUSPEND。如果此功能被禁用,則所有任務都將以“使用就緒”狀態啟動。
NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT支持在應用程序計時器過期時調用函數。如果禁用,則在計時器過期時不執行任何操作。
支持使用系統時鐘。
盡可能多地將Nucleus配置包括在u中。它會激活已配置對象的所有可選功能和每個API函數。它被用作創建Nucleus SE配置的速記,以執行內核代碼的新端口。
設置nuse_config.c
在nuse_config.h中指定內核配置后,需要初始化各種基于ROM的數據結構。這是在nuse_config.c中完成的。數據結構的定義由條件編譯控制,因此它們都存在于nuse_config.c的默認副本中。
任務數據
數組NUSE_Task_Start_Address[]應該用每個任務的開始地址初始化–這通常只是一個函數名的列表(沒有括號)。任務輸入函數的原型也必須是可見的。在默認文件中,一個任務被配置為NUSE_Idle_task()——這可能會被應用程序任務替換。
除非正在使用“運行到完成”計劃程序,否則每個任務都需要自己的堆棧。RAM中的數組通常是為每個任務的堆棧創建的。數組的類型應該是ADDR,每個數組的地址都應該放在NUSE_Task_Stack_Base[]中。估計數組的大小很困難,最好通過測量來完成——請參閱本文后面的調試。每個數組的大小(即用文字表示的堆棧大小)應放在NUSE_Task_stack_size[]中。
如果指定初始任務狀態的功能已啟用(使用NUSE_initial_task_STATE_支持),則數組NUSE_task_initial_STATE[]應初始化為NUSE_READY或NUSE_PURE_SUSPEND。
分區池數據
如果配置了任何分區池,則需要在RAM中為每個分區池定義一個數組(U8類型)。這些數組的大小是這樣計算的:(分區數*(分區大小+1))。這些數組的地址(即它們的名稱)應該分配給NUSE_Partition_Pool_Data_Address[]的適當元素。對于每個池,分區數和分區大小應分別分配給NUSE_Partition_pool_Partition_number[]和NUSE_Partition_pool_Partition_size[]。
隊列數據
如果配置了任何隊列,則需要在RAM中為每個隊列定義一個數組(ADDR類型)。這些數組的大小只是每個隊列中所需的元素數。這些數組的地址(即它們的名稱)應該分配給NUSE_Queue_Data[]的適當元素。對于每個隊列,其大小應分配到NUSE_queue_size[]的適當元素中。
管道數據
如果配置了任何管道,則需要在RAM中為每個管道定義一個數組(U8類型)。這些數組的大小是這樣計算的:(pipe size*pipe message size)。這些數組的地址(即它們的名稱)應該分配給NUSE_Pipe_Data[]的適當元素。對于每個管道,其大小和消息大小應分別分配到NUSE_pipe_size[]和NUSE_pipe_message_size[]的適當元素中。
信號量數據
如果配置了任何信號量,則需要將數組NUSE_simaphore_Initial_Value[]初始化為下計數器開始值。
應用程序計時器數據
如果配置了任何計時器,則需要將數組NUSE_Timer_Initial_Time[]初始化為計數器的起始值。另外,需要將NUSE_Timer_Reschedule_Time[]設置為重新啟動值。這些是初始序列過期后要使用的計數器值。如果重新啟動值設置為0,計數器在一個循環后停止。
如果配置了對計時器過期例程的支持(通過將NUSE_timer_expiration_ROUTINE_support設置為TRUE),則需要再初始化兩個數組。過期例程的地址(只是函數名的列表(不帶括號)應分配給NUSE_Timer_expiration_Routine_Address[]。數組NUSE_Timer_Expiration_Routine_參數[]應初始化為到期例程參數值。
哪個API?
所有操作系統都有某種應用程序接口(API)。Nucleus SE也不例外,組成其API的函數調用在本系列中已經有了廣泛的介紹。
因此,當編寫一個包含Nucleus SE的應用程序時,很明顯您將使用它所描述的API。情況未必總是如此。
對于許多用戶來說,Nucleus SE API將是新的,可能是他們使用操作系統API的第一次體驗,而且由于它相當簡單和直接,因此它可能會很好地介紹這個主題。在這種情況下,前進的道路是明確的。
對于其他一些用戶,另一種API可能很有吸引力。在這種情況下,有三種明顯的情況:
Nucleus SE應用程序只是系統的一部分,其他操作系統正在/正在用于其他組件。具有代碼的可移植性,更重要的是,在所使用的操作系統之間具有專業知識是非常有吸引力的。
用戶對另一個操作系統的API有豐富的經驗。重用這些專業知識是非??扇〉?。
用戶希望重用為其他操作系統的API編寫的代碼。重新編碼以更改API調用是可能的,但很耗時。
由于提供了Nucleus SE的完整源代碼,因此完全可以編輯每個API函數,使其看起來與另一個操作系統中的等效函數相同。然而,這將是一個耗時的過程,最終是徒勞的。更好的方法是編寫一個“包裝器”。這可以通過多種方式實現,但最簡單的是一個頭文件(#include),其中包含一系列從“外來”API映射到Nucleus SE API的“定義宏”。
發行版中包含了一個將Nucleus RTOS API映射到Nucleus SE的包裝器。有Nucleus RTOS經驗的開發人員可以使用它,或者將來有可能遷移到這個RTOS。這個包裝器也可以作為一個例子來幫助另一個包裝器的開發。
調試Nucleus SE應用程序
使用多任務內核編寫嵌入式應用程序是一個挑戰。驗證這段代碼是否有效,以及錯誤的識別可能會非常麻煩。盡管它只是在處理器上執行代碼,但多個任務的明顯并發執行意味著專注于特定的執行線程并不容易。當代碼在任務之間共享時,這種情況會更加復雜;最糟糕的情況是兩個任務使用完全相同的代碼(但顯然操作不同的數據)。另一個問題是用于實現內核對象的數據結構的解構,以便以有意義的方式查看信息。
調試使用Nucleus SE構建的應用程序不需要任何特殊的庫或其他工具。所有的內核源代碼都在那里,并且可能對調試器“可見”。因此,所有的符號信息都可供使用和詢問。任何現代調試工具都可以用于基于Nucleus SE的應用程序。
使用調試器
專門為嵌入式應用程序設計的調試工具已經與我們一起使用了30多年,因此變得非常復雜。與桌面程序相比,嵌入式應用程序的主要特點是每個嵌入式系統都不同(但一臺PC看起來非常相似)。一個好的嵌入式調試器的訣竅在于它要足夠靈活和可定制,以適應從一個用戶到另一個用戶的需求變化。調試器的可定制性以各種形式表現出來,但通常有一些腳本功能。尤其是這個工具,可以利用它使調試器在基于內核的應用程序中運行良好。我將在這里回顧一些可能性。
需要注意的是,調試器通常是一系列工具,而不僅僅是一個程序。調試器可能有不同的操作模式,因此它可以幫助在模擬目標或真實目標硬件上開發代碼。
任務感知斷點
如果一個應用程序中的多個任務共享代碼,那么使用斷點的常規調試會非常混亂。很可能您只希望代碼在您嘗試調試的特定任務上下文中達到斷點時停止。您需要的是一個任務感知斷點。
幸運的是,現代調試器的腳本工具和Nucleus SE符號的可見性使得實現任務感知斷點非常簡單。所需要的只是一個簡單的腳本,它附加到一個斷點上,您希望使它成為“任務感知”的。此腳本將接受一個參數,即您感興趣的任務的索引(ID)。腳本只需將該值與當前正在運行的任務(在NUSE_task_Active中)的索引進行比較。如果值匹配,則停止執行;如果它們不同,則允許繼續執行。應該指出的是,此腳本的執行將對應用程序的實時配置文件產生一些影響,但是,除非它處于腳本可能非常頻繁地執行的循環中,否則這種影響將是最小的。
核心對象信息
在調試基于Nucleus SE的應用程序時,一個明顯的需求是能夠了解內核對象的特性和當前狀態。這需要回答這樣的問題:“隊列有多大,其中有多少消息?”
一種促進這一點的方法是向應用程序添加一些額外的調試代碼,這可以使用“information”API調用,比如NUSE_Queue_information()。當然,這意味著您的應用程序包含額外的代碼,部署后不需要這些代碼。使用一個#define符號,使用條件編譯來切換此代碼,將是一個明智的解決方案。
有些調試器可以執行目標函數調用,即直接調用informationapi函數。這就避免了添加額外代碼的需要,除了必須為調試器配置API函數才能使用它。
另一種方法是直接訪問內核對象的數據結構,這種方法更靈活,但不太“經得起未來考驗”。最好使用調試器的腳本功能來完成。在我們的例子中,隊列的大小可以從NUSE_queue_size[]獲得,而它們的當前使用情況可以從NUSE_queue_Items[]中獲得。此外,使用隊列數據區域的地址(來自NUSE_queue_data[])和頭/尾指針(NUSE_queue_head[]和NUSE_queue_tail[]),可以顯示排隊的消息。
API調用返回值
許多API函數返回一個狀態值,該值指示調用是否成功。監視這些值并在它們不是NUSE_SUCCESS(值為零)的情況下標記這些值是很有用的。由于此監視僅用于調試目的,所以有條件編譯是正確的。全局RAM變量的定義(例如NUSE_API_Call_Status)可以有條件地編譯(在#define符號的控制下)。然后API調用的賦值部分(即NUSE_API_Call_Status=)可以類似地有條件地編譯。例如,出于調試目的,調用通常如下所示:
使用郵箱發送(mbox、msg、NUSE_SUSPEND);
變成
NUSE_API_Call_Status=NUSE_郵箱發送(mbox、msg NUSE_SUSPEND);
如果啟用任務阻塞,許多API函數調用只能返回成功或對象已重置的指示。但是,如果啟用了API參數檢查,則可能會有其他各種返回值。
任務堆棧大小和溢出
在前面的文章中討論了堆棧溢出保護的主題。在調試過程中,還有其他幾種可能性:
堆棧內存區域可以用一個特征值填充,而不是全1或全零。然后可以使用調試器來監視內存位置,值的更改程度指示堆棧使用的程度。如果所有內存位置都已更改,則不一定意味著堆棧已溢出,但可能意味著堆棧僅足夠大,這是脆弱的。它應該擴大,并接受進一步的測試。
如前一篇文章中所討論的,在實現診斷時,添加位置-“保護字”可能位于堆棧內存區域的任一端。調試器可用于監視對這些字的訪問,因為任何寫入操作都會指示堆棧下溢或溢出。
Nucleus SE配置清單
因為Nucleus SE的設計非常靈活,并且可以定制以滿足應用程序的精確需求,因此需要大量的配置。這就是為什么整篇文章基本上都致力于這個主題。為了便于確保涵蓋所有內容,以下是構建基于Nucleus SE的嵌入式應用程序所涉及的所有關鍵步驟的清單:
獲取Nucleus SE–盡管Nucleus SE的幾乎所有代碼都已在本系列中發布,但下一篇文章將告訴您如何以更直接的可用形式獲取它。
考慮CPU/工具支持——可能需要重寫匯編語言部分并重新起草構建腳本。
構建一個簡單的演示-這將驗證您是否擁有所有組件和工具是否兼容。
計劃你的任務結構-有多少任務,他們做什么。設置它們的起始地址和堆棧大小。當然,你可以稍后再調整。一個應用程序中最多可以有16個任務。
應用程序初始化–是否需要向main()添加任何代碼?
選擇調度程序–您有四個可供選擇,這可能會在以后更改。
驗證定時器中斷向量-如果你有一個定時器。
驗證上下文開關陷阱向量
Signals–如果您要使用信號,請啟用對它們的支持。
系統時鐘–如果需要,啟用時鐘。
內核對象計數-確定您需要的每種對象的數量。你可以稍后更改。每種類型最多16個對象。
內核對象ROM–為您正在使用的每種類型的對象初始化ROM數據。
ramdata–為需要它的對象(隊列、管道和分區池)設置RAM數據空間。
API enables–啟用所需的所有API調用。
總結
以上是生活随笔為你收集整理的使用Nucleus SE实时操作系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nucleus-SE迁移:未实现的设施和
- 下一篇: 新的微芯片MCU增加了来自外部闪存的安全