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的第2九、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;
}linux