1 前言
標定是指根據(jù)整車的各種性能要求(如動力性、經(jīng)濟性,、排放及輔助功能等),,來調(diào)整、優(yōu)化和確定整車上各ECU(如發(fā)動機,、AT等各子系統(tǒng) ECU)控制參數(shù)的控制算法,。標定系統(tǒng)主要是由上位機和底層ECU這二部分組成,因此,,上位機和底層ECU的通信方式對整個標定系統(tǒng)的性能起到了至關(guān)重要的作用,。目前,一般的標定系統(tǒng)都是采用基于串行口的點對點的通信方式,,這種通信方式容易實現(xiàn),,但存在著通信速度較慢、可靠性較低等缺陷,。這里我們采用的是CAN總線的通信方式,,相對串口通信,基于CAN總線的通信方式具有通信可靠[1],、傳輸速度快,、可實現(xiàn)在線編程等優(yōu)點。
2 總體設計
CAN通信可視為系統(tǒng)的一個I/O字符流設備[3],,它在完成普通收發(fā)功能的同時,,還要能實現(xiàn)驅(qū)動程序必備的設備無關(guān)性。即驅(qū)動程序應將系統(tǒng)所有的硬件特性封裝起來,,為使用該設備的應用程序提供與硬件無關(guān)的、通用的編程接口,,應用層程序編寫人員無需了解設備的原理,,即可順利實現(xiàn)對設備的控制,,通過該設備實現(xiàn)可靠的數(shù)據(jù)交換。另外,,針對CAN通信和嵌入式系統(tǒng)的實時性要求,,該驅(qū)動程序要求收發(fā)數(shù)據(jù)代碼可靠,延遲短,,占用系統(tǒng)時間短,,中斷執(zhí)行時間短,關(guān)閉中斷時間短,,并在收發(fā)錯誤和發(fā)生異常情況時,,向應用程序匯報。另外,,該驅(qū)動程序需要監(jiān)控CAN控制器的工作狀態(tài),,在出現(xiàn)致命錯誤和脫離總線時,為CAN模塊復位,,并向系統(tǒng)匯報,。
圖1 驅(qū)動程序總體結(jié)構(gòu)圖
基于以上需求分析,結(jié)合其他OS中實現(xiàn)I/O串行設備的驅(qū)動方案及CAN的總線要求特點,,設計總體驅(qū)動程序結(jié)構(gòu)如圖1,。
3 CAN驅(qū)動模塊的實現(xiàn)
基于以上總體設計框架,首先定義一個CAN類來封裝CAN通信中的數(shù)據(jù)結(jié)構(gòu)和函數(shù),,最下面一層為中斷級程序,,中斷處理程序在每次CAN控制器完成收發(fā)時,喚醒驅(qū)動程序,,進行下一步工作,。在中斷處理程序中,根據(jù)不同的中斷向量來確定當前發(fā)生的是發(fā)送完成中斷還是接受完成中斷,,并完成相應工作,。中間一層為底層驅(qū)動程序,底層驅(qū)動程序主要是通過對CAN控制器寄存器的讀寫,,完成對CAN端口的配置和狀態(tài)檢測等工作,,同時為設備無關(guān)軟件和用戶程序提供接口。在這一層中,,必須要建立一個環(huán)狀緩沖結(jié)構(gòu),,該緩沖由一個接收環(huán)狀緩沖區(qū)和一個發(fā)送環(huán)狀緩沖區(qū)組成,其數(shù)據(jù)結(jié)構(gòu)如下代碼所示,,對于每個環(huán)狀緩沖區(qū),,設計了一個存入指針指向下一個待存入CANMsg的存入地址,一個讀出指針指向緩沖區(qū)下一個待取出的(最舊的)CANMsg的地址,,一個計數(shù)器記錄目前緩沖區(qū)中有多少個CANMsg待取出,,一個信號量,,用于與應用程序交換消息。接收環(huán)狀緩沖區(qū)用于緩沖接收到的總線消息,,等待應用程序處理,,發(fā)送環(huán)狀緩沖區(qū)用于緩沖應用程序發(fā)送出的消息,等待發(fā)送中斷程序來處理,。
typedef struct{ //環(huán)形緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)
INT16U RingBufRxCtr; //接收計數(shù)器
OS_EVENT *RingBufRxSem; //信號量
CAN_msg *RingBufRxInPtr; //接收緩沖區(qū)的存入指針
CAN_msg *RingBufRxOutPtr; //接收緩沖區(qū)的讀出指針
CAN_msg RingBufRx[CAN_RX_BUF_SIZE]; //接收緩沖區(qū)的消息存儲
INT16U RingBufTxCtr; //發(fā)送計數(shù)器
OS_EVENT *RingBufTxSem;
CAN_msg *RingBufTxInPtr; //發(fā)送緩沖區(qū)的存入指針
CAN_msg *RingBufTxOutPtr; //發(fā)送緩沖區(qū)的讀出指針
CAN_msg RingBufTx[CAN_TX_BUF_SIZE]; //發(fā)送緩沖區(qū)的消息存儲
}CAN_RING_BUF;
3.1 底層驅(qū)動
底層驅(qū)動模塊為我們應用程序提供了接收和發(fā)送消息的接口函數(shù),。
圖2 CAN接收消息
當接收消息時[3],,如圖2所示,,應用程序在信號量處等待,;收到一個消息后,,ISR從串行端口讀入消息,,將其存入環(huán)型緩沖區(qū),。然后ISR發(fā)出信號量,,通知在等待串口數(shù)據(jù)的任務已收到一個消息,。等待任務收到信號量后,,進入就緒狀態(tài),,準備被OS調(diào)度器激活。當內(nèi)核調(diào)度該任務運行時,,該任務從環(huán)狀緩沖區(qū)中取出消息,,完成接收消息的過程。
void CAN_GetMsg(CAN_msg *msg){
INT8U oserr;
OS_CPU_SR cpu_sr;
CAN_RING_BUF *pbuf;
pbuf = &ringbuf;
OSSemPend(pbuf->RingBufRxSem,0,&oserr); //等待信號量
OS_ENTER_CRITICAL();//關(guān)中斷
pbuf->RingBufRxCtr--;//接收計數(shù)器減1
CopyMsg(pbuf->RingBufRxOutPtr++,msg); //從環(huán)形緩沖區(qū)中取出信號量
if(pbuf->RingBufRxOutPtr==&pbuf->RingBufRx[CAN_RX_BUF_SIZE]) {pbuf->RingBufRxOutPtr= &pbuf->RingBufRx[0];
//如果環(huán)形緩沖區(qū)的讀出指針達到緩沖區(qū)的最末端,,將其改為指向緩沖區(qū)的首地址 }
OS_EXIT_CRITICAL(); //開中斷,,允許CPU響應中斷 }
發(fā)送CAN消息與接受消息類似。后臺進程將欲發(fā)送的消息幀存儲于發(fā)送環(huán)狀緩沖區(qū)中,。當CAN端口準備發(fā)送一幀消息時,,產(chǎn)生一個中斷,CAN消息從緩沖區(qū)中取出,,并由ISR輸出[4],。但其中出現(xiàn)了一個問題:CAN端口只能在發(fā)送上一個數(shù)據(jù)結(jié)束的時候才會產(chǎn)生一個中斷,這個產(chǎn)生中斷的時刻與我們需要執(zhí)行中斷任務的時間是不一致的,。解決這個問題的方法就是,,禁止發(fā)送端中斷使能直到需要再發(fā)送消息為止。在系統(tǒng)啟動時,,禁止發(fā)送中斷,,發(fā)送一個啟動消息幀,這時發(fā)送完成中斷標志位已經(jīng)被置位,,但由于發(fā)送中斷使能位為低,,所以無法發(fā)生中斷,系統(tǒng)繼續(xù)執(zhí)行,。當需發(fā)送第一個消息時,,將該消息放入發(fā)送環(huán)狀緩沖區(qū),,然后運行發(fā)送中斷,,這時,,上一次發(fā)送消息完成中斷產(chǎn)生,發(fā)送該消息,。在發(fā)送消息結(jié)束時,,若發(fā)送環(huán)狀緩沖區(qū)中有其他數(shù)據(jù)需要發(fā)送,則清中斷源,,等待該消息發(fā)送完成中斷產(chǎn)生,,來發(fā)送下一個消息,若沒有其他數(shù)據(jù)需要被發(fā)送,,則直接禁止發(fā)送中斷,,將該消息發(fā)送完成時產(chǎn)生的中斷保留到下一次有消息需要發(fā)送時發(fā)生。
圖3 CAN發(fā)送消息
發(fā)送消息的方法如圖3,。當發(fā)送環(huán)狀緩沖區(qū)已滿時,,信號量作為指示,暫停發(fā)送任務,。發(fā)送消息時,,任務等待信號量。如果環(huán)狀緩沖區(qū)未滿,,則任務繼續(xù)向環(huán)狀緩沖區(qū)存儲欲發(fā)送的消息,。如果存儲的消息是緩沖區(qū)第一個字節(jié),則發(fā)送中斷允許,,中斷程序準備啟動,。CAN發(fā)送ISR從環(huán)行緩沖區(qū)中取出最舊的消息,同時發(fā)送信號量,,通知發(fā)送任務,,表明環(huán)狀緩沖區(qū)有空間接收另外的消息,接著ISR將消息從發(fā)送到總線上,。其實現(xiàn)代碼如下所示:
void CAN_PutMsg(CAN_msg *msg) {
INT8U oserr;
OS_CPU_SR cpu_sr;
CAN_RING_BUF *pbuf;
pbuf = &ringbuf;
OSSemPend(pbuf->RingBufTxSem, 0, &oserr); //等待信號量
OS_ENTER_CRITICAL();//關(guān)中斷
pbuf->RingBufTxCtr++; //發(fā)送計數(shù)器加1
CopyMsg(msg, pbuf->RingBufTxInPtr++); //將消息放入環(huán)形緩沖區(qū)
if(pbuf->RingBufTxInPtr==&pbuf->RingBufTx[CAN_TX_BUF_SIZE]) {pbuf->RingBufTxInPtr=&pbuf->RingBufTx[0];
}
if (pbuf->RingBufTxCtr==1) {
CAN_TxIntEn();//為環(huán)形緩沖區(qū)的第一則消息,,開發(fā)送中斷
}
OS_EXIT_CRITICAL();
}
3.2 中斷服務程序
根據(jù)前面談到發(fā)送和接收消息的軟件結(jié)構(gòu),在CAN初始化時就要求CAN的接收中斷處入開啟狀態(tài),,而發(fā)送中斷僅僅是在發(fā)送緩沖區(qū)里面有了第一則消息后再開啟的,,因此在這里設計兩個接口函數(shù),CAN.TxIntEn()和CAN.TxIntDis(),,分別將發(fā)送屏蔽位置1(允許發(fā)送完成中斷)和置0(禁止發(fā)送完成中斷),。
圖4 發(fā)送接收中斷程序流程圖
中斷級程序的核心就是CANRX_ISR()和CANTX_ISR(),它們由初始化時對該模塊的中斷設置寄存器設置的中斷級別,。如圖4所示,,若為接收完成中斷,,則清除中斷源,將接收到的消息放入接收緩沖區(qū),;將該消息存入接收緩沖區(qū)存入指針所指向的地址,,將該指針向下移動,接收緩沖區(qū)計數(shù)器加1,,并發(fā)出信號量通知應用程序有新的消息已經(jīng)接收到,,若有任務正在等待CAN上的新消息,則該任務進入就緒狀態(tài)等待OS的調(diào)度,。若為發(fā)送完成中斷,,則將發(fā)送緩沖區(qū)的待發(fā)送消息讀出;將有待發(fā)送消息且優(yōu)先級最高的一個中讀取最舊的消息(緩沖區(qū)取出指針所指向的消息),,發(fā)送緩沖區(qū)計數(shù)器減1,,發(fā)出信號量通知應用程序有一個消息被發(fā)出,并匯報當前發(fā)送緩沖區(qū)的狀態(tài),;還應判斷是否為最后一個待發(fā)送的消息,,若不是,則清除中斷源并將消息發(fā)送到總線上,,若是最后一個,,則禁止發(fā)送完成中斷后發(fā)送該消息,將這個發(fā)送完成中斷保留到應用程序下一次發(fā)送消息的時候允許并產(chǎn)生,。
3.3 應用
該驅(qū)動程序的應用,,如下代碼所示,這里使用的是uCOS-II,,首先定義一個CAN消息對象(msg)和一個環(huán)狀緩沖區(qū)數(shù)據(jù)結(jié)構(gòu)(CANRingBuf),,在主程序中,初始化OS以后調(diào)用Ringbuf_Init()函數(shù)初始化環(huán)形緩存區(qū),,然后調(diào)用CAN_Init()函數(shù)初始化CAN端口,。在啟動OS后,用戶就可用在任何任務中調(diào)用CAN_PutMsg(CAN_msg *msg)和CAN_GetMsg(CAN_msg *msg)發(fā)送和接收總線消息了,。
CAN_msg msg,;
CAN_RING_BUF CANRingBuf;
void main(void) {
OSInit();
Ringbuf_Init();
CAN_Init();
/* Creat task1 */
OSStart(); }
void task1 (void * data)
{ CAN_PutMsg(&msg);
CAN_GettMsg(&msg);
}
4 結(jié)束語
通過改變芯片總線頻率、CAN通信速率這樣多次反復不斷的調(diào)試,,此CAN驅(qū)動在實時操作系統(tǒng)上運行穩(wěn)定可靠,,未出現(xiàn)數(shù)據(jù)丟失,較好的實現(xiàn)了上位機與ECU的通信,,因此,,具有很強的實用價值。