1. 程式人生 > >linux CAN程式設計(一)

linux CAN程式設計(一)

轉載自:https://blog.csdn.net/lizhu_csdn/article/details/51490958

Linux 系統中CAN 介面配置

在 Linux 系統中, CAN 匯流排介面裝置作為網路裝置被系統進行統一管理。在控制檯下, CAN 匯流排的配置和乙太網的配置使用相同的命令。

在控制檯上輸入命令:
ifconfig –a

可以得到以下結果:

image_thumb.png

在上面的結果中, 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 中定義。 套接字的初始化方法如下:

1 int s;
2 struct sockaddr_can addr;
3 struct ifreq ifr;
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 結構體將資料封裝成幀。 結構體定義如下:

1 struct can_frame {
2 canid_t can_id;//CAN 識別符號
3 __u8 can_dlc;//資料場的長度
4 __u8 data[8];//資料
5 };

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 不等於幀長度,就說明發送失敗
7 printf("Error\n!");

如果要傳送遠端幀(識別符號為 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 結構體來實現,定義如下:

1 struct can_filter {
2 canid_t can_id;
3 canid_t can_mask;
4 };

過濾的規則為:

接收到的資料幀的 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. 報文傳送程式

01 /* 1. 報文傳送程式 */
02 #include <stdio.h>
03 #include <stdlib.h>
04 #include <string.h>
05 #include <unistd.h>
06 #include <net/if.h>
07 #include <sys/ioctl.h>
08 #include <sys/socket.h>
09 #include <linux/can.h>
10 #include <linux/can/raw.h>
11  
12 int main()
13 {
14     int s, nbytes;
15     struct sockaddr_can addr;
16     struct ifreq ifr;
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);
26     //生成兩個報文
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';
33     //迴圈傳送兩個報文
34     while(1)
35     {
36         nbytes = write(s, &frame[0], sizeof(frame[0])); //傳送 frame[0]
37         if(nbytes != sizeof(frame[0]))
38         {
39             printf("Send Error frame[0]\n!");
40             break//傳送錯誤,退出
41         }
42         sleep(1);
43         nbytes = write(s, &frame[1], sizeof(frame[1])); //傳送 frame[1]
44         if(nbytes != sizeof(frame[0]))
45         {
46             printf("Send Error frame[1]\n!");
47             break;
48         }
49         sleep(1);
50     }
51     close(s);
52     return 0;
53 }

2. 報文過濾接收程式

01 /* 2. 報文過濾接收程式 */
02 #include <stdio.h>
03 #include <stdlib.h>
04 #include <string.h>
05 #include <unistd.h>
06 #include <net/if.h>
07 #include <sys/ioctl.h>
08 #include <sys/socket.h>
09 #include <linux/can.h>
10 #include <linux/can/raw.h>
11  
12 int main()
13 {
14     int s, nbytes;
15     struct sockaddr_can addr;
16     struct ifreq ifr;
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;
28     //設定過濾規則
29     setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
30     while(1)
31     {
32         nbytes = read(s, &frame, sizeof(frame)); //接收報文
33         //顯示報文
34         if(nbytes > 0)
35         {
36             printf(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id,
37                 frame.can_dlc, frame.data[0]);
38         }
39     }
40     close(s);
41     return 0;
42 }

這個示例程式博主並未編譯測試驗證。更完整的程式詳見本人編寫的linux socket can程式cantool