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的存在),無法保證確定的週期性執行任務

隨著任務數量的增加,系統會越來越慢

如果遇上長時間任務,會拖累整個系統變慢

嵌入式小書3-嵌入式平臺軟體搭建

圖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電機控制器,一般小的家電控制器,都可以搞定。但是使用時有幾點要注意:

單個任務的最長時間長度務必保證不超過單個時間片,否則會導致週期性延遲

對於嚴格實時的控制週期任務,定時排程器不能夠保證

對於長週期任務(比如通訊等待等),定時任務排程器要麼把任務切割為小任務,要麼安排幾個連續的空閒週期來執行

嵌入式小書3-嵌入式平臺軟體搭建

圖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-嵌入式平臺軟體搭建

圖3。長週期排程方式

實時作業系統RTOS排程

實時作業系統,常用的小型RTOS有uCosII,FreeRTOS,Rt-thread,主要是任務優先順序的排程方式不一樣,這裡感興趣的同學,可以參見相關的專業書籍,對RTOS核心程式碼不做詳細介紹。RTOS的對任務的排程方式如圖4所示。Task0的優先順序高,可以中斷優先順序低的Task1,等Task0執行完,然後RTOS會切換到Task1繼續執行。

嵌入式小書3-嵌入式平臺軟體搭建

圖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);

}

嵌入式小書3-嵌入式平臺軟體搭建

圖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-系統測試與分析