在Tricore上移植μC/OS-III(三)—— Trap和中斷
本文介紹了在英飛凌Tricore核心微控制器陷阱(Trap)和中斷(Interrupt)。例程程式碼可在公眾號“汽車軟體雜談”後臺回覆“Tricore uCOS”獲取。
本文是《在Tricore上移植μC/OS-III》系列文章的第三篇,前兩篇分別介紹了Tricore核心的CSA機制和上下文切換的具體使用方法。這篇文章主要講用Trap機制來呼叫上下文切換函式(OSCtxSw()),以及系統時鐘的實現等內容。
1. Tricore的Trap機制
Trap簡單來說,就是在發生一些異常情況或錯誤操作的時候,系統自動進入一個類似於中斷的函式中,以防止錯誤的操作對系統產生損害,並允許使用者對系統當時的執行狀態進行跟蹤檢視,和ARM的HardFault類似。例如我們前兩篇文章提到過的,CSA使用不正確、指令呼叫不正確這一類的操作,都會觸發Trap。
Trap是不能在應用程式中被禁止的。
關於Trap的詳細介紹可在TricoreArchitecture_UM_VO1的第六章中找到。共有8個大類,包含了指令錯誤、記憶體管理錯誤、CSA使用錯誤等多種型別,我這裡就不詳細說了,有需要了解的可以檢視手冊。
在這些大類之下,還根據觸發Trap的原因不同來劃分了很多子類,這些子類通過TIN(Trap Identification Number)來進行標識,在觸發Trap的時候,TIN被存入系統暫存器D[15]中,進入Trap函式後,我們可以通過呼叫暫存器D[15]中的值來獲取TIN,進而獲取觸發Trap的詳細原因。
2. System Call
下面我們來說一下System Call這個特殊的Trap。其實除了我們上文說到的系統錯誤或指令錯誤會觸發Trap外,Tricore還為使用者提供了一個可以人為觸發Trap的機制
我們之前說過的OSCtxSw()這個任務切換函式,就是放在System Call 的Trap函式中,之所以採用這種方法,就是因為進入Trap的時候會自動儲存之前任務的UCX和LCX,省去了手動儲存任務執行狀態的麻煩,而且在Trap中,我們使用“RFE”指令就可以返回上一個任務的執行狀態。
3. System Call的使用方法
iLLD庫中已經為我們寫好了Trap函式,我們需要做的就是使能並編寫一個Hook函式。
① 首先要在Ifx_Cfg.h中,使能 IFX_CFG_EXTEND_TRAP_HOOKS 這個巨集定義:
/*********************************************************************************************************************/
/*---------------------------------Configuration for Trap Hook Functions' Extensions---------------------------------*/
/*********************************************************************************************************************/
#define IFX_CFG_EXTEND_TRAP_HOOKS /* Decomment this line if the project needs to extend trap hook functions */
② 然後在Ifx_Cpu_Trap.c中,新增包含一個“Ifx_Cfg_Trap.h”,這個檔案需要我們自己建立,並在裡面編寫Hook函式。
/*******************************************************************************
** Includes **
*******************************************************************************/
#include "IfxCpu_Trap.h"
#include "Cpu/Std/IfxCpu.h"
#include "Cpu/Std/IfxCpu_Intrinsics.h"
#include "IfxCpu_reg.h"
#include "Ifx_Cfg.h"
#ifdef IFX_CFG_EXTEND_TRAP_HOOKS
#include "Ifx_Cfg_Trap.h"
#endif
③ Ifx_Cfg_Trap.h 中的內容如下圖所示,這個Hook函式就是我們在SystemCall Trap中將要呼叫的函式,注意這個函式必須是 inline 函式,以避免函式巢狀而產生的CSA連結串列變化。trapInfo這個結構體中包含著我們傳遞進去的TIN,我們這次使用的是0。
#include "Ifx_Types.h"
#include "IfxCpu_Intrinsics.h"
#include "IfxPort_Io.h"
#include "os_cpu.h"
IFX_INLINE void SysCallExtensionHook(IfxCpu_Trap trapInfo)
{
switch (trapInfo.tId)
{
case 0: //切換任務Trap
{
/*__svlcx(); lower context was stored in the trap vector */
OSCtxSw();
break;
}
default:
{
break;
}
}
}
#define IFX_CFG_CPU_TRAP_SYSCALL_CPU0_HOOK(trapInfo) SysCallExtensionHook(trapInfo)
④ 呼叫SystemCall。μC/OS-III中的任務切換介面是OS_TASK_SW(),也就是說每次執行任務切換的時候都會呼叫這個語句,我們把這個語句定義成呼叫SystemCall,用的是iLLD的__syscall()介面。這樣就可以實現:需要執行任務切換的時候,觸發SystemCall Trap並自動儲存任務狀態,然後呼叫OSCtxSw()函式執行任務切換。
#define OS_SYSCALL_CTXSW 0
#define OS_TASK_SW() __syscall(OS_SYSCALL_CTXSW)
4. 系統時鐘的實現
在RTOS中,通常都需要一個系統時鐘中斷,以固定的頻率觸發,以為系統提供計時和任務排程功能。本次的系統中斷採用Tricore的STM0定時器,每1ms觸發一次,即系統時鐘頻率是1KHz。相關函式在Bsp.c中。
作業系統中與系統時鐘相關的兩個函式分別是:
- 初始化函式:OS_CPU_SysTickInit (CPU_INT32U cnts),傳入的引數cnt就是時鐘振盪多少次後觸發中斷,關於這個數值我們後面再說。
- 時鐘滴答函式:OS_CPU_SysTickHandler (void),每次觸發系統中斷的時候要呼叫這個函式。
void OS_CPU_SysTickHandler (void)
{
OSIntEnter (); /* Tell uC/OS-III that we are starting an ISR */
OSTimeTick(); /* Call uC/OS-III's OSTimeTick() */
OSIntExit(); /* Tell uC/OS-III that we are leaving the ISR */
}
void OS_CPU_SysTickInit (CPU_INT32U cnts)
{
InitSTM0(cnts);
}
在初始化系統時鐘的時候初始化STM0模組:
static void InitSTM0(CPU_INT32U cnts)
{
IfxStm_initCompareConfig(&g_STMConf); /* Initialize the configuration structure with default values */
g_STMConf.triggerPriority = OS_CFG_TICK_TASK_PRIO; /* Set the priority of the interrupt */
g_STMConf.typeOfService = IfxSrc_Tos_cpu0; /* Set the service provider for the interrupts */
g_STMConf.ticks = cnts; /* Set the number of ticks after which the timer triggers an
* interrupt for the first time */
IfxStm_initCompare(BSP_DEFAULT_TIMER, &g_STMConf); /* Initialize the STM with the user configuration */
}
宣告STM中斷,並定義中斷入口函式。中斷優先順序配置為系統的TickTask相同的優先順序,當然這倆優先順序其實不是一個概念,一個是對微控制器來說的,一個是對μC/OS來說的,不要混淆,配置成不同的優先順序也完全可以。在中斷函式中重新配置STM0暫存器值,以設定下一次觸發中斷的時間。然後呼叫系統滴答函式。
IFX_INTERRUPT(IsrSTM0, 0, OS_CFG_TICK_TASK_PRIO);
IfxStm_CompareConfig g_STMConf;
void IsrSTM0(void)
{
/* Update the compare register value that will trigger the next interrupt and toggle the LED */
IfxStm_increaseCompare(BSP_DEFAULT_TIMER, g_STMConf.comparator,
(CPU_INT32U)((TimeConst_1s)/OSCfg_TickRate_Hz));
// IfxPort_togglePin(&MODULE_P14, 9);
OS_CPU_SysTickHandler();
}
系統時鐘初始化函式在main函式中被呼叫,初始化的值=1S/系統時鐘頻率,這個值在os_cfg_app.h中可以配置,這次配置為1000。同樣的,上面的系統中斷函式的暫存器重置的值也要是這個值。
OS_CPU_SysTickInit((CPU_INT32U)(TimeConst_1s)/OSCfg_TickRate_Hz); /* Init uC/OS periodic time src (SysTick). */
至此,系統時鐘就配置完成了。
5. 補充內容
1. 開啟和關閉中斷:
μC/OS在程式中比較關鍵的位置,會設定臨界區,也就是關閉所有中斷,保證程式在這段執行時間內不被打斷。關閉和開啟中斷的介面在cpu.h中,如下圖所示,直接呼叫的iLLD中的介面:
#define CPU_INT_DIS() do { cpu_sr = disableInterrupts(); } while (0) /* Save CPU status word & disable interrupts.*/
#define CPU_INT_EN() do { restoreInterrupts((boolean)cpu_sr);} while (0) /* Restore CPU status word. */
2. CPU_CntLeadZeros (CPU_DATA val)函式:
這個函式的作用是計算出一個32位數中第一個為1的位之前所有0的個數,比如:
輸入:0x00008010,返回結果:16,即在第一個1前有16個0.
這個函式是μC/OS-III在計算優先順序最高的任務時使用的,具體邏輯就不展開講了,反正它使用頻率比較高,每次任務排程都會用到,如果能提高這個函式的執行效率,將顯著提高任務排程的速度。
這個函式可以用C語言實現,但比較麻煩,所以一般的微控制器都會設定一個彙編指令來實現這個功能,能節省不少時間,Tricore的實現方法如下,Tasking編譯器支援在C語言中內嵌組合語言,且支援輸入和輸出資料,詳細的內容可以參考Tasking使用手冊。
CPU_DATA CPU_CntLeadZeros (CPU_DATA val)
{
CPU_DATA ret;
__asm ("clz %0, %1" : "=d" (ret) : "d" (val));
return ret;
}
到此為止,在Tricore上移植μC/OS-III的方法就介紹完了。這個專案花了我近一個月的時間,寫這三篇文章的過程,是一個對其認識不斷加深的過程,也是對程式碼不斷優化的過程。最大的受益者還是我自己,如果同時也能對諸位有一點幫助,也是我莫大的榮幸了。下個專案見。