ZYNQ學習之路7.CAN總線學習

CAN總線是控制器局域網(Controller Area Network)的簡稱,是國際上應用最廣泛的現場總線之一,CAN總線協議已成爲汽車控制系統和嵌入式工業局域網的標準總線。CAN總線有很多優秀的特點,比如:傳輸速度最高達1Mbps,通信距離最遠到10Km,無損位仲裁機制,多主結構,理論上掛載到總線上的設備沒有數量限制。

因此掌握CAN總線協議是很重要的,本文簡要介紹CAN總線協議,以Linux驅動CAN網絡爲重點介紹。

一. CAN總線的物理特性

1.1 CAN總線的網絡結構

CAN總線有CAN_H和CAN_L兩根線組成,線上傳輸差分信號,爲了避免信號的反射和不連續,需要在總線的兩個端點接120歐姆電阻,不可不接或單接,因爲雙絞線的特性阻抗爲120歐姆,在終端模擬無限遠的傳輸線。CAN網絡一般採用"T"型連接,如下圖1-1所示,在波特率爲1Mbps的情況下,分支長度最好不要超過0.3m。

圖1-1:CAN總線T型網絡結構
圖1-1: CAN總線T型網絡結構

當然也可採用星型拓撲結構,如圖1-2所示:

圖1-2: CAN總線星型網絡結構

如果圖中節點採用等長接線連接,可以不使用CAN集線器設備,調節每個節點的終端電阻即可實現組網。終端電阻R=N*60Ω,N是分支節點的個數。注意網絡中心不能加任何電阻。

在實際的應用中,我們幾乎無法做到等長,在T型網絡中也很難做到支線較短的情況,這個時候我們就需要使用CAN集線器來進行分支,如圖1-3所示。

圖1-3: 使用集線器的CAN網絡

集線器的使用可以使佈線靈活,可根據需要進行任意分支,減少了約束條件。

1.2 CAN信號

CAN報文傳送的位流信號採用非歸零碼(NZR)編碼,也就是一個完整的電平要麼是顯性要麼是隱性,在“隱性”狀態下,CAN_H和CAN_L都是平均電壓電平,Vdiff近似爲零,在“顯性”狀態下,以大於最小閾值的差分電壓表示。CAN電平標準有兩個,IOS11898和IOS11519,兩者的差別在於電平特性的不同,如圖1-4所示:

圖1-4: CAN電平標準

CAN總線的通信距離與波特率成反比,一般的工程中比較常用的500kbps,CAN總線中任意兩個節點的最大傳輸距離與速率如下表所示:

波特率/kbps

1000

500

250

125

100

50

20

10

5

最大距離/m

40

130

270

530

620

1300

3300

6700

10000

1.3 CAN控制器與收發器

CAN控制器和CAN收發器是實現CAN網絡物理層和數據鏈路層所必備的組件,其中CAN控制器是將欲發送的信息(報文)轉換成符合CAN規範的CAN幀,通過CAN收發器在CAN總線上交換信息。

CAN控制器分爲兩類:獨立的控制器芯片和集成在微控制器中的外設。ZYNQ7000中集成了CAN控制器。

CAN控制器原理框圖如圖1-5所示:

圖1-5: CAN控制器原理

CAN核心模塊用於將串行接收的數據轉換爲並行數據,發送則相反。驗收濾波器根據用戶的設置過濾掉不需要接收的報文。

CAN收發器是CAN控制器與物理總線之間的接口,用於將CAN控制器的邏輯電平轉換爲CAN總線的差分電平,將二進制碼流轉換爲差分信號發送,將差分信號轉換爲二進制碼流接收。ZTurn board上使用的CAN收發器是TJA1050,電平轉換示意圖如圖1-6所示:

圖1-6: CAN收發器轉換電平示意圖

二. CAN總線協議

CAN總線是一種廣播類型的總線,在總線上連接的所有節點都可以監聽總線上傳輸的數據。CAN總線的控制器提供了過濾功能,接收信息時只保留與自己相關的信息。

2.1 總線仲裁

只要總線處於空閒狀態,總線上的任何節點都可以發送報文,如果兩個或兩個以上的節點開始發送報文,那麼就會存在總線衝突的可能。CAN使用了標識符的逐位仲裁方法,在發送數據的同時監控總線電平,如果電平相同,則這個單元可以繼續發送。如果不同則失去仲裁退出發送狀態,如果出現不匹配的位不是在總裁期間則產生錯誤事件。

圖2-1: CAN總線仲裁

2.2 幀結構

CAN總線傳輸的基本單位是CAN幀,CAN的通信幀分爲5中類型,分別是數據幀、遠程幀、錯誤幀、過載幀和幀間隔。

數據幀是節點之間用來收發數據,是使用最多的幀類型;遠程幀用來接收節點向發送節點接收數據;錯誤幀是某個節點發送幀錯誤來向其他節點通知的幀;過載幀是接收節點用來向發送節點告知自身接收能力的幀;幀間隔是用來將數據幀、遠程幀與前面幀隔離的幀。

數據幀根據仲裁域格式的不同,分爲標準幀(CAN2.0 A)和擴展幀(CAN2.0 B),如圖2-2所示:

 

圖2-2: 數據幀幀結構

其中SRR爲"替代遠程請求位",IDE爲"擴展標識符位",RTR爲"遠程傳輸請求位",CRC爲"循環冗餘校驗",ACK爲應答。

從圖2-2可以看出,基本幀的格式可以分爲仲裁段,數據段,CRC段和ACK段。

遠程幀與數據幀非常相似,只是遠程幀沒有數據域,一個遠程幀如圖2-3所示:

圖2-3: CAN遠程幀

遠程幀分爲6個段,也分爲標準幀和擴展幀,且RTR位爲1(隱性電平),遠程幀與數據幀的差別如下表所示:

比較項

數據幀

遠程幀

ID

發送節點的ID

被請求發送節點的ID

SRR

0(顯性電平)

1(隱性電平)

RTR

0(顯性電平)

1(隱性電平)

DLC

發送數據長度

請求的數據長度

是否有數據段

CRC校驗範圍

幀起始+仲裁段+控制段+數據段

幀起始+仲裁段+控制段

三. ZYNQ使用CAN

3.1 構建硬件系統

使用ZturnBoard的模板工程,在此基礎上添加CAN0外設,引腳爲MIO14, MIO15.時鐘頻率默認即可,編譯綜合之後生成fsbl文件,製作SD卡啓動鏡像。

配置內核,將CAN的驅動編譯進內核:

<*>Networking support --->
    <*>CAN bus subsystem support --->
        CAN device Drivers --->
        <*>Xilinx CAN
圖3-1: Linux內核添加CAN驅動

修改設備樹文件,添加CAN0節點。ZturnBoard開發板提供了設備樹zynq-zturn.dts文件,該文件引用了zynq-7000.dts文件,該文件包含了PS外設所有的設備樹描述節點,CAN0的描述信息如下:

can0: can@e0008000 {
    compatible = "xlnx,zynq-can-1.0";
    status = "disabled";
    clocks = <&clkc 19>, <&clkc 36>;
    clock-names = "can_clk", "pclk";
    reg = <0xe0008000 0x1000>;
    interrupts = <0 28 4>;
    interrupt-parent = <&intc>;
    tx-fifo-depth = <0x40>;
    rx-fifo-depth = <0x40>;
};

所以在zynt-zturn.dts文件中添加以下描述即可:

&can0 {
	status = "okay";
};

準備好以上的文件之後,啓動Linux系統。

3.2 Linux系統中使用CAN網絡

在Linux系統中,CAN總線接口設備作爲網絡設備被系統進行統一管理,本節介紹控制檯下CAN總線的使用。

Linux系統啓動之後,終端輸入ifconfig -a後能看到網絡設備中增加了can0:

圖3-2: can0網絡設備信息

爲了使用CAN,需要下載CAN的工具包,將canutils_install目錄複製到開發板,libskt_install文件夾中的libsocketcan.so.2.2.0複製到開發板的lib目錄下,並建立軟鏈接:ln -s libsocketcan.so.2.2.0 libsocketcan.so.2;

在發行版Linux中可以使用以下一些命令:

  1. 設置can0的波特率,這裏設置爲100kbps:ip link set can0 type can bitrate 100000
  2. 設置完成後可以通過以下命令查詢can0設備的參數:ip -details link show can0
  3. 當設置完成後,可以使用以下命令使能can0設備:ifconfig can0 up
  4. 使用以下命令關閉can0設備:ifconfig can0 down
  5. 在設備工作中,可以使用下面的命令來查詢工作狀態:ip -d -s link show can0
  6. 設置can0爲迴環模式,自發自收: ip link set can0 up type can loopback on

在ramdisk文件系統中,複製canutils_install到系統目錄中,進入canutils_install目錄,使用sbin目錄下的工具:

  1. 設置can0的波特率:./canconfig can0 bitrate 100000
  2. 啓動can0:./canconfig can0 start
  3. 關閉can0: ./canconfig can0 stop
  4. 設置迴環模式: ./canconfig can0 ctrlmode loopback on
  5. 發送can數據: ./cansend can0 -i 0x14
  6. 接收can數據: ./candump can0

3.3 CAN網絡應用程序開發

Linux系統將CAN設備作爲網絡設備進行管理,提供了SocketCAN接口,使得CAN總線通信可以像以太網一樣,應用程序開發接口更加通用,也更靈活。

(1)初始化

SocketCAN中大部分的數據結構和函數定義在linux/can.h中,CAN總線套接字的創建採用標準的網絡套接字來完成。

int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame[2] = {{0}};
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//create CAN socket
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);	//can0 device
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr*)&addr, sizeof(addr)); //bind socket to can0

(2)數據發送

CAN總線每次接收數據都是以can_frame爲單位,該結構體定義如下:

struct canfd_frame {
    canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
    __u8    len;     /* frame payload length in byte */
    __u8    flags;   /* additional flags for CAN FD */
    __u8    __res0;  /* reserved / padding */
    __u8    __res1;  /* reserved / padding */
    __u8    data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
};

can_id爲幀的標識符,如果發送的是標準幀,就使用can_id的低11位;如果爲擴展幀,就是用0~28位。can_id的低29,30,31位是幀的標識位,用來定義幀的類型,如下所示:

/* special address description flags for the CAN_ID */
#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
#define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
#define CAN_ERR_FLAG 0x20000000U /* error message frame */

數據發送使用write函數實現,例如:發送數據幀標識符爲0x123,包含單個字節0xAB的數據,發送方法如下:

struct can_frame frame;
frame.can_id = 0x123;
frame.can_dlc = 1;
frame.data[0] = 0xAB;
int nbytes = write(s, &frame, sizeof(frame));
if(nbytes != sizeof(frame))
printf("Error\n");

如果發送的是遠程幀,則frame.can_id = CAN_RTR_FLAG | 0x123

(3)數據接收

數據接收使用read函數來完成,實現如下:

struct can_frame frame;
int nbytes = read(s, &frame, sizeof(frame))

(4)錯誤處理

當接收到數據幀,可以通過判斷can_id中的CAN_ERR_FLAG位來判斷接收的幀是否爲錯誤幀,如果爲錯誤幀,可以通過can_id中的其它位來判斷具體的錯誤原因。

(5)過濾設置

通過設置過濾規則,可以過濾掉不需要接收的數據。過濾規則使用can_filter結構體來實現,定義如下:

struct can_filter {
    canid_t can_id;
    canid_t can_mask;
};

接收到的數據幀的can_id & can_mask == can_filter .can_id & can_filter .can_mask則接收。

(6)迴環功能

在默認情況下,本地迴環功能是開啓的,可以使用下面的方法關閉/開啓:

int loopback = 0;//0:關閉,1:開啓
setsockopt(s_s,SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

在本地迴環功能開啓的情況下,所有的發送的幀都會被迴環到與CAN總線接口對應的套接字上。默認情況下,發送CAN報文不想接收自己發送的報文,因此發送套接字上的迴環功能是關閉的,打開這一功能可以使用如下方法:

int ro = 1;//0:關閉,1:開啓
setsockopt(s_s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));

3.4 Linux系統中CAN接口應用程序示例:

首先使用兩塊ZturnBoard開發板,使用連根導線連接CAN的H和L兩個端點,複製libsocketcan.so.2.2.0到開發板並建立軟鏈接,設置兩個開發板的can0波特率一致,啓動can0。

can發送程序:

#include "unistd.h"
#include "net/if.h"
#include "sys/ioctl.h"
#include "linux/can/raw.h"
#include "linux/can.h"
#include "sys/socket.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

using namespace std;

int main()
{
	cout<<"test for can socket send!"<<endl;

	int s, nbytes;
	struct sockaddr_can addr;
	struct ifreq ifr;
	struct can_frame frame[2];
	s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//create CAN socket
	strcpy(ifr.ifr_name, "can0");
	ioctl(s, SIOCGIFINDEX, &ifr);	//can0 device
	addr.can_family = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
	bind(s, (struct sockaddr*)&addr, sizeof(addr));//bind socket to can0

	//disable filter
	setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
	//two frame
	frame[0].can_id = 0x11;
	frame[0].can_dlc = 1;
	frame[0].data[0] = 'A';
	frame[1].can_id = 0x22;
	frame[1].can_dlc = 1;
	frame[1].data[0] = 'B';
	for(int i = 0; i<10; i++)
	{
        cout<<"send can frame"<<endl;
        nbytes = write(s, &frame[0], sizeof(frame[0]));//send frame[0]
        if(nbytes != sizeof(frame[0]))
        {
            cout<<"Send error frame[0]"<<endl;
        }
        sleep(1);//wait 1s
        nbytes = write(s, &frame[1], sizeof(frame[1]));//send frame[0]
        if(nbytes != sizeof(frame[1]))
        {
            cout<<"Send error frame[1]"<<endl;
        }
        sleep(1);//wait 1s
	}
	close(s);
	cout<<"send can frame over!!!"<<endl;

	return 0;
}

can接收程序:

#include "unistd.h"
#include "net/if.h"
#include "sys/ioctl.h"
#include "linux/can/raw.h"
#include "linux/can.h"
#include "sys/socket.h"
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

using namespace std;

int main()
{
	cout<<"test for can socket!"<<endl;

	int s, nbytes;
	struct sockaddr_can addr;
	struct ifreq ifr;
	//receive frame which id==0x11
	struct can_filter rfilter;
	struct can_frame frame;
	s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
	strcpy(ifr.ifr_name, "can0");
	ioctl(s, SIOCGIFINDEX, &ifr);
	addr.can_family = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
	bind(s, (struct sockaddr *)&addr, sizeof(addr));

	rfilter.can_id = 0x11;
	rfilter.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%0x DLC=%d data[0]=0x%x\n", frame.can_id,
        	frame.can_dlc,frame.data[0]);
        }
	}

	return 0;
}

分別再兩個開飯中運行兩個程序,在接收端可以看到只接收了地址ID=0x11的數據幀。

圖3-3: CAN發送與接收測試

四 總結

本文詳細介紹了CAN總線的原理以及在Linux系統中的使用,在實驗過程中需要注意動態鏈接庫的使用以及CAN的設置,確保數據鏈接正常,然後再調試軟件部分,實驗並不難,僅在於學習如何使用CAN網絡。

參考資料

[1]. CAN總線要點

[2]. CAN總線(一)

[3].Linux CAN編程詳解

發佈了12 篇原創文章 · 獲贊 12 · 訪問量 8973
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章