1. 程式人生 > >CAN總線基礎和在linux下使用實戰

CAN總線基礎和在linux下使用實戰

for 驅動開發 概念 str 種類型 log 中斷控制 socket pro

CAN總線基礎和在linux下使用實戰

CAN 是Controller Area Network 的縮寫
有CANH和CANL兩線,即差分信號通信。當然設備芯片還會有電源和地等線。
在總線空閑時,所有的單元都可開始發送消息(多主控制)。
最先訪問總線的單元可獲得發送權(CSMA/CA 方式)。
多個單元同時開始發送時,發送高優先級 ID 消息的單元可獲得發送權。
沒有目標地址和源地址的概念,只有標識符,根據標識符決定優先級,根據表示符,設備自己判斷是否接收給上層,讓上層處理。即消息是廣播的形式。
兩個以上的單元同時開始發送消息時,對各消息ID 的每個位進行逐個仲裁比較。仲裁獲勝(被判定為優先級最高)的單元可繼續發送消息,仲裁失利的單元則立刻停止發送而進行接收工作。這個是與CAN總線中,多個電平同時出現時,顯式電平為最終值這個機理有關。所以,設備一邊發送,一遍檢查總線的實際值,即可知道是否有別的設備在同時發送了。這種不算出錯誤,而是算仲裁是否取勝。 後面CRC檢驗錯誤等,才是錯誤。

標準格式有11 個位的標識符(Identifier: 以下稱ID),擴展格式有29 個位的ID。
通信是通過以下5 種類型的幀進行的。 數據幀、 遙控幀、錯誤幀、 過載幀、 幀間隔

技術分享圖片

技術分享圖片

作為驅動開發人員,應該了解,總線協議哪些部分是硬件實現的,哪些部分是軟件實現的。
數據幀的數據內容和遙控幀的數據內容,應該是軟件填入,並由硬件進行協助處理。錯誤幀、過載幀、幀間隔,這種東西,應該是硬件直接處理,只是可能會轉換為一個中斷控制器的一個status來通知上層軟件邏輯,出現某些問題。

對於linux軟件來說,CAN host controller驅動會把硬件收到的CAN數據組織為struct can_frame 。

linux內核代碼來看
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags /
__u8 can_dlc; / frame payload length in byte (0 … CAN_MAX_DLEN) /
__u8 data[CAN_MAX_DLEN] attribute((aligned(8)));
};
can_id就是表示符字段,並含CAN_ID + EFF/RTR/ERR flags
例如imx6的flexcan的驅動,在can_id上也指明了一些錯誤信息給上層使用。通過can_id的標記位指明。具體看flexcan的代碼。
/

Controller Area Network Identifier structure
bit 0-28 : CAN identifier (11/29 bit)
bit 29 : error message frame flag (0 = data frame, 1 = error message)
bit 30 : remote transmission request flag (1 = rtr frame)
bit 31 : frame format flag (0 = standard 11 bit, 1 = extended 29 bit)
*/
即linux重新定義了canid,這個與can總線上的格式不同,但意義類似。而且提供了filter機制,讓應用層的socket只是關心某些canframe的包。註意,其他操作系統,實現方式可能不同。
使用CAN分析儀,安裝驅動,並使用CANTest工具,
選擇好設備和can通道後,並設置頻率後,即可接收和發送。

CANH接CANH,CANL接CANL

此CANTest工具運行後,並設置速率為500KHz

另外一側,即設備側,linux中運行
ip link set can0 down
ip link set can0 up type can bitrate 500000
即設置為500KHz。
然後運行
cansend can0 1F334455#11223355
linux上發送上面的數據,#之前是can id,#號之後是數據。CANTest上,就會收到並顯示。

linux設備側,運行candump -l any,0:0,#FFFFFFFF,然後CANTest上發送數據。
然後candump會告訴你保存到哪個文件,你即可
cat這個文件看到內容。

linux側的can使用介紹,具體可以看Documentation/networking/can.txt

linux CAN應用開發涉及的api:參考candump和cansend的代碼
struct sockaddr_can addr;
struct iovec iov;
struct msghdr msg;
struct cmsghdr cmsg;
struct can_filter
rfilter;
can_err_mask_t err_mask;
struct canfd_frame frame;
struct ifreq ifr;
socket(PF_CAN, SOCK_RAW, CAN_RAW);
ioctl(s[i], SIOCGIFINDEX, &ifr)
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_ERR_FILTER,&err_mask, sizeof(err_mask)); //根據需要
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS,&join_filter, sizeof(join_filter)) //根據需要
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FILTER,rfilter, numfilter sizeof(struct can_filter)); //根據需要, 設置filter,過濾某些CAN_ID的數據
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on)); //根據需要
bind(s[i], (struct sockaddr
)&addr, sizeof(addr))
select(s[currmax-1]+1, &rdfs, NULL, NULL, timeout_current)
recvmsg(s[i], &msg, 0); //或者recv()
close(s[i]);
ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
ioctl(s, SIOCGIFMTU, &ifr)
bind(s, (struct sockaddr *)&addr, sizeof(addr)
write(s, &frame, required_mtu)

由於linux暴露給上層的CAN網絡設備只支持
socket(PF_CAN, SOCK_RAW, CAN_RAW);
而android上層使用的是java,所以需要native層的service把此種socket轉為另外的tcp或者udp或者進程間通信機制的socket才能發給java上層。

具體請參考我的免費的linux各種驅動開發課程如下:
https://edu.51cto.com/course/17138.html

另外我的相關培訓視頻請看:
歡迎觀看我發布的各個課程: https://edu.51cto.com/lecturer/8896847.html

CAN總線基礎和在linux下使用實戰