Linux CAN編程詳解


CAN總線
CAN 是控制器局域網絡(Controller Area Network,CAN)的簡稱,由德國BOSCH公司開發,並
最終成為國際標准(ISO 11898-1)。CAN總線主要應用於工業控制和汽車電子領域,是國際上應用最廣
泛的現場總線之一。
1 CAN總線簡介
CAN 總線是一種串行通信協議,能有效地支持具有很高安全等級的分布實時控制。CAN 總線的應
用范圍很廣,從高速的網絡到低價位的多路接線都可以使用CAN。在汽車電子行業里,使用CAN 連接
發動機的控制單元、傳感器、防剎車系統等,傳輸速度可達1 Mbps。
與前面介紹的一般通信總線相比,CAN總線的數據通信具有突出的可靠性、實時性和靈活性,在汽
車領域的應用最為廣泛,世界上一些著名的汽車制造廠商 都采用CAN 總線來實現汽車內部控制系統與
各檢測和執行機構之間的數據通信。目前,CAN總線的應用范圍已不僅僅局限於汽車行業,而且已經在
自動控制、航 空航天、航海、過程工業、機械工業、紡織機械、農用機械、機器人、數控機床、醫療器
械及傳感器等領域中得到了廣泛應用。
CAN 總線規范從最初的CAN 1.2 規范(標准格式)發展為兼容CAN 1.2 規范的CAN 2.0 規范
(CAN 2.0A為標准格式,CAN 2.0B為擴展格式),目前應用的CAN器件大多符合CAN 2.0規范。
2 CAN總線的工作原理
當CAN 總線上的節點發送數據時,以報文形式廣播給網絡中的所有節點,總線上的所有節點都不
使用節點地址等系統配置信息,只根據每組報文開頭的11位標識符(CAN 2.0A規范)解釋數據的含義來
決定是否接收。這種數據收發方式稱為面向內容的編址方案。
當某個節點要向其他節點發送數據時,這個節點的處理器將要發送的數據和自己的標識符傳送給該
節點的CAN總線接口控制器,並處於准備狀態;當收到總 線分配時,轉為發送報文狀態。數據根據協
議組織成一定的報文格式后發出,此時網絡上的其他節點處於接收狀態。處於接收狀態的每個節點對接
收到的報文進行檢 測,判斷這些報文是否是發給自己的以確定是否接收。
由於CAN 總線是一種面向內容的編址方案,因此很容易建立高水准的控制系統並靈活地進行配置
我們可以很容易地在CAN總線上加進一些新節點而無須在硬件或軟件上進行修改。
當提供的新節點是純數據接收設備時,數據傳輸協議不要求獨立的部分有物理目的地址。此時允許
分布過程同步化,也就是說,當總線上的控制器需要測量數據時,數據可由總線上直接獲得,而無需每
個控制器都有自己獨立的傳感器。
3 CAN總線的工作特點
CAN總線的有以下三方面特點:
可以多主方式工作,網絡上的任意節點均可以在任意時刻主動地向網絡上的其他節點發送信息,而
不分主從,通信方式靈活。
網絡上的節點(信息)可分成不同的優先級,可以滿足不同的實時要求。
采用非破壞性位仲裁總線結構機制,當兩個節點同時向網絡上傳送信息時,優先級低的節點主動停
止數據發送,而優先級高的節點可不受影響地繼續傳輸數據。
4 CAN總線協議的層次結構
與前面介紹的簡單總線邏輯不同,CAN是一種復雜邏輯的總線結構。從層次上可以將CAN總線划
分為三個不同層次:
(1) 物理層
在物理層中定義實際信號的傳輸方法,包括位的編碼和解碼、位的定時和同步等內容,作用是定義
不同節點之間根據電氣屬性如何進行位的實際傳輸。
在物理連接上,CAN總線結構提供兩個引腳--CANH和CANL,總線通過CANH和CANL之間
的差分電壓完成信號的位傳輸。
在不同系統中,CAN總線的位速率不同;在系統中,CAN總線的位速率是唯一的,並且是固定的,
這需要對總線中的每個節點配置統一的參數。
(2) 傳輸層
傳輸層是CAN總線協議的核心。傳輸層負責把接收到的報文提供給對象層,以及接收來自對象層的
報文。傳輸層負責位的定時及同步、報文分幀、仲裁、應答、錯誤檢測和標定、故障界定。
(3) 對象層
在對象層中可以為遠程數據請求以及數據傳輸提供服務,確定由實際要使用的傳輸層接收哪一個報
文,並且為恢復管理和過載通知提供手段。
5 CAN總線的報文結構
CAN總線上的報文傳輸由以下4 個不同的幀類型表示和控制。
(1) 數據幀
數據幀攜帶數據從發送器至接收器。總線上傳輸的大多是這種幀。從標識符長度上,又可以把數據
幀分為標准幀(11位標識符)和擴展幀(29位標識符)。
數據幀由7個不同的位場組成:幀起始、仲裁場、控制場、數據場、CRC 場、應答場、幀結束。其
中,數據場的長度為0~8個字節。標識符位於仲裁場中,報文接收節點通過標識符進行報文濾波。幀結
構如圖所示。
(2) 遠程幀
由總線上的節點發出,用於請求其他節點發送具有同一標識符的數據幀。當某個節點需要數據時,
可以發送遠程幀請求另一節點發送相應數據幀。與數據幀相比,遠程幀沒有數據場,結構如圖所示。
(3) 錯誤幀
任何單元,一旦檢測到總線錯誤就發出錯誤幀。錯誤幀由兩個不同的場組成,第一個場是由不同站
提供的錯誤標志的疊加(錯誤標志),第二個場是錯誤界定符。幀結構如圖所示。
4. 過載幀
過載幀用於在先行的和后續的數據幀(或遠程幀)之間提供附加延時。過載幀包括兩個場:過載標志和
過載界定符。幀結構如圖所示。
6 CAN總線配置
在Linux系統中,CAN總線接口設備作為網絡設備被系統進行統一管理。在控制台下, CAN總線
的配置和以太網的配置使用相同的命令。
在控制台上輸入命令:
ifconfig –a
可以得到以下結果:
can0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
NOARP MTU:16 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:10
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) Interrupt:18
eth0 Link encap:Ethernet HWaddr 00:50:c2:22:3b:0e
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
eth1 Link encap:Ethernet HWaddr 00:50:c2:22:3b:60
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
Interrupt:41 Base address:0xe000 lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:256 errors:0 dropped:0 overruns:0 frame:0
TX packets:256 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0 RX bytes:19952 (19.9 KB) TX bytes:19952 (19.9 KB)
在上面的結果中,eth0和eth1設備為以太網接口,can0設備為CAN總線接口。接下來使用ip命
令來配置CAN總線的位速率:
ip link set can0 type cantq 125 prop-seg 6phase-seg1 7 phase-seg2 2 sjw 1
也可以使用ip命令直接設定位速率:
ip link set can0 type can bitrate 125000
當設置完成后,可以通過下面的命令查詢can0設備的參數設置:
ip -details link show can0
當設置完成后,可以使用下面的命令使能can0設備:
ifconfig can0 up
使用下面的命令取消can0設備使能:
ifconfig can0 down
在設備工作中,可以使用下面的命令來查詢工作狀態:
ip -details -statistics link show can0
7 CAN總線應用開發接口
由於系統將CAN設備作為網絡設備進行管理,因此在CAN總線應用開發方面,Linux提供了
SocketCAN接口,使得CAN總線通信近似於和以太網的通信,應用程序開發接口更加通用,也更加靈
活。
此外,通過https://gitorious.org/linux-can/can-utils 網站發布的基於SocketCAN的can-utils工具
套件,也可以實現簡易的CAN總線通信。
下面具體介紹使用SocketCAN實現通信時使用的應用程序開發接口。
(1) 初始化
SocketCAN中大部分的數據結構和函數在頭文件linux/can.h 中進行了定義。CAN總線套接字的
創建采用標准的網絡套接字操作來完成。網絡套接字在頭文件sys/socket.h中定義。套接字的初始化方
法如下:
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//創建SocketCAN套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr);//指定can0設備
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //將套接字與can0綁定
(2)數據發送
在數據收發的內容方面,CAN總線與標准套接字通信稍有不同,每一次通信都采用can_ frame結
構體將數據封裝成幀。結構體定義如下:
struct can_frame {
canid_t can_id;//CAN標識符
__u8 can_dlc;//數據場的長度
__u8 data[8];//數據
};
can_id為幀的標識符,如果發出的是標准幀,就使用can_id的低11位;如果為擴展幀,就使用0
~28位。can_id的第29、30、31位是幀的標志位,用來定義幀的類型,定義如下:
#define CAN_EFF_FLAG 0x80000000U //擴展幀的標識
#define CAN_RTR_FLAG 0x40000000U //遠程幀的標識
#define CAN_ERR_FLAG 0x20000000U //錯誤幀的標識,用於錯誤檢查
數據發送使用write函數來實現。如果發送的數據幀(標識符為0x123)包含單個字節(0xAB)的數據,
可采用如下方法進行發送:
struct can_frame frame;
frame.can_id = 0x123;//如果為擴展幀,那么frame.can_id = CAN_EFF_FLAG | 0x123;
frame.can_dlc = 1; //數據長度為1
frame.data[0] = 0xAB; //數據內容為0xAB
int nbytes = write(s, &frame, sizeof(frame)); //發送數據
if(nbytes != sizeof(frame)) //如果nbytes不等於幀長度,就說明發送失敗
printf("Error\n!");
如果要發送遠程幀(標識符為0x123),可采用如下方法進行發送:
struct can_frame frame;
frame.can_id = CAN_RTR_FLAG | 0x123;
write(s, &frame, sizeof(frame));
(3) 數據接收
數據接收使用read函數來完成,實現如下:
struct can_frame frame;
int nbytes = read(s, &frame, sizeof(frame));
當然,套接字數據收發時常用的send、sendto、sendmsg以及對應的recv函數也都可以用於CAN
總線數據的收發。
4. 錯誤處理
當幀接收后,可以通過判斷can_id中的CAN_ERR_FLAG位來判斷接收的幀是否為錯誤幀。如果
為錯誤幀,可以通過can_id的其他符號位來判斷錯誤的具體原因。
錯誤幀的符號位在頭文件linux/can/error.h中定義。
5. 過濾規則設置
在數據接收時,系統可以根據預先設置的過濾規則,實現對報文的過濾。過濾規則使用can_filter結構體
來實現,定義如下:
struct can_filter {
canid_t can_id;
canid_t can_mask;};
過濾的規則為:
接收到的數據幀的can_id &mask== can_id & mask
通過這條規則可以在系統中過濾掉所有不符合規則的報文,使得應用程序不需要對無關的報文進行
處理。在can_filter結構的can_id中,符號位CAN_INV_FILTER在置位時可以實現can_id在執行過
濾前的位反轉。
用戶可以為每個打開的套接字設置多條獨立的過濾規則,使用方法如下:
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU
rfilter[1].can_id = 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));//設置規則
在極端情況下,如果應用程序不需要接收報文,可以禁用過濾規則。這樣的話,原始套接字就會忽略所有
接收到的報文。在這種僅僅發送數據的應用中,可以在內核中省略接收隊列,以此減少CPU資源的消耗。禁
用方法如下:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用過濾規則
通過錯誤掩碼可以實現對錯誤幀的過濾,例如:
can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, err_mask, sizeof(err_mask));
在默認情況下,本地回環功能是開啟的,可以使用下面的方法關閉回環/開啟功能:
int loopback = 0; // 0表示關閉, 1表示開啟(默認)
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
在本地回環功能開啟的情況下,所有的發送幀都會被回環到與CAN總線接口對應的套接字上。默認情況
下,發送CAN報文的套接字不想接收自己發送的報文,因此發送套接字上的回環功能是關閉的。可以在
需要的時候改變這一默認行為:
int ro = 1; // 0表示關閉(默認), 1表示開啟
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));
/* 1.報文發送程序 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame[2] = {{0}};
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//創建套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr); //指定can0 設備
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));//將套接字與can0 綁定
//禁用過濾規則,本進程不接收報文,只負責發送
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
//生成兩個報文
frame[0].can_id = 0x11;
frame[0]. can_dlc = 1;
frame[0].data[0] = 'Y';
frame[0].can_id = 0x22;
frame[0]. can_dlc = 1;
frame[0].data[0] = 'N';
//循環發送兩個報文
while(1)
{
nbytes = write(s, &frame[0], sizeof(frame[0])); //發送frame[0]
if(nbytes != sizeof(frame[0]))
{
printf("Send Error frame[0]\n!");
break; //發送錯誤,退出
}
sleep(1);
nbytes = write(s, &frame[1], sizeof(frame[1])); //發送frame[1]
if(nbytes != sizeof(frame[0]))
{
printf("Send Error frame[1]\n!");
break;
}
sleep(1);
}
close(s);
return 0;
}
/* 2. 報文過濾接收程序 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
struct can_filter rfilter[1];
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //創建套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr); //指定can0 設備
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //將套接字與can0 綁定
//定義接收規則,只接收表示符等於0x11 的報文
rfilter[0].can_id = 0x11;
rfilter[0].can_mask = CAN_SFF_MASK;
//設置過濾規則
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
while(1)
{
nbytes = read(s, &frame, sizeof(frame)); //接收報文
//顯示報文
if(nbytes > 0)
{
printf(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id,
frame.can_dlc, frame.data[0]);
}
}
close(s);
return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM