ERIKA是一款開源的、遵循OSEK/VDX標准的實時操作系統。
一、周期任務的實現(方式一:使用普通COUNTER,Alarm通過SetRelAlarm()函數手動啟動和設置)
(1)code.c
/* ERIKA Enterprise. */ #include "ee.h" /* TASKs */ DeclareTask( Task1 ); extern void clock_handler( void ); ISR( stm0_handler ) { osEE_tc_stm_set_sr0_next_match( 1000U ); IncrementCounter( myCounter ); //(1)在stm中斷中為counter計數。counter在OIL文件的定義(COUNTER myCounter),用於喚醒Task1的Alarm會與myCounter綁定, } //並且也會和Task1綁定,並通過函數SetRelAlarm() 來設置Task1的喚醒周期T,一旦myCounter計數達到T,就喚醒Task1. TASK(Task1) { toggle_led( LED_1 ); TerminateTask(); //每個任務的最后必須添加這一函數用於結束任務 }int main(void) { osEE_tc_stm_set_clockpersec(); osEE_tc_stm_set_sr0( 1000U, 1U ); //設置stm leds_init(); SetRelAlarm( AlarmTask1, 2000U, 500U ); //(2)設置用於激活Task1的Alarm的開始時間和周期 StartOS( OSDEFAULTAPPMODE ); //啟動erika os。在本條語句之前,不能使用其他erika os的原語,除非在啟用startupHook並在其中可以調用部分原語 return 0; }
(2)OIL
CPU mySystem { OS myOs { EE_OPT = "OSEE_DEBUG"; //OS的一些可選項 EE_OPT = "OS_EE_APPL_BUILD_DEBUG"; EE_OPT = "OS_EE_BUILD_DEBUG"; CPU_DATA = TRICORE { //CPU類型必須選擇TRICORE CPU_CLOCK = 300.0; MULTI_STACK = TRUE; COMPILER = GCC; IDLEHOOK = FALSE; }; MCU_DATA = TC29X { //MCU選擇相應的芯片型號,這里選擇TC29x后再編譯,ERIKA就會引入TC29x對應的寄存器和外設等相應的定義(默認是TC27x的)
DERIVATIVE = "tc297tf"; //下面這兩個參數影響不大 REVISION = "B"; }; KERNEL_TYPE = OSEK { //內核類型。除了選擇正常的OSEK,還可以選擇FP/EDF/FRSK/HR,這些類型是ERIKA在OSEK標准之外自己加入的一些一致性類 CLASS = BCC1; //一致性類 BCC1和BCC2只支持基礎任務,不能使用waitevent等阻塞相關的功能,可以共享堆棧;而ECC1和ECC2支持擴展任務,可以使用阻塞相關 }; //功能,但因為阻塞,必須使用私有堆棧;BCC1和ECC1不支持pending activation,即連續激活一個任務時,能否掛起激活次數;BCC2和ECC2支持。 }; APPDATA periodic_task { APP_SRC = "code.c"; }; TASK Task1 { //設置Task1的屬性 PRIORITY = 1; // 設置優先級 STACK = PRIVATE { //使用私有棧 SIZE = 1024; }; SCHEDULE = FULL; //參與基於優先級的搶占 }; COUNTER myCounter; //定義counter ALARM AlarmTask1 { //將Alarm與counter及task進行綁定 COUNTER = myCounter; ACTION = ACTIVATETASK { TASK = Task1; }; }; ISR TimerISR { // 設置STM的相關屬性 CATEGORY = 2; //ISR的類型設置為type2,即由Erika進行管理。ISR Type1和ISR Type2的區別是:ISR1是一般的中斷,與OS無關,handler中只能執行少數OS API; SOURCE = "STM0SR0"; //ISR2由OS進行管理,該類型的中斷類似於任務。Handler中可以執行OS API,該類型的中斷需要在OIL文件中聲明;ISR1的優先級必須高於ISR2。 HANDLER = "stm0_handler"; //使用的STM資源和中斷處理函數名 PRIORITY = 1; }; };
由此可以看出.c文件用於編寫任務實體,而OIL文件中定義任務屬性,如下圖所示。
二、周期任務的實現(方式二:使用Hardware COUNTER——System Timer,且Alarm自動啟動和設置)
(1)code.c
/* ERIKA Enterprise. */ #include "ee.h"
/* TASKs */ DeclareTask( Task1 ); TASK(Task1) { toggle_led( LED_1 ); TerminateTask(); }
// 不用初始化STM,也不用在STM中斷里為COUNTER計數,Alarm設置為自啟動,且啟動時間和周期在OIL文件中設置
int main(void) { leds_init(); StartOS( OSDEFAULTAPPMODE ); return 0; }
(2)OIL文件
CPU mySystem {
OS myOs { EE_OPT = "OSEE_DEBUG"; //OS的一些可選項 EE_OPT = "OS_EE_APPL_BUILD_DEBUG"; EE_OPT = "OS_EE_BUILD_DEBUG"; CPU_DATA = TRICORE { //CPU類型必須選擇TRICORE CPU_CLOCK = 300.0; MULTI_STACK = TRUE; COMPILER = GCC; IDLEHOOK = FALSE; }; MCU_DATA = TC29X { //MCU選擇相應的芯片型號,這里選擇TC29x后再編譯,ERIKA就會引入TC29x對應的寄存器和外設等相應的定義(默認是TC27x的)
DERIVATIVE = "tc297tf"; //下面這兩個參數影響不大 REVISION = "B"; }; KERNEL_TYPE = OSEK { //內核類型。除了選擇正常的OSEK,還可以選擇FP/EDF/FRSK/HR,這些類型是ERIKA在OSEK標准之外自己加入的一些一致性類 CLASS = BCC1; //一致性類 BCC1和BCC2只支持基礎任務,不能使用waitevent等阻塞相關的功能,可以共享堆棧;而ECC1和ECC2支持擴展任務,可以使用阻塞相關 }; //功能,但因為阻塞,必須使用私有堆棧;BCC1和ECC1不支持pending activation,即連續激活一個任務時,能否掛起激活次數;BCC2和ECC2支持。 }; APPDATA periodic_task { APP_SRC = "code.c"; }; TASK Task1 { //設置Task1的屬性 PRIORITY = 1; // 設置優先級 STACK = PRIVATE { //使用私有棧 SIZE = 1024; }; SCHEDULE = FULL; //參與基於優先級的搶占 };
COUNTER system_timer_1 {
CPU_ID = 0x0; //指定system timer是哪個內核的,一個內核只能使用一個system timer
MINCYCLE = 1; //alarm周期的最小tick數
MAXALLOWEDVALUE = 2147483647; //counter的最大tick計數值
TICKSPERBASE = 1; // 每個計數單元(unit)包含的tick數,即時基
TYPE = HARDWARE { //設置使用hardware counter
DEVICE = "STM_SR0"; //設置system counter所使用的stm資源
SYSTEM_TIMER = TRUE;
PRIORITY = 2;
};
SECONDSPERTICK = 0.001; //設置tick的粒度,這里為每個tick為1ms
};
ALARM AlarmTask1 {
COUNTER = system_timer_1; //與hardware counter進行綁定
ACTION = ACTIVATETASK { TASK = Task1; }; //與對應的task進行綁定
AUTOSTART = TRUE { ALARMTIME = 500; CYCLETIME = 2000; }; //設置alarm自啟動,同時設置開始時間和周期。任務也可以設置autostart
};
};
三、Erika如何應用在多核中
(1)master.c
/* ERIKA Enterprise. */ #include "shared.h"
//master.c對應於core0,其任務TaskCore0執行后,延遲200ms喚醒TaskCore1,TaskCore1延遲200ms喚醒TaskCore2.
TASK(TaskCore0) { led_blink(OSEE_TRIBOARD_2X5_LED_1); SetRelAlarm( AlarmCore1, 200, 0 ); TerminateTask(); } OsEE_reg myErrorCounter; void ErrorHook(StatusType Error) //當OS運行出錯時進行ErrorHook進行處理,ErrorHook通過OIL文件設置是否啟用。 { (void)Error; ++myErrorCounter; led_blink(OSEE_TRIBOARD_2X5_ALL_LEDS); } void idle_hook_core0(void); void idle_hook_core0(void) { idle_hook_body(); } /* * MAIN TASK */ int main(void) { StatusType status; AppModeType mode; CoreIdType const core_id = GetCoreID(); if (core_id == OS_CORE_ID_MASTER) { //注意main函數,每個核都會進入main函數,core0啟動另外兩個核,而core1和core2只需要進行各自的初始化工作 /* Init leds */ osEE_tc2x5_leds_init(); StartCore(OS_CORE_ID_1, &status); StartCore(OS_CORE_ID_2, &status); mode = OSDEFAULTAPPMODE; } else { mode = DONOTCARE; } StartOS(mode); return 0; }
(2)slave1.c和slave2.c
#include "shared.h" void idle_hook_core1(void); void idle_hook_core1(void) { idle_hook_body(); } TASK(TaskCore1) { led_blink(OSEE_TRIBOARD_2X5_LED_2); SetRelAlarm( AlarmCore2, 200, 0 ); TerminateTask(); } #include "shared.h" void idle_hook_core2(void); void idle_hook_core2(void) { idle_hook_body(); } TASK(TaskCore2) { led_blink(OSEE_TRIBOARD_2X5_LED_3); TerminateTask(); }
(3)OIL文件
CPU test_application { OS EE { /* EE_OPT = "OS_EE_VERBOSE"; */ EE_OPT = "OSEE_DEBUG"; EE_OPT = "OSEE_ASSERT"; EE_OPT = "OS_EE_APPL_BUILD_DEBUG"; EE_OPT = "OS_EE_BUILD_DEBUG"; //EE_OPT = "OSEE_TC_CLONE_OS"; CPU_DATA = TRICORE { ID = 0x0; CPU_CLOCK = 300.0; COMPILER = GCC; IDLEHOOK = TRUE { HOOKNAME = "idle_hook_core0"; }; }; CPU_DATA = TRICORE { //啟用core1 ID = 0x1; MULTI_STACK = TRUE; IDLEHOOK = TRUE { HOOKNAME = "idle_hook_core1"; }; }; CPU_DATA = TRICORE { //啟用core2 ID = 0x2; IDLEHOOK = TRUE { HOOKNAME = "idle_hook_core2"; }; }; MCU_DATA = TC29X { DERIVATIVE = "tc297tf"; REVISION = "BD"; }; STATUS = EXTENDED; ERRORHOOK = TRUE; //使用errorhook USERESSCHEDULER = FALSE; USEORTI = TRUE; KERNEL_TYPE = OSEK { CLASS = ECC1; RQ = MQ; //就緒任務列表類型選擇:RQ=LL,用鏈表,復雜度為O(n),n為就緒隊列中的任務數;RQ=MQ,用多隊列,復雜度為O(1) }; }; APPDATA tricore_mc { APP_SRC="master.c"; APP_SRC="slave1.c"; APP_SRC="slave2.c"; }; TASK TaskCore0 { CPU_ID = 0x0; //指定任務所屬內核 PRIORITY = 1; }; TASK TaskCore1 { CPU_ID = 0x1; PRIORITY = 1; }; TASK TaskCore2 { CPU_ID = 0x2; PRIORITY = 1; }; COUNTER system_timer_core0 { CPU_ID = 0x0; MINCYCLE = 1; MAXALLOWEDVALUE = 2147483647; TICKSPERBASE = 1; TYPE = HARDWARE { DEVICE = "STM_SR0"; SYSTEM_TIMER = TRUE; PRIORITY = 2; }; SECONDSPERTICK = 0.001; }; COUNTER system_timer_core1 { CPU_ID = 0x1; MINCYCLE = 1; MAXALLOWEDVALUE = 2147483647; TICKSPERBASE = 1; TYPE = HARDWARE { DEVICE = "STM_SR0"; SYSTEM_TIMER = TRUE; PRIORITY = 2; }; SECONDSPERTICK = 0.001; }; COUNTER system_timer_core2 { CPU_ID = 0x2; MINCYCLE = 1; MAXALLOWEDVALUE = 2147483647; TICKSPERBASE = 1; TYPE = HARDWARE { DEVICE = "STM_SR0"; SYSTEM_TIMER = TRUE; PRIORITY = 2; }; SECONDSPERTICK = 0.001; }; ALARM AlarmCore0 { COUNTER = system_timer_core0; ACTION = ACTIVATETASK { TASK = TaskCore0; }; AUTOSTART = TRUE { ALARMTIME = 500; CYCLETIME = 2000; }; }; ALARM AlarmCore1 { COUNTER = system_timer_core1; ACTION = ACTIVATETASK { TASK = TaskCore1; }; }; ALARM AlarmCore2 { COUNTER = system_timer_core2; ACTION = ACTIVATETASK { TASK = TaskCore2; }; }; };
四、Erika特點
(1)Erika的任務調度:①完全搶占式任務(Full Preemptive):參與基於優先級的搶占式任務調度;②非搶占式任務(Non Preemptive):不會被其他任務搶占;③混合式任務。
(2)堆棧使用:①main堆棧用於運行main()函數,當一個task設置堆棧為shared時,將會共享使用main堆棧;而當task設置了private堆棧時,則分配私有堆棧;②ISR1類中斷發生時,使用當前激活的堆棧;ISR2型中斷使用main堆棧。
(3)Erika優先級最大值:127
五、問題及解決方案
(1)TC297下,使用BootLoader時如何修改ERIKA OS在ROM中的位置?——以ERIKA其實地址在0xa00e0020為例,修改鏈接腳本:
- 鏈接腳本所在位置:
-
修改內容:
(2)TC397下如何運行ERIKA OS?
- 首先,在創建TC397的工程后,修改OIL文件,在OS設置中添加OSEE_TC_LINK_BMHD選項。
-
然后編譯工程,此時生成的可執行文件中包含2部分內容:①ERIKA OS的二進制代碼;②TC397的BMHD(注意:ERIKA生成的BMHD與官方出廠的BMHD在應用程序起始地址的設置上有所不同,不過可以放心燒寫,后續也可以改回出廠設置)。
- 先通過MemTool工具將BMHD燒寫到DFlash中的UCBS中,再將ERIKA代碼燒寫到PFlash中,二者的區分如下圖所示,其中陰影部分是BMHD,前面4行為ERIKA的二進制代碼,夾在BMHD中間的一行無效,不需要燒寫。
(3)TC397下ERIKA如何運行在多核環境下?
- 實際上與單核無異,不過在實際研究過程中遇到了一個坑,這里記錄一下。具體問題為:在調試模式下,ERIKA正常運行,而上電復位后只執行了初始化過程,而沒有正常運行,甚至連Cpu0都沒有正常運行ERIKA。於是與單核下的OIL設置進行了對比,發現有一個選項設置,即OSEE_DEBUG,如下圖所示。將其注釋掉即可解決。
(4)Erika如何使用iLLD底層驅動庫?——底層驅動庫實際上就是將MCU的寄存器操作或一系列寄存器操作用函數進行封裝,與直接進行寄存器操作無異。但iLLD庫除了寄存器操作外,還進行了中斷和陷阱相關的設計(包括:中斷和陷阱向量表的定義、中斷服務函數的定義等),而Erika OS已經將中斷和陷阱的處理納入自己的管理范圍,且管理方式(主要是相應的函數)與iLLD不同,因此在中斷和陷阱方面iLLD與Erika存在不兼容的問題,從而需要舍棄iLLD中的相應部分,而將其他主要部分融入到Erika中,具體方法如下:
- ①將iLLD中除了中斷與陷阱以外的庫文件添加到工程,其中被排除的主要是Cpu文件夾中的Irq和Trap子文件夾,以及CStart文件夾(Erika模仿其編寫了相應的內核啟動文件,因此不再需要該文件夾),如圖所示。
- ②設置包含路徑。就像在Hightec中使用iLLD一樣,需要包含庫的相應路徑,一般在工程設置中的“path and symbols”中設置。由於eclipse-phonon中相應的設置不起作用,因此采用了在oil文件中設置編譯屬性的方式進行了路徑包含,如下圖所示。
-
③設置需要被編譯的源文件。Erika工程中的源文件,除了OS的源代碼文件外都不會自動參與編譯,而是需要手動設置,因此需要將iLLD中需要用到的源文件設置為編譯對象,具體設置在oil文件中進行,這里我們設置了基本的源文件,如IfxCpu_cfg.c、IfxSrc_cfg.c、IfxCpu.c、IfxSrc.c,還設置了需要用到的外設對應的源文件,包括IfxStm.c和IfxPort.c,如圖所示。
- ④ 使用實例——設置STMsr1中斷,並在中斷中閃燈,具體程序如下所示。在Erika中使用iLLD時,主要是中斷服務函數的定義方法會有所不同。在HighTec中設置一個外設的中斷時,首先要設置相應外設的中斷優先級並開啟中斷,然后通過IFX_INTERRUPT宏來定義相應優先級的中斷向量和中斷服務函數(使用硬件管理中斷時);而在ErikaOS下,前面設置優先級和開啟中斷是與前者相同的,直接用iLLD中的相應函數即可,不同之處在於中斷向量和中斷服務函數的定義,中斷向量的定義在ErikaOS的源文件ee_tc_intvec.c中進行,而中斷服務函數的聲明需要在oil文件中設置並在源文件中定義(見下圖)。
-