WindowsCE.Net下CAN卡的驅(qū)動程序設計
關(guān)鍵詞:WinCE.NET CAN 驅(qū)動
引言
近年來電力行業(yè)為了快速部署變電站,采用了建造整體變電所的方法:在生產(chǎn)基地將變電站的內(nèi)部設備安裝、調(diào)試完成,只留下與外界的接口,整體運到變電站所在地后進行安裝和簡單調(diào)試即可投入運行。其內(nèi)部設備通過CAN總線進行通信,系統(tǒng)原有的監(jiān)控軟件基于DOS系統(tǒng),維護調(diào)試比較困難,因此想要尋求更方便、友好的系統(tǒng)支持。經(jīng)過比較,嵌入式操作系統(tǒng)市場上風頭正勁的Windows CE .NET成為最終選擇。微軟的最新產(chǎn)品Windows CE.NET提供了端對端的開發(fā)、調(diào)試手段,可以不拆卸設備的情況下通過Telnet登錄到WindowsCE上進行調(diào)試和維護,其系統(tǒng)本身為嵌入式市場進行重新設計,包括創(chuàng)建一個基于WindowsCE的定制設備所需的一切。這樣就需要將原來DOS下的程序移植到WindowsCE.NET下,但是各個硬件廠商目前還沒有提供CAN通信卡在Windows CE.NET下的驅(qū)動,所以開發(fā)Windows CE.NET下的CAN卡驅(qū)動成為項目推行中的關(guān)鍵一環(huán)。
本文主要針對研華的雙口CAN卡PCM3680進行分析,介紹在WindowsCE.ENT系統(tǒng)下進行底層設備驅(qū)動開發(fā)的方法并提供CAN通信的實例。
1 CAN總線通信協(xié)議及CAN通信卡介紹
CAN總線是德國Bosch公司20世紀80年代初為解決現(xiàn)代汽車中眾多的控制與測試儀器之間的數(shù)據(jù)交換而開的一種串行數(shù)據(jù)通信協(xié)議。它是一種多主總線,廢除了傳統(tǒng)的站地址編碼,而代之以對通信數(shù)據(jù)塊進行編碼。這種方法使網(wǎng)絡內(nèi)節(jié)點個數(shù)在理論上不受限制,擴展格式中的29位的標識碼便可以定義2 29個不同的數(shù)據(jù)塊。
在本項目中使用的是研華的PCM3680,這是一塊嵌入式PC104的雙口CAN總線通信卡;CAN控制器采用Philips的獨立CAN控制器SJA1000芯片;CAN收發(fā)器采用Philips的P82C250,可以同時操作兩個CAN網(wǎng)絡,提供高達1Mb/s的傳輸速度。PCM3680支持很寬的中斷范圍:中斷3、4、5、6、7、9、10、11、12、15,同時1000V的光電隔離提供系統(tǒng)高可靠性。在CAN卡通信中,要用到CAN控制器中的很多寄存器,各個寄存器的含義和作用可以參考控制芯片的說明書。圖1列出驅(qū)動程序設計中用到最主要的寄存器結(jié)構(gòu)。
2 CAN卡驅(qū)動底層函數(shù)設計
本方案設計CAN驅(qū)動是放在Windows CE操作系統(tǒng)的內(nèi)核下層,位于OEM adaptation layer(OAL)層的一個真正的驅(qū)動,而不是在主程序中的串口操作。在Windows CE的設備管理器可以看到CAN1和CAN2兩個端口,并且可以查看其工作的正常與否和對其進行配置。如:中斷號和I/O地址。
2.1 CAN卡寄存器讀寫函數(shù)
CAN卡的通信是通過操作CAN卡上的CAN控制器進行的。在CAN控制器中有很多寄存器,如控制寄存器、命令寄存器、狀態(tài)寄存器、中斷寄存器等,通過讀寫這些寄存器中的命令狀態(tài)字可以檢測和控制CAN卡的行為。在Windows CE.NET下,通過調(diào)用DOK中的API函數(shù)HalTranslateBusAddress,將CAN卡分配的物理地址映射為邏輯地址。這樣各個寄存器對應的就是CAN卡基地址的偏移地址,因此,對寄存器的讀寫就轉(zhuǎn)化為對內(nèi)存地址的讀寫。下面是CAN卡寄存器的讀寫函數(shù):
*在偏移量為off的地址讀取一個字節(jié)的數(shù)據(jù)inline BYTE CANR(LPCAN_HW_OPEN_INFO hCan,DWORD off)
{
return hCan->lpCanHWInfo->lpCanObj->lpMappedBaseAddr[off];
*將一個字節(jié)數(shù)據(jù)寫到偏移量為off的地址中inline VOID CANW(LPCAN_HW_OPEN_INFO hCan,DWORD off,BYTE val)
{
hCan->lpCanHWInfo->lpCanObj->lpMappedBaseAddr[off]=val;
}
參數(shù)LPCAN_HW_OPEN_INFO定義的是CAN卡的數(shù)據(jù)結(jié)構(gòu),其中成員lpMappeBaseAddr[0]表示的是映射后基地址,lpMappedBaseAddr[1]就是基地址+1的地址,對應CAN卡的寄存器是命令寄存器。通過上述兩個函數(shù)可操作CAN卡上的所有寄存器。
2.2 CAN卡初始化
CAN卡的控制器比較復雜,在通信前必須確認硬件信息正確性、初始化各寄存器。初始化函數(shù)的基本流程如圖3所示。
第一步,檢查端口號和硬件信息的正確性,主要是CAN卡中斷號是否有效。
第二卡,設置CAN卡默認參數(shù):
CanCardConfigInfo CAN_DEFAULT_SETTING=
{0X00,0XFF,0X03,0X1C};/*設置默認波特率為125Kbps*/
DWORD dwThreadID =0;
PHYSICAL_ADDRESS phyAddr={hwInfo->dwIOBaseAddr *16,0 };
第三卡,用WinCE API函數(shù)LocalAlloc為CAN卡驅(qū)動中用到的數(shù)據(jù)結(jié)構(gòu)分配緩沖區(qū);通過HalTranslateBusAddress和MmMapIoSpace函數(shù)映射I/O地址,提供直接訪問設備的虛擬地址:
if(!HalTranslateBusAddress(Isa,0,phyAddr,0,&phyAddr))
goto _ExitInit;
hCan->lpCanHWInfo->lpCanObj->lpMappedBaseAddr=
(LPBYTE)MmMapIoSpace(phyAddr,CANCARDADDRLEN,FALSE);
if(!hCan->lpCanHWInfo->lpCanObj->lpMappedBaseAddr)
goto _ExitInit;
如果分配內(nèi)存或映射邏輯地址失敗,則退出初始化程序,CAN卡初始化失敗。
第四步,初始化讀寫屬性、共享模式、讀超時時間和第二個CAN口的基地址。
第五步,創(chuàng)建CAN卡事件和數(shù)據(jù)接收事件:hCan->lpCanHWInfo->hCanEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
hCan->lpCanHWInfo->hRecvMsgEvent=CreateEvent(NULL,FALSE,FALSE,NULL);
第六步,初始化中斷,如果CAN卡有復位請求就退出初始化程序。設置好中斷后啟動數(shù)據(jù)接收線程,設置線程優(yōu)先級繼續(xù)線程處理;最后配置CAN卡參數(shù),進入正常運行狀態(tài)。
2.3 CAN卡信息發(fā)送
CAN卡的信息發(fā)送分為兩個步驟。在對CAN卡基本信息進行檢查后,首先設置發(fā)送緩沖的ID號。CAN標準模式的ID號為11位,偏移地址10中存放的是ID號的高8位,偏移地址11的高3位存放的是ID號的低3位,剩下5位分別是RTR位(遠程傳送請求位)和數(shù)據(jù)長度。通過CANW函數(shù)將處理后的數(shù)據(jù)寫入到相應的偏移地址,設置完相應的地址數(shù)據(jù)后,通過循環(huán)將偏移地址12~19的數(shù)據(jù)采集回來存到數(shù)組中。然后,設置CAN卡的傳輸請求為允許并不斷偵測狀態(tài)寄存器的變化,當傳輸緩沖滿標志或傳輸結(jié)束標志為1時通出程序,完成一次數(shù)據(jù)采集。傳輸緩沖區(qū)的寄存器如表1所列。
表1
ID號 | 10 | ID.10 | ID.9 | ID.8 | ID.7 | ID.6 | ID.5 | ID.4 | ID.3 |
RTR,數(shù)據(jù)長度碼 | 11 | ID.2 | ID.1 | ID.0 | RTR | DLC.3 | DLC.2 | DLC.1 | DLC.0 |
數(shù)據(jù)1~8 | 12~19 | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) |
表2
ID號 | 20 | ID.10 | ID.9 | ID.8 | ID.7 | ID.6 | ID.5 | ID.4 | ID.3 |
RTR,數(shù)據(jù)長度碼 | 21 | ID.2 | ID.1 | ID.0 | RTR | DLC.3 | DLC.2 | DLC.1 | DLC.0 |
數(shù)據(jù)1~8 | 22~29 | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) | 數(shù)據(jù) |
CAN消息發(fā)送函數(shù)的實現(xiàn)如下:
BOOL CAN_SendMessage(LPCAN_HW_OPEN_INFO hCan,LPCanCardMessageBuflpMsg)
{
BOOL bSuc=FALSE;
ASSERT(hCan && lpMsg && lpMsg->dwMessageLen <=8); /*防錯處理*/
if(0= =(hCan->dwAccessCode & GENERIC_WRITE))
return FALSE;
:: EnterCriticalSection(&hCan->lpCanHWInfo->
TransmitCritSec); /*進入臨界區(qū)*/
BYTE byV=static_cast<BYTE>(1pMsg->dwMsgID>>3);
CANW(hCan,10,byV); /*設置ID值高8位*/
byV=static_cast<BYTE>=((lpMsg->dwMsgID & 7)<<5);
if(lpMsg->bRTR) byV|=0x10;
byV+=static_cast<BYTE>(lpMsg->dwMessageLen);
CANW(hCan,11,byV);/*設置ID值低3位、RTR及數(shù)據(jù)長度*/
for(UINT i=0;<lpMsg->dwMessageLen;++i)
{
CANW(hCan,12+i,lpMsg->byMsg[i]);
} /*采集數(shù)據(jù)*/
CANW(hCan,1,1);/*重置傳輸請求*/
while(TRUE)
{byV=CANR(hCan,2);
if(byV & 0X40) /*傳輸緩沖區(qū)滿,退出*/
{break;}
if(byV & 0X8){ /*傳輸結(jié)束,正確返回退出*/
bSuc = TRUE;
break;}
}
::LeaveCriticalSection(&hCan->lpCanHWInfo->TransmitCritSec); /*離開臨界區(qū)*/
return bSuc;
}
2.4 CAN卡信息接收
CAN卡的信息接收是發(fā)送的逆過程,當接收緩沖區(qū)標志為1時,表示緩沖區(qū)已滿可以接收數(shù)據(jù),將數(shù)據(jù)接收到數(shù)組后釋放接收緩沖區(qū),然后對接收到的數(shù)據(jù)進行分解并存儲到CAN卡信息緩沖區(qū)的結(jié)構(gòu)體。接收緩沖區(qū)的寄存器結(jié)構(gòu)如表2所列。
CAN消息接收函數(shù)的實現(xiàn)如下:
BOOL CAN_RecvRecvMessage(LPCAN_HW_OPEN_INFO
HCan,OUT LPCanCardMessageBuflpMsg)
{……
if(CANR(hCan,2)&1){ /*判斷接收緩沖區(qū)是否已滿*/
for(UINT i=0;i<10;++i)
recvBuf[i]=CANR(hCan,20+i);/*將數(shù)據(jù)暫存到臨時緩沖區(qū)*/
CANW(hCan,1,4); /*釋放接收緩沖區(qū)*/
LpMsg->dwMsgID=recvBuf[0]<<3; /*取出ID的高8位*/
BYTE byV =recvBuf[1];
LpMsg->dwMsgID+=byV >>5;/*取出ID低3位,然后和高8位合并*/
LpMsg->bRTR =byV &0x10?TRUE:/*返回RTR狀態(tài)*/
LpMsg->dwMessageLen = byV &0XF; /*返回數(shù)據(jù)長度*/
……
}
else
{++hCan->lpCanHWInfo->dwErrorMsgCount;}/*沒有收到數(shù)據(jù),錯誤計數(shù)加1*/
::LeaveCriticalSection(&hCan->lpCanHWInfo->
ReceiveCritSec); /*離開臨界區(qū)*/
Return bSuc;
}
2.5 CAN卡事件處理
CAN卡事件處理函數(shù)是CAN卡驅(qū)動程序中很重要的部分。驅(qū)動設計要求具有消息通知的功能,當事件發(fā)生時及時捕獲事件并進行消息處理。
下面是事件處理函數(shù)的實現(xiàn):
staric DWORD WINAPI CAN_EventHanle(LPVOID lpParam)
{
ASSERT(lpParam);
LPCAN_HW_OPEN_INFO hCan=(LPCAN_HW_OPEN_INFO)lpParam;
CanCardMessageBuf bufMsg;
while(TEUE)
{ /*循環(huán)等待CAN卡消息產(chǎn)生,然后進行處理*/
::WaitForSingleObject(hCan->lpCanHWInfo->hCanEvent,0XFFFFFFFF);
if(hCan->lpCanHWInfo->bKillCanThread) break; /*若CAN線程已關(guān)閉則中斷*/
if(CAN_RecvMessage(hCan,&hufMsg)){ /*正確接收數(shù)據(jù)后*/
CAN_RecvBufPush(hCan,&bufMsg);} /*將數(shù)據(jù)壓入緩沖*/
BYTE byV=CANR(hCan,3); /*將3號寄存器讀出然后立即寫入*/
CANW(hCan,3,byV);/*能夠獲取每次中斷*/
InterruptDone(hCan->lpCanHWInfo->lpCanObj->dwSysIrqt);
} /*本次中斷結(jié)束,等待下次中斷*/
return 0;
}
2.6 其它函數(shù)
為了提供更多的功能和更方便地使用CAN卡進行通信,在CAN卡驅(qū)動程序中還設計了一些函數(shù)如CAN_Config用CAN卡信息配置、CAN_RecvBufPop用于處理接收緩沖區(qū)、CAN_Reset用于復位CAN卡、CheckHWInfo用于硬件信息檢查等。這些函數(shù)提供了對CAN通信卡的設置、檢查等功能,在這里不再詳述了。
3 CAN卡驅(qū)動封裝設計
CAN卡底層驅(qū)動函數(shù)雖然功能完整,但是對于用戶使用比較復雜并且一般用戶不需要了解底層實現(xiàn)的機制。為了便于使用,最后對CAN卡的驅(qū)動進行了封裝,提供CanOpenFile、CanSendMsg等五個函數(shù)用于CAN總線的通信,以動態(tài)連接庫(DLL)的形式提供給用戶調(diào)用。封裝函數(shù)及功能如下:
*CanOpenFile;初始化并打開CAN卡的一個端口。
*CanCloseFile;關(guān)閉由CanOpenFile打開的CAN卡端口。
*CanRecvMsg;接收CAN卡數(shù)據(jù),打開CAN卡時必須具有GENERIC_READ權(quán)限。
*CanSendMsg;通過CAN卡發(fā)送數(shù)據(jù)。打開CAN卡時必須具有GENERIC_WRITE權(quán)限。
*CanIOControl;設置或獲取CAN卡I/O參數(shù)支持的I/O控制包括:IOCTL_CAN_CONFIG,IOCTL_CAN_RESET,IOCTL_CAN_TIMEOUT,IOCTL_CAN_SENDREADY,IOCTL_CAN_RECVREADY。
下面是CanSendMsg函數(shù)實現(xiàn)的代碼:
BOOL CanSendMSg(
HANDLE hCan,
LPCanCardMessageBuflpMsg)
{
if(!hCan||INVALID_HANDLE_VALUE= =hCan||
!lpMsg||lpMsg->dwMessageLen>8)return FALSE;
return CAN_SendMessage(LPCAN_HW_OPEN_INFO)
hCan,lpMsg);
該函數(shù)就是通過封裝CAN卡的底層驅(qū)動函數(shù)SendMessage來實現(xiàn)的,這樣將功能集中的五個函數(shù)更方便了用戶使用。
結(jié)語
程序開發(fā)的上位機是普通的PC機,軟件環(huán)境是:Windows2000 Professional、Embedded Visual C++4.0、與下位機中WinCE.NET對應的SDK,該SDK是在用Platform Builder 4.0定制WinCE時編譯生成的。下位機使用的硬件是研華的嵌入式PC104主板PCM3346N,操作系統(tǒng)為WinCE.ENT。
本文設計開發(fā)的驅(qū)動已經(jīng)在北京懷柔的變電站項目中得到成功的應用,CAN卡通信穩(wěn)定,系統(tǒng)在WINCE.NET下運行可靠,保證了項目的順利實施。