王保和
(電子科技大學,四川 成都 611731)
嵌入式Linux下CAN總線驅動程序設計
王保和
(電子科技大學,四川 成都 611731)
嵌入式Linux下開發各類CAN總線設備,需設計相應的驅動程序,以CAN控制器MCP2510為例,詳細介紹了CAN總線控制器硬件接口設計,以及嵌入式Linux下設備驅動程序的開發流程和技巧,結合CAN控制器的特點,設計相關的數據結構和操作代碼來實現CAN控制器的設備驅動程序。
嵌入式Linux;CAN總線;S3C2440;設備驅動程序;MCP2510
CAN現場總線已經成為在儀表裝置通信的新標準,其在短距離條件下具有高達1Mbps的數據傳輸能力,由于其成本低,實時性好,抗干擾能力強,因此廣泛應用于車載數據采集系統及汽車電子控制網絡。與此同時,利用 ARM處理器具有高性能耗能低等優點和嵌入式 linux的多任務處理的能力,在ARM和嵌入式Linux平臺下開發CAN總線系統已經得到廣泛應用。本文主要介紹如何在基于ARM和嵌入式linux平臺下開發CAN總線控制器的驅動程序。
CAN節點主要有微控制器,CAN控制器,CAN收發器組成,在本設計中采用三星S3C2440作為微控制器,CAN控制器和收發器采用的是Microchip公司的MCP2510和MCP2551,S3C2440是一款ARM9TDMI內核的RISC處理器,CPU主頻最高可達500M,處理速度快,可滿足 CAN節點實時性高的需求,但由于其片內不帶CAN控制器,所以硬件上需要外擴CAN控制器MCP2510,MCP2510完全支持CAN1.2、CAN2.0A、CAN2.0B等版本的協議,能夠發送和接受標準和擴展報文,它還同時具備驗收濾波以及報文管理功能,它與微控制器的通訊是采用SPI口實現的,其 SPI口數據傳輸速率高達 5Mb/s,同時高速 CAN收發器MCP2551把 CAN控制器生成的數字信號轉化成為適合總線傳輸的差分信號,它也為CAN控制器和CAN總線的之間加入了緩沖器可以有效抑制高壓尖峰信號,具有很強的抗噪特性。在本設計中S3C2440被設置為SPI的主設備,MCP2551作為從設備,GPG6引腳控制對從設備的操作,MISO是主設備輸入從設備輸出,MOSI是主設備輸出從設備輸入,在這里為了減少I/O口占用,MCP2510采用通用中斷引腳與S3C2440的外部中斷1相連,MCP2510與S3C2440硬件接口電路如圖1所示:

圖1 MCP2510與S3C2440硬件接口電路
MCP2510是Microchip公司推出的功能很強的CAN控制器,該芯片包含三個發送緩存和兩個接收緩存,可以對發送優先級進行管理,可濾除無用信息,其主要功能是在 MCU的控制下實現 CAN規范,其內部的所有寄存器都映射在一個地址表上,MCU通過 SPI口發送相應的命令和數據來完成對MCP2510的初始化,工作狀態的控制以及數據的讀寫。在實現驅動程序之前需要完成對 MCP2510進行讀寫或控制的底層函數,而這些函數主要是根據 SPI口的通信時序完成 MCP2510的讀寫操作或狀態控制如下:比如MCP2510的SPI口寫時序如圖2所示,可以按照此時序實現對MCP2510進行寫操作的的底層函數。其他的操作以此類推。

圖2 MCP2510的SPI口寫時序
write2510(u8 cmd,u8 addr,u8 data)
{CS_L;//拉低GPG6引腳啟動對MCP2510的操作
iowrite8(cmd,SPTDAT0);//寫指令到 SPI 口
wait;//等待發送操作完成
iowrite8(addr,SPTDAT0);//寫地址到SPI口
wait;//等待發送操作完成
iowrite8(data,SPTDAT0);//寫數據到SPI口
wait;//等待發送操作完成
CS_H;//拉高GPG6引腳結束對MCP2510的操作
}
由于對MCP2510的控制是以字節為單位逐個進行I/O操作的設備,所以其屬于字符設備,驅動程序是用戶應用程序與硬件的接口,需要屏蔽設備的工作細節提供給用戶程序一系列的標準調用,其主要就是調用操作 MCP2510的底層函數實現open,read,write,ioctl,release等系統調用函數,來完成與內核的通信,為了方便用戶程序與驅動的交互,可以根據CAN控制器的工作特點定義MCP2510的設備結構體。MCP2510設備結構體的定義
struct MCP2510{
wait_queue_head_t read_wq;//讀進程的等待隊列
wait_queue_head_t write_wq;//寫進程的等待隊列
unsigned char en_read;//讀進程等待的條件
unsigned char en_write;//寫進程等待的條件
struct CANR_MSG rec_buf;//接收報文緩沖器
struct CANT_MSG tra_buf;//發送報文緩沖器
struct SET_FILTER set_filter; //MCP2510寄存器配置
struct cdev cdev; // 字符設備結構體
}
CANR_MSG結構體作為接收報文的緩沖區,CANR_MSG結構體作為發送報文的緩沖區,en_read是讀進程等待隊列的等待標志,en_write是寫進程等待隊列的等待標志,在驅動程序中運用這種機制可以很好地解決與應用程序通信的問題,定義 SET_FILTER結構體是為了用戶應用程序可以配置MCP2510。
1.open函數的實現
open函數實現對S3C2440的SPI口的初始化,以及通過SPI口對MCP2510的寄存器進行相應的初始化。包括CAN總線波特率的設置,設置報文濾波以及屏蔽寄存器,開啟中斷使能等。
2.ioctl函數的實現
該函數在 linux中標準定義是 int (*ioctl)(struct inode* inode,struct file* filp,unsigned int cmd,unsigned long arg),用戶程序可以通過傳遞參數給該函數實現對MCP2510的控制,其中形參cmd是命令,arg是命令的參數,cmd可以控制CAN控制器復位,進入各種模式,如配置,監聽,正常,回環,睡眠等模式,還可以配置有關報文濾波的寄存器,因此可實現用戶程序動態配置所需要接收的報文,其中arg是用戶程序傳遞的一個指向SET_FILTER結構體的用戶空間指針。
3.read函數的實現
當用戶進程調用read函數讀取接收的報文時,必須等待en_read這個變量變為 1,如果為 1的話,就會調用copy_to_user函數將儲存在內核空間的報文數據(即設備結構體MCP2510里面的CANR_MESSAGE)拷貝到用戶空間,否則該進程就會進入 read_wq等待隊列,直到接收中斷程序中喚醒它。
4.write函數的實現
當用戶進程調用 write函數發送報文時,將同樣需要等待 en_write變量,當發送完一幀報文后中斷程序就會將en_write變量置位,這時將調用 copy_from_user 函數將用戶空間的報文數據拷貝到內核空間的發送緩沖區(即設備結構體MCP2510里面的CANT_MESSAGE),然后將MCP2510中的發送緩沖控制寄存器(TXBnCTL)的TXREQ置位,啟動報文發送請求。
5.release函數的實現
關閉設備,以及釋放申請的中斷號和分配的內存空間。
6.中斷函數的實現
Linux內核將所有的中斷統一編號,使用一個 irq_desc結構數組來描述這些中斷;每個數組項對應一個中斷號,里面記錄了中斷處理函數入口,底層的硬件訪問接口,中斷狀態等,嵌入式 linux內核會維護一個中斷信號線注冊表,所以在使用中斷前必須先申請中斷號,使用完之后要釋放該中斷號,這里使用的就是函數 request_irq(IRQ_EINT1,can_interrupt,IRQT_FALLING,DEVICE_NAME,dev_id);來注冊一個中斷處理程序,IRQ_EINT1是所要申請的中斷號,can_interrupt是中斷處理函數的指針,IRQT_FALLING是中斷觸發的方式,這里選擇的是下降沿觸發,DEVICE_NAME是產生中斷的設備名稱,dev_id主要用于共享中斷線,當一個中斷處理程序需要釋放時,內核可以根據該參數找到中斷處理函數鏈表中的需要刪除的中斷處理程序, 由于 MCP2510的中斷比較多,所以在中斷處理程序需要處理各緩沖器的接收和發送中斷以及喚醒和錯誤處理。中斷函數代碼片段如下:
icode=read2510(0x0e)&0x0e;//讀取CANSTA寄存器
switch(icode)
{case 0x0c://接收緩沖器0中斷
for(i=1;i<=13;i++)
{rbaddr=(icode<<3)+i;
temp=read2510(rbaddr);
*((unsigned char *)(&mcp2510->rbuf)+i-1)=temp;
}//拷貝接收到的報文ID和數據到設備結構體的緩沖區
mcp2510->en_read=1;//將讀使能標志置1
modbits2510(0x2c,0x01,0x00); //清除中斷標志
wake_up_interruptible(&mcp2510->read_wq);//喚醒正在等待的讀進程
break;
case 0x0a://接收緩沖器1中斷
…...
}
上述代碼中icode保存的是從MCP2510的CANSTA寄存器中讀取的中斷信息,將icode值左移3位即可得到接收緩沖器0在CAN控制器中的寄存器映射地址,然后將接收到的報文ID和數據拷貝到設備結構體中的CANR_MSG類型緩沖器,然后喚醒讀進程,用戶應用程序就可以得到底層硬件接收到的報文數據。其他的中斷都可以按這種方法實現。
Linux驅動程序的開發有兩種實現方式,一種是直接將相關的驅動程序編譯進內核,但這樣勢必會造成內核非常龐大,而且在開發中非常不方便,每次得重新燒寫內核鏡像。另外一種就是將具體的設備驅動程序獨立編譯,成為可安裝的模塊,可以動態地加載和卸載模塊。本文采用后一種方式,因此需要實現 init_module和 cleanup_module函數,init_module函數會調用 register_chrdev函數來申請設備號,同時動態地給設備結構體分配空間,而cleanup_module函數則實現在卸載模塊時的釋放內存空間,注銷主設備號,以及釋放中斷號等。
在編寫完嵌入式 linux驅動程序后,需要將驅動程序添加到內核中,然后編譯內核,具體步驟如下:
1.將驅動程序 mcp2510.c文件拷貝到內核的drivers/char目錄下,該目錄下是字符設備的設備驅動程序。
2.修改drivers/char目錄下的Kconfig文件,添加如下腳本:
config MCP2510
tristate 〝CAN controler driver〞
default m
help
XXXXXXXX
添加這段腳本意味著在配置內核時,在字符設備驅動的配置菜單中就會有相應的 MCP2510這個選項,這個驅動模塊可以配置為編譯進內核,編譯為模塊,要么不編譯,在這里默認的是編譯為模塊,“help”后面的內容為幫助信息。在內核頂層目錄下運行make menuconfig 后會生成.config文件,該文件決定文件是否編譯進內核或模塊。
3.修改drivers/char目錄下的Makefile文件,在其中添加obj-$(CONFIG_MCP2510)=
MCP2510.o,在這里CONFIG_MCP2510的變量值為m,表示該文件要作為模塊編譯,可以在第二步生成的.config文件中找到該變量的賦值。
4.在內核源代碼頂層目錄下運行make modules命令,就會在drivers/char目錄下生成MCP2510.ko文件。
現在將驅動程序MCP2510.ko和用戶測試程序從PC交叉編譯平臺拷貝到arm+linux平臺下,也可以通過NFS服務掛載宿主機下的目錄,這樣更易于調試,運行 insmod MCP2510.ko這時可以查看/proc/devices文件可以看到加載的設備信息,查看/proc/interrupts文件可以看到申請的中斷號信息,當這些都沒問題時就可以運行用戶測試程序./mcp2510_test.o。
本文介紹了基于ARM平臺的CAN總線設備的硬件接口設計以及在嵌入式linux系統下開發CAN控制器的驅動程序的開發流程,結合 CAN控制器的工作特點,合理地設計了數據結構以及使用了等待隊列的機制和使用緩沖區的控制方法。經過應用程序測試證明該驅動可以正確運行。同時也給在嵌入式Linux操作系統下開發類似的接口驅動程序提供了參考。
[1] 王黎明,夏立,邵英,等.CAN 現場總線系統的設計與應用[M].北京:電子工業出版社,2008.
[2] 宋寶華.Linux設備驅動開發詳解[M].北京:人民郵電出版社,2008.
[3] 饒運濤,王進宏,等.現場總線 CAN 原理與應用技術[M].北京:北京航空航天大學出版社,2007.
[4] 鄭靈翔.嵌入式接口技術與 Linux驅動開發[M].北京:北京航空航天大學出版社,2010.
TP336
A
1008-1151(2011)06-0019-02
2011-04-26
王保和,電子科技大學碩士研究生,研究方向為嵌入式系統。