linux CAN程式設計(一)
轉載自: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 中定義。 套接字的初始化方法如下:
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