1。從任務排程說起
最開始我們在微控制器寫程式碼的樣子是怎樣的呢?在ch1那一章我們對模組和分層進行了討論,模組是對功能程式碼的封裝,分層是在平臺層面封裝,都是在解決專案複雜度控制的問題,但是我們拿微控制器最主要的目的是來執行任務Task幫我們做事的,比如讀取ADC取樣資料,讀取鍵盤按鍵,輸出PWM,I2C通訊,執行PID控制,等等。
那在微控制器裡如何組織任務排程的設計?
大迴圈排程
最初的最初,我們的任務排程簡單直接——也就是大迴圈方式,示例程式碼如下:
int main()
{
Dis_Interrupt();
System_Init();
En_Interrupt();
while(1)
{
Task0_Run();
Task1_Run();
Task2_Run();
Task3_Run();
Task4_Run();
}
}
void Task0_Run(void)
{
Pot1Calc(); //加速器訊號計算
Pot2Calc(); //制動器訊號計算(保留)
TempCalc(); //電機及控制器溫度計算
}
。。。。。
大迴圈方式的任務排程如圖1所示,優點就是簡單直接,適合比較簡單的系統,帶來的不好的地方:
每個任務的排程週期和時間是不固定的(if else的存在),無法保證確定的週期性執行任務
隨著任務數量的增加,系統會越來越慢
如果遇上長時間任務,會拖累整個系統變慢
圖1。大迴圈任務排程圖
定時任務排程
為了克服大迴圈方式的缺點(任務排程週期性無法保證,任務數量增加系統會變慢),提出了定時的任務排程的方式,不過需要使用微控制器一個定時器,來實現一個簡單的任務排程器,利用定時器將CPU切割為一個等週期的時間片排程單元,然後利用標誌位控制在每個時間片只調用一個任務。整個系統程式碼結構如下所示:
#define TASK_MAX_LENGTH 10
typedef struct
{
Int16 Flag[TASK_MAX_LENGTH];
Int16 Timer;
Int32 Number;
} USERTASK;
USERTASK UserTask0={0,0,0,0,0,0,0,0,0,0,0,TASK_MAX_LENGTH};//任務初始化
//任務排程函式
void TaskScheduler(USERTASK* v)
{
v->Flag[v->Timer++] = 1;
if(v->Timer >= v->Number)
{
v->Timer = 0;
}
}
//主函式
int main()
{
Dis_Interrupt();
System_Init();
En_Interrupt();
while(1)
{
Task0_Run();
Task1_Run();
Task2_Run();
Task3_Run();
Task4_Run();
}
}
//1ms定時中斷
__interrupt void Timer0_INT_MapedISR(void)
{
TaskScheduler(&UserTask0);
}
//單個任務示例函式
void Task0_Run(void)
{
if(UserTask0。Flag[0])
{
Pot1Calc(); //加速器訊號計算
Pot2Calc(); //制動器訊號計算(保留)
TempCalc(); //電機及控制器溫度計算
UserTask0。Flag[0] = 0;
}
}
……
定時任務排程的流程圖如圖2所示。與大迴圈排程方式對比,這種方式能夠實現週期性的任務排程,同時隨著任務的增加,依然能夠保證排程的週期性,這種排程能夠應對大多數的控制系統,比如TI的PMSM電機控制器,一般小的家電控制器,都可以搞定。但是使用時有幾點要注意:
單個任務的最長時間長度務必保證不超過單個時間片,否則會導致週期性延遲
對於嚴格實時的控制週期任務,定時排程器不能夠保證
對於長週期任務(比如通訊等待等),定時任務排程器要麼把任務切割為小任務,要麼安排幾個連續的空閒週期來執行
圖2。定時任務排程圖
針對第1點,需要測試或者預估任務的最長執行時間,這個可以採用IO測試的方式解決(具體參見ch6)。
針對第2點,對於實時性要求高,並且週期控制快的任務(比如PID控制),只能將這個任務放到定時中斷裡做,示例程式碼如下:
//1ms定時中斷
__interrupt void Timer0_INT_MapedISR(void)
{
TaskScheduler(&UserTask0);
Task_SpeedPID_Control();
}
//實時性要求高的任務,示例函式,如果控週期慢的話,也可以選擇加入if(UserTask0。Flag[0])做判斷
void Task_SpeedPID_Control(void)
{
SpeedPID_Input(); //讀取輸入指令和反饋訊號
SpeedPID_Run(); //執行PID
SpeedPID_Output(); //輸出PWM控制
}
……
針對第3點,我們可以將長週期任務放在最後面,如圖3所示,可以把最後幾個空閒週期都留給Task4執行。但是要注意,如果有多個長週期任務,依然會拖慢整個排程週期,於是就出現了基於優先順序的任務排程方式,高優先順序的任務可以中斷低優先順序的任務,在保證長週期任務排程的同時,短週期任務的排程依然能夠保證,這就是RTOS。
圖3。長週期排程方式
實時作業系統RTOS排程
實時作業系統,常用的小型RTOS有uCosII,FreeRTOS,Rt-thread,主要是任務優先順序的排程方式不一樣,這裡感興趣的同學,可以參見相關的專業書籍,對RTOS核心程式碼不做詳細介紹。RTOS的對任務的排程方式如圖4所示。Task0的優先順序高,可以中斷優先順序低的Task1,等Task0執行完,然後RTOS會切換到Task1繼續執行。
圖4。RTOS任務排程方式圖
2。智慧車總體任務排程
智慧車排程平臺總體上只有兩個任務SpeedControlTask和ControlGraphTask,考慮到系統簡單,沒有用RTOS和任務排程器,直接中斷配合While實現,程式碼示例如下,執行時序如圖5所示。
//main主迴圈
void main(void)
{
DisableInterrupts;
CarSystem_Init();
EnableInterrupts;
Car_Test();//主迴圈在這裡
while(1);
}
//主迴圈
void Car_Test(void)
{
while(1)
{
if(ImageOver)//影象DMA傳輸結束
{
ImageOver=0;
img_extract((uint8 *)Image_Data, (uint8 *)imgbuff0, CAMERA_SIZE);//解壓影象
ControlGraphTask();//影象處理任務
DataLog_Add();//資料記錄
if(DataLog_CheckEN())
DataLog_Print();
}
}
}
//中斷
#define CAM_VSYNC 29
void PORTA_handler(void)
{
uint32 flag = PORTA_ISFR;
PORTA_ISFR = ~0;
if(flag & (1 << CAM_VSYNC)) //PTA29觸發攝像頭幀中斷
{
ImageOver=0; //清除影象採集標誌
camera_vsync();
gVar。time++;
}
}
//DMA傳輸影象資料
void DMA0_IRQHandler()
{
camera_dma();
ImageOver=1;
}
//定時10ms中斷,執行速度PID控制任務
void PIT0_IRQHandler(void)
{
SpeedControlTask();
PIT_Flag_Clear(PIT0);
}
圖5。系統任務時序圖
總體思路就是,每一幅影象的幀中斷VSYNC觸發PORTA_handler(PA29)中斷函式,此時ImageOver清零,同時DMA開始傳輸影象,當DMA傳輸結束觸發DMA0_IRQHandler中斷,此時ImageOver=1,如果ControlGraphTask檢測到的話,那就開始執行,如果ControlGraphTask在VSYNC到來清零ImageOver之前沒有開始執行的話,那隻能等待下一次DMA中斷。最終測試結果,每兩幀觸發一次ControlGraphTask執行,控制週期為13。33ms。
3。嵌入式驅動層設計
嵌入式驅動層大部分複用了Vcan山外的板級庫,新加入比較重要的庫有EITMotorL,EITMotor_R,EIT_Steer和EIT_Log,封裝在EITLib資料夾,總體的思路就是,。h負責介面,。c負責功能實現。
這裡以Motor庫為例,介紹一下嵌入式驅動庫的封裝。
考慮到速度控制用到Motor和Encode,所以把二者整合放到了一起,整體MotorR程式碼解析如下所示。
#ifndef __EIT_MOTORR_DEF__
#define __EIT_MOTORR_DEF__
#include “include。h”
/*Motor Driver*/
#define MOTORR_PWM_MAX 1000 //PWM範圍:-1000到1000
#define MOTORR_PWM_MIN (-1000)
#define MOTORR_PWM_FREQ 15000 //PWM工作頻率
#define MOTORR_FTM FTM0
#define MOTORR_EN PTA24
#define MOTORR_PWMA FTM_CH3
#define MOTORR_PWMB FTM_CH4
#define MOTORR_PWMAIO PTA6
#define MOTORR_PWMBIO PTA7
/*Encode */
#define MOTORR_ENCODE_FTM FTM2 //左編碼器用FTM1
#define MOTORR_GEAR_N 36 //B車電機自帶齒輪齒輪數
#define ENCODR_GEAR_N 40 //B車主動軸齒輪齒輪數
#define WHEELR_GEAR_N 105 //編碼器比例係數
#define ENCODR_CYCLE 2000 //編碼器一圈觸發2000個脈衝
#define WHEELR_LENGTH 18 //17。8cm車輪周長
#define SPEEDR_FS 100 //速度取樣頻率Hz,週期10ms
extern void MotorR_Init(void); //電機初始化
extern void MotorR_Run(int32 pwm); //電機PWM控制
extern void MotorR_Brake(void); //電機剎車
extern void MotorR_Slip(void); //電機滑行
extern int32 MotorR_GetWheelSpeed(int32 CntInTs); //speed單位為cm/s
extern int32 MotorR_GetTsCount(void); //10ms週期內,編碼器脈衝計數值
#endif
嵌入式小書(智慧車)介紹
嵌入式小書0-智慧車系統介紹
嵌入式小書1-系統分層設計
嵌入式小書2-機械與硬體設計部分
嵌入式小書3-嵌入式平臺軟體搭建
嵌入式小書4-Matlab處理和驗證影象演算法
嵌入式小書5-轉速和轉向控制器設計
嵌入式小書6-系統測試與分析