uClinux下CAN匯流排控制器驅動程序的設計

admin @ 2014-03-25 , reply:0

    CAN匯流排技術是現今流行的一種先進的現場匯流排技術,可以有效的支持分散式控制和實時控制的串列通信網路。由於CAN匯流排具有通信速率高,可靠性高,連接方便和性能價格比高等諸多優點,因此在嵌入式系統開發中有普遍的應用。目前,CAN匯流排通信控制晶元眾多,要在uClinux平台下開發基於CAN匯流排的應用系統,就需要自己開發uClinux下的驅動程序。本文將基於一個CAN 匯流排在汽車電子中的應用詳細介紹在uClinux下CAN匯流排控制器驅動程序的設計過程。

1 系統硬體結構
    本嵌入式系統主要的硬體組成為:處理器採用三星公司的S3C44B0X,CAN匯流排控制器和收發器分別採用MicroChip公司的MCP2510和MCP2551。開發一個uClinux的驅動,在熟悉uClinux內核結構之外,大量的工作在於閱讀相應的控制晶元手冊。硬體信息決定驅動的主要結構。S3C44B0X 採用的是ARM公司的16/32位ARM7TDMI內核,它是三星公司為一般應用提供的高性價比和高性能的微控制器解決方案,特別適合對成本和功耗敏感的應用場合。MCP2510是一款帶有符合工業標準的SPI介面的CAN匯流排控制晶元,它支持CAN技術規範V2.0A/B,並能夠發送和接收標準的和擴展的信息幀,同時具有接收濾波和信息管理的功能。MCP2510在目前市場上是體積最小、最易於使用也是最節約成本的獨立CAN控制器。MCP2551是與MCP2510相配的高速CAN匯流排收發器,它擔負著節點和匯流排之間接收和發送電平轉換的任務。MCP2510通過SPI介面與S3C44B0X進行數據傳輸,最高數據傳輸數率可達5 Mb/s。MCP2510再通過CAN收發器連接到CAN匯流排上,CAN匯流排上可以掛接多個節點,S3C44B0X通過MCP2510與CAN 匯流排上的其它微處理器進行通信。MCP2510內含3個發送緩衝區和兩個接收緩衝區,同時具有靈活的中斷管理能力,幀屏蔽和過濾、幀優先順序設定等特性,這使得微處理器對CAN 匯流排的操作變得非常簡便。系統原理如圖1所示。
 
圖1 嵌入式應用系統結構

2 CAN匯流排應用系統的軟體設計
2.1 嵌入式操作系統選擇uClinux
    uClinux是Linux2.0版本得一個分支,被設計用在微型控制應用領域。uClinux具備標準Linux系統的穩定性,並且支持Linux內核約定的全部特性。uClinux同標準Linux的最大的區別就是在於內存管理。標準Linux是針對有內存管理單元(memory management unit,MMU)的處理器設計的。在這種處理器上,虛擬地址被送到MMU,MMU把虛擬地址映射為物理地址。嵌入式應用對成本和實時性敏感,其使用的CPU中有很多都沒有MMU,例如本系統採用的S3C44B0X就是一款不帶MMU 的微處理器。標準Linux無法適用於這部分嵌入式應用。uClinux通過對標準Linux中內存管理的改寫,去掉了對MMU的依賴,保存了Linux內核的大多數優點,因此它在嵌入式應用中有很好的前景。uClinux的應用主要體現在驅動程序的編寫和上層應用軟體的編寫。所以,針對CAN匯流排控制器MCP2510的驅動程序需要我們自己編寫。
2.2 CAN匯流排控制器驅動程序編寫
    驅動程序是應用程序與硬體之間的一個中間軟體層。它使某個特定的硬體響應一個定義良好的內部編程介面,同時完全隱蔽了設備的工作細節。用戶通過一組標準化的調用來完成相關操作,這些標準化的調用是和具體設備驅動無關的,而驅動程序的任務就是把這些調用映射到具體設備對於實際硬體的特定操作上。驅動程序應該為應用程序展現硬體的所有功能,不應該強加其它的約束,對於硬體使用的許可權和限制應該由應用程序層控制。驅動程序設計主要需要考慮下面3個方面:提供盡量多的選項給用戶;提高驅動程序的速度和效率;盡量使驅動程序簡單,使之易於維護。
    uClinux支持的設備驅動可分為3種:字元設備,塊設備,網路介面設備。MCP2510就屬於字元設備。字元設備是uClinux中最簡單的設備,所謂字元設備就是以位元組為單位逐個進行I/O操作的設備。在uClinux中它們被映射為文件系統的一個節點,這個設備就像是一個普通文件,應用程序使用標準系統調用對它進行打開(open)、讀取(read)、寫入(write)和關閉(release)等操作。
2.2.1 驅動程序中定義的主要數據結構
    驅動程序中讀寫函數需要傳輸多個CAN消息,我們根據CAN通信協議和系統應用的需要,設計一個稱為CanData的結構體來定義所傳輸的數據
struct{
unsigned int id;
unsigned char data[8];
unsigned char dlc;
int IsExt;
int rxRTR;
)CanData;
其中id為CAN消息ID號;data是要傳輸的消息數據,最大是8個位元組;dlc表示實際傳輸的數據長度,取值範圍為0到8;IsExt是判斷CAN消息是否使用擴展ID;rxRTR是判斷該消息是數據幀還是遠程幀。
    MCP2510中有3個發送緩衝區,可以循環使用,也可以只使用一個發送緩衝區,但是必須保證在發送的時候,前一次的數據已經發送結束。兩個接收緩衝區也是一樣。處理器通過SPI介面對緩存區進行讀取和寫入。MCP2510對CAN匯流排的數據發送沒有限制,只要用處理器通過SPI介面將待發送的數據寫入MCP2510的發送緩存區,然後再調用RTS(發送請求)命令即可將數據發送到CAN匯流排上。而對CAN匯流排上的數據接收是通過兩個接收緩衝區,兩個接收屏蔽器,6個接收過濾器的組合來實現的。CAN匯流排上的幀只有同時滿足至少任意一個接收屏蔽器和一個接收過濾器的條件才可以進入接收緩衝區。這裡定義了一個MCP2510 REV的數據結構,用於記錄接收緩衝區運行的各種狀態
struct{
CanDate MCP2510_Candata[128];
int nCanRevpos;
int nCanReadpos;
int loopbackmode;
wait_queue_head_t wq;
spinlock_t lock;
)MCP2510_REV;
    該結構中首先定義了一個接收緩衝區,nCanRevpos和nCanReadpos分別表示接收緩衝區和用戶讀取緩衝區數據的狀態;loopbackmode表示支持迴環模式,該模式可使器件內部發送緩衝器和接受緩衝器之間進行報文自發自收;wq定義的是一個等待隊列,包含一個鎖變數和一個正在睡眠進程鏈表,作用是當有好幾個進程都在等待某件事時,uClinux會把這些進程記錄到這個等待隊列;lock定義的是自旋鎖,自旋鎖是基於共享變數來工作的,函數可以通過給某個變數設置一個特殊值來獲得鎖。而其它需要鎖的函數則會循環查詢鎖是否可用,作用是實現互斥訪問。
2.2.2 驅動程序的介面
    驅動程序的介面主要分為3部分:
①與設備的介面,完成對設備的讀寫等操作;
②與內核通信的介面,由file_operations數據結構完成;
③與系統啟動代碼的介面,完成對設備的初始化。
    uClinux繼承了LimLx操作系統下用戶對設備的訪問方式。設備驅動與內核通信時使用的是設備類型,主次設備號等參數,但是對於應用程序的用戶來說比較難於理解和記憶,所以uClinux使用了設備文件的概念。它抽象了對硬體文件的管理,為用戶程序提供了一個統一的、抽象的虛擬文件系統(virtual file system,VFS)界面。如圖2所示,VFS主要由一組標準的,抽象的文件操作構成,以系統調用的形式提供給用戶,如open()、release()、read()、write()、ioctl()等。
 
圖2 文件層次結構
    內核是通過主設備號這個變數將設備驅動程序和設備文件相連的,而構成驅動程序的一個重要數據結構就是file_operations,內核就是通過這個結構來訪問驅動程序的。file_operations結構定義於linux/fs.h文件中,它包含指向驅動程序內部大多數函數指針,它的每一個成員名稱對應著一個系統調用。系統引導時,內核調用每一個驅動程序的初始化函數,將驅動程序的主設備號以及程序內部的函數地址結構的指針傳輸給內核。這樣,內核就能通過設備驅動程序的主設備號索引訪問驅動程序內部的子程序,完成打開,讀寫等操作。下面給出了file_operations結構體中的一些主要成員
struct file_operations{
struct module *owner;//module的擁有者
loff_t(*llseek)(struct file *,loff_t,int);//移動文件指針的位置,只能用於可以隨機存取的設備
ssize_t(*write)(struct file *,const char *,size_t,loff_t *);//向字元設備中寫入數據
ssize_t(*read)(struct file *,char *,size_t,loft_t *);//從設備中讀取數據,與write類似
unsigned int(*poll)(struct file *,struct poll_table_struct *);
//用於查詢設備是否可讀寫或處於某種狀態
int(*ioctl)(struct inode *,struct file *,unsigned int,unsigned long);//控制設備,除讀寫操作外的其它控制命令
int(*mmap)(struct file *,struct vm_area_struct *);//用於把設備的內容映射到地址空間
int(*lock)(struct file *,int,struct file lock*);//文件鎖定,用於文件共享時的互斥訪問
int(*open)(struct inode *,struct file *);//打開設備進行I/O操作
int(*release)(struct inode *,struct file *);//關閉設備並釋放資源
……
};//file_operations結構中的成員全部是函數指針,所以實質上就是函數跳轉表
    由於在file_operations結構中,每個欄位都必須指向驅動程序中實現特定操作的函數,可以想象,隨著內核新功能不斷的增加,file_operations結構也就會變得越開越龐大。所以,現在通常採用“標記化”的方法來為該結構初始化,即對驅動中用到的函數記錄到相應欄位中,沒用到的就不管,這樣代碼就精簡了許多。代碼片斷如下
static struct file_operations s3c44b0x_fops={
owner: THIS_MODULE,
write: mcp2510_write,
read: mcp2510_read,
ioctl: mcp2510_ioctl,
open: mcp2510_open,
release: mcp2510_release,
};
    要注意的是這種表示方法不是標準C語法,而是uClinux下GNU 編譯器的一種特殊擴展。它使用函數名對各結構欄位初始化,好處是結構清晰,易於理解,並且避免了結構發生變化帶來的許多問題。上面代碼中,owner聲明模塊的擁有者,mcp2510_write和mcp_2510_read負責對緩衝區讀寫數據,mcp2510_open負責打開CAN匯流排控制器,並清空3個發送緩衝區,mcp2510_release負責關閉CAN匯流排控制器,mcp2510_ioctl負責向CAN匯流排控制器發送各種控制命令。mcp2510write()代碼片段編寫如下
static ssize_t mcp2510 write(struct file*file,const char *buffer,size_t count,loff_t *ppos)
//*flip為打開的文件,*bufer為數據緩存,count為請求傳送數據長度,*ppos為用戶在文件中進行存儲操作的位置
{char sendbufer[sizeof(CanData)];
if(count==sizeof(CanData)){//根據發送數據的長度,以兩種模式發送數據
copy_from_user(sendbufer,bufer,sizeof(CanData));
//將數據從應用數據空間拷貝到內核
MCP2510_canWrite((PCanData)sendbufer);
……
}
if(count>8)
return 0;
copy_from_user(sendbufer,bufer,count);
MCP2510_canWriteData(sendbufer,count);
}
2.2.3 驅動程序的初始化與設備註冊
    定義並初始化完成file_operations結構后,下面必須定義一個初始化函數,這裡我們定義了一個名為mcp2510_init()的函數。在uClinux初始化的時候要調用該初始化函數。初始化函數要完成的任務很多,主要有以下幾點:
(1)初始化設備相關的參數。對MCP2510來說,這裡主要完成CAN匯流排波特率的設置,ID過濾器的設置,清空接收和發送緩衝區,開啟中斷等工作。
(2)註冊設備。註冊設備所使用的函數原型是
int register_chrdev(unsigned int major,const char *name,struct file_operations *fops)
    其中major是主設備號,name是設備名稱,fops就是內核訪問設備的介面。前面提到uClinux內核是通過主設備號將設備驅動程序和設備文件相連。uClinux支持的主設備號有限,2.0以前版本的內核支持l28個主設備號,在2.4版內核中已經增加到256個。為了不造成使用上的混亂,對主設備號的分配常常採用動態分配的方法,即在用register_chrdev()註冊模塊時,給major參數賦值為0,這樣系統就會在所有未被使用的設備號中為我們選定一個,作為函數的返回值返回給我們。註冊設備代碼片斷編寫如下
#define DEVICE_NAME "s3c44b0x-mcp2510"
……
ret=register_chrdev(0,DEVICE_NAME,&s3c44b0x_fops);
(3)註冊設備使用的中斷。因為中斷信號往往是通過特定的中斷信號線傳輸的,任何一款晶元留給中斷信號的介面都是有限的,所以內核會維護一個中斷信號線註冊表,模塊要使用中斷就得向它申請一個中斷通道,當它使用完該通道之後要釋放該通道。這裡使用的就是函數
request_irq(IRQ_MCP2510,s3c44b0x_isr_mcp2510,SA_INTERRUPT,DEVICE_NAME,isr_mcp2510);
其中IRQ_MCP2510是請求的中斷號;s3c44b0x_isr_mcp2510是中斷處理函數的指針;SA_INTERRUPT是一個與中斷管理有關的位掩碼選項:DEVICE_NAME是設備名,它被用來在/proc/interrupts中顯示中斷擁有者;isr_mcp2510是一個惟一的標誌符,通過該指針多個設備可共享信號線,驅動程序也可用它指向自己的私有數據區,用來識別哪個設備產生了中斷。

3 設備驅動程序的編譯和添加
    在uClinux下,對驅動程序的編譯添加一般有兩種方式。可以靜態編譯進內核,再運行新的內核來測試;也可以編譯成模塊在運行時載入。第1種方法效率較低,但在某些場合是惟一的方法。模塊方式調試效率很高,它使用insmod工具將編譯的模塊直接插入內核,如果出現故障,可以使用rmmod從內核中卸載模塊。不需要重新啟動內核,這使驅動調試效率大大提高。但嵌入式系統是針對具體應用的,所以一般採用將設備驅動程序以靜態的方法編譯進內核。具體步驟如下:
(1)將驅動mcp2510.c添加到/uclinux_dist/linux/drivers/char目錄之下;
(2)修改該目錄下的mem.c文件;在int chr_dev_init()函數中增加如下代碼:
#ifdef CONFIG_S3C44B0X_MCP2510
mcp2510_init();
#endif
(3)修改該目錄下的Makefile文件;增加如下代碼:
ifeq($(CONFIG_S3C44B0X_MCP2510),y)
L_OBJS+=mcp2510.o
endif
(4)修改/uclinux dist/linux/arch/armnommu目錄下config.in文件;在comment'Characterdevices'語句下面加上:
bool'Add CAN Controller MCP2510'CONFIG_S3C44B0X_MCP2510
(5)編譯內核。我們在配置字元設備時就會有選項Add CAN Controller MCP2510,當選中這個選項的時候,設備驅動就加到內核中去了。編譯成功后,就可以像使用普通文件一樣對設備進行操作,在編寫的應用程序中先打開設備再讀寫數據。

4 結束語
    本文詳細介紹了在嵌入式操作系統uClinux下CAN匯流排控制器MCP2510驅動程序的設計開發過程。該驅動程序根據CAN匯流排的通訊協議和匯流排控制器MCP2510的工作特點,合理的設計了數據結構和緩存區控制方法,並結合uClinux下驅動程序編寫的一般規則,編寫了相關的操作代碼,實踐證明該驅動程序正確可行。CAN匯流排是一種廣泛應用的優秀現場匯流排技術,再藉助源碼開放的uClinux在嵌入式開發中的特點與優勢,開發工作者就可以靈活快捷的開發各類相關產品。




[admin via 研發互助社區 ] uClinux下CAN匯流排控制器驅動程序的設計已經有1765次圍觀

http://cocdig.com/docs/show-post-42087.html