@[TOC]
14 CAN編程應(yīng)用開發(fā)
14.1 CAN介紹
14.1.1 CAN是什么?
? CAN,全稱為“Controller Area Network”,即控制器局域網(wǎng),是國(guó)際上應(yīng)用最廣泛的現(xiàn)場(chǎng)總線之一。
最初,CAN 被設(shè)計(jì)作為汽車環(huán)境中的微控制器通訊,在車載各電子控制裝置 ECU 之間交換信息,形成汽車
電子控制網(wǎng)絡(luò)。比如:發(fā)動(dòng)機(jī)管理系統(tǒng)、變速箱控制器、儀表裝備、電子主干系統(tǒng)中,均嵌入 CAN 控制裝
置。
? 一個(gè)由 CAN 總線構(gòu)成的單一網(wǎng)絡(luò)中,理論上可以掛接無(wú)數(shù)個(gè)節(jié)點(diǎn)。實(shí)際應(yīng)用中,節(jié)點(diǎn)數(shù)目受網(wǎng)絡(luò)硬件
的電氣特性所限制。例如,當(dāng)使用 Philips P82C250 作為 CAN 收發(fā)器時(shí),同一網(wǎng)絡(luò)中允許掛接 110 個(gè)節(jié)點(diǎn)。
CAN 可提供高達(dá) 1Mbit/s 的數(shù)據(jù)傳輸速率,這使實(shí)時(shí)控制變得非常容易。另外,硬件的錯(cuò)誤檢定特性也增
強(qiáng)了 CAN 的抗電磁干擾能力。
14.1.2 CAN的起源
? CAN 最初出現(xiàn)在 80 年代末的汽車工業(yè)中,由德國(guó) Bosch 公司最先提出。當(dāng)時(shí),由于消費(fèi)者對(duì)于汽車功
能的要求越來(lái)越多,而這些功能的實(shí)現(xiàn)大多是基于電子操作的,這就使得電子裝置之間的通訊越來(lái)越復(fù)雜,
同時(shí)意味著需要更多的連接信號(hào)線。提出 CAN 總線的最初動(dòng)機(jī)就是為了解決現(xiàn)代汽車中龐大的電子控制裝
置之間的通訊,減少不斷增加的信號(hào)線。于是,他們?cè)O(shè)計(jì)了一個(gè)單一的網(wǎng)絡(luò)總線,所有的外圍器件可以被
掛接在該總線上。1993 年,CAN 已成為國(guó)際標(biāo)準(zhǔn) ISO11898(高速應(yīng)用)和 ISO11519(低速應(yīng)用)。
CAN 是一種多主方式的串行通訊總線,基本設(shè)計(jì)規(guī)范要求有高的位速率,高抗電磁干擾性,而且能夠檢
測(cè)出產(chǎn)生的任何錯(cuò)誤。當(dāng)信號(hào)傳輸距離達(dá)到 10Km 時(shí),CAN 仍可提供高達(dá) 50Kbit/s 的數(shù)據(jù)傳輸速率。
由于 CAN 總線具有很高的實(shí)時(shí)性能,因此,CAN 已經(jīng)在汽車工業(yè)、航空工業(yè)、工業(yè)控制、安全防護(hù)等領(lǐng)
域中得到了廣泛應(yīng)用。
14.1.3 CAN傳輸模型
? CAN 通訊協(xié)議主要描述設(shè)備之間的信息傳遞方式。CAN 層的定義與開放系統(tǒng)互連模型(OSI)一致。每
一層與另一設(shè)備上相同的那一層通訊。實(shí)際的通訊發(fā)生在每一設(shè)備上相鄰的兩層,而設(shè)備只通過(guò)模型物理
層的物理介質(zhì)互連。CAN 的規(guī)范定義了模型的最下面兩層:數(shù)據(jù)鏈路層和物理層。下表中展示了 OSI 開放
式互連模型的各層。應(yīng)用層協(xié)議可以由 CAN 用戶定義成適合特別工業(yè)領(lǐng)域的任何方案。已在工業(yè)控制和制
造業(yè)領(lǐng)域得到廣泛應(yīng)用的標(biāo)準(zhǔn)是 DeviceNet,這是為 PLC 和智能傳感器設(shè)計(jì)的。在汽車工業(yè),許多制造商
都應(yīng)用他們自己的標(biāo)準(zhǔn)。
表格 OSI開發(fā)系統(tǒng)互聯(lián)模型 | ||
---|---|---|
序號(hào) | 層次 | 描述 |
7 | 應(yīng)用層 | 最高層。用戶、軟件、網(wǎng)絡(luò)終端等之間用來(lái)進(jìn)行信息交換。 |
6 | 表示層 | 將兩個(gè)應(yīng)用不同數(shù)據(jù)格式的系統(tǒng)信息轉(zhuǎn)化為能共同理解的格式 |
5 | 會(huì)話層 | 依靠低層的通信功能來(lái)進(jìn)行數(shù)據(jù)的有效傳遞。 |
4 | 傳輸層 | 兩通訊節(jié)點(diǎn)之間數(shù)據(jù)傳輸控制。操作如:數(shù)據(jù)重發(fā),數(shù)據(jù)錯(cuò)誤修復(fù) |
3 | 網(wǎng)絡(luò)層 | 規(guī)定了網(wǎng)絡(luò)連接的建立、維持和拆除的協(xié)議。如:路由和尋址 |
2 | 數(shù)據(jù)鏈路層 | 規(guī)定了在介質(zhì)上傳輸?shù)臄?shù)據(jù)位的排列和組織。如:數(shù)據(jù)校驗(yàn)和幀結(jié)構(gòu) |
1 | 物理層 | 規(guī)定通訊介質(zhì)的物理特性。如:電氣特性和信號(hào)交換的解釋 |
? 雖然CAN傳輸協(xié)議參考了OSI 七層模型,但是實(shí)際上CAN協(xié)議只定義了兩層“物理層”和“數(shù)據(jù)鏈路層”,因此出現(xiàn)了各種不同的“應(yīng)用層”協(xié)議,比如用在自動(dòng)化技術(shù)的現(xiàn)場(chǎng)總線標(biāo)準(zhǔn)DeviceNet,用于工業(yè)控制的CanOpen,用于乘用車的診斷協(xié)議OBD、UDS(統(tǒng)一診斷服務(wù),ISO14229),用于商用車的CAN總線協(xié)議SAEJ1939.
表格 CAN的 | ||
---|---|---|
序號(hào) | 層次 | 描述 |
7 | 應(yīng)用層 | 主要定義CAN應(yīng)用層。 |
2 | 數(shù)據(jù)鏈路層 | 數(shù)據(jù)鏈路層分為邏輯鏈接控制子層LLC和介質(zhì)訪問(wèn)控制子層MAC。<br>MAC 子層是 CAN 協(xié)議的核心。它把接收到的報(bào)文提供給 LLC 子<br/>層,并接收來(lái)自 LLC 子層的報(bào)文。 MAC 子層負(fù)責(zé)報(bào)文分幀、仲<br/>裁、應(yīng)答、錯(cuò)誤檢測(cè)和標(biāo)定。MAC 子層也被稱作故障界定的管理<br/>實(shí)體監(jiān)管 LLC 子層涉及報(bào)文濾波、過(guò)載通知、以及恢復(fù)管理。<br/>LLC = Logical Link Control MAC = Medium Access Control |
1 | 物理層 | 物理層,為物理編碼子層PCS. 該層定義信號(hào)是如何實(shí)際地傳輸<br/>的,因此涉及到位時(shí)間、位編碼、同步。 |
14.1.4 CAN網(wǎng)絡(luò)拓?fù)?/h3>
? CAN總線是一種分布式的控制總線。
? CAN總線作為一種控制器局域網(wǎng),和普通以太網(wǎng)一樣,它的網(wǎng)絡(luò)很多CAN節(jié)點(diǎn)構(gòu)成。
其網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)如下圖所示:
? CAN網(wǎng)絡(luò)的每個(gè)節(jié)點(diǎn)非常簡(jiǎn)單,均由一個(gè)MCU(微控制器)、一個(gè)CAN控制器和一個(gè)CAN收發(fā)器構(gòu)成,然后使用雙絞線連接到CAN網(wǎng)絡(luò)中。
14.1.5 CAN物理特性
? CAN總線遵循國(guó)際標(biāo)準(zhǔn)ISO11898,如ISO11898-1,ISO11898-2,ISO11898-3,ISO11898-4標(biāo)準(zhǔn)。
序號(hào) | 標(biāo)準(zhǔn) | 描述 |
---|---|---|
1 | ISO11898-1 | 數(shù)據(jù)鏈路層和物理層信號(hào) |
2 | ISO11898-2 | 高速接入單元 |
3 | ISO11898-3 | 低速容錯(cuò)接入單元 |
4 | ISO11898-4 | 時(shí)間觸發(fā)通訊 |
5 | ISO11898-5 | 低功耗的接入單元 |
6 | ISO11898-6 | 選擇性喚醒的高速接入單元 |
CAN 能夠使用多種物理介質(zhì),例如雙絞線、光纖等。最常用的就是雙絞線。
信號(hào)使用差分電壓傳送,兩條信號(hào)線被稱為“CAN_H”和“CAN_L”。
靜態(tài)時(shí)CAN_H和CAN_L均是 2.5V 左右,此時(shí)狀態(tài)表示為邏輯“1”,也可以叫做 “隱性”。
用 CAN_H 比 CAN_L 高表示邏輯“0”,稱為“顯形”,此時(shí),通常電壓值為:CAN_H = 3.5V 和 CAN_L
= 1.5V 。
目前實(shí)際常用的CAN收發(fā)器有如下幾種型號(hào):
序號(hào) | 型號(hào) | 描述 |
---|---|---|
1 | PCA82C250 | 高速 CAN 收發(fā)器 |
2 | PCA82C251 | 高速 CAN 收發(fā)器 |
3 | PCA82C252 | 容錯(cuò) CAN 收發(fā)器 |
4 | TJA1040 | 高速 CAN 收發(fā)器 |
5 | TJA1041 | 高速 CAN 收發(fā)器 |
6 | TJA1042 | 高速 CAN 收發(fā)器 |
7 | TJA1043 | 高速 CAN 收發(fā)器 |
8 | TJA1050 | 高速 CAN 收發(fā)器 |
9 | TJA1053 | 容錯(cuò) CAN 收發(fā)器 |
10 | TJA1054 | 容錯(cuò) CAN 收發(fā)器 |
目前實(shí)際常用的CAN控制器有如下幾種型號(hào):
序號(hào) | 型號(hào) | 描述 |
---|---|---|
1 | SJA1000 | 獨(dú)立CAN控制器 |
2 | MCU內(nèi)部控制器 | 目前市面上如STM32系列,S32K系列,IMX6系列等等很多單片機(jī)均內(nèi)部集成了CAN控制。 |
14.1.6 CAN報(bào)文幀
14.1.6.1 CAN報(bào)文格式
標(biāo)準(zhǔn) CAN 的標(biāo)志符長(zhǎng)度是 11 位,而擴(kuò)展格式 CAN 的標(biāo)志符長(zhǎng)度可達(dá) 29 位。
CAN 協(xié)議的 2.0A 版本 規(guī)定 CAN 控制器必須有一個(gè) 11 位的標(biāo)志符。
同時(shí),在 2.0B 版本中規(guī)定,CAN 控制器的標(biāo)志符長(zhǎng)度可以是 11 位或 29 位。
遵循 CAN2.0B 協(xié)議的 CAN 控制器可以發(fā)送和接收 11 位標(biāo)識(shí)符的標(biāo)準(zhǔn)格式報(bào)文或 29 位標(biāo)識(shí)符的擴(kuò)展格式報(bào)文。
標(biāo)準(zhǔn)幀&擴(kuò)展幀對(duì)比 | ||
---|---|---|
幀格式 | 標(biāo)準(zhǔn)幀 | 擴(kuò)展幀 |
規(guī)范 | CAN2.0A | CAN2.0B |
CAN ID(標(biāo)識(shí)符)長(zhǎng)度 | 11 bits | 29 bits |
CAN ID(標(biāo)識(shí)符)范圍 | 0x000~0x7FF | 0x00000000~0x1FFFFFFF |
14.1.6.2 CAN報(bào)文幀類型
CAN報(bào)文類型又分如5種幀類型:
數(shù)據(jù)幀:主要用于發(fā)送方向接收方傳輸數(shù)據(jù)的幀;
遙控幀:主要用于接收方向具有相同ID的發(fā)送方請(qǐng)求數(shù)據(jù)的幀;
錯(cuò)誤幀:主要用于當(dāng)檢測(cè)出錯(cuò)誤時(shí)向其他節(jié)點(diǎn)通知錯(cuò)誤的幀。
過(guò)載幀:主要用于接收方通知其他尚未做好接收準(zhǔn)備的幀。
間隔幀:主要用于將數(shù)據(jù)幀及遙控幀與前一幀分隔開來(lái)的幀。
其中數(shù)據(jù)幀是使用最多的幀類型,這里重點(diǎn)介紹以下數(shù)據(jù)幀。
數(shù)據(jù)幀如下圖所示:
由上圖所示,數(shù)據(jù)幀包括:
(1)幀起始。表示數(shù)據(jù)幀開始的段。
(2)仲裁段。表示該幀優(yōu)先級(jí)的段。
(3)控制段。表示數(shù)據(jù)的字節(jié)數(shù)及保留位的段。
(4)數(shù)據(jù)段。數(shù)據(jù)的內(nèi)容,一幀可發(fā)送0~8個(gè)字節(jié)的數(shù)據(jù)。
(5)CRC段。檢查幀的傳輸錯(cuò)誤的段。
(6)ACK段。表示確認(rèn)正常接收的段。
(7)幀結(jié)束。表示數(shù)據(jù)幀結(jié)束的段。
具體介紹可以查看”CAN2.0A”、”CAN2.0B”詳細(xì)介紹。
我們主要關(guān)注我們編程所需要關(guān)注的幾個(gè)段:
ID: CAN報(bào)文ID;
IDE: 為0是標(biāo)準(zhǔn)幀,為1是擴(kuò)展幀;
RTR: 為0是數(shù)據(jù)幀,為1是遠(yuǎn)程幀;
DLC: CAN報(bào)文數(shù)據(jù)長(zhǎng)度,范圍0~8字節(jié);
Data:數(shù)據(jù),0~8個(gè)字節(jié);
14.2 CAN編程框架創(chuàng)建
當(dāng)前我們所學(xué)習(xí)的是應(yīng)用編程,為了以后CAN編程框架的通用性和可移植性,我們創(chuàng)建一個(gè)抽象的CAN應(yīng)用編程框架,此框架可以適用于單片機(jī)應(yīng)用編程,也可以適用于linux應(yīng)用編程。
因此,根據(jù)CAN總線編程的通用屬性,我們抽象出如下屬性:
屬性 | 屬性描述 | 說(shuō)明 |
---|---|---|
CAN端口號(hào) | 描述CAN端口,如CAN1,CAN2,CAN3,與具體硬件外設(shè)有關(guān)。 | |
CAN收發(fā)器配置 | 描述CAN收發(fā)器模式設(shè)置,收發(fā)器模式有Normal,Stanby,<br>Sleep,ListenOnly等模式; 本章節(jié)所使用的收發(fā)器是硬件默<br/>認(rèn)配置,因此不需要配置。 | |
CAN控制器配置 | 描述CAN收發(fā)器配置,如CAN波特率配置,采樣率設(shè)置,過(guò)<br/>濾器設(shè)置等; | |
CAN中斷配置 | 描述CAN中斷接收函數(shù)配置 | |
讀取CAN報(bào)文 | 描述CAN讀取報(bào)文實(shí)現(xiàn) | |
發(fā)送CAN報(bào)文 | 描述CAN發(fā)送報(bào)文實(shí)現(xiàn) |
根據(jù)上面表格所描述的屬性,創(chuàng)建CAN應(yīng)用編程框架如下:
typedef struct _CAN_COMM_STRUCT
{
/* CAN硬件名稱 */
char name[10];
/* CAN端口號(hào),裸機(jī)里為端口號(hào);linux應(yīng)用里作為socket套接口 */
int can_port;
/* CAN控制器配置函數(shù),返回端口號(hào)賦值給can_port */
int (*can_set_controller)( void );
/* CAN接口中斷創(chuàng)建,在linux中對(duì)應(yīng)創(chuàng)建接收線程 */
void (*can_set_interrput)( int can_port , pCanInterrupt callback );
/* CAN讀取報(bào)文接口 */
void (*can_read)( int can_port , CanRxMsg* recv_msg);
/* CAN發(fā)送報(bào)文接口*/
void (*can_write)( int can_port , CanTxMsg send_msg);
}CAN_COMM_STRUCT, *pCAN_COMM_STRUCT;
此框架可以用類比套用在單片機(jī)上,也可以使用在linux socketcan應(yīng)用編程上。
14.3 STM32 CAN應(yīng)用編程
本節(jié)主要使用14.2中的應(yīng)用編程框架,在單片機(jī)上試驗(yàn)框架的可行性,以一個(gè)基本的接收和發(fā)送的案例來(lái)做講解;
14.3.1 STM32 CAN接口電路
如下圖所示,為本章STM32例程所使用的開發(fā)板STM32最小系統(tǒng)和CAN收發(fā)器接口電路。
<center><p>圖14.3.1-1 STM32F407最小系統(tǒng)</p></center>
<center><p>圖14.3.1-1 TJA1050 CAN收發(fā)器接口電路</p></center>
14.3.2 STM32 CAN應(yīng)用編程步驟
下面我們按照CAN通信的編程框架來(lái)一步一步實(shí)現(xiàn)基于STM32的CAN應(yīng)用編程。
STM32 CAN應(yīng)用編程,步驟如下:
14.3.2.1準(zhǔn)備STM32工程模版
請(qǐng)參見(jiàn)第14章節(jié)代碼“01_stm32f407_can”例程;
所使用的開發(fā)環(huán)境為:MDK 5.24.
打開MDK工程后,如下圖所示:
上圖中目錄CMSIS, STM32F407_LIB,main均為STM32運(yùn)行的基礎(chǔ)框架。
目錄app_can為CAN應(yīng)用編程所需要的文件。
14.3.2.2 編寫CAN抽象框架的實(shí)現(xiàn)函數(shù)
(1)定義CAN端口號(hào)
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“can_controller.h”文件。
主要根據(jù)STM32硬件的CAN有多路,依次定義為CAN_PORTCAN1, CAN_PORT_CAN2等,從“14.3.1 STM32 CAN接口電路”可知道,當(dāng)前使用的CAN1.
25 /* CAN端口號(hào)定義*/
26 enum
27 {
28 CAN_PORT_NONE = 0,
29 CAN_PORT_CAN1,
30 CAN_PORT_CAN2,
31 CAN_PORT_MAX
32 };
(2)配置CAN控制器
配置CAN控制器有3個(gè)部分:GPIO(CAN_TX,CAN_RX管腳)配置,CAN波特率配置,CAN過(guò)濾器配置。
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“can_controller.c”文件int CAN_Set_Controller( void )函數(shù)。
A.GPIO(CAN_TX,CAN_RX管腳)配置
配置GPIO代碼如下:
96 /*************************************************************/
97 /*CAN相關(guān)GPIO配置,此處為:CAN_TX, CAN_RX*/
98
99 /*使能GPIO時(shí)鐘*/
100 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
101 /*初始化管腳配置*/
102 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;
103 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
104 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
105 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
106 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
107 GPIO_Init(GPIOD, &GPIO_InitStructure);
108
109 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
110 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
111 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
112 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
113 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
114 GPIO_Init(GPIOD, &GPIO_InitStructure);
115 /*將GPIO設(shè)置為CAN復(fù)用模式*/
116 GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_CAN1);
117 GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_CAN1);
B.配置波特率,工作模式
按照如下代碼,使能CAN外設(shè),設(shè)置CAN工作模式為Normal,設(shè)置波特率為500kbps。
119 /*************************************************************/
120 /*CAN控制器相關(guān)配置,此處為波特率,采樣率等*/
121
122 /* 使能CAN時(shí)鐘 */
123 RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
124
125 /* 初始化CAN控制器工作模式*/
126 CAN_DeInit(CAN1);
127 CAN_StructInit(&CAN_InitStructure);
128 CAN_InitStructure.CAN_TTCM = DISABLE;
129 CAN_InitStructure.CAN_ABOM = DISABLE;
130 CAN_InitStructure.CAN_AWUM = DISABLE;
131 CAN_InitStructure.CAN_NART = DISABLE;
132 CAN_InitStructure.CAN_RFLM = DISABLE;
133 CAN_InitStructure.CAN_TXFP = DISABLE;
134 CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;//CAN工作模式
135
136 /* 初始化CAN波特率 */
137 CAN_Baud_Process(500,&CAN_InitStructure);
138 CAN_Init(CAN1, &CAN_InitStructure);
其中配置波特率的函數(shù)是一個(gè)自定義函數(shù),這里可以不了解,只需要知道是配置波特率即可,如果需要使用本章代碼,可以查看具體的源碼工程。
C. 配置CAN過(guò)濾器
如下代碼為配置過(guò)濾器:
141 /*************************************************************/
142 /* 初始化CAN過(guò)濾器 */
143 CAN_FilterInitStructure.CAN_FilterNumber = 0; /* CAN1濾波器號(hào)從0到13 */
144 CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; /* 濾波屏蔽模式 */
145 CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
146 CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
147 CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
148 CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; /* 不屏蔽任何ID */
149 CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; /* 不屏蔽任何ID */
150 CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;
151
152 CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
153 CAN_FilterInit(&CAN_FilterInitStructure);
154
155 /*************************************************************/
156 /* 設(shè)置完CAN后,返回當(dāng)前設(shè)置的CAN的端口號(hào),此處主要類比linux socketcan中的套接口 */
此處我們?cè)O(shè)置過(guò)濾器不屏蔽任何報(bào)文ID,這里只是了解單片機(jī)下的一些過(guò)程。
(3)配置CAN接收中斷
CAN總線支持發(fā)送中斷和接收中斷,此處僅僅使用了接收中斷。
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“can_controller.c”文件void CAN_Set_Interrupt(int can_port, pCanInterrupt callback)函數(shù)。
CAN中斷配置代碼如下:
163 /**********************************************************************
164 * 函數(shù)名稱: void CAN_Set_Interrupt(int can_port, pCanInterrupt callback)
165 * 功能描述: 使能CAN中斷處理,并傳入應(yīng)用的的回調(diào)函數(shù),回調(diào)函數(shù)主要處理應(yīng)用層的功能
166 * 輸入?yún)?shù): can_port,端口號(hào)
167 * callback: 中斷具體處理應(yīng)用功能的回調(diào)函數(shù)
168 * 輸出參數(shù): 無(wú)
169 * 返 回 值: 無(wú)
170 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
171 * -----------------------------------------------
172 * 2020/05/13 V1.0 bert 創(chuàng)建
173 ***********************************************************************/
174 void CAN_Set_Interrupt(int can_port, pCanInterrupt callback)
175 {
176 NVIC_InitTypeDef NVIC_InitStructure;
177
178 /* 根據(jù)CAN端口號(hào)配置中斷 */
179 switch( can_port )
180 {
181 case CAN_PORT_CAN1:
182 {
183 /* 初始化回調(diào)接口函數(shù) */
184 if ( NULL != callback )
185 {
186 g_pCanInterrupt = callback;
187 }
188
189 /* 使用CAN0_RX中斷,在linux socket can中類似創(chuàng)建接收線程 */
190 NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;
191 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
192 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
193 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
194 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
195 NVIC_Init(&NVIC_InitStructure);
196 CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
197 }
198 break;
199
200 default:
201 break;
202
203 }
204 return ;
205 }
CAN接收中斷函數(shù)如下:
275 /**********************************************************************
276 * 函數(shù)名稱: void CAN1_RX0_IRQHandler(void)
277 * 功能描述: CAN接收中斷函數(shù)
278 * 輸入?yún)?shù): 無(wú)
279 * 輸出參數(shù): 無(wú)
280 * 返 回 值: 無(wú)
281 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
282 * -----------------------------------------------
283 * 2020/05/13 V1.0 bert 創(chuàng)建
284 ***********************************************************************/
285 void CAN1_RX0_IRQHandler(void)
286 {
287 /* 如果回調(diào)函數(shù)存在,則執(zhí)行回調(diào)函數(shù) */
288 if( g_pCanInterrupt != NULL)
289 {
290 g_pCanInterrupt();
291 }
292
293 /* 清除掛起中斷 */
294 CAN_ClearITPendingBit(CAN1,CAN_IT_FMP0);
295 }
此處CAN中斷通過(guò)回調(diào)函數(shù)g_pCanInterrupt()函數(shù)將應(yīng)用層需要的代碼分層到應(yīng)用層,此處為驅(qū)動(dòng)部分通用接口。
(4)CAN報(bào)文讀取函數(shù)
當(dāng)CAN接收中斷產(chǎn)生,通過(guò)CAN報(bào)文讀取函數(shù)從FIFO中讀取已經(jīng)接收到的CAN報(bào)文。
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“can_controller.c”文件void CAN_Read(int can_port, CanRxMsg* recv_msg)函數(shù)。
CAN報(bào)文讀取函數(shù)如下:
208 /**********************************************************************
209 * 函數(shù)名稱: void CAN_Read(int can_port, CanRxMsg* recv_msg)
210 * 功能描述: CAN讀取接收寄存器,取出接收到的報(bào)文
211 * 輸入?yún)?shù): can_port,端口號(hào)
212 * 輸出參數(shù): recv_msg:接收?qǐng)?bào)文
213 * 返 回 值: 無(wú)
214 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
215 * -----------------------------------------------
216 * 2020/05/13 V1.0 bert 創(chuàng)建
217 ***********************************************************************/
218 void CAN_Read(int can_port, CanRxMsg* recv_msg)
219 {
220 switch( can_port )
221 {
222 case CAN_PORT_CAN1:
223 {
224 /* 從FIFO中讀取CAN報(bào)文 */
225 CAN_Receive(CAN1,CAN_FIFO0, recv_msg);
226 }
227 break;
228
229 default:
230 break;
231 }
232 return ;
233 }
(5)CAN報(bào)文發(fā)送函數(shù)
當(dāng)需要發(fā)送CAN報(bào)文時(shí),通過(guò)向CAN發(fā)送郵箱填充數(shù)據(jù),啟動(dòng)發(fā)送報(bào)文。
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“can_controller.c”文件void CAN_Write(int can_port, CanTxMsg send_msg)函數(shù)。
CAN報(bào)文讀取函數(shù)如下:
235 /**********************************************************************
236 * 函數(shù)名稱: void CAN_Write(int can_port, CanTxMsg send_msg)
237 * 功能描述: CAN報(bào)文發(fā)送接口,調(diào)用發(fā)送寄存器發(fā)送報(bào)文
238 * 輸入?yún)?shù): can_port,端口號(hào)
239 * 輸出參數(shù): send_msg:發(fā)送報(bào)文
240 * 返 回 值: 無(wú)
241 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
242 * -----------------------------------------------
243 * 2020/05/13 V1.0 bert 創(chuàng)建
244 ***********************************************************************/
245 void CAN_Write(int can_port, CanTxMsg send_msg)
246 {
247 unsigned char i;
248 uint8_t transmit_mailbox = 0;
249 CanTxMsg TxMessage;
250
251 switch( can_port )
252 {
253 case CAN_PORT_CAN1:
254 {
255 TxMessage.StdId = send_msg.StdId; // 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF
256 TxMessage.ExtId = 0x0000; // 擴(kuò)展標(biāo)識(shí)符0x0000
257 TxMessage.IDE = CAN_ID_STD; // 使用標(biāo)準(zhǔn)標(biāo)識(shí)符
258 TxMessage.RTR = CAN_RTR_DATA; // 設(shè)置為數(shù)據(jù)幀
259 TxMessage.DLC = send_msg.DLC; // 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié)
260
261 for(i=0; i<TxMessage.DLC; i++)
262 {
263 TxMessage.Data[i] = send_msg.Data[i];
264 }
265 transmit_mailbox = CAN_Transmit(CAN1,&TxMessage); /* 返回這個(gè)信息請(qǐng)求發(fā)送的郵箱號(hào)0,1,2或沒(méi)有郵箱申請(qǐng)發(fā)送no_box */
266 }
267 break;
268
269 default:
270 break;
271 }
272 return ;
273 }
(6)CAN抽象結(jié)構(gòu)體框架初始化
定義一個(gè)can1通信結(jié)構(gòu)實(shí)例CAN_COMM_STRUCT can1_controller;
使用(1)~(5)步驟實(shí)現(xiàn)的函數(shù),初始化can1_controller,構(gòu)成與應(yīng)用層關(guān)聯(lián)的一個(gè)連接點(diǎn)。
298 /**********************************************************************
299 * 名稱: can1_controller
300 * 功能描述: CAN1結(jié)構(gòu)體初始化
301 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
302 * -----------------------------------------------
303 * 2020/05/13 V1.0 bert 創(chuàng)建
304 ***********************************************************************/
305 CAN_COMM_STRUCT can1_controller = {
306 .name = "can0",
307 .can_port = CAN_PORT_CAN1,
308 .can_set_controller = CAN_Set_Controller,
309 .can_set_interrput = CAN_Set_Interrupt,
310 .can_read = CAN_Read,
311 .can_write = CAN_Write,
312 };
14.3.2.3 編寫CAN應(yīng)用層代碼
根據(jù)14.3.2.2 已經(jīng)將具體的CAN硬件操作已經(jīng)實(shí)現(xiàn),并且已經(jīng)抽象實(shí)例化了CAN編程框架。
但是我們現(xiàn)在還沒(méi)關(guān)聯(lián)到應(yīng)用層,應(yīng)用層并不知道調(diào)用哪個(gè)接口。
(1)CAN應(yīng)用層注冊(cè)實(shí)例
在應(yīng)用層編寫一個(gè)通用的實(shí)例化注冊(cè)函數(shù)。
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“app_can.c”文件int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)函數(shù)。
代碼實(shí)現(xiàn)如下:
62 /**********************************************************************
63 * 函數(shù)名稱: int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
64 * 功能描述: 應(yīng)用層進(jìn)行CAN1結(jié)構(gòu)體注冊(cè)
65 * 輸入?yún)?shù): p_can_controller,CAN控制器抽象結(jié)構(gòu)體
66 * 輸出參數(shù): 無(wú)
67 * 返 回 值: 無(wú)
68 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
69 * -----------------------------------------------
70 * 2020/05/13 V1.0 bert 創(chuàng)建
71 ***********************************************************************/
72 int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
73 {
74 /* 判斷傳入的p_can_controller為非空,目的是確認(rèn)這個(gè)結(jié)構(gòu)體是實(shí)體*/
75 if( p_can_controller != NULL )
76 {
77 /* 將傳入的參數(shù)p_can_controller賦值給應(yīng)用層結(jié)構(gòu)體gCAN_COMM_STRUCT */
78
79 /*端口號(hào),類比socketcan套接口*/
80 gCAN_COMM_STRUCT.can_port = p_can_controller->can_port;
81 /*CAN控制器配置函數(shù)*/
82 gCAN_COMM_STRUCT.can_set_controller = p_can_controller->can_set_controller;
83 /*CAN中斷配置*/
84 gCAN_COMM_STRUCT.can_set_interrput = p_can_controller->can_set_interrput;
85 /*CAN報(bào)文讀函數(shù)*/
86 gCAN_COMM_STRUCT.can_read = p_can_controller->can_read;
87 /*CAN報(bào)文發(fā)送函數(shù)*/
88 gCAN_COMM_STRUCT.can_write = p_can_controller->can_write;
89 return 1;
90 }
91 return 0;
92 }
然后通過(guò)調(diào)用register_can_controller( &can1_controller );將實(shí)例can1_controller注冊(cè)給應(yīng)用的4 static CAN_COMM_STRUCT gCAN_COMM_STRUCT;
之后應(yīng)用層只需要調(diào)用應(yīng)用層自己的gCAN_COMM_STRUCT實(shí)例即可操作CAN通信功能。
315 /**********************************************************************
316 * 函數(shù)名稱: void CAN1_contoller_add(void)
317 * 功能描述: CAN結(jié)構(gòu)體注冊(cè)接口,應(yīng)用層在使用can1_controller前調(diào)用
318 * 輸入?yún)?shù): 無(wú)
319 * 輸出參數(shù): 無(wú)
320 * 返 回 值: 無(wú)
321 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
322 * -----------------------------------------------
323 * 2020/05/13 V1.0 bert 創(chuàng)建
324 ***********************************************************************/
325 void CAN1_contoller_add(void)
326 {
327 /*將can1_controller傳遞給應(yīng)用層*/
328 register_can_controller( &can1_controller );
329 }
(2)CAN應(yīng)用層初始化
CAN應(yīng)用層初始化代碼如下;
94 /**********************************************************************
95 * 函數(shù)名稱: void app_can_init(void)
96 * 功能描述: CAN應(yīng)用層初始化
97 * 輸入?yún)?shù): 無(wú)
98 * 輸出參數(shù): 無(wú)
99 * 返 回 值: 無(wú)
100 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
101 * -----------------------------------------------
102 * 2020/05/13 V1.0 bert 創(chuàng)建
103 ***********************************************************************/
104 void app_can_init(void)
105 {
106 /**
107 * 應(yīng)用層進(jìn)行CAN1結(jié)構(gòu)體注冊(cè)
108 */
109 CAN1_contoller_add();
110
111 /*
112 *調(diào)用can_set_controller進(jìn)行CAN控制器配置,
113 *返回can_port,類比linux socketcan中的套接口,單片機(jī)例程中作為自定義CAN通道
114 */
115 gCAN_COMM_STRUCT.can_port = gCAN_COMM_STRUCT.can_set_controller();
116 /**
117 * 調(diào)用can_set_interrput配置CAN接收中斷,類比socketcan中的接收線程
118 */
119 gCAN_COMM_STRUCT.can_set_interrput( gCAN_COMM_STRUCT.can_port, CAN_RX_IRQHandler_Callback );
120 }
(3)設(shè)計(jì)一個(gè)簡(jiǎn)單的周期發(fā)送報(bào)文功能
CAN周期發(fā)送報(bào)文的功能代碼實(shí)現(xiàn)如下:
123 /**********************************************************************
124 * 函數(shù)名稱: void app_can_tx_test(void)
125 * 功能描述: CAN應(yīng)用層報(bào)文發(fā)送函數(shù),用于測(cè)試周期發(fā)送報(bào)文
126 * 輸入?yún)?shù): 無(wú)
127 * 輸出參數(shù): 無(wú)
128 * 返 回 值: 無(wú)
129 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
130 * -----------------------------------------------
131 * 2020/05/13 V1.0 bert 創(chuàng)建
132 ***********************************************************************/
133 void app_can_tx_test(void)
134 {
135 // 以10ms為基準(zhǔn),運(yùn)行CAN測(cè)試程序
136
137 unsigned char i=0;
138
139 /* 發(fā)送報(bào)文定義 */
140 CanTxMsg TxMessage;
141
142 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為計(jì)數(shù)器 */
143 static unsigned char tx_counter = 0;
144
145 /* 以10ms為基準(zhǔn),通過(guò)timer計(jì)數(shù)器設(shè)置該處理函數(shù)后面運(yùn)行代碼的周期為1秒鐘*/
146 static unsigned int timer =0;
147 if(timer++>100)
148 {
149 timer = 0;
150 }
151 else
152 {
153 return ;
154 }
155
156 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
157 TxMessage.StdId = TX_CAN_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
158 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
159 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
160 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
161 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
162
163 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
164 TxMessage.Data[0] = tx_counter++; /* 用來(lái)識(shí)別報(bào)文發(fā)送計(jì)數(shù)器 */
165 for(i=1; i<TxMessage.DLC; i++)
166 {
167 TxMessage.Data[i] = i;
168 }
169
170 /* 調(diào)用can_write發(fā)送CAN報(bào)文 */
171 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
172
173 }
(4)設(shè)計(jì)一個(gè)簡(jiǎn)單的接收?qǐng)?bào)文功能
220 /**********************************************************************
221 * 函數(shù)名稱: void CAN_RX_IRQHandler_Callback(void)
222 * 功能描述: CAN1接收中斷函數(shù);在linux中可以類比用線程,或定時(shí)器去讀CAN數(shù)據(jù)
223 * 輸入?yún)?shù): 無(wú)
224 * 輸出參數(shù): 無(wú)
225 * 返 回 值: 無(wú)
226 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
227 * -----------------------------------------------
228 * 2020/05/13 V1.0 bert 創(chuàng)建
229 ***********************************************************************/
230 void CAN_RX_IRQHandler_Callback(void)
231 {
232 /* 接收?qǐng)?bào)文定義 */
233 CanRxMsg RxMessage;
234
235 /* 接收?qǐng)?bào)文清零 */
236 memset( &RxMessage, 0, sizeof(CanRxMsg) );
237
238 /* 通過(guò)can_read接口讀取寄存器已經(jīng)接收到的報(bào)文 */
239 gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);
240
241 /* 將讀取到的CAN報(bào)文存拷貝到全局報(bào)文結(jié)構(gòu)體g_CAN1_Rx_Message */
242 memcpy(&g_CAN1_Rx_Message, &RxMessage, sizeof( CanRxMsg ) );
243
244 /* 設(shè)置當(dāng)前接收完成標(biāo)志,判斷當(dāng)前接收?qǐng)?bào)文ID為RX_CAN_ID,則設(shè)置g_CAN1_Rx_Flag=1*/
245 if( g_CAN1_Rx_Message.StdId == RX_CAN_ID )
246 {
247 g_CAN1_Rx_Flag = 1;
248 }
249 }
176 /**********************************************************************
177 * 函數(shù)名稱: void app_can_rx_test(void)
178 * 功能描述: CAN應(yīng)用層接收?qǐng)?bào)文處理函數(shù),用于處理中斷函數(shù)中接收的報(bào)文
179 * 輸入?yún)?shù): 無(wú)
180 * 輸出參數(shù): 無(wú)
181 * 返 回 值: 無(wú)
182 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
183 * -----------------------------------------------
184 * 2020/05/13 V1.0 bert 創(chuàng)建
185 ***********************************************************************/
186 void app_can_rx_test(void)
187 {
188 unsigned char i=0;
189
190 /* 發(fā)送報(bào)文定義 */
191 CanTxMsg TxMessage;
192
193 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為計(jì)數(shù)器 */
194 static unsigned char rx_counter = 0;
195
196
197 if( g_CAN1_Rx_Flag == 1)
198 {
199 g_CAN1_Rx_Flag = 0;
200
201 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
202 TxMessage.StdId = RX_TO_TX_CAN_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
203 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
204 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
205 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
206 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
207
208 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
209 TxMessage.Data[0] = rx_counter++; /* 用來(lái)識(shí)別報(bào)文發(fā)送計(jì)數(shù)器 */
210 for(i=1; i<TxMessage.DLC; i++)
211 {
212 TxMessage.Data[i] = g_CAN1_Rx_Message.Data[i];
213 }
214
215 /* 調(diào)用can_write發(fā)送CAN報(bào)文 */
216 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
217 }
218 }
14.3.2.4 STM32 CAN案例測(cè)試
在前面幾個(gè)章節(jié)將代碼編寫完成之后,我們做個(gè)測(cè)試;
測(cè)試工具使用的是:英特蓓斯的Valuecan3(CAN協(xié)議盒),Vehicle Vspy3(電腦端軟件)。
也可以在淘寶上購(gòu)買便宜的USB轉(zhuǎn)CAN的工具即可。
測(cè)試步驟如下:
Step1:將已經(jīng)完成的STM32 CAN測(cè)試程序下載到實(shí)際開發(fā)板上;
Step2:通過(guò)CAN測(cè)試工具Vehicle Vspy3發(fā)送報(bào)文ID為0X201的報(bào)文;
Step3:觀察CAN測(cè)試軟件顯示如下:
報(bào)文ID為0x101的報(bào)文是按照1秒周期進(jìn)行發(fā)送,如圖14.3.2.4-1。
報(bào)文ID為0x201的報(bào)文是Vehicle Spy3按照周期500ms發(fā)送給STM32開發(fā)板,如圖14.3.2.4-1
報(bào)文ID為0x301的報(bào)文是在接收到報(bào)文ID為0x201的報(bào)文后,然后轉(zhuǎn)發(fā)出報(bào)文ID為0x301的報(bào)文,如圖14.3.2.4-2。
<center><p>圖14.3.2.4-1 報(bào)文發(fā)送結(jié)果查看</p></center>
<center><p>圖14.3.2.4-2 報(bào)文接收情況查看</p></center>
14.4 Linux socketcan基礎(chǔ)應(yīng)用編程
14.4.1 socketcan概述
? socketcan是在Linux下CAN協(xié)議(Controller Area Network)實(shí)現(xiàn)的一種實(shí)現(xiàn)方法。 CAN是一種在世界范圍內(nèi)廣泛用于自動(dòng)控制、嵌入式設(shè)備和汽車領(lǐng)域的網(wǎng)絡(luò)技術(shù)。Linux下最早使用CAN的方法是基于字符設(shè)備來(lái)實(shí)現(xiàn)的,與之不同的是Socket CAN使用伯克利的socket接口和linux網(wǎng)絡(luò)協(xié)議棧,這種方法使得can設(shè)備驅(qū)動(dòng)可以通過(guò)網(wǎng)絡(luò)接口來(lái)調(diào)用。Socket CAN的接口被設(shè)計(jì)的盡量接近TCP/IP的協(xié)議,讓那些熟悉網(wǎng)絡(luò)編程的程序員能夠比較容易的學(xué)習(xí)和使用。
? 使用Socket CAN的主要目的就是為用戶空間的應(yīng)用程序提供基于Linux網(wǎng)絡(luò)層的套接字接口。與廣為人知的TCP/IP協(xié)議以及以太網(wǎng)不同,CAN總線沒(méi)有類似以太網(wǎng)的MAC層地址,只能用于廣播。CAN ID僅僅用來(lái)進(jìn)行總線的仲裁。因此CAN ID在總線上必須是唯一的。當(dāng)設(shè)計(jì)一個(gè)CAN-ECU(Electronic Control Unit 電子控制單元)網(wǎng)絡(luò)的時(shí)候,CAN報(bào)文ID可以映射到具體的ECU。因此CAN報(bào)文ID可以當(dāng)作發(fā)送源的地址來(lái)使用。
14.4.2 socketcan基本知識(shí)點(diǎn)
? 在“14.3 STM32 CAN應(yīng)用編程”中我們已經(jīng)完整的構(gòu)建了CAN應(yīng)用編程框架,但是在linux應(yīng)用編程中,操作CAN底層驅(qū)動(dòng)與STM32思路上相似,但是操作方法或者說(shuō)調(diào)用的接口還是差異很大的,因?yàn)镾TM32是直接調(diào)用的SDK包或直接操作寄存器,但是linux系統(tǒng)是需要通過(guò)調(diào)用系統(tǒng)命令或linuxCAN驅(qū)動(dòng)來(lái)實(shí)現(xiàn)物理層的操作。
因此這里我們重點(diǎn)介紹linux上的一些系統(tǒng)調(diào)用命令,和一些socketcan相關(guān)的概念。
14.4.2.1 CAN設(shè)備操作
? CAN設(shè)備有開啟、關(guān)閉、設(shè)置參數(shù)3個(gè)功能。因?yàn)閘inux下CAN設(shè)備是模擬網(wǎng)絡(luò)操作的方式,這里CAN設(shè)備的開啟、關(guān)閉和設(shè)置,均通過(guò)ip命令來(lái)操作。
? 在100ask_IMX6ULL開發(fā)板上打開串口,使用“ifconfig -a”查看所有的網(wǎng)絡(luò)節(jié)點(diǎn),發(fā)現(xiàn)第1個(gè)節(jié)點(diǎn)就是“can0”。
(1)Linux CAN設(shè)備開啟:
#define ip_cmd_open "ifconfig can0 up" /* 打開CAN0 */
說(shuō)明:can0:can設(shè)備名;
up: 打開設(shè)備命令
(2)Linux CAN設(shè)備關(guān)閉:
#define ip_cmd_close "ifconfig can0 down" /* 關(guān)閉CAN0 */
說(shuō)明:can0:can設(shè)備名;
down: 關(guān)閉設(shè)備命令
(2)Linux CAN參數(shù)設(shè)置(波特率,采樣率):
#define ip_cmd_set_can_params "ip link set can0 type can bitrate 500000 triple-sampling on"
/* 將CAN0波特率設(shè)置為500000 bps */
說(shuō)明:can0:can設(shè)備名;
down: 關(guān)閉設(shè)備命令
Type can: 設(shè)備類型為can
Bitrate 500000: 波特率設(shè)置為500kbps
Triple-sampleing on: 采樣打開
14.4.2.2 什么是Socket套接口
? 在linux里網(wǎng)絡(luò)操作使用socket進(jìn)行接口創(chuàng)建,竟然CAN設(shè)備也是虛擬成網(wǎng)絡(luò)接口,也是使用的socket套接口。
? 如下圖所示,電話A呼叫電話B,電話A會(huì)輸入電話B的號(hào)碼,電話B會(huì)接收到電話A的來(lái)電。
電話A和電話B是兩個(gè)端點(diǎn)。而linux套接口與這個(gè)電話通信類似,套接口就是一個(gè)通信的端點(diǎn),端點(diǎn)之間是通信鏈路;電話通信是通過(guò)電話號(hào)碼進(jìn)行撥號(hào)通信,而套接口是使用地址進(jìn)行識(shí)別對(duì)方的。
<center><p>圖14.2.2.2 電話通信模型</p></center>
14.4.2.3 Socket接口函數(shù)
我們要?jiǎng)?chuàng)建并使用socket套接口進(jìn)行通信編程,就需要了解一下socket相關(guān)的接口函數(shù)。
需要查詢linux系統(tǒng)里的函數(shù),可以通過(guò)man命令查看。
舉例:
man socket / 查看socket函數(shù)描述 /
(1)socket()函數(shù)
在linux系統(tǒng)下,通過(guò)“man socket”命令,查詢socket()函數(shù)描述如下:
Socket函數(shù)原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol); /* 套接口函數(shù)原型 */
函數(shù)三個(gè)參數(shù)如下:
domain:即協(xié)議域,又稱為協(xié)議族(family)。 常用的協(xié)議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協(xié)議族決定了socket的地址類型,在通信中必須采用對(duì)應(yīng)的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址。 |
---|
type: 指定socket類型。常用的socket類型有, SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。 |
protocol:就是指定協(xié)議。 常用的協(xié)議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對(duì)應(yīng)TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。 |
注意:
? 1.并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)protocol為0時(shí),會(huì)自動(dòng)選擇type類型對(duì)應(yīng)的默認(rèn)協(xié)議。
? 當(dāng)我們調(diào)用socket創(chuàng)建一個(gè)socket時(shí),返回的socket描述字它存在于協(xié)議族(address family,AF_XXX)空間中,但沒(méi)有一個(gè)具體的地址。如果想要給它賦值一個(gè)地址,就必須調(diào)用bind()函數(shù),否則就當(dāng)調(diào)用connect()、listen()時(shí)系統(tǒng)會(huì)自動(dòng)隨機(jī)分配一個(gè)端口。
? 2. Socketcan使用的domain協(xié)議域是AF_CAN(或PF_CAN),type類型是SOCK_RAW, 指定協(xié)議protocol是CAN_RAW.
(2)bind()函數(shù)
? 在linux系統(tǒng)下,通過(guò)“man bind”命令,查詢bind()函數(shù)描述如下:
? bind()函數(shù)把一個(gè)地址族中的特定地址賦給socket。例如對(duì)應(yīng)AF_INET、AF_INET6就是把一個(gè)ipv4或ipv6地址和端口號(hào)組合賦給socket。
? Bind函數(shù)原型如下所示:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
函數(shù)的三個(gè)參數(shù)分別為:
sockfd:即socket描述字,它是通過(guò)socket()函數(shù)創(chuàng)建了,唯一標(biāo)識(shí)一個(gè)socket。bind()函數(shù)就是將給這個(gè)描述字綁定一個(gè)名字。
addr:一個(gè)const struct sockaddr *指針,指向要綁定給sockfd的協(xié)議地址。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同,
如ipv4對(duì)應(yīng)的是:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
ipv6對(duì)應(yīng)的是:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
Unix域?qū)?yīng)的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
CAN域?qū)?yīng)的是:
在文件“Linux-4.9.88includeuapilinuxcan.h”中有定義,這個(gè)是本章需要重點(diǎn)了解的。
/**
* struct sockaddr_can - CAN sockets的地址結(jié)構(gòu)
* @can_family: 地址協(xié)議族 AF_CAN.
* @can_ifindex: CAN網(wǎng)絡(luò)接口索引
* @can_addr: 協(xié)議地址信息
*/
struct sockaddr_can {
__kernel_sa_family_t can_family;
int can_ifindex;
union {
/* 傳輸協(xié)議類地址信息 (e.g. ISOTP) */
struct { canid_t rx_id, tx_id; } tp;
/* 預(yù)留給將來(lái)使用的CAN協(xié)議地址信息*/
} can_addr;
};
addrlen:對(duì)應(yīng)的是地址的長(zhǎng)度。
通常服務(wù)器在啟動(dòng)的時(shí)候都會(huì)綁定一個(gè)眾所周知的地址(如ip地址+端口號(hào)),用于提供服務(wù),客戶就可以通過(guò)它來(lái)接連服務(wù)器;而客戶端就不用指定,有系統(tǒng)自動(dòng)分配一個(gè)端口號(hào)和自身的ip地址組合。這就是為什么通常服務(wù)器端在listen之前會(huì)調(diào)用bind(),而客戶端就不會(huì)調(diào)用,而是在connect()時(shí)由系統(tǒng)隨機(jī)生成一個(gè)。
(3)ioctl()函數(shù)
在linux系統(tǒng)下,通過(guò)“man ioctl”命令,查詢ioctl()函數(shù)描述如下:
Ioctl()函數(shù)調(diào)用層次如下圖所示:
Ioctl()函數(shù)原型如下:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
用ioctl獲得本地網(wǎng)絡(luò)接口地址時(shí)要用到兩個(gè)結(jié)構(gòu)體ifconf和ifreq。
struct ifreq定義
ifreq用來(lái)保存某個(gè)接口的信息。
在文件“Linux-4.9.88includeuapilinuxif.h”中有定義struct ifreq,這個(gè)只需要了解是在ioctl()函數(shù)調(diào)用時(shí)用來(lái)獲取CAN設(shè)備索引(ifr_ifindex)使用,其他的參數(shù)可以不用關(guān)注。
/*
* Interface request structure used for socket
* ioctl's. All interface ioctl's must have parameter
* definitions which begin with ifr_name. The
* remainder may be interface specific.
*/
/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_IFREQ
struct ifreq {
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#endif /* __UAPI_DEF_IF_IFREQ */
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
struct ifconf定義
ifconf通常是用來(lái)保存所有接口信息的,本章節(jié)未使用到,在此不作詳細(xì)介紹。
(4)setsockopt()函數(shù)
? 在linux系統(tǒng)下,通過(guò)“man setsockopt”命令,查詢setsockopt()函數(shù)描述如下:
setsockopt()和getsockopt函數(shù)原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
Setsockopt()用于任意類型、任意狀態(tài)套接口的設(shè)置選項(xiàng)值。盡管在不同協(xié)議層上存在選項(xiàng),但本函數(shù)僅定義了最高的“套接口”層次上的選項(xiàng)。
其函數(shù)參數(shù)如下:可以看出其參數(shù)
sockfd:標(biāo)識(shí)一個(gè)套接口的描述字。 |
---|
level:選項(xiàng)定義的層次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP,IPPROTO_IPV6,SOL_CAN_RAW等。 |
optname:需設(shè)置的選項(xiàng)。 |
optval:指針,指向存放選項(xiàng)待設(shè)置的新值的緩沖區(qū)。 |
optlen:optval緩沖區(qū)長(zhǎng)度。 |
函數(shù)調(diào)用示例如下:
示例1:設(shè)置CAN過(guò)濾器為不接收所有報(bào)文。 |
---|
//禁用過(guò)濾規(guī)則,本進(jìn)程不接收?qǐng)?bào)文,只負(fù)責(zé)發(fā)送 <br><br/> //設(shè)置過(guò)濾規(guī)則 setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); |
示例2:設(shè)置CAN過(guò)濾器為接收某個(gè)指定報(bào)文 |
---|
//定義接收規(guī)則,只接收表示符等于 0x201 的報(bào)文 <br/><br/> //在linux頭文件有定義,也可以自己定義 <br/>#define CAN_SFF_MASK 0x000007ffU <br/><br/> //定義過(guò)濾器(1個(gè)) <br/>struct can_filter rfilter[1]; <br/> rfilter[0].can_id = 0x201; <br/>rfilter[0].can_mask = CAN_SFF_MASK; <br/>//設(shè)置過(guò)濾規(guī)則 <br/>setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter)); |
示例2:設(shè)置CAN過(guò)濾器為接收某個(gè)指定報(bào)文 |
---|
//定義接收規(guī)則,只接收表示符等于 0x201 的報(bào)文 <br/><br/> //在linux頭文件有定義,也可以自己定義 <br/>#define CAN_SFF_MASK 0x000007ffU <br/><br/>//定義過(guò)濾器(3個(gè)) <br/>struct can_filter rfilter[3]; <br/>rfilter[0].can_id = 0x201; <br/>rfilter[0].can_mask = CAN_SFF_MASK; <br/><br/>rfilter[1].can_id = 0x401; <br/>rfilter[1].can_mask = CAN_SFF_MASK; <br/><br/>rfilter[2].can_id = 0x601; <br/>rfilter[2].can_mask = CAN_SFF_MASK; <br/><br/>//設(shè)置過(guò)濾規(guī)則 <br/>setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter)); |
(5)write()函數(shù)
在linux系統(tǒng)下,通過(guò)“man 2 write”命令,查詢write()函數(shù)描述如下:
Write函數(shù)原型如下:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
(6)read()函數(shù)
在linux系統(tǒng)下,通過(guò)“man 2 read”命令,查詢r(jià)ead()函數(shù)描述如下:
Read函數(shù)原型如下:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
(7)close()函數(shù)
在linux系統(tǒng)下,通過(guò)“man 2 close”命令,查詢close()函數(shù)描述如下:
close()函數(shù)原型如下:
#include <unistd.h>
int close(int fd);
14.4.3 socket_can簡(jiǎn)單發(fā)送實(shí)例
簡(jiǎn)單發(fā)送實(shí)例代碼目錄:“02_socketcan_send”
案例描述:
- 實(shí)現(xiàn)周期1秒發(fā)送報(bào)文ID:0x101的報(bào)文;
了解內(nèi)容:IMX6 CAN接口電路
從下面CAN外圍電路看,和STM32是完全相同的,只是處理內(nèi)部的CAN控制器因?yàn)椴煌酒圃鞆S家的不同,會(huì)有一些較小的差異。這里電路只是對(duì)比了解一下,做linux應(yīng)用可以不需要關(guān)注底層驅(qū)動(dòng)處理。
那我們現(xiàn)在按照14.3章節(jié)構(gòu)建STM32下CAN應(yīng)用編程的框架,一步一步編寫linux下socketCAN的應(yīng)用編程。
準(zhǔn)備工作:
我們按照14.3章節(jié)準(zhǔn)備好can應(yīng)用的代碼文件:
文件名 | 文件內(nèi)容描述 |
---|---|
App_can.c | CAN應(yīng)用功能實(shí)現(xiàn) |
App_can.h | CAN應(yīng)用功能頭文件 |
Can_controller.c | CAN驅(qū)動(dòng)操作抽象層具體實(shí)現(xiàn) |
Can_controller.h | CAN驅(qū)動(dòng)操作抽象層頭文件 |
Can_msg.h | CAN報(bào)文基本結(jié)構(gòu)體,從STM32 CAN驅(qū)動(dòng)拷貝過(guò)來(lái)的,主要在使用CAN報(bào)文時(shí)使用我們最熟悉的結(jié)構(gòu)體。 此文件相對(duì)STM32為新增文件,因?yàn)槲覀兊目蚣苁腔趩纹瑱C(jī)應(yīng)用,然后類比遷移學(xué)習(xí)到linux上。 |
Makefile | Makefile編譯腳本 |
14.4.3.1編寫抽象框架的實(shí)現(xiàn)函數(shù)
首先我們使用14.3章節(jié)已經(jīng)構(gòu)建好的抽象結(jié)構(gòu)體,如下:
見(jiàn)第14章節(jié)代碼“02_socketcan_send_addline”中“can_controller.h”。
34 /* CAN通信抽象結(jié)構(gòu)體定義*/
35 typedef struct _CAN_COMM_STRUCT
36 {
37 /* CAN硬件名稱 */
38 char *name;
39 /* CAN端口號(hào),裸機(jī)里為端口號(hào);linux應(yīng)用里作為socket套接口 */
40 int can_port;
41 /* CAN控制器配置函數(shù),返回端口號(hào)賦值給can_port */
42 int (*can_set_controller)( void );
43 /* CAN接口中斷創(chuàng)建,在linux中對(duì)應(yīng)創(chuàng)建接收線程 */
44 void (*can_set_interrput)( int can_port , pCanInterrupt callback );
45 /* CAN讀取報(bào)文接口 */
46 void (*can_read)( int can_port , CanRxMsg* recv_msg);
47 /* CAN發(fā)送報(bào)文接口*/
48 void (*can_write)( int can_port , CanTxMsg send_msg);
49 }CAN_COMM_STRUCT, *pCAN_COMM_STRUCT;
50
我們就按照這個(gè)結(jié)構(gòu)體的順序依次編寫can_controller.c中的CAN驅(qū)動(dòng)操作具體實(shí)現(xiàn)函數(shù)。
(1)定義CAN設(shè)備
根據(jù)14.4.2.1章節(jié)描述,linux應(yīng)用層操作CAN設(shè)備,需要知道設(shè)備名。.
在100ask_IMX6ULL開發(fā)板上打開串口,使用“ifconfig -a”命令查看,知道當(dāng)前CAN設(shè)備名稱為”can0”。
直接在linux命令行直接使用ip命令可以打開,設(shè)置,和關(guān)閉CAN設(shè)備,因此我們定義了三個(gè)宏ip_cmd_open, ip_cmd_close,ip_cmd_set_can_params, 這三個(gè)宏可以通過(guò)系統(tǒng)調(diào)用system()進(jìn)行執(zhí)行。
見(jiàn)第14章節(jié)代碼“02_socketcan_send_addline”中“can_controller.c”文件中宏定義。
29 /**************宏定義**************************************************/
30
31 /* 將CAN0波特率設(shè)置為500000 bps */
32 #define ip_cmd_set_can_params "ip link set can0 type can bitrate 500000 triple-sampling on"
33
34 /* 打開CAN0 */
35 #define ip_cmd_open "ifconfig can0 up"
36
37 /* 關(guān)閉CAN0 */
38 #define ip_cmd_close "ifconfig can0 down"
(2)配置CAN控制器
配置CAN控制器有3個(gè)部分:打開can0設(shè)備,CAN波特率配置,CAN過(guò)濾器配置。
見(jiàn)第14章節(jié)代碼“01_stm32f407_can_addline”中“can_controller.c”文件int CAN_Set_Controller( void )函數(shù)。
A.配置波特率,打開can0設(shè)備
使用(1)中的三個(gè)命令ip_cmd_open, ip_cmd_close,ip_cmd_set_can_params,通過(guò)system系統(tǒng)調(diào)用:具體代碼如下
77 /* 通過(guò)system調(diào)用ip命令設(shè)置CAN波特率 */
78 system(ip_cmd_close);
79 system(ip_cmd_set_can_params);
80 system(ip_cmd_open);
B.創(chuàng)建套接口
因?yàn)閘inux應(yīng)用操作設(shè)備均使用讀read寫write操作,linux一切皆文件,而socketcan又是一個(gè)特殊的文件,因此我們需要調(diào)用socket()函數(shù)創(chuàng)建一個(gè)socketcan接口,獲取sock_fd描述符。
具體代碼如下:
82 /*************************************************************/
83 /* 創(chuàng)建套接口 sock_fd */
84 sock_fd = socket(AF_CAN, SOCK_RAW, CAN_RAW);
85 if(sock_fd < 0)
86 {
87 perror("socket create error!
");
88 return -1;
89 }
C.綁定can0設(shè)備與套接口
具體代碼如下:
92 //將套接字與 can0 綁定
93 strcpy(ifr.ifr_name, "can0");
94 ioctl(sock_fd, SIOCGIFINDEX,&ifr); // 設(shè)置設(shè)備為can0
95
96 ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
97 printf("ifr_name:%s
",ifr.ifr_name);
98 printf("can_ifindex:%d
",ifr.ifr_ifindex);
99
100 addr.can_family = AF_CAN;
101 addr.can_ifindex = ifr.ifr_ifindex;
102
103 if( bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 )
104 {
105 perror("bind error!
");
106 return -1;
107 }
C.配置過(guò)濾器
具體代碼如下:
109 /*************************************************************/
110 //禁用過(guò)濾規(guī)則,本進(jìn)程不接收?qǐng)?bào)文,只負(fù)責(zé)發(fā)送
111 setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
D.配置非阻塞操作
Linux系統(tǒng)調(diào)用read和write函數(shù)有阻塞和非阻塞,我們?cè)谥芷谡{(diào)用時(shí),此采用非阻塞方式對(duì)CAN報(bào)文進(jìn)行讀寫操作。
具體代碼實(shí)現(xiàn)如下:
114 //設(shè)置read()和write()函數(shù)設(shè)置為非堵塞方式
115 int flags;
116 flags = fcntl(sock_fd, F_GETFL);
117 flags |= O_NONBLOCK;
118 fcntl(sock_fd, F_SETFL, flags);
E.返回sock_fd套接口
具體代碼實(shí)現(xiàn)如下:
int CAN_Set_Controller( void )函數(shù)直接結(jié)束后,返回值賦值給CAN_COMM_STRUCT的can_port成員。
后續(xù)應(yīng)用層所訪問(wèn)的sock_fd描述符即為can_port.
(3)創(chuàng)建CAN接收線程
在STM32中,接收使用的接收FIFO中斷進(jìn)行處理,在linux應(yīng)用中,我們則采用線程輪詢?nèi)プx取報(bào)文。
因此我們需要?jiǎng)?chuàng)建一個(gè)CAN接收線程,具體代碼實(shí)現(xiàn)如下:
127 /**********************************************************************
128 * 函數(shù)名稱: void CAN_Set_Interrupt(int can_port, pCanInterrupt callback)
129 * 功能描述: 創(chuàng)建CAN接收線程,并傳入應(yīng)用的的回調(diào)函數(shù),回調(diào)函數(shù)主要處理應(yīng)用層的功能
130 * 輸入?yún)?shù): can_port,端口號(hào)
131 * callback: 中斷具體處理應(yīng)用功能的回調(diào)函數(shù)
132 * 輸出參數(shù): 無(wú)
133 * 返 回 值: 無(wú)
134 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
135 * -----------------------------------------------
136 * 2020/05/13 V1.0 bert 創(chuàng)建
137 ***********************************************************************/
138 void CAN_Set_Interrupt(int can_port, pCanInterrupt callback)
139 {
140 int err;
141
142 if ( NULL != callback )
143 {
144 g_pCanInterrupt = callback;
145 }
146
147 err = pthread_create(&ntid, NULL,CAN1_RX0_IRQHandler, NULL );
148 if( err !=0 )
149 {
150 printf("create thread fail!
");
151 return ;
152 }
153 printf("create thread success!
");
154
155
156 return ;
157 }
創(chuàng)建后的線程函數(shù)如下所示:
CAN1_RX0_IRQHandler是一個(gè)CAN接收線程函數(shù),與CAN接收中斷功能相似,只這里采用輪詢方式讀取CAN報(bào)文。
253 /**********************************************************************
254 * 函數(shù)名稱: void CAN1_RX0_IRQHandler(void)
255 * 功能描述: CAN接收線程函數(shù)
256 * 輸入?yún)?shù): 無(wú)
257 * 輸出參數(shù): 無(wú)
258 * 返 回 值: 無(wú)
259 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
260 * -----------------------------------------------
261 * 2020/05/13 V1.0 bert 創(chuàng)建
262 ***********************************************************************/
263 void *CAN1_RX0_IRQHandler(void *arg)
264 {
265 /* 接收?qǐng)?bào)文定義 */
266 while( 1 )
267 {
268 /* 如果回調(diào)函數(shù)存在,則執(zhí)行回調(diào)函數(shù) */
269 if( g_pCanInterrupt != NULL)
270 {
271 g_pCanInterrupt();
272 }
273 usleep(10000);
274 }
275 }
(4)CAN報(bào)文讀取函數(shù)
161 /**********************************************************************
162 * 函數(shù)名稱: void CAN_Read(int can_port, CanRxMsg* recv_msg)
163 * 功能描述: CAN讀取接收寄存器,取出接收到的報(bào)文
164 * 輸入?yún)?shù): can_port,端口號(hào)
165 * 輸出參數(shù): recv_msg:接收?qǐng)?bào)文
166 * 返 回 值: 無(wú)
167 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
168 * -----------------------------------------------
169 * 2020/05/13 V1.0 bert 創(chuàng)建
170 ***********************************************************************/
171 void CAN_Read(int can_port, CanRxMsg* recv_msg)
172 {
173 unsigned char i;
174 static unsigned int rxcounter =0;
175
176 int nbytes;
177 struct can_frame rxframe;
178
179
180 nbytes = read(can_port, &rxframe, sizeof(struct can_frame));
181 if(nbytes>0)
182 {
183 printf("nbytes = %d
",nbytes );
184
185 recv_msg->StdId = rxframe.can_id;
186 recv_msg->DLC = rxframe.can_dlc;
187 memcpy( recv_msg->Data, &rxframe.data[0], rxframe.can_dlc);
188
189 rxcounter++;
190 printf("rxcounter=%d, ID=%03X, DLC=%d, data=%02X %02X %02X %02X %02X %02X %02X %02X
",
191 rxcounter,
192 rxframe.can_id, rxframe.can_dlc,
193 rxframe.data[0],
194 rxframe.data[1],
195 rxframe.data[2],
196 rxframe.data[3],
197 rxframe.data[4],
198 rxframe.data[5],
199 rxframe.data[6],
200 rxframe.data[7] );
201 }
202
203 return ;
204 }
205
(5)CAN報(bào)文發(fā)送函數(shù)
206 /**********************************************************************
207 * 函數(shù)名稱: void CAN_Write(int can_port, CanTxMsg send_msg)
208 * 功能描述: CAN報(bào)文發(fā)送接口,調(diào)用發(fā)送寄存器發(fā)送報(bào)文
209 * 輸入?yún)?shù): can_port,端口號(hào)
210 * 輸出參數(shù): send_msg:發(fā)送報(bào)文
211 * 返 回 值: 無(wú)
212 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
213 * -----------------------------------------------
214 * 2020/05/13 V1.0 bert 創(chuàng)建
215 ***********************************************************************/
216 void CAN_Write(int can_port, CanTxMsg send_msg)
217 {
218 unsigned char i;
219 static unsigned int txcounter=0;
220 int nbytes;
221
222 struct can_frame txframe;
223
224 txframe.can_id = send_msg.StdId;
225 txframe.can_dlc = send_msg.DLC;
226 memcpy(&txframe.data[0], &send_msg.Data[0], txframe.can_dlc);
227
228 nbytes = write(can_port, &txframe, sizeof(struct can_frame)); //發(fā)送 frame[0]
229
230 if(nbytes == sizeof(txframe))
231 {
232 txcounter++;
233 printf("txcounter=%d, ID=%03X, DLC=%d, data=%02X %02X %02X %02X %02X %02X %02X %02X
",
234 txcounter,
235 txframe.can_id, txframe.can_dlc,
236 txframe.data[0],
237 txframe.data[1],
238 txframe.data[2],
239 txframe.data[3],
240 txframe.data[4],
241 txframe.data[5],
242 txframe.data[6],
243 txframe.data[7] );
244 }
245 else
246 {
247 //printf("Send Error frame[0], nbytes=%d
!",nbytes);
248 }
249
250 return ;
251 }
252
(6)CAN抽象結(jié)構(gòu)體框架初始化
與14.3章節(jié)STM32定義實(shí)例類似。
定義一個(gè)can1通信結(jié)構(gòu)實(shí)例CAN_COMM_STRUCT can1_controller;
使用(1)~(5)步驟實(shí)現(xiàn)的函數(shù),初始化can1_controller,構(gòu)成與應(yīng)用層關(guān)聯(lián)的一個(gè)連接點(diǎn)。
298 /**********************************************************************
299 * 名稱: can1_controller
300 * 功能描述: CAN1結(jié)構(gòu)體初始化
301 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
302 * -----------------------------------------------
303 * 2020/05/13 V1.0 bert 創(chuàng)建
304 ***********************************************************************/
305 CAN_COMM_STRUCT can1_controller = {
306 .name = "can0",
307 .can_port = CAN_PORT_CAN1,
308 .can_set_controller = CAN_Set_Controller,
309 .can_set_interrput = CAN_Set_Interrupt,
310 .can_read = CAN_Read,
311 .can_write = CAN_Write,
312 };
14.4.3.2 編寫應(yīng)用層代碼
根據(jù)14.4.3.1 已經(jīng)將具體的linux下socketCAN硬件操作已經(jīng)實(shí)現(xiàn),并且已經(jīng)抽象實(shí)例化了CAN編程框架。
但是我們現(xiàn)在還沒(méi)關(guān)聯(lián)到應(yīng)用層,應(yīng)用層并不知道調(diào)用哪個(gè)接口。
(1)CAN應(yīng)用層注冊(cè)實(shí)例
在應(yīng)用層編寫一個(gè)通用的實(shí)例化注冊(cè)函數(shù)。
見(jiàn)第14章節(jié)代碼“02_socketcan_send_addline”中“app_can.c”文件int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)函數(shù)。
代碼實(shí)現(xiàn)如下:(和STM32應(yīng)用編程完全一樣,代碼幾乎不用更改)
73 /**********************************************************************
74 * 函數(shù)名稱: int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
75 * 功能描述: 應(yīng)用層進(jìn)行CAN1結(jié)構(gòu)體注冊(cè)
76 * 輸入?yún)?shù): p_can_controller,CAN控制器抽象結(jié)構(gòu)體
77 * 輸出參數(shù): 無(wú)
78 * 返 回 值: 無(wú)
79 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
80 * -----------------------------------------------
81 * 2020/05/13 V1.0 bert 創(chuàng)建
82 ***********************************************************************/
83 int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
84 {
85 /* 判斷傳入的p_can_controller為非空,目的是確認(rèn)這個(gè)結(jié)構(gòu)體是實(shí)體*/
86 if( p_can_controller != NULL )
87 {
88 /* 將傳入的參數(shù)p_can_controller賦值給應(yīng)用層結(jié)構(gòu)體gCAN_COMM_STRUCT */
89
90 /*端口號(hào),類比socketcan套接口*/
91 gCAN_COMM_STRUCT.can_port = p_can_controller->can_port;
92 /*CAN控制器配置函數(shù)*/
93 gCAN_COMM_STRUCT.can_set_controller = p_can_controller->can_set_controller;
94 /*CAN中斷配置*/
95 gCAN_COMM_STRUCT.can_set_interrput = p_can_controller->can_set_interrput;
96 /*CAN報(bào)文讀函數(shù)*/
97 gCAN_COMM_STRUCT.can_read = p_can_controller->can_read;
98 /*CAN報(bào)文發(fā)送函數(shù)*/
99 gCAN_COMM_STRUCT.can_write = p_can_controller->can_write;
100 return 1;
101 }
102 return 0;
103 }
(2)CAN應(yīng)用層初始化
CAN應(yīng)用層代碼初始化如下:(和STM32 CAN應(yīng)用代碼完全一樣)
105 /**********************************************************************
106 * 函數(shù)名稱: void app_can_init(void)
107 * 功能描述: CAN應(yīng)用層初始化
108 * 輸入?yún)?shù): 無(wú)
109 * 輸出參數(shù): 無(wú)
110 * 返 回 值: 無(wú)
111 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
112 * -----------------------------------------------
113 * 2020/05/13 V1.0 bert 創(chuàng)建
114 ***********************************************************************/
115 void app_can_init(void)
116 {
117 /**
118 * 應(yīng)用層進(jìn)行CAN1結(jié)構(gòu)體注冊(cè)
119 */
120 CAN1_contoller_add();
121
122 /*
123 *調(diào)用can_set_controller進(jìn)行CAN控制器配置,
124 *返回can_port,類比linux socketcan中的套接口,單片機(jī)例程中作為自定義CAN通道
125 */
126 gCAN_COMM_STRUCT.can_port = gCAN_COMM_STRUCT.can_set_controller();
127 /**
128 * 調(diào)用can_set_interrput配置CAN接收中斷,類比socketcan中的接收線程,本例不用接收,因此回調(diào)函數(shù)傳入NULL
129 */
130 gCAN_COMM_STRUCT.can_set_interrput( gCAN_COMM_STRUCT.can_port, NULL );
131 }
(3)設(shè)計(jì)一個(gè)簡(jiǎn)單的周期發(fā)送報(bào)文功能
我們需要先設(shè)計(jì)一個(gè)在10ms周期函數(shù)中調(diào)用的void app_can_tx_test(void)功能函數(shù),這個(gè)函數(shù)在main主線程函數(shù)中進(jìn)行調(diào)用。
CAN周期發(fā)送報(bào)文的功能函數(shù)代碼實(shí)現(xiàn)如下:
134 /**********************************************************************
135 * 函數(shù)名稱: void app_can_tx_test(void)
136 * 功能描述: CAN應(yīng)用層報(bào)文發(fā)送函數(shù),用于測(cè)試周期發(fā)送報(bào)文
137 * 輸入?yún)?shù): 無(wú)
138 * 輸出參數(shù): 無(wú)
139 * 返 回 值: 無(wú)
140 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
141 * -----------------------------------------------
142 * 2020/05/13 V1.0 bert 創(chuàng)建
143 ***********************************************************************/
144 void app_can_tx_test(void)
145 {
146 // 以10ms為基準(zhǔn),運(yùn)行CAN測(cè)試程序
147
148 unsigned char i=0;
149
150 /* 發(fā)送報(bào)文定義 */
151 CanTxMsg TxMessage;
152
153 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為計(jì)數(shù)器 */
154 static unsigned char tx_counter = 0;
155
156 /* 以10ms為基準(zhǔn),通過(guò)timer計(jì)數(shù)器設(shè)置該處理函數(shù)后面運(yùn)行代碼的周期為1秒鐘*/
157 static unsigned int timer =0;
158 if(timer++>100)
159 {
160 timer = 0;
161 }
162 else
163 {
164 return ;
165 }
166
167 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
168 TxMessage.StdId = TX_CAN_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
169 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
170 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
171 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
172 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
173
174 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
175 TxMessage.Data[0] = tx_counter++; /* 用來(lái)識(shí)別報(bào)文發(fā)送計(jì)數(shù)器 */
176 for(i=1; i<TxMessage.DLC; i++)
177 {
178 TxMessage.Data[i] = i;
179 }
180
181 /* 調(diào)用can_write發(fā)送CAN報(bào)文 */
182 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
183
184 }
185
然后將void app_can_tx_test(void)函數(shù)加入到main函數(shù)中,進(jìn)行10ms周期執(zhí)行,其代碼實(shí)現(xiàn)如下:
188 /**********************************************************************
189 * 函數(shù)名稱: int main(int argc, char **argv)
190 * 功能描述: 主函數(shù)
191 * 輸入?yún)?shù): 無(wú)
192 * 輸出參數(shù): 無(wú)
193 * 返 回 值: 無(wú)
194 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
195 * -----------------------------------------------
196 * 2020/05/13 V1.0 bert 創(chuàng)建
197 ***********************************************************************/
198 int main(int argc, char **argv)
199 {
200 /* CAN應(yīng)用層初始化 */
201 app_can_init();
202
203 while(1)
204 {
205 /* CAN應(yīng)用層周期發(fā)送報(bào)文 */
206 app_can_tx_test();
207
208 /* 利用linux的延時(shí)函數(shù)設(shè)計(jì)10ms的運(yùn)行基準(zhǔn) */
209 usleep(10000);
210 }
211 }
14.4.3.3 案例測(cè)試驗(yàn)證
當(dāng)我們上面代碼完成編寫后,目錄文件如下:
(1)編寫Makfile
Makefile文件內(nèi)容如下:
all:
arm-linux-gnueabihf-gcc -lpthread -o socketcan_send can_controller.c app_can.c
clean:
rm socketcan_send
(2)編譯socket_send
注意:編譯是在100ask-vmware_ubuntu18.04虛擬機(jī)環(huán)境中。
進(jìn)入ubuntu虛擬機(jī)對(duì)應(yīng)的socket_send目錄下
輸入make命令:
通過(guò)make命令編譯后,生成socket_send可執(zhí)行文件。
(3)運(yùn)行socket_send
注意:運(yùn)行在100ask_imx6開發(fā)板上運(yùn)行。
此處使用的是nfs文件進(jìn)行運(yùn)行。
先給100ask_imx6ull開發(fā)板上電,打開串口:
輸入root用戶登錄進(jìn)入開發(fā)板linux系統(tǒng);
然后掛載nfs,操作如下:
Mount -t nfs -o nolock 192.168.1.100:/home/book /mnt
注意:目前我的開發(fā)板IP:192.168.1.101, Ubuntu虛擬機(jī)是192.168.1.100.
然后再運(yùn)行./socketcan_send
如果運(yùn)行時(shí)提示權(quán)限不允許,可以使用chmod命令設(shè)置權(quán)限:
Chmod 777 socketcan_send
運(yùn)行后串口查看打印信息如下:
然后再觀察Vehcile Spy3上位機(jī)測(cè)試結(jié)果如下:
報(bào)文按照時(shí)間1S的周期性發(fā)送報(bào)文ID為0x101的CAN報(bào)文。
(4)測(cè)試總結(jié)
到此為止,我們已經(jīng)通過(guò)socketcan建立起來(lái)了linux下應(yīng)用編程的框架,并且成功的調(diào)試成功了CAN周期發(fā)送報(bào)文的功能編程。
后面將基于此框架,一步一步的了解linux下CAN應(yīng)用編程;
對(duì)于相關(guān)案例章節(jié)的目的設(shè)置如下:
章節(jié) | 目的 |
---|---|
14.4.3 socket_can簡(jiǎn)單發(fā)送實(shí)例 | 簡(jiǎn)單直接的了解發(fā)送報(bào)文 |
14.4.4 socket_can簡(jiǎn)單接收實(shí)例 | 簡(jiǎn)單直接的了解接收?qǐng)?bào)文 |
14.4.5 socket_can接收和發(fā)送實(shí)例 | 發(fā)送和接收?qǐng)?bào)文的組合操作 |
14.4.4 socket_can 簡(jiǎn)單接收實(shí)例
簡(jiǎn)單接收實(shí)例代碼目錄:“03_socketcan_recv”
我們?cè)?4.4.3章節(jié)已經(jīng)了解了發(fā)送報(bào)文發(fā)送的功能,而且已經(jīng)建立起了linux下應(yīng)用編程的框架;本節(jié)重點(diǎn)了解簡(jiǎn)單接收功能。
案例描述:
1.實(shí)現(xiàn)接收?qǐng)?bào)文0x201的報(bào)文。
14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)
(1)定義CAN設(shè)備
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(1)定義CAN設(shè)備”描述。
(2)配置CAN控制器
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(2)配置CAN控制器”描述。
因?yàn)樵凇?4.4.3.1”中我們只發(fā)送,并且設(shè)置了過(guò)濾器為禁止所有報(bào)文。具體代碼如下:
109 /*************************************************************/
110 //禁用過(guò)濾規(guī)則,本進(jìn)程不接收?qǐng)?bào)文,只負(fù)責(zé)發(fā)送
111 setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
而本案例需要配置接收,過(guò)濾器配置會(huì)有相應(yīng)差異,我們目前是配置僅僅接收?qǐng)?bào)文ID為0x201的報(bào)文,
具體實(shí)現(xiàn)代碼如下:
110 //定義接收規(guī)則,只接收表示符等于 0x201 的報(bào)文
111 struct can_filter rfilter[1];
112 rfilter[0].can_id = 0x201;
113 rfilter[0].can_mask = CAN_SFF_MASK;
114 //設(shè)置過(guò)濾規(guī)則
115 setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
作為擴(kuò)展,我們也可以設(shè)置多個(gè)過(guò)濾器:
定義過(guò)濾器:
struct can_filter rfilter[5]; /*定義10個(gè)過(guò)濾器*/
rfilter[0].can_id = 0x201;
rfilter[0].can_mask = 0x7FF; /*過(guò)濾規(guī)則:can_id & mask = 0x201 & 0x7FF = 0x201*/
rfilter[1].can_id = 0x302;
rfilter[1].can_mask = 0x7FF; /*過(guò)濾規(guī)則:can_id & mask = 0x302& 0x7FF = 0x302*/
rfilter[2].can_id = 0x403;
rfilter[2].can_mask = 0x7FF; /*過(guò)濾規(guī)則:can_id & mask = 0x403& 0x7FF = 0x403*/
rfilter[3].can_id = 0x504;
rfilter[3].can_mask = 0x700; /*過(guò)濾規(guī)則:can_id & mask = 0x504 & 0x700 = 0x500,即接收?qǐng)?bào)文ID為0x5**的報(bào)文*/
rfilter[3].can_id = 0x605;
rfilter[3].can_mask = 0x700; /*過(guò)濾規(guī)則:can_id & mask = 0x504 & 0x700 = 0x600*/
setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
(3)創(chuàng)建CAN接收線程
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(3)創(chuàng)建CAN接收線程”描述。
(4)CAN報(bào)文讀取函數(shù)
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(4)CAN報(bào)文讀取函數(shù)”描述。
(5)CAN報(bào)文發(fā)送函數(shù)
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(5)CAN報(bào)文發(fā)送函數(shù)”描述。
(6)CAN抽象結(jié)構(gòu)體框架初始化
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(6)CAN抽象結(jié)構(gòu)體框架初始化”描述。
14.4.4.2編寫應(yīng)用層代碼
(1)CAN應(yīng)用層注冊(cè)實(shí)例
參考“14.4.3.2 編寫應(yīng)用層代碼”中“(1)CAN應(yīng)用層注冊(cè)實(shí)例”描述。
(2)CAN應(yīng)用層初始化
在本簡(jiǎn)單接收實(shí)例中,我們需要將接收線程里的回調(diào)指針函數(shù)CAN_RX_IRQHandler_Callback傳入,在這個(gè)函數(shù)里,應(yīng)用層可以自行進(jìn)行讀取CAN報(bào)文等處理。
105 /**********************************************************************
106 * 函數(shù)名稱: void app_can_init(void)
107 * 功能描述: CAN應(yīng)用層初始化
108 * 輸入?yún)?shù): 無(wú)
109 * 輸出參數(shù): 無(wú)
110 * 返 回 值: 無(wú)
111 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
112 * -----------------------------------------------
113 * 2020/05/13 V1.0 bert 創(chuàng)建
114 ***********************************************************************/
115 void app_can_init(void)
116 {
117 /**
118 * 應(yīng)用層進(jìn)行CAN1結(jié)構(gòu)體注冊(cè)
119 */
120 CAN1_contoller_add();
121
122 /*
123 *調(diào)用can_set_controller進(jìn)行CAN控制器配置,
124 *返回can_port,類比linux socketcan中的套接口,單片機(jī)例程中作為自定義CAN通道
125 */
126 gCAN_COMM_STRUCT.can_port = gCAN_COMM_STRUCT.can_set_controller();
127 /**
128 * 調(diào)用can_set_interrput配置CAN接收中斷,類比socketcan中的接收線程
129 */
130 gCAN_COMM_STRUCT.can_set_interrput( gCAN_COMM_STRUCT.can_port, CAN_RX_IRQHandler_Callback );
131 }
(3)設(shè)計(jì)一個(gè)簡(jiǎn)單的接收?qǐng)?bào)文功能
關(guān)于void CAN_RX_IRQHandler_Callback(void)的具體實(shí)現(xiàn)如下所示:
CAN_RX_IRQHandler_Callback是在接收線程中循環(huán)執(zhí)行,應(yīng)用層在CAN_RX_IRQHandler_Callback函數(shù)進(jìn)行g(shù)CAN_COMM_STRUCT.can_read讀取CAN報(bào)文。
133 /**********************************************************************
134 * 函數(shù)名稱: void CAN_RX_IRQHandler_Callback(void)
135 * 功能描述: CAN1接收中斷函數(shù);在linux中可以類比用線程,或定時(shí)器去讀CAN數(shù)據(jù)
136 * 輸入?yún)?shù): 無(wú)
137 * 輸出參數(shù): 無(wú)
138 * 返 回 值: 無(wú)
139 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
140 * -----------------------------------------------
141 * 2020/05/13 V1.0 bert 創(chuàng)建
142 ***********************************************************************/
143 void CAN_RX_IRQHandler_Callback(void)
144 {
145 /* 接收?qǐng)?bào)文定義 */
146 CanRxMsg RxMessage;
147
148 /* 接收?qǐng)?bào)文清零 */
149 memset( &RxMessage, 0, sizeof(CanRxMsg) );
150
151 /* 通過(guò)can_read接口讀取寄存器已經(jīng)接收到的報(bào)文 */
152 gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);
153
154 /* 將讀取到的CAN報(bào)文存拷貝到全局報(bào)文結(jié)構(gòu)體g_CAN1_Rx_Message */
155 memcpy(&g_CAN1_Rx_Message, &RxMessage, sizeof( CanRxMsg ) );
156
157 }
本案例無(wú)發(fā)送報(bào)文功能,主線程中無(wú)代碼處理,只需要空跑運(yùn)行即可。
159 /**********************************************************************
160 * 函數(shù)名稱: int main(int argc, char **argv)
161 * 功能描述: 主函數(shù)
162 * 輸入?yún)?shù): 無(wú)
163 * 輸出參數(shù): 無(wú)
164 * 返 回 值: 無(wú)
165 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
166 * -----------------------------------------------
167 * 2020/05/13 V1.0 bert 創(chuàng)建
168 ***********************************************************************/
169 int main(int argc, char **argv)
170 {
171 /* CAN應(yīng)用層初始化 */
172 app_can_init();
173
174 while(1)
175 {
176 /* 利用linux的延時(shí)函數(shù)設(shè)計(jì)10ms的運(yùn)行基準(zhǔn) */
177 usleep(10000);
178 }
179 }
180
14.4.4.3 案例測(cè)試驗(yàn)證
(1)編寫Makfile
Makefile文件內(nèi)容如下:
all:
arm-linux-gnueabihf-gcc -lpthread -o socketcan_recv can_controller.c app_can.c
clean:
rm socketcan_recv
(2)編譯socket_recv
注意:編譯是在100ask-vmware_ubuntu18.04虛擬機(jī)環(huán)境中。
進(jìn)入ubuntu虛擬機(jī)對(duì)應(yīng)的socket_recv目錄下,執(zhí)行make all進(jìn)行編譯。
編譯過(guò)程如下:
(3)運(yùn)行socket_recv
注意:運(yùn)行在100ask_imx6開發(fā)板上運(yùn)行。
此處使用的是nfs文件進(jìn)行運(yùn)行。
Nfs掛載,請(qǐng)參考“14.4,3.3 案例測(cè)試驗(yàn)證”。
在100ask_imx6開發(fā)板環(huán)境下,執(zhí)行“./socket_recv”,運(yùn)行程序;
然后通過(guò)Vhicle Spy3向100ask_imx6開發(fā)板CAN端發(fā)送報(bào)文ID未0x201的報(bào)文,報(bào)文trace如下:
100ask_imx6開發(fā)板串口打印信息如下:
(4)測(cè)試總結(jié)
到此為止,我們已經(jīng)調(diào)試成功了CAN報(bào)文接收的功能編程。
14.4.5 socket_can 接收和發(fā)送實(shí)例
簡(jiǎn)單接收實(shí)例代碼目錄:“04_socketcan_recv_send”
本案例整合了“14.4.3 簡(jiǎn)單發(fā)送實(shí)例”和“14.4.3 簡(jiǎn)單接收實(shí)例”,構(gòu)建成一個(gè)發(fā)送和接收均有的組合案例。
案例描述:
-
實(shí)現(xiàn)周期1秒發(fā)送報(bào)文ID:0x101的報(bào)文;
- 實(shí)現(xiàn)接收?qǐng)?bào)文0x201的報(bào)文,并將內(nèi)容復(fù)制到報(bào)文0x301的報(bào)文,并發(fā)送出去;
14.4.5.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)
(1)定義CAN設(shè)備
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(1)定義CAN設(shè)備”描述。
參考“14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(1)定義CAN設(shè)備”描述。
(2)配置CAN控制器
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(2)配置CAN控制器”描述。
參考“14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(2)配置CAN控制器”描述。
(3)創(chuàng)建CAN接收線程
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(3)創(chuàng)建CAN接收線程”描述。
參考“14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(3)創(chuàng)建CAN接收線程”描述。
(4)CAN報(bào)文讀取函數(shù)
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(4)CAN報(bào)文讀取函數(shù)”描述。
參考“14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(4)CAN報(bào)文讀取函數(shù)”描述。
(5)CAN報(bào)文發(fā)送函數(shù)
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(5)CAN報(bào)文發(fā)送函數(shù)”描述。
參考“14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(5)CAN報(bào)文發(fā)送函數(shù)”描述。
(6)CAN抽象結(jié)構(gòu)體框架初始化
參考“14.4.3.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(6)CAN抽象結(jié)構(gòu)體框架初始化”描述。
參考“14.4.4.1 編寫抽象框架的實(shí)現(xiàn)函數(shù)”中“(6)CAN抽象結(jié)構(gòu)體框架初始化”描述。
14.4.5.2 編寫應(yīng)用層代碼
(1)CAN應(yīng)用層注冊(cè)實(shí)例
參考“14.4.3.2 編寫應(yīng)用層代碼”中“(1)CAN應(yīng)用層注冊(cè)實(shí)例”描述。
參考“14.4.4.2 編寫應(yīng)用層代碼”中“(1)CAN應(yīng)用層注冊(cè)實(shí)例”描述。
(2)CAN應(yīng)用層初始化
參考“14.4.4.2 編寫應(yīng)用層代碼”中“(2)CAN應(yīng)用層初始化”描述。
(3)設(shè)計(jì)一個(gè)簡(jiǎn)單的周期發(fā)送報(bào)文功能
參考“14.4.3.2 編寫應(yīng)用層代碼”中“(3)設(shè)計(jì)一個(gè)簡(jiǎn)單的發(fā)送報(bào)文功能”描述。
(4)設(shè)計(jì)一個(gè)簡(jiǎn)單的周期接收?qǐng)?bào)文功能
參考“14.4.4.2 編寫應(yīng)用層代碼”中“(3)設(shè)計(jì)一個(gè)簡(jiǎn)單的接收?qǐng)?bào)文功能”描述。
? 同時(shí),我們此處需要將接收到的ID:0X201的報(bào)文,將內(nèi)容復(fù)制給報(bào)文ID:0x301的報(bào)文,并發(fā)送出去。
我們?cè)凇?4.4.4 簡(jiǎn)單接收?qǐng)?bào)文”的基礎(chǔ)上增加一個(gè)簡(jiǎn)單的邏輯,在接收線程的回調(diào)函數(shù)CAN_RX_IRQHandler_Callback中,調(diào)用gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);接收到報(bào)文ID:0x201的報(bào)文后,設(shè)置標(biāo)志g_CAN1_Rx_Flag = 1; 然后去主線程去判斷此標(biāo)志是否被設(shè)置為1,標(biāo)識(shí)已經(jīng)接收到,則在void app_can_rx_test(void)中去拷貝報(bào)文ID:0X201的報(bào)文內(nèi)容,然后賦值給報(bào)文ID:0x301的報(bào)文。
接收線程回調(diào)函數(shù)CAN_RX_IRQHandler_Callback的實(shí)現(xiàn)代碼如下:
231 /**********************************************************************
232 * 函數(shù)名稱: void CAN_RX_IRQHandler_Callback(void)
233 * 功能描述: CAN1接收中斷函數(shù);在linux中可以類比用線程,或定時(shí)器去讀CAN數(shù)據(jù)
234 * 輸入?yún)?shù): 無(wú)
235 * 輸出參數(shù): 無(wú)
236 * 返 回 值: 無(wú)
237 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
238 * -----------------------------------------------
239 * 2020/05/13 V1.0 bert 創(chuàng)建
240 ***********************************************************************/
241 void CAN_RX_IRQHandler_Callback(void)
242 {
243 /* 接收?qǐng)?bào)文定義 */
244 CanRxMsg RxMessage;
245
246 /* 接收?qǐng)?bào)文清零 */
247 memset( &RxMessage, 0, sizeof(CanRxMsg) );
248
249 /* 通過(guò)can_read接口讀取寄存器已經(jīng)接收到的報(bào)文 */
250 gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);
251
252 /* 將讀取到的CAN報(bào)文存拷貝到全局報(bào)文結(jié)構(gòu)體g_CAN1_Rx_Message */
253 memcpy(&g_CAN1_Rx_Message, &RxMessage, sizeof( CanRxMsg ) );
254
255 /* 設(shè)置當(dāng)前接收完成標(biāo)志,判斷當(dāng)前接收?qǐng)?bào)文ID為RX_CAN_ID,則設(shè)置g_CAN1_Rx_Flag=1*/
256 if( g_CAN1_Rx_Message.StdId == RX_CAN_ID )
257 {
258 g_CAN1_Rx_Flag = 1;
259 }
260 }
主線程中app_can_rx_test的接收觸發(fā)處理函數(shù)代碼如下:
187 /**********************************************************************
188 * 函數(shù)名稱: void app_can_rx_test(void)
189 * 功能描述: CAN應(yīng)用層接收?qǐng)?bào)文處理函數(shù),用于處理中斷函數(shù)中接收的報(bào)文
190 * 輸入?yún)?shù): 無(wú)
191 * 輸出參數(shù): 無(wú)
192 * 返 回 值: 無(wú)
193 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
194 * -----------------------------------------------
195 * 2020/05/13 V1.0 bert 創(chuàng)建
196 ***********************************************************************/
197 void app_can_rx_test(void)
198 {
199 unsigned char i=0;
200
201 /* 發(fā)送報(bào)文定義 */
202 CanTxMsg TxMessage;
203
204 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為計(jì)數(shù)器 */
205 static unsigned char rx_counter = 0;
206
207
208 if( g_CAN1_Rx_Flag == 1)
209 {
210 g_CAN1_Rx_Flag = 0;
211
212 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
213 TxMessage.StdId = RX_TO_TX_CAN_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
214 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
215 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
216 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
217 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
218
219 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
220 TxMessage.Data[0] = rx_counter++; /* 用來(lái)識(shí)別報(bào)文發(fā)送計(jì)數(shù)器 */
221 for(i=1; i<TxMessage.DLC; i++)
222 {
223 TxMessage.Data[i] = g_CAN1_Rx_Message.Data[i];
224 }
225
226 /* 調(diào)用can_write發(fā)送CAN報(bào)文 */
227 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
228 }
229 }
14.4.5.3 案例測(cè)試驗(yàn)證
(1)編寫Makfile
Makefile文件容如下:
all:
arm-linux-gnueabihf-gcc -lpthread -o socketcan_recv_send can_controller.c app_can.c
clean:
rm socketcan_recv_send
(2)編譯socket_recv_send
注意:編譯是在100ask-vmware_ubuntu18.04虛擬機(jī)環(huán)境中。
進(jìn)入ubuntu虛擬機(jī)對(duì)應(yīng)的socket_send目錄下,執(zhí)行make all進(jìn)行編譯。
編譯過(guò)程如下:
(3)運(yùn)行socket_recv_send
注意:運(yùn)行在100ask_imx6開發(fā)板上運(yùn)行。
此處使用的是nfs文件進(jìn)行運(yùn)行。
Nfs掛載,請(qǐng)參考“14.4,3.3 案例測(cè)試驗(yàn)證”。
在100ask_imx6開發(fā)板環(huán)境下,執(zhí)行“./socket_recv_send”,運(yùn)行程序;
然后通過(guò)Vhicle Spy3向100ask_imx6開發(fā)板CAN端發(fā)送報(bào)文ID未0x201的報(bào)文,報(bào)文trace如下:
然后觀察100ask_imx6開發(fā)串口打印信息如下:
(4)測(cè)試總結(jié)
到此為止,我們已經(jīng)調(diào)試成功了CAN報(bào)文接收和發(fā)送的功能編程。
14.5 汽車行業(yè)CAN總線應(yīng)用
14.5.1 車廠CAN總線需求
CAN總線應(yīng)用最廣泛的應(yīng)該是汽車領(lǐng)域,幾乎所有的車均支持CAN總線,在這里就簡(jiǎn)單介紹一些汽車相關(guān)的CAN總線需求。
14.5.1.1 網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)
下面這張網(wǎng)絡(luò)拓?fù)鋱D和我開發(fā)過(guò)的大部分實(shí)際車輛拓?fù)浯笾乱恢?。一般是汽車廠商提供給零部件供應(yīng)商的。
如下圖所示:
<center><p>圖14.5.1 整車網(wǎng)絡(luò)拓?fù)?lt;/p></center>
一般車身網(wǎng)絡(luò)分為如下6個(gè)局域CAN網(wǎng)絡(luò):
車輛拓?fù)浞纸M | 描述 |
---|---|
PT CAN <br/>(PowerTrain CAN ) <br/>動(dòng)力總成CAN總線 | 主要負(fù)責(zé)車輛動(dòng)力相關(guān)的ECU組網(wǎng),是整車要求傳輸速率最高的一路CAN網(wǎng)絡(luò);<br> 一般包括如下相關(guān)ECU單元: <br/> ECM(Engine Control Module)發(fā)動(dòng)機(jī)控制模塊; <br/> SRS ( SupplementalRestraintSystem) 電子安全氣囊 <br/> BMS ( Battery Management System ) 電池管理系統(tǒng) <br/>EPB Electronic Park Brake, 電子駐車系統(tǒng) |
CH CAN <br/>(Chassis CAN) <br/>底盤控制CAN總線 | CH CAN負(fù)責(zé)汽車底盤及4個(gè)輪子的制動(dòng)/穩(wěn)定/轉(zhuǎn)向,由于涉及整車制動(dòng)/助力轉(zhuǎn)向等, <br/>所以其網(wǎng)絡(luò)信號(hào)優(yōu)先級(jí)也是較高的。 <br/>一般包括如下相關(guān)ECU單元: <br/>ABS ( Antilock Brake System ) 防抱死制動(dòng)系統(tǒng) <br/> ESP(Electronic Stability Program)車身電子穩(wěn)定系統(tǒng) <br/>EPS(Electric Power Steering)電子轉(zhuǎn)向助力 |
Body CAN <br/>車身控制總線 | Body CAN負(fù)責(zé)車身上的一些提高舒適性/安全性的智能硬件的管理與控制,其網(wǎng)絡(luò)信<br/>號(hào)優(yōu)先級(jí)較低, 因?yàn)橐陨显O(shè)備都是輔助設(shè)備。 <br/>一般包括如下相關(guān)ECU單元: <br/>AC ( Air Condition ) 空調(diào) <br/>AVM(Around View Monitor) 360環(huán)視 <br/>BCM(Body Control Module) 天窗, 車窗, 霧燈, 轉(zhuǎn)向燈, 雨刮… <br/>IMMO(Immobilizer) 發(fā)動(dòng)機(jī)防盜系統(tǒng) <br/>TPMS(Tire Pressure Monitoring System) 胎壓監(jiān)控系統(tǒng) |
Info CAN <br/><br/> ( Infomercial CAN ) <br/>娛樂(lè)系統(tǒng)總線 | Info CAN是輔助可選設(shè)備, 所以優(yōu)先級(jí)也是較低的,主要負(fù)責(zé)車身上的一些提高娛樂(lè)性的智能硬件的管理與控制。 <br/>一般包括如下相關(guān)ECU單元: <br/>VAES( Video Audio Entertainment System) 車載娛樂(lè)系統(tǒng)(中控) <br/>IP(Instrument Pack) 組合儀表, 當(dāng)今的數(shù)字儀表, 基本有音樂(lè), 地圖, 通話等娛樂(lè)功能. |
DiagCAN ( Diagnose CAN ) 診斷控制總線 | DiagCAN總線主要提供遠(yuǎn)程診斷功能,只有一個(gè)ECU: <br/> Tbox(Telematics BOX) 遠(yuǎn)程控制模塊 |
OBD CAN | OBD一般是提供外接診斷儀,基本是接在整車網(wǎng)關(guān)ECU上。 |
14.5.1.2 CAN 報(bào)文分類
在汽車CAN網(wǎng)絡(luò)里面,CAN報(bào)文主要分為三種:應(yīng)用報(bào)文,網(wǎng)絡(luò)報(bào)文,和診斷報(bào)文。
不論是網(wǎng)絡(luò)報(bào)文,還是診斷報(bào)文,均是按照不同的功能需求劃分的,根據(jù)不同的需求,制定CAN報(bào)文數(shù)據(jù)的不同協(xié)議。
(1)CAN應(yīng)用報(bào)文
CAN應(yīng)用報(bào)文,主要用于車身網(wǎng)絡(luò)中不同ECU節(jié)點(diǎn)之間的數(shù)據(jù)信息的發(fā)送和接收,與具體應(yīng)用功能相關(guān);
汽車CAN應(yīng)用報(bào)文,由車廠進(jìn)行定義和發(fā)布“信號(hào)矩陣表(excel格式)”和“信號(hào)矩陣(DBC格式)”。
詳見(jiàn)“14.5.2 CAN應(yīng)用報(bào)文應(yīng)用分析及實(shí)例”。
(2)CAN網(wǎng)絡(luò)管理報(bào)文
汽車電子系統(tǒng)通過(guò)車載網(wǎng)絡(luò)對(duì)所有的ECU 進(jìn)行配置管理和協(xié)調(diào)工作的過(guò)程稱之為網(wǎng)絡(luò)管理。
網(wǎng)絡(luò)管理可以通過(guò)對(duì)于網(wǎng)絡(luò)上的各個(gè) ECU 的控制,發(fā)出一些命令規(guī)則,實(shí)現(xiàn)各個(gè) ECU 的協(xié)同睡眠和喚醒,用于協(xié)同控制的CAN報(bào)文,就是網(wǎng)絡(luò)管理報(bào)文。
網(wǎng)絡(luò)管理有OSEK網(wǎng)絡(luò)管理和AUTOSAR網(wǎng)絡(luò)管理兩種。一般前裝車廠項(xiàng)目才會(huì)要求支持網(wǎng)絡(luò)管理。
(3)CAN診斷報(bào)文
CAN診斷主要是是實(shí)現(xiàn)車輛的功能監(jiān)控,故障檢測(cè),記錄/存儲(chǔ)故障信息,存儲(chǔ)/讀取數(shù)據(jù),還有EOL下線檢測(cè),ECU升級(jí)等功能。
基于CAN的通信分層模型:
OSI分層 | 車廠診斷標(biāo)準(zhǔn) | OBD標(biāo)準(zhǔn) |
---|---|---|
診斷應(yīng)用 | 用戶定義 | ISO15031-5 |
應(yīng)用層 | ISO15765-3 / ISO14229-1 | ISO15031-5 |
表示層 | 無(wú) | 無(wú) |
會(huì)話層 | ISO15765-3 | ISO15765-4 |
傳輸層 | 無(wú) | 無(wú) |
網(wǎng)絡(luò)層 | ISO15765-2 | ISO15765-4 |
數(shù)據(jù)鏈路層 | ISO11898-1 | ISO15765-4 |
物理層 | 用戶定義 | ISO15765-4 |
<center><p>圖 CAN診斷服務(wù)OSI模型</p></center>
14.5.2 CAN 應(yīng)用報(bào)文應(yīng)用分析及實(shí)例
14.5.2.1 CAN 應(yīng)用報(bào)文定義
當(dāng)一個(gè)車廠項(xiàng)目啟動(dòng)之后,根據(jù)項(xiàng)目的需求,車廠會(huì)提供CAN信號(hào)矩陣(excel),和DBC信號(hào)矩陣數(shù)據(jù)庫(kù)。
(1)CAN信號(hào)矩陣-excel格式
車廠提供的信號(hào)矩陣(excel)的文件格式,詳見(jiàn)第14章代碼目錄:CAN_Signal_Matrix.xlsx,
從CAN_Signal_Matrix.xlsx中截取報(bào)文定義,如下所示:
ECU_TX_MSG1: (周期發(fā)送報(bào)文,ID:0x123)
ECU_TX_MSG2: (事件發(fā)送報(bào)文,ID:0x124)
ECU_TX_MSG3: (周期&事件發(fā)送報(bào)文,ID:0x125)
ECU_RX_MSG1:(事件接收?qǐng)?bào)文,ID: 0X201)
? 從上報(bào)文定義可以看出,車廠會(huì)定義報(bào)文的很多屬性,如報(bào)文名稱,報(bào)文ID,報(bào)文長(zhǎng)度, 報(bào)文周期,報(bào)文發(fā)送類型, 以及報(bào)文中的信號(hào)名稱,信號(hào)起始字節(jié),信號(hào)長(zhǎng)度,排列格式(Intel或Motorala),信號(hào)的取值范圍,信號(hào)的發(fā)送方式等等。
(2)CAN信號(hào)矩陣-DBC
本章提供的示例CAN矩陣“CAN_Signal_Matrix.xlsx”對(duì)應(yīng)的DBC文件,該DBC文件使用vector CANdb+ Editor編輯;如下圖所示為DBC文件所顯示的報(bào)文信息內(nèi)容,和excel表格所展示內(nèi)容是一致的,文件格式不是最關(guān)鍵的,只要理解車廠對(duì)CAN信號(hào)的要求即可。
<center><p>圖 CAN信號(hào)矩陣DBC</p></center>
14.5.2.2 CAN應(yīng)用報(bào)文發(fā)送規(guī)則
? 我們提到車廠會(huì)提供CAN信號(hào)矩陣表,會(huì)定義周期報(bào)文,事件報(bào)文,周期事件混合報(bào)文,那么定義這些信號(hào)的通用規(guī)則在哪里?一般車廠會(huì)提供關(guān)于CAN總線的通信規(guī)范,車廠根據(jù)通信規(guī)范才定義出CAN信號(hào)矩陣。
? 下面是某車廠的通信規(guī)范《XXX Communication Requirement Specification.pdf》,其規(guī)范目錄如下圖所示,從目錄可以看出,主要介紹CAN物理層,數(shù)據(jù)鏈路層,通信交互層等相關(guān)規(guī)則。
? 本小節(jié),我們主要介紹應(yīng)用報(bào)文相關(guān)的通信交互層“4 Interaction Layer”相關(guān)的內(nèi)容:CAN報(bào)文發(fā)送類型(Message Send Type)。
CAN報(bào)文發(fā)送類型按照之前矩陣表展示的逐一介紹如下:
(1)周期型報(bào)文(Cyclic Message)
周期報(bào)文,即為周期定時(shí)發(fā)送,周期為T。
如下圖所示:
當(dāng)系統(tǒng)運(yùn)行后,ECU就按照周期T定時(shí)發(fā)送CAN報(bào)文。
(2)事件型報(bào)文(Event Message)
觸發(fā)事件時(shí)發(fā)送事件型消息,如下圖所示:
當(dāng)系統(tǒng)運(yùn)行后,ECU并不主動(dòng)發(fā)送事件型報(bào)文,而是當(dāng)ECU被某一條件觸發(fā)(Event),則ECU會(huì)連續(xù)發(fā)送三幀事件報(bào)文。
當(dāng)然車廠要求不僅僅如此,車廠還會(huì)有更多其他要求,
比如,
要求1,:觸發(fā)發(fā)送三幀報(bào)文后,要求信號(hào)恢復(fù)為默認(rèn)值;
要求2:觸發(fā)發(fā)送三幀,幀與幀間間隔要求50ms;
(3)周期事件型報(bào)文(Cyclic And Event Message)
? 周期事件混合型報(bào)文(簡(jiǎn)稱CE),當(dāng)無(wú)事件觸發(fā)的情況下,按照周期T定時(shí)發(fā)送報(bào)文,當(dāng)有事件觸發(fā)的情況下,按照event事件觸發(fā)方式發(fā)送報(bào)文。
如下圖所示:
實(shí)際車廠定義的CAN報(bào)文發(fā)送類型并不僅僅是上面三種,但是這三種是最重要的發(fā)送方式。
14.5.2.3 汽車CAN應(yīng)用報(bào)文發(fā)送應(yīng)用實(shí)例
通過(guò)上一小節(jié)的描述,我們已經(jīng)了解了車廠規(guī)范中三個(gè)應(yīng)用報(bào)文發(fā)送類型,現(xiàn)在我們就開始在100ask_imx6開發(fā)板上進(jìn)行試驗(yàn),實(shí)現(xiàn)車廠應(yīng)用報(bào)文的需求。
關(guān)于linux socketcan的應(yīng)用編程框架我們已經(jīng)在“14.4 linux socketcan基礎(chǔ)應(yīng)用編程”詳細(xì)講解了,我們現(xiàn)在就基于“14.4.5 socketcan接收和發(fā)送實(shí)例”進(jìn)行本章案例應(yīng)用編程,重點(diǎn)側(cè)重于app_can.c編程,can_controller.c可以完全沿用。
以下應(yīng)用編程,我們使用14.5.2.1中介紹的CAN報(bào)文矩陣中的CAN報(bào)文。
(1)linux can編程框架準(zhǔn)備
使用案例“04_socketcan_recv_send”代碼,復(fù)制文件夾改名為“06_socketcan_ecu_application”。
在app_can.c文件中定義報(bào)文ID:
30 /**************宏定義**************************************************/
31 /* 本例程中測(cè)試周期發(fā)送的CAN報(bào)文ID */
32 #define TX_CAN_CYCLIC_ID 0X123
33 #define TX_CAN_EVENT_ID 0X124
34 #define TX_CAN_CE_ID 0X125
35
36 /* 本例程中測(cè)試接收的CAN報(bào)文ID */
37 #define RX_CAN_ID 0x201
(2)周期型報(bào)文實(shí)現(xiàn)
實(shí)現(xiàn)功能:
A.編程實(shí)現(xiàn)周期發(fā)送報(bào)文ID:0x123, 周期T為1000ms。
代碼實(shí)現(xiàn)如下:
136 /**********************************************************************
137 * 函數(shù)名稱: void app_can_cyclicmsg_test(void)
138 * 功能描述: CAN應(yīng)用層測(cè)試發(fā)送周期型報(bào)文(ID:0X123)
139 * 輸入?yún)?shù): 無(wú)
140 * 輸出參數(shù): 無(wú)
141 * 返 回 值: 無(wú)
142 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
143 * -----------------------------------------------
144 * 2020/05/13 V1.0 bert 創(chuàng)建
145 ***********************************************************************/
146 void app_can_cyclicmsg_test(void)
147 {
148 // 以10ms為基準(zhǔn),運(yùn)行CAN測(cè)試程序
149
150 unsigned char i=0;
151
152 /* 發(fā)送報(bào)文定義 */
153 CanTxMsg TxMessage;
154
155 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為計(jì)數(shù)器 */
156 static unsigned char tx_counter = 0;
157
158 /* 以10ms為基準(zhǔn),通過(guò)timer計(jì)數(shù)器設(shè)置該處理函數(shù)后面運(yùn)行代碼的周期為1秒鐘*/
159 static unsigned int timer =0;
160 if(timer++>100)
161 {
162 timer = 0;
163 }
164 else
165 {
166 return ;
167 }
168
169 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
170 TxMessage.StdId = TX_CAN_CYCLIC_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
171 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
172 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
173 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
174 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
175
176 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
177 TxMessage.Data[0] = tx_counter++; /* 用來(lái)識(shí)別報(bào)文發(fā)送計(jì)數(shù)器 */
178 for(i=1; i<TxMessage.DLC; i++)
179 {
180 TxMessage.Data[i] = i;
181 }
182
183 /* 調(diào)用can_write發(fā)送CAN報(bào)文 */
184 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
185
186 }
(3)事件型報(bào)文實(shí)現(xiàn)
實(shí)現(xiàn)功能:
A. 編程實(shí)現(xiàn)當(dāng)接收到一幀報(bào)文(ID:0x201)的信號(hào)ECU_RX_MSG1_signal1=1時(shí),觸發(fā)發(fā)送事件型報(bào)文(ID:0x124),讓ECU_MSG2_signal2(Byte1字節(jié))=2 且兩幀報(bào)文間時(shí)間間隔為50ms。
B. 事件觸發(fā)條件:接收到報(bào)文(ID:0x201),且ECU_RX_MSG1_signal1(Byte0字節(jié)bit0)為1
代碼實(shí)現(xiàn)如下:
188 /**********************************************************************
189 * 函數(shù)名稱: void app_can_eventmsg_test(void)
190 * 功能描述: CAN應(yīng)用層測(cè)試發(fā)送事件型報(bào)文(ID:0X124)
191 * 輸入?yún)?shù): 無(wú)
192 * 輸出參數(shù): 無(wú)
193 * 返 回 值: 無(wú)
194 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
195 * -----------------------------------------------
196 * 2020/05/13 V1.0 bert 創(chuàng)建
197 ***********************************************************************/
198 void app_can_eventmsg_test(void)
199 {
200 unsigned char i=0;
201
202 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為事件觸發(fā)計(jì)數(shù)器 */
203 static unsigned char tx_counter = 0;
204
205 /* 發(fā)送報(bào)文定義 */
206 CanTxMsg TxMessage;
207
208 if( g_CAN1_Rx_Event_Flag == 1 )
209 {
210 g_CAN1_Rx_Event_Flag = 0;
211 printf("Message:0x124 is Triggered!
");
212
213 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
214 TxMessage.StdId = TX_CAN_EVENT_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
215 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
216 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
217 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
218 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
219
220 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
221 for(i=0; i<TxMessage.DLC; i++)
222 {
223 TxMessage.Data[i] = 0x00;
224 }
225 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
226 tx_counter = 0;
227
228 /*更新第1幀數(shù)據(jù)*/
229 TxMessage.Data[1] = 0x02;
230 TxMessage.Data[7] = (++tx_counter);
231 /* 調(diào)用can_write發(fā)送CAN報(bào)文,第1幀 */
232 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
233 /*延時(shí)50ms,作為事件報(bào)文間隔*/
234 usleep(50000);
235
236 /*更新第2幀數(shù)據(jù)*/
237 TxMessage.Data[1] = 0x02;
238 TxMessage.Data[7] = (++tx_counter);
239 /* 調(diào)用can_write發(fā)送CAN報(bào)文,第2幀 */
240 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
241 /*延時(shí)50ms,作為事件報(bào)文間隔*/
242 usleep(50000);
243
244 /*更新第3幀數(shù)據(jù)*/
245 TxMessage.Data[1] = 0x02;
246 TxMessage.Data[7] = (++tx_counter);
247 /* 調(diào)用can_write發(fā)送CAN報(bào)文,第3幀 */
248 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
249 /*延時(shí)50ms,作為事件報(bào)文間隔*/
250 usleep(50000);
251 }
252 }
(4)周期事件型報(bào)文實(shí)現(xiàn)
實(shí)現(xiàn)功能:
A. 編程實(shí)現(xiàn)周期發(fā)送報(bào)文(ID:0x125);
B. 而當(dāng)接收到一幀報(bào)文(ID:0x201)的信號(hào)ECU_RX_MSG1_signal2=1時(shí),觸發(fā)發(fā)送周期事件型報(bào)文(ID:0x125), 讓ECU_MSG3_signal9(Byte1字節(jié)bit0)=1,且連續(xù)發(fā)送三幀,且兩幀報(bào)文間時(shí)間間隔為50ms,三幀發(fā)送完成后恢復(fù)成ECU_MSG3_signal5=0;
A. 事件觸發(fā)條件:接收到報(bào)文(ID:0x201),且ECU_RX_MSG1_signal2(Byte0字節(jié)bit1)為1
代碼實(shí)現(xiàn)如下:
255 /**********************************************************************
256 * 函數(shù)名稱: void app_can_cycliceventmsg_test(void)
257 * 功能描述: CAN應(yīng)用層測(cè)試發(fā)送周期事件混合報(bào)文(ID:0X125)
258 * 輸入?yún)?shù): 無(wú)
259 * 輸出參數(shù): 無(wú)
260 * 返 回 值: 無(wú)
261 * 修改日期 版本號(hào) 修改人 修改內(nèi)容
262 * -----------------------------------------------
263 * 2020/05/13 V1.0 bert 創(chuàng)建
264 ***********************************************************************/
265 void app_can_cycliceventmsg_test(void)
266 {
267 unsigned char i=0;
268
269 /* 發(fā)送報(bào)文定義 */
270 CanTxMsg TxMessage;
271
272 /* 發(fā)送報(bào)文中用一個(gè)字節(jié)來(lái)作為事件觸發(fā)計(jì)數(shù)器 */
273 static unsigned char tx_counter = 0;
274
275 /* 以10ms為基準(zhǔn),通過(guò)timer計(jì)數(shù)器設(shè)置該處理函數(shù)后面運(yùn)行代碼的周期為1秒鐘*/
276 static unsigned int timer =0;
277
278 if( g_CAN1_Rx_CE_Flag == 1)
279 {
280 g_CAN1_Rx_CE_Flag = 0;
281 printf("Message:0x125 is Triggered!
");
282
283 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
284 TxMessage.StdId = TX_CAN_CE_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
285 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
286 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
287 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
288 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
289
290 /* 清零數(shù)據(jù)區(qū) */
291 for(i=0; i<TxMessage.DLC; i++)
292 {
293 TxMessage.Data[i] = 0x00;
294 }
295 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
296 tx_counter = 0;
297
298 /*更新第1幀數(shù)據(jù)*/
299 TxMessage.Data[1] = 0x01;
300 TxMessage.Data[7] = (++tx_counter);
301 /* 調(diào)用can_write發(fā)送CAN報(bào)文,第1幀 */
302 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
303 /*延時(shí)50ms,作為事件報(bào)文間隔*/
304 usleep(50000);
305
306 /*更新第2幀數(shù)據(jù)*/
307 TxMessage.Data[1] = 0x01;
308 TxMessage.Data[7] = (++tx_counter);
309 /* 調(diào)用can_write發(fā)送CAN報(bào)文,第2幀 */
310 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
311 /*延時(shí)50ms,作為事件報(bào)文間隔*/
312 usleep(50000);
313
314 /*更新第3幀數(shù)據(jù)*/
315 TxMessage.Data[1] = 0x01;
316 TxMessage.Data[7] = (++tx_counter);
317 /* 調(diào)用can_write發(fā)送CAN報(bào)文,第3幀 */
318 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
319 /*延時(shí)50ms,作為事件報(bào)文間隔*/
320 usleep(50000);
321 }
322
323 /* 以10ms為基準(zhǔn),通過(guò)timer計(jì)數(shù)器設(shè)置該處理函數(shù)后面運(yùn)行代碼的周期為1秒鐘*/
324 if(timer++>100)
325 {
326 timer = 0;
327 }
328 else
329 {
330 return ;
331 }
332
333 /* 發(fā)送報(bào)文報(bào)文數(shù)據(jù)填充,此報(bào)文周期是1秒 */
334 TxMessage.StdId = TX_CAN_CE_ID; /* 標(biāo)準(zhǔn)標(biāo)識(shí)符為0x000~0x7FF */
335 TxMessage.ExtId = 0x0000; /* 擴(kuò)展標(biāo)識(shí)符0x0000 */
336 TxMessage.IDE = CAN_ID_STD; /* 使用標(biāo)準(zhǔn)標(biāo)識(shí)符 */
337 TxMessage.RTR = CAN_RTR_DATA; /* 設(shè)置為數(shù)據(jù)幀 */
338 TxMessage.DLC = 8; /* 數(shù)據(jù)長(zhǎng)度, can報(bào)文規(guī)定最大的數(shù)據(jù)長(zhǎng)度為8字節(jié) */
339
340 /* 填充數(shù)據(jù),此處可以根據(jù)實(shí)際應(yīng)用填充 */
341 for(i=1; i<TxMessage.DLC; i++)
342 {
343 TxMessage.Data[i] = 0x00;
344 }
345
346 /* 調(diào)用can_write發(fā)送CAN報(bào)文 */
347 gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
348 }
(4)案例測(cè)試
第一步:測(cè)試周期報(bào)文
運(yùn)行socket_ecu_test,串口打印信息如下所示:
然后觀察Vehicle Spy3軟件獲取的報(bào)文trace,如下所示:
報(bào)文ID:0x123,0x125兩個(gè)報(bào)文均以1000ms的周期發(fā)送報(bào)文;
第二步:測(cè)試事件型報(bào)文
在Vehicle Spy3軟件上Messages里面過(guò)濾出報(bào)文ID:0X201,0X124.
然后手動(dòng)點(diǎn)擊右側(cè)的Tx Panel上的ID:0X201的報(bào)文,左側(cè)Messages的記錄為100ask_imx6開發(fā)板發(fā)出3幀ID:0x124的報(bào)文。
通過(guò)開發(fā)板串口打印信息看出:“Message:0x124 is Triggered!”,在這條打印信息之后,存在三幀ID:0x124的報(bào)文。
觀察出左側(cè)的Messages的trace如下圖所示:
第三步:測(cè)試周期事件型報(bào)文
在Vehicle Spy3軟件上Messages里面過(guò)濾出報(bào)文ID:0X201,0X125.
然后手動(dòng)點(diǎn)擊右側(cè)的Tx Panel上的ID:0X201的報(bào)文,左側(cè)Messages的記錄為100ask_imx6開發(fā)板發(fā)出3幀ID:0x125的報(bào)文,但是報(bào)文數(shù)據(jù)與默認(rèn)數(shù)據(jù)不同,數(shù)據(jù)內(nèi)容Byte7依次為0x01,0x02,0x03。
通過(guò)開發(fā)板串口打印信息看出:“Message:0x125 is Triggered!”,在這條打印信息之后,存在三幀ID:0x124的報(bào)文。
觀察出左側(cè)的Messages的trace如下圖所示:
ID:0X125正常情況下以1000ms的周期發(fā)送默認(rèn)報(bào)文,當(dāng)ID:201的報(bào)文觸發(fā)事件,引起ID:0X125發(fā)送事件報(bào)文。
(5)事件報(bào)文發(fā)送改進(jìn)
通過(guò)前面步驟,我們已經(jīng)了解應(yīng)用報(bào)文的發(fā)送類型和實(shí)現(xiàn)不同發(fā)送類型的方式,但是上面事件處理有一個(gè)缺陷,就是當(dāng)事件觸發(fā)時(shí),發(fā)送時(shí)通過(guò)ucsleep()函數(shù)實(shí)現(xiàn)的報(bào)文間隔,這個(gè)延時(shí)會(huì)使得周期報(bào)文的周期變長(zhǎng),這個(gè)可以通過(guò)觀察CAN報(bào)文trace查找到。
這里對(duì)案例“06_socketcan_ecu_application”做了一個(gè)小小的改進(jìn),對(duì)觸發(fā)事件的處理采用周期計(jì)數(shù)來(lái)實(shí)現(xiàn),具體請(qǐng)查看案例代碼“07_socketcan_ecu_application_new”。
本文摘自 :https://blog.51cto.com/w