轉載自:https://blog.csdn.net/lizhu_csdn/article/details/51490958
Linux 系統中CAN 接口配置
在 Linux 系統中, CAN 總線接口設備作爲網絡設備被系統進行統一管理。在控制檯下, CAN 總線的配置和以太網的配置使用相同的命令。
在控制檯上輸入命令:
ifconfig –a
可以得到以下結果:
在上面的結果中, eth0 設備爲以太網接口, can0和can1 設備爲兩個 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
Linux 系統中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 中定義。 套接字的初始化方法如下:
2 |
struct sockaddr_can addr; |
4 |
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //創建 SocketCAN 套接字 |
5 |
strcpy (ifr.ifr_name, "can0" ); |
6 |
ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 設備 |
7 |
addr.can_family = AF_CAN; |
8 |
addr.can_ifindex = ifr.ifr_ifindex; |
9 |
bind(s, ( struct sockaddr *)&addr, sizeof (addr)); //將套接字與 can0 綁定 |
(2). 數據發送
在數據收發的內容方面, CAN 總線與標準套接字通信稍有不同,每一次通信都採用 can_ frame 結構體將數據封裝成幀。 結構體定義如下:
2 |
canid_t can_id; //CAN 標識符 |
can_id 爲幀的標識符, 如果發出的是標準幀, 就使用 can_id 的低 11 位; 如果爲擴展幀, 就使用 0~ 28 位。 can_id 的第 29、 30、 31 位是幀的標誌位,用來定義幀的類型,定義如下:
1 |
#define CAN_EFF_FLAG 0x80000000U //擴展幀的標識 |
2 |
#define CAN_RTR_FLAG 0x40000000U //遠程幀的標識 |
3 |
#define CAN_ERR_FLAG 0x20000000U //錯誤幀的標識,用於錯誤檢查 |
數據發送使用 write 函數來實現。 如果發送的數據幀(標識符爲 0x123)包含單個字節(0xAB)的數據,可採用如下方法進行發送:
1 |
struct can_frame frame; |
2 |
frame.can_id = 0x123; //如果爲擴展幀,那麼 frame.can_id = CAN_EFF_FLAG | 0x123; |
3 |
frame.can_dlc = 1; //數據長度爲 1 |
4 |
frame.data[0] = 0xAB; //數據內容爲 0xAB |
5 |
int nbytes = write(s, &frame, sizeof (frame)); //發送數據 |
6 |
if (nbytes != sizeof (frame)) //如果 nbytes 不等於幀長度,就說明發送失敗 |
如果要發送遠程幀(標識符爲 0x123),可採用如下方法進行發送:
1 |
struct can_frame frame; |
2 |
frame.can_id = CAN_RTR_FLAG | 0x123; |
3 |
write(s, &frame, sizeof (frame)); |
(3). 數據接收
數據接收使用 read 函數來完成,實現如下:
1 |
struct can_frame frame; |
2 |
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 結構體來實現,定義如下:
過濾的規則爲:
接收到的數據幀的 can_id & mask == can_id & mask
通過這條規則可以在系統中過濾掉所有不符合規則的報文,使得應用程序不需要對無關的報文進行處理。在 can_filter 結構的 can_id 中,符號位 CAN_INV_FILTER 在置位時可以實現 can_id 在執行過濾前的位反轉。
用戶可以爲每個打開的套接字設置多條獨立的過濾規則,使用方法如下:
1 |
struct can_filter rfilter[2]; |
2 |
rfilter[0].can_id = 0x123; |
3 |
rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU |
4 |
rfilter[1].can_id = 0x200; |
5 |
rfilter[1].can_mask = 0x700; |
6 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof (rfilter)); //設置規則 |
在極端情況下,如果應用程序不需要接收報文,可以禁用過濾規則。這樣的話,原始套接字就會忽略所有接收到的報文。在這種僅僅發送數據的應用中,可以在內核中省略接收隊列,以此減少 CPU 資源的消耗。禁用方法如下:
1 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用過濾規則 |
通過錯誤掩碼可以實現對錯誤幀的過濾, 例如:
1 |
can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF ); |
2 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, err_mask, sizeof (err_mask)); |
(6). 迴環功能設置
在默認情況下, 本地迴環功能是開啓的,可以使用下面的方法關閉迴環/開啓功能:
1 |
int loopback = 0; // 0 表示關閉, 1 表示開啓( 默認) |
2 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof (loopback)); |
在本地迴環功能開啓的情況下,所有的發送幀都會被迴環到與 CAN 總線接口對應的套接字上。 默認情況下,發送 CAN 報文的套接字不想接收自己發送的報文,因此發送套接字上的迴環功能是關閉的。可以在需要的時候改變這一默認行爲:
1 |
int ro = 1; // 0 表示關閉( 默認), 1 表示開啓 |
2 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof (ro)); |
Linux 系統中CAN 接口應用程序示例
該文檔提供了一個很簡單的程序示例,如下:
1. 報文發送程序
07 |
#include <sys/ioctl.h> |
08 |
#include <sys/socket.h> |
09 |
#include <linux/can.h> |
10 |
#include <linux/can/raw.h> |
15 |
struct sockaddr_can addr; |
17 |
struct can_frame frame[2] = {{0}}; |
18 |
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //創建套接字 |
19 |
strcpy (ifr.ifr_name, "can0" ); |
20 |
ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 設備 |
21 |
addr.can_family = AF_CAN; |
22 |
addr.can_ifindex = ifr.ifr_ifindex; |
23 |
bind(s, ( struct sockaddr *)&addr, sizeof (addr)); //將套接字與 can0 綁定 |
24 |
//禁用過濾規則,本進程不接收報文,只負責發送 |
25 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); |
27 |
frame[0].can_id = 0x11; |
28 |
frame[0]. can_dlc = 1; |
29 |
frame[0].data[0] = 'Y' ; |
30 |
frame[0].can_id = 0x22; |
31 |
frame[0]. can_dlc = 1; |
32 |
frame[0].data[0] = 'N' ; |
36 |
nbytes = write(s, &frame[0], sizeof (frame[0])); //發送 frame[0] |
37 |
if (nbytes != sizeof (frame[0])) |
39 |
printf ( "Send Error frame[0]\n!" ); |
43 |
nbytes = write(s, &frame[1], sizeof (frame[1])); //發送 frame[1] |
44 |
if (nbytes != sizeof (frame[0])) |
46 |
printf ( "Send Error frame[1]\n!" ); |
2. 報文過濾接收程序
07 |
#include <sys/ioctl.h> |
08 |
#include <sys/socket.h> |
09 |
#include <linux/can.h> |
10 |
#include <linux/can/raw.h> |
15 |
struct sockaddr_can addr; |
17 |
struct can_frame frame; |
18 |
struct can_filter rfilter[1]; |
19 |
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //創建套接字 |
20 |
strcpy (ifr.ifr_name, "can0" ); |
21 |
ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 設備 |
22 |
addr.can_family = AF_CAN; |
23 |
addr.can_ifindex = ifr.ifr_ifindex; |
24 |
bind(s, ( struct sockaddr *)&addr, sizeof (addr)); //將套接字與 can0 綁定 |
25 |
//定義接收規則,只接收表示符等於 0x11 的報文 |
26 |
rfilter[0].can_id = 0x11; |
27 |
rfilter[0].can_mask = CAN_SFF_MASK; |
29 |
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof (rfilter)); |
32 |
nbytes = read(s, &frame, sizeof (frame)); //接收報文 |
36 |
printf (“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id, |
37 |
frame.can_dlc, frame.data[0]); |
這個示例程序博主並未編譯測試驗證。更完整的程序詳見本人編寫的linux socket can程序cantool