當前位置:首頁 > IT技術 > 系統服務 > 正文

Linux應用開發【第十四章】CAN編程應用開發
2021-12-13 17:42:07

@[TOC]

14 CAN編程應用開發

14.1 CAN介紹

14.1.1 CAN是什么?

? CAN,全稱為“Controller Area Network”,即控制器局域網,是國際上應用最廣泛的現場總線之一。

最初,CAN 被設計作為汽車環境中的微控制器通訊,在車載各電子控制裝置 ECU 之間交換信息,形成汽車

電子控制網絡。比如:發動機管理系統、變速箱控制器、儀表裝備、電子主干系統中,均嵌入 CAN 控制裝

置。

? 一個由 CAN 總線構成的單一網絡中,理論上可以掛接無數個節點。實際應用中,節點數目受網絡硬件

的電氣特性所限制。例如,當使用 Philips P82C250 作為 CAN 收發器時,同一網絡中允許掛接 110 個節點。

CAN 可提供高達 1Mbit/s 的數據傳輸速率,這使實時控制變得非常容易。另外,硬件的錯誤檢定特性也增

強了 CAN 的抗電磁干擾能力。

14.1.2 CAN的起源

? CAN 最初出現在 80 年代末的汽車工業中,由德國 Bosch 公司最先提出。當時,由于消費者對于汽車功

能的要求越來越多,而這些功能的實現大多是基于電子操作的,這就使得電子裝置之間的通訊越來越復雜,

同時意味著需要更多的連接信號線。提出 CAN 總線的最初動機就是為了解決現代汽車中龐大的電子控制裝

置之間的通訊,減少不斷增加的信號線。于是,他們設計了一個單一的網絡總線,所有的外圍器件可以被

掛接在該總線上。1993 年,CAN 已成為國際標準 ISO11898(高速應用)和 ISO11519(低速應用)。

CAN 是一種多主方式的串行通訊總線,基本設計規范要求有高的位速率,高抗電磁干擾性,而且能夠檢

測出產生的任何錯誤。當信號傳輸距離達到 10Km 時,CAN 仍可提供高達 50Kbit/s 的數據傳輸速率。

由于 CAN 總線具有很高的實時性能,因此,CAN 已經在汽車工業、航空工業、工業控制、安全防護等領

域中得到了廣泛應用。

14.1.3 CAN傳輸模型

? CAN 通訊協議主要描述設備之間的信息傳遞方式。CAN 層的定義與開放系統互連模型(OSI)一致。每

一層與另一設備上相同的那一層通訊。實際的通訊發生在每一設備上相鄰的兩層,而設備只通過模型物理

層的物理介質互連。CAN 的規范定義了模型的最下面兩層:數據鏈路層和物理層。下表中展示了 OSI 開放

式互連模型的各層。應用層協議可以由 CAN 用戶定義成適合特別工業領域的任何方案。已在工業控制和制

造業領域得到廣泛應用的標準是 DeviceNet,這是為 PLC 和智能傳感器設計的。在汽車工業,許多制造商

都應用他們自己的標準。

表格 OSI開發系統互聯模型
序號 層次 描述
7 應用層 最高層。用戶、軟件、網絡終端等之間用來進行信息交換。
6 表示層 將兩個應用不同數據格式的系統信息轉化為能共同理解的格式
5 會話層 依靠低層的通信功能來進行數據的有效傳遞。
4 傳輸層 兩通訊節點之間數據傳輸控制。操作如:數據重發,數據錯誤修復
3 網絡層 規定了網絡連接的建立、維持和拆除的協議。如:路由和尋址
2 數據鏈路層 規定了在介質上傳輸的數據位的排列和組織。如:數據校驗和幀結構
1 物理層 規定通訊介質的物理特性。如:電氣特性和信號交換的解釋

? 雖然CAN傳輸協議參考了OSI 七層模型,但是實際上CAN協議只定義了兩層“物理層”和“數據鏈路層”,因此出現了各種不同的“應用層”協議,比如用在自動化技術的現場總線標準DeviceNet,用于工業控制的CanOpen,用于乘用車的診斷協議OBD、UDS(統一診斷服務,ISO14229),用于商用車的CAN總線協議SAEJ1939.

表格 CAN的
序號 層次 描述
7 應用層 主要定義CAN應用層。
2 數據鏈路層 數據鏈路層分為邏輯鏈接控制子層LLC和介質訪問控制子層MAC。<br>MAC 子層是 CAN 協議的核心。它把接收到的報文提供給 LLC 子<br/>層,并接收來自 LLC 子層的報文。 MAC 子層負責報文分幀、仲<br/>裁、應答、錯誤檢測和標定。MAC 子層也被稱作故障界定的管理<br/>實體監管 LLC 子層涉及報文濾波、過載通知、以及恢復管理。<br/>LLC = Logical Link Control MAC = Medium Access Control
1 物理層 物理層,為物理編碼子層PCS. 該層定義信號是如何實際地傳輸<br/>的,因此涉及到位時間、位編碼、同步。

14.1.4 CAN網絡拓撲

? CAN總線是一種分布式的控制總線。

? CAN總線作為一種控制器局域網,和普通以太網一樣,它的網絡很多CAN節點構成。

其網絡拓撲結構如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

? CAN網絡的每個節點非常簡單,均由一個MCU(微控制器)、一個CAN控制器和一個CAN收發器構成,然后使用雙絞線連接到CAN網絡中。

14.1.5 CAN物理特性

? CAN總線遵循國際標準ISO11898,如ISO11898-1,ISO11898-2,ISO11898-3,ISO11898-4標準。

序號 標準 描述
1 ISO11898-1 數據鏈路層和物理層信號
2 ISO11898-2 高速接入單元
3 ISO11898-3 低速容錯接入單元
4 ISO11898-4 時間觸發通訊
5 ISO11898-5 低功耗的接入單元
6 ISO11898-6 選擇性喚醒的高速接入單元

CAN 能夠使用多種物理介質,例如雙絞線、光纖等。最常用的就是雙絞線。

信號使用差分電壓傳送,兩條信號線被稱為“CAN_H”和“CAN_L”。

靜態時CAN_H和CAN_L均是 2.5V 左右,此時狀態表示為邏輯“1”,也可以叫做 “隱性”。

用 CAN_H 比 CAN_L 高表示邏輯“0”,稱為“顯形”,此時,通常電壓值為:CAN_H = 3.5V 和 CAN_L

= 1.5V 。

目前實際常用的CAN收發器有如下幾種型號:

序號 型號 描述
1 PCA82C250 高速 CAN 收發器
2 PCA82C251 高速 CAN 收發器
3 PCA82C252 容錯 CAN 收發器
4 TJA1040 高速 CAN 收發器
5 TJA1041 高速 CAN 收發器
6 TJA1042 高速 CAN 收發器
7 TJA1043 高速 CAN 收發器
8 TJA1050 高速 CAN 收發器
9 TJA1053 容錯 CAN 收發器
10 TJA1054 容錯 CAN 收發器

目前實際常用的CAN控制器有如下幾種型號:

序號 型號 描述
1 SJA1000 獨立CAN控制器
2 MCU內部控制器 目前市面上如STM32系列,S32K系列,IMX6系列等等很多單片機均內部集成了CAN控制。

14.1.6 CAN報文幀

14.1.6.1 CAN報文格式

標準 CAN 的標志符長度是 11 位,而擴展格式 CAN 的標志符長度可達 29 位。

CAN 協議的 2.0A 版本 規定 CAN 控制器必須有一個 11 位的標志符。

同時,在 2.0B 版本中規定,CAN 控制器的標志符長度可以是 11 位或 29 位。

遵循 CAN2.0B 協議的 CAN 控制器可以發送和接收 11 位標識符的標準格式報文或 29 位標識符的擴展格式報文。

標準幀&擴展幀對比
幀格式 標準幀 擴展幀
規范 CAN2.0A CAN2.0B
CAN ID(標識符)長度 11 bits 29 bits
CAN ID(標識符)范圍 0x000~0x7FF 0x00000000~0x1FFFFFFF

14.1.6.2 CAN報文幀類型

CAN報文類型又分如5種幀類型:

數據幀:主要用于發送方向接收方傳輸數據的幀;

遙控幀:主要用于接收方向具有相同ID的發送方請求數據的幀;

錯誤幀:主要用于當檢測出錯誤時向其他節點通知錯誤的幀。

過載幀:主要用于接收方通知其他尚未做好接收準備的幀。

間隔幀:主要用于將數據幀及遙控幀與前一幀分隔開來的幀。

其中數據幀是使用最多的幀類型,這里重點介紹以下數據幀。

數據幀如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

由上圖所示,數據幀包括:

(1)幀起始。表示數據幀開始的段。

(2)仲裁段。表示該幀優先級的段。

(3)控制段。表示數據的字節數及保留位的段。

(4)數據段。數據的內容,一幀可發送0~8個字節的數據。

(5)CRC段。檢查幀的傳輸錯誤的段。

(6)ACK段。表示確認正常接收的段。

(7)幀結束。表示數據幀結束的段。

具體介紹可以查看”CAN2.0A”、”CAN2.0B”詳細介紹。

我們主要關注我們編程所需要關注的幾個段:

ID: CAN報文ID;

IDE: 為0是標準幀,為1是擴展幀;

RTR: 為0是數據幀,為1是遠程幀;

DLC: CAN報文數據長度,范圍0~8字節;

Data:數據,0~8個字節;

14.2 CAN編程框架創建

當前我們所學習的是應用編程,為了以后CAN編程框架的通用性和可移植性,我們創建一個抽象的CAN應用編程框架,此框架可以適用于單片機應用編程,也可以適用于linux應用編程。

因此,根據CAN總線編程的通用屬性,我們抽象出如下屬性:

屬性 屬性描述 說明
CAN端口號 描述CAN端口,如CAN1,CAN2,CAN3,與具體硬件外設有關。
CAN收發器配置 描述CAN收發器模式設置,收發器模式有Normal,Stanby,<br>Sleep,ListenOnly等模式; 本章節所使用的收發器是硬件默<br/>認配置,因此不需要配置。
CAN控制器配置 描述CAN收發器配置,如CAN波特率配置,采樣率設置,過<br/>濾器設置等;
CAN中斷配置 描述CAN中斷接收函數配置
讀取CAN報文 描述CAN讀取報文實現
發送CAN報文 描述CAN發送報文實現

根據上面表格所描述的屬性,創建CAN應用編程框架如下:

typedef struct _CAN_COMM_STRUCT
{
    /* CAN硬件名稱 */
    char name[10];
    /* CAN端口號,裸機里為端口號;linux應用里作為socket套接口 */
    int  can_port;                                
    /* CAN控制器配置函數,返回端口號賦值給can_port */
    int  (*can_set_controller)( void );                  
    /* CAN接口中斷創建,在linux中對應創建接收線程 */
    void (*can_set_interrput)( int can_port , pCanInterrupt callback );             
    /* CAN讀取報文接口 */
    void (*can_read)( int can_port , CanRxMsg* recv_msg);   
    /* CAN發送報文接口*/
    void (*can_write)( int can_port , CanTxMsg send_msg);   
}CAN_COMM_STRUCT, *pCAN_COMM_STRUCT;

此框架可以用類比套用在單片機上,也可以使用在linux socketcan應用編程上。

14.3 STM32 CAN應用編程

本節主要使用14.2中的應用編程框架,在單片機上試驗框架的可行性,以一個基本的接收和發送的案例來做講解;

14.3.1 STM32 CAN接口電路

如下圖所示,為本章STM32例程所使用的開發板STM32最小系統和CAN收發器接口電路。

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖14.3.1-1 STM32F407最小系統</p></center>

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖14.3.1-1 TJA1050 CAN收發器接口電路</p></center>

14.3.2 STM32 CAN應用編程步驟

下面我們按照CAN通信的編程框架來一步一步實現基于STM32的CAN應用編程。

STM32 CAN應用編程,步驟如下:

14.3.2.1準備STM32工程模版

請參見第14章節代碼“01_stm32f407_can”例程;

所使用的開發環境為:MDK 5.24.

打開MDK工程后,如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

上圖中目錄CMSIS, STM32F407_LIB,main均為STM32運行的基礎框架。

目錄app_can為CAN應用編程所需要的文件。

14.3.2.2 編寫CAN抽象框架的實現函數

(1)定義CAN端口號

見第14章節代碼“01_stm32f407_can_addline”中“can_controller.h”文件。

主要根據STM32硬件的CAN有多路,依次定義為CAN_PORTCAN1, CAN_PORT_CAN2等,從“14.3.1 STM32 CAN接口電路”可知道,當前使用的CAN1.

25 /* CAN端口號定義*/
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個部分:GPIO(CAN_TX,CAN_RX管腳)配置,CAN波特率配置,CAN過濾器配置。

見第14章節代碼“01_stm32f407_can_addline”中“can_controller.c”文件int CAN_Set_Controller( void )函數。

A.GPIO(CAN_TX,CAN_RX管腳)配置

配置GPIO代碼如下:

96     /*************************************************************/
97     /*CAN相關GPIO配置,此處為:CAN_TX, CAN_RX*/
98
99     /*使能GPIO時鐘*/
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設置為CAN復用模式*/
116     GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_CAN1);
117     GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_CAN1);

B.配置波特率,工作模式

按照如下代碼,使能CAN外設,設置CAN工作模式為Normal,設置波特率為500kbps。

119     /*************************************************************/
120     /*CAN控制器相關配置,此處為波特率,采樣率等*/
121
122     /* 使能CAN時鐘 */
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);

其中配置波特率的函數是一個自定義函數,這里可以不了解,只需要知道是配置波特率即可,如果需要使用本章代碼,可以查看具體的源碼工程。

C. 配置CAN過濾器

如下代碼為配置過濾器:

141     /*************************************************************/
142     /* 初始化CAN過濾器 */
143     CAN_FilterInitStructure.CAN_FilterNumber = 0;                       /* CAN1濾波器號從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     /* 設置完CAN后,返回當前設置的CAN的端口號,此處主要類比linux socketcan中的套接口 */

此處我們設置過濾器不屏蔽任何報文ID,這里只是了解單片機下的一些過程。

(3)配置CAN接收中斷

CAN總線支持發送中斷和接收中斷,此處僅僅使用了接收中斷。

見第14章節代碼“01_stm32f407_can_addline”中“can_controller.c”文件void CAN_Set_Interrupt(int can_port, pCanInterrupt callback)函數。

CAN中斷配置代碼如下:

163 /**********************************************************************
164 * 函數名稱: void CAN_Set_Interrupt(int can_port,  pCanInterrupt callback)
165 * 功能描述: 使能CAN中斷處理,并傳入應用的的回調函數,回調函數主要處理應用層的功能
166 * 輸入參數: can_port,端口號
167 *            callback: 中斷具體處理應用功能的回調函數
168 * 輸出參數: 無
169 * 返 回 值: 無
170 * 修改日期             版本號        修改人           修改內容
171 * -----------------------------------------------
172 * 2020/05/13         V1.0             bert            創建
173 ***********************************************************************/
174 void CAN_Set_Interrupt(int can_port,  pCanInterrupt callback)
175 {
176     NVIC_InitTypeDef NVIC_InitStructure;
177
178     /* 根據CAN端口號配置中斷 */
179     switch( can_port )
180     {
181         case CAN_PORT_CAN1:
182         {
183             /* 初始化回調接口函數 */
184             if ( NULL != callback )
185             {
186                 g_pCanInterrupt = callback;
187             }
188
189             /* 使用CAN0_RX中斷,在linux socket can中類似創建接收線程 */
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接收中斷函數如下:

275 /**********************************************************************
276 * 函數名稱: void CAN1_RX0_IRQHandler(void)
277 * 功能描述: CAN接收中斷函數
278 * 輸入參數: 無
279 * 輸出參數: 無
280 * 返 回 值: 無
281 * 修改日期             版本號        修改人           修改內容
282 * -----------------------------------------------
283 * 2020/05/13         V1.0             bert            創建
284 ***********************************************************************/
285 void CAN1_RX0_IRQHandler(void)
286 {
287     /* 如果回調函數存在,則執行回調函數 */
288     if( g_pCanInterrupt != NULL)
289     {
290         g_pCanInterrupt();
291     }
292
293     /* 清除掛起中斷 */
294     CAN_ClearITPendingBit(CAN1,CAN_IT_FMP0);
295 }

此處CAN中斷通過回調函數g_pCanInterrupt()函數將應用層需要的代碼分層到應用層,此處為驅動部分通用接口。

(4)CAN報文讀取函數

當CAN接收中斷產生,通過CAN報文讀取函數從FIFO中讀取已經接收到的CAN報文。

見第14章節代碼“01_stm32f407_can_addline”中“can_controller.c”文件void CAN_Read(int can_port, CanRxMsg* recv_msg)函數。

CAN報文讀取函數如下:

208 /**********************************************************************
209 * 函數名稱: void CAN_Read(int can_port, CanRxMsg* recv_msg)
210 * 功能描述: CAN讀取接收寄存器,取出接收到的報文
211 * 輸入參數: can_port,端口號
212 * 輸出參數: recv_msg:接收報文
213 * 返 回 值: 無
214 * 修改日期             版本號        修改人           修改內容
215 * -----------------------------------------------
216 * 2020/05/13         V1.0             bert            創建
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報文 */
225             CAN_Receive(CAN1,CAN_FIFO0, recv_msg);
226         }
227         break;
228
229         default:
230             break;
231     }
232     return ;
233 }

(5)CAN報文發送函數

當需要發送CAN報文時,通過向CAN發送郵箱填充數據,啟動發送報文。

見第14章節代碼“01_stm32f407_can_addline”中“can_controller.c”文件void CAN_Write(int can_port, CanTxMsg send_msg)函數。

CAN報文讀取函數如下:

235 /**********************************************************************
236 * 函數名稱: void CAN_Write(int can_port, CanTxMsg send_msg)
237 * 功能描述: CAN報文發送接口,調用發送寄存器發送報文
238 * 輸入參數: can_port,端口號
239 * 輸出參數: send_msg:發送報文
240 * 返 回 值: 無
241 * 修改日期             版本號        修改人           修改內容
242 * -----------------------------------------------
243 * 2020/05/13         V1.0             bert            創建
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;     // 標準標識符為0x000~0x7FF
256             TxMessage.ExtId = 0x0000;             // 擴展標識符0x0000
257             TxMessage.IDE   = CAN_ID_STD;         // 使用標準標識符
258             TxMessage.RTR   = CAN_RTR_DATA;       // 設置為數據幀
259             TxMessage.DLC   = send_msg.DLC;       // 數據長度, can報文規定最大的數據長度為8字節
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);  /* 返回這個信息請求發送的郵箱號0,1,2或沒有郵箱申請發送no_box */
266         }
267         break;
268
269         default:
270             break;
271     }
272     return ;
273 }

(6)CAN抽象結構體框架初始化

定義一個can1通信結構實例CAN_COMM_STRUCT can1_controller;

使用(1)~(5)步驟實現的函數,初始化can1_controller,構成與應用層關聯的一個連接點。

298 /**********************************************************************
299 * 名稱:     can1_controller
300 * 功能描述: CAN1結構體初始化
301 * 修改日期             版本號        修改人           修改內容
302 * -----------------------------------------------
303 * 2020/05/13         V1.0             bert            創建
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應用層代碼

根據14.3.2.2 已經將具體的CAN硬件操作已經實現,并且已經抽象實例化了CAN編程框架。

但是我們現在還沒關聯到應用層,應用層并不知道調用哪個接口。

(1)CAN應用層注冊實例

在應用層編寫一個通用的實例化注冊函數。

見第14章節代碼“01_stm32f407_can_addline”中“app_can.c”文件int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)函數。

代碼實現如下:

62 /**********************************************************************
63 * 函數名稱: int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
64 * 功能描述: 應用層進行CAN1結構體注冊
65 * 輸入參數: p_can_controller,CAN控制器抽象結構體
66 * 輸出參數: 無
67 * 返 回 值: 無
68 * 修改日期             版本號        修改人           修改內容
69 * -----------------------------------------------
70 * 2020/05/13         V1.0             bert            創建
71 ***********************************************************************/
72 int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
73 {
74     /* 判斷傳入的p_can_controller為非空,目的是確認這個結構體是實體*/
75     if( p_can_controller != NULL )
76     {
77         /* 將傳入的參數p_can_controller賦值給應用層結構體gCAN_COMM_STRUCT */
78
79         /*端口號,類比socketcan套接口*/
80         gCAN_COMM_STRUCT.can_port              = p_can_controller->can_port;
81         /*CAN控制器配置函數*/
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報文讀函數*/
86         gCAN_COMM_STRUCT.can_read              = p_can_controller->can_read;
87         /*CAN報文發送函數*/
88         gCAN_COMM_STRUCT.can_write             = p_can_controller->can_write;
89         return 1;
90     }
91      return 0;
92 }

然后通過調用register_can_controller( &can1_controller );將實例can1_controller注冊給應用的4 static CAN_COMM_STRUCT gCAN_COMM_STRUCT;

之后應用層只需要調用應用層自己的gCAN_COMM_STRUCT實例即可操作CAN通信功能。

315 /**********************************************************************
316 * 函數名稱: void CAN1_contoller_add(void)
317 * 功能描述: CAN結構體注冊接口,應用層在使用can1_controller前調用
318 * 輸入參數: 無
319 * 輸出參數: 無
320 * 返 回 值: 無
321 * 修改日期             版本號        修改人           修改內容
322 * -----------------------------------------------
323 * 2020/05/13         V1.0             bert            創建
324 ***********************************************************************/
325 void CAN1_contoller_add(void)
326 {
327     /*將can1_controller傳遞給應用層*/
328     register_can_controller( &can1_controller );
329 }

(2)CAN應用層初始化

CAN應用層初始化代碼如下;

94 /**********************************************************************
95 * 函數名稱: void app_can_init(void)
96 * 功能描述: CAN應用層初始化
97 * 輸入參數: 無
98 * 輸出參數: 無
99 * 返 回 值: 無
100 * 修改日期             版本號        修改人           修改內容
101 * -----------------------------------------------
102 * 2020/05/13         V1.0             bert            創建
103 ***********************************************************************/
104 void app_can_init(void)
105 {
106     /**
107     * 應用層進行CAN1結構體注冊
108     */
109     CAN1_contoller_add();
110
111     /*
112     *調用can_set_controller進行CAN控制器配置,
113     *返回can_port,類比linux socketcan中的套接口,單片機例程中作為自定義CAN通道
114     */
115     gCAN_COMM_STRUCT.can_port = gCAN_COMM_STRUCT.can_set_controller();
116     /**
117     * 調用can_set_interrput配置CAN接收中斷,類比socketcan中的接收線程
118     */
119     gCAN_COMM_STRUCT.can_set_interrput( gCAN_COMM_STRUCT.can_port, CAN_RX_IRQHandler_Callback );
120 }

(3)設計一個簡單的周期發送報文功能

CAN周期發送報文的功能代碼實現如下:

123 /**********************************************************************
124 * 函數名稱: void app_can_tx_test(void)
125 * 功能描述: CAN應用層報文發送函數,用于測試周期發送報文
126 * 輸入參數: 無
127 * 輸出參數: 無
128 * 返 回 值: 無
129 * 修改日期             版本號        修改人           修改內容
130 * -----------------------------------------------
131 * 2020/05/13         V1.0             bert            創建
132 ***********************************************************************/
133 void app_can_tx_test(void)
134 {
135     // 以10ms為基準,運行CAN測試程序
136
137     unsigned char i=0;
138
139     /* 發送報文定義 */
140     CanTxMsg TxMessage;
141
142     /* 發送報文中用一個字節來作為計數器 */
143     static unsigned char tx_counter = 0;
144
145     /* 以10ms為基準,通過timer計數器設置該處理函數后面運行代碼的周期為1秒鐘*/
146     static unsigned int timer =0;
147     if(timer++>100)
148     {
149         timer = 0;
150     }
151     else
152     {
153         return ;
154     }
155
156     /* 發送報文報文數據填充,此報文周期是1秒 */
157     TxMessage.StdId = TX_CAN_ID;          /* 標準標識符為0x000~0x7FF */
158     TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
159     TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
160     TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
161     TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
162
163     /* 填充數據,此處可以根據實際應用填充 */
164     TxMessage.Data[0] = tx_counter++;       /* 用來識別報文發送計數器 */
165     for(i=1; i<TxMessage.DLC; i++)
166     {
167         TxMessage.Data[i] = i;
168     }
169
170     /*  調用can_write發送CAN報文 */
171     gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
172
173 }

(4)設計一個簡單的接收報文功能

220 /**********************************************************************
221 * 函數名稱: void CAN_RX_IRQHandler_Callback(void)
222 * 功能描述: CAN1接收中斷函數;在linux中可以類比用線程,或定時器去讀CAN數據
223 * 輸入參數: 無
224 * 輸出參數: 無
225 * 返 回 值: 無
226 * 修改日期             版本號        修改人           修改內容
227 * -----------------------------------------------
228 * 2020/05/13         V1.0             bert            創建
229 ***********************************************************************/
230 void CAN_RX_IRQHandler_Callback(void)
231 {
232     /* 接收報文定義 */
233     CanRxMsg RxMessage;
234
235     /* 接收報文清零 */
236     memset( &RxMessage, 0, sizeof(CanRxMsg) );
237
238     /* 通過can_read接口讀取寄存器已經接收到的報文 */
239     gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);
240
241     /* 將讀取到的CAN報文存拷貝到全局報文結構體g_CAN1_Rx_Message */
242     memcpy(&g_CAN1_Rx_Message, &RxMessage, sizeof( CanRxMsg ) );
243
244     /* 設置當前接收完成標志,判斷當前接收報文ID為RX_CAN_ID,則設置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 * 函數名稱: void app_can_rx_test(void)
178 * 功能描述: CAN應用層接收報文處理函數,用于處理中斷函數中接收的報文
179 * 輸入參數: 無
180 * 輸出參數: 無
181 * 返 回 值: 無
182 * 修改日期             版本號        修改人           修改內容
183 * -----------------------------------------------
184 * 2020/05/13         V1.0             bert            創建
185 ***********************************************************************/
186 void app_can_rx_test(void)
187 {
188     unsigned char i=0;
189
190     /* 發送報文定義 */
191     CanTxMsg TxMessage;
192
193     /* 發送報文中用一個字節來作為計數器 */
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         /* 發送報文報文數據填充,此報文周期是1秒 */
202         TxMessage.StdId = RX_TO_TX_CAN_ID;    /* 標準標識符為0x000~0x7FF */
203         TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
204         TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
205         TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
206         TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
207
208         /* 填充數據,此處可以根據實際應用填充 */
209         TxMessage.Data[0] = rx_counter++;      /* 用來識別報文發送計數器 */
210         for(i=1; i<TxMessage.DLC; i++)
211         {
212             TxMessage.Data[i] = g_CAN1_Rx_Message.Data[i];
213         }
214
215         /*  調用can_write發送CAN報文 */
216         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
217     }
218 }

14.3.2.4 STM32 CAN案例測試

在前面幾個章節將代碼編寫完成之后,我們做個測試;

測試工具使用的是:英特蓓斯的Valuecan3(CAN協議盒),Vehicle Vspy3(電腦端軟件)。

也可以在淘寶上購買便宜的USB轉CAN的工具即可。

測試步驟如下:

Step1:將已經完成的STM32 CAN測試程序下載到實際開發板上;

Step2:通過CAN測試工具Vehicle Vspy3發送報文ID為0X201的報文;

Step3:觀察CAN測試軟件顯示如下:

報文ID為0x101的報文是按照1秒周期進行發送,如圖14.3.2.4-1。

報文ID為0x201的報文是Vehicle Spy3按照周期500ms發送給STM32開發板,如圖14.3.2.4-1

報文ID為0x301的報文是在接收到報文ID為0x201的報文后,然后轉發出報文ID為0x301的報文,如圖14.3.2.4-2。

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖14.3.2.4-1 報文發送結果查看</p></center>

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖14.3.2.4-2 報文接收情況查看</p></center>

14.4 Linux socketcan基礎應用編程

14.4.1 socketcan概述

? socketcan是在Linux下CAN協議(Controller Area Network)實現的一種實現方法。 CAN是一種在世界范圍內廣泛用于自動控制、嵌入式設備和汽車領域的網絡技術。Linux下最早使用CAN的方法是基于字符設備來實現的,與之不同的是Socket CAN使用伯克利的socket接口和linux網絡協議棧,這種方法使得can設備驅動可以通過網絡接口來調用。Socket CAN的接口被設計的盡量接近TCP/IP的協議,讓那些熟悉網絡編程的程序員能夠比較容易的學習和使用。

? 使用Socket CAN的主要目的就是為用戶空間的應用程序提供基于Linux網絡層的套接字接口。與廣為人知的TCP/IP協議以及以太網不同,CAN總線沒有類似以太網的MAC層地址,只能用于廣播。CAN ID僅僅用來進行總線的仲裁。因此CAN ID在總線上必須是唯一的。當設計一個CAN-ECU(Electronic Control Unit 電子控制單元)網絡的時候,CAN報文ID可以映射到具體的ECU。因此CAN報文ID可以當作發送源的地址來使用。

14.4.2 socketcan基本知識點

? 在“14.3 STM32 CAN應用編程”中我們已經完整的構建了CAN應用編程框架,但是在linux應用編程中,操作CAN底層驅動與STM32思路上相似,但是操作方法或者說調用的接口還是差異很大的,因為STM32是直接調用的SDK包或直接操作寄存器,但是linux系統是需要通過調用系統命令或linuxCAN驅動來實現物理層的操作。

因此這里我們重點介紹linux上的一些系統調用命令,和一些socketcan相關的概念。

14.4.2.1 CAN設備操作

? CAN設備有開啟、關閉、設置參數3個功能。因為linux下CAN設備是模擬網絡操作的方式,這里CAN設備的開啟、關閉和設置,均通過ip命令來操作。

? 在100ask_IMX6ULL開發板上打開串口,使用“ifconfig -a”查看所有的網絡節點,發現第1個節點就是“can0”。

Linux應用開發【第十四章】CAN編程應用開發

(1)Linux CAN設備開啟:

#define ip_cmd_open      "ifconfig can0 up"     /* 打開CAN0 */

說明:can0:can設備名;

up: 打開設備命令

(2)Linux CAN設備關閉:

#define ip_cmd_close      "ifconfig can0 down"    /* 關閉CAN0 */

說明:can0:can設備名;

down: 關閉設備命令

(2)Linux CAN參數設置(波特率,采樣率):

#define ip_cmd_set_can_params "ip link set can0 type can bitrate 500000 triple-sampling on"

/* 將CAN0波特率設置為500000 bps */

說明:can0:can設備名;

down: 關閉設備命令

Type can: 設備類型為can

Bitrate 500000: 波特率設置為500kbps

Triple-sampleing on: 采樣打開

14.4.2.2 什么是Socket套接口

? 在linux里網絡操作使用socket進行接口創建,竟然CAN設備也是虛擬成網絡接口,也是使用的socket套接口。

? 如下圖所示,電話A呼叫電話B,電話A會輸入電話B的號碼,電話B會接收到電話A的來電。

電話A和電話B是兩個端點。而linux套接口與這個電話通信類似,套接口就是一個通信的端點,端點之間是通信鏈路;電話通信是通過電話號碼進行撥號通信,而套接口是使用地址進行識別對方的。

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖14.2.2.2 電話通信模型</p></center>

14.4.2.3 Socket接口函數

我們要創建并使用socket套接口進行通信編程,就需要了解一下socket相關的接口函數。

需要查詢linux系統里的函數,可以通過man命令查看。

舉例:

man socket / 查看socket函數描述 /

(1)socket()函數

在linux系統下,通過“man socket”命令,查詢socket()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

Socket函數原型如下:

#include <sys/types.h>     

#include <sys/socket.h>

int socket(int domain, int type, int protocol);  /* 套接口函數原型 */

函數三個參數如下:

domain:即協議域,又稱為協議族(family)。 常用的協議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須采用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。
type: 指定socket類型。常用的socket類型有, SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
protocol:就是指定協議。 常用的協議有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。

注意:

? 1.并不是上面的type和protocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol為0時,會自動選擇type類型對應的默認協議。

? 當我們調用socket創建一個socket時,返回的socket描述字它存在于協議族(address family,AF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須調用bind()函數,否則就當調用connect()、listen()時系統會自動隨機分配一個端口。

? 2. Socketcan使用的domain協議域是AF_CAN(或PF_CAN),type類型是SOCK_RAW, 指定協議protocol是CAN_RAW.

(2)bind()函數

? 在linux系統下,通過“man bind”命令,查詢bind()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

? bind()函數把一個地址族中的特定地址賦給socket。例如對應AF_INET、AF_INET6就是把一個ipv4或ipv6地址和端口號組合賦給socket。

? Bind函數原型如下所示:

#include <sys/types.h>          
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

函數的三個參數分別為:

sockfd:即socket描述字,它是通過socket()函數創建了,唯一標識一個socket。bind()函數就是將給這個描述字綁定一個名字。

addr:一個const struct sockaddr *指針,指向要綁定給sockfd的協議地址。這個地址結構根據地址創建socket時的地址協議族的不同而不同,

如ipv4對應的是:

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對應的是:

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域對應的是:

#define UNIX_PATH_MAX  108
struct sockaddr_un { 
  sa_family_t sun_family;        /* AF_UNIX */ 
  char    sun_path[UNIX_PATH_MAX]; /* pathname */ 
};

CAN域對應的是:

在文件“Linux-4.9.88includeuapilinuxcan.h”中有定義,這個是本章需要重點了解的。

/**
 * struct sockaddr_can - CAN sockets的地址結構
 * @can_family:  地址協議族 AF_CAN.
 * @can_ifindex:  CAN網絡接口索引
 * @can_addr:    協議地址信息
 */
struct sockaddr_can {
    __kernel_sa_family_t can_family;
    int         can_ifindex;
    union {
        /* 傳輸協議類地址信息 (e.g. ISOTP) */
        struct { canid_t rx_id, tx_id; } tp;

        /* 預留給將來使用的CAN協議地址信息*/
    } can_addr;
};

addrlen:對應的是地址的長度。

通常服務器在啟動的時候都會綁定一個眾所周知的地址(如ip地址+端口號),用于提供服務,客戶就可以通過它來接連服務器;而客戶端就不用指定,有系統自動分配一個端口號和自身的ip地址組合。這就是為什么通常服務器端在listen之前會調用bind(),而客戶端就不會調用,而是在connect()時由系統隨機生成一個。

(3)ioctl()函數

在linux系統下,通過“man ioctl”命令,查詢ioctl()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

Ioctl()函數調用層次如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

Ioctl()函數原型如下:

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

用ioctl獲得本地網絡接口地址時要用到兩個結構體ifconf和ifreq。

struct ifreq定義
ifreq用來保存某個接口的信息。

在文件“Linux-4.9.88includeuapilinuxif.h”中有定義struct ifreq,這個只需要了解是在ioctl()函數調用時用來獲取CAN設備索引(ifr_ifindex)使用,其他的參數可以不用關注。

/*
 * 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通常是用來保存所有接口信息的,本章節未使用到,在此不作詳細介紹。

(4)setsockopt()函數

? 在linux系統下,通過“man setsockopt”命令,查詢setsockopt()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

setsockopt()和getsockopt函數原型如下:

#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()用于任意類型、任意狀態套接口的設置選項值。盡管在不同協議層上存在選項,但本函數僅定義了最高的“套接口”層次上的選項。

其函數參數如下:可以看出其參數

sockfd:標識一個套接口的描述字。
level:選項定義的層次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP,IPPROTO_IPV6,SOL_CAN_RAW等。
optname:需設置的選項。
optval:指針,指向存放選項待設置的新值的緩沖區。
optlen:optval緩沖區長度。

函數調用示例如下:

示例1:設置CAN過濾器為不接收所有報文。
//禁用過濾規則,本進程不接收報文,只負責發送 <br><br/> //設置過濾規則 setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
示例2:設置CAN過濾器為接收某個指定報文
//定義接收規則,只接收表示符等于 0x201 的報文 <br/><br/> //在linux頭文件有定義,也可以自己定義 <br/>#define CAN_SFF_MASK 0x000007ffU <br/><br/> //定義過濾器(1個) <br/>struct can_filter rfilter[1]; <br/> rfilter[0].can_id = 0x201; <br/>rfilter[0].can_mask = CAN_SFF_MASK; <br/>//設置過濾規則 <br/>setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
示例2:設置CAN過濾器為接收某個指定報文
//定義接收規則,只接收表示符等于 0x201 的報文 <br/><br/> //在linux頭文件有定義,也可以自己定義 <br/>#define CAN_SFF_MASK 0x000007ffU <br/><br/>//定義過濾器(3個) <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/>//設置過濾規則 <br/>setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

(5)write()函數

在linux系統下,通過“man 2 write”命令,查詢write()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

Write函數原型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

(6)read()函數

在linux系統下,通過“man 2 read”命令,查詢read()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

Read函數原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

(7)close()函數

在linux系統下,通過“man 2 close”命令,查詢close()函數描述如下:

Linux應用開發【第十四章】CAN編程應用開發

close()函數原型如下:

#include <unistd.h>
int close(int fd);

14.4.3 socket_can簡單發送實例

簡單發送實例代碼目錄:“02_socketcan_send”

案例描述:

  1. 實現周期1秒發送報文ID:0x101的報文;

了解內容:IMX6 CAN接口電路

從下面CAN外圍電路看,和STM32是完全相同的,只是處理內部的CAN控制器因為不同芯片制造廠家的不同,會有一些較小的差異。這里電路只是對比了解一下,做linux應用可以不需要關注底層驅動處理。

Linux應用開發【第十四章】CAN編程應用開發

那我們現在按照14.3章節構建STM32下CAN應用編程的框架,一步一步編寫linux下socketCAN的應用編程。

準備工作:

我們按照14.3章節準備好can應用的代碼文件:

文件名 文件內容描述
App_can.c CAN應用功能實現
App_can.h CAN應用功能頭文件
Can_controller.c CAN驅動操作抽象層具體實現
Can_controller.h CAN驅動操作抽象層頭文件
Can_msg.h CAN報文基本結構體,從STM32 CAN驅動拷貝過來的,主要在使用CAN報文時使用我們最熟悉的結構體。 此文件相對STM32為新增文件,因為我們的框架是基于單片機應用,然后類比遷移學習到linux上。
Makefile Makefile編譯腳本

14.4.3.1編寫抽象框架的實現函數

首先我們使用14.3章節已經構建好的抽象結構體,如下:

見第14章節代碼“02_socketcan_send_addline”中“can_controller.h”。

34 /* CAN通信抽象結構體定義*/
35 typedef struct _CAN_COMM_STRUCT
36 {
37     /* CAN硬件名稱 */
38     char *name;
39     /* CAN端口號,裸機里為端口號;linux應用里作為socket套接口 */
40     int  can_port;                                
41     /* CAN控制器配置函數,返回端口號賦值給can_port */
42     int  (*can_set_controller)( void );                  
43     /* CAN接口中斷創建,在linux中對應創建接收線程 */
44     void (*can_set_interrput)( int can_port , pCanInterrupt callback );             
45     /* CAN讀取報文接口 */
46     void (*can_read)( int can_port , CanRxMsg* recv_msg);   
47     /* CAN發送報文接口*/
48     void (*can_write)( int can_port , CanTxMsg send_msg);   
49 }CAN_COMM_STRUCT, *pCAN_COMM_STRUCT;
50 

我們就按照這個結構體的順序依次編寫can_controller.c中的CAN驅動操作具體實現函數。

(1)定義CAN設備

根據14.4.2.1章節描述,linux應用層操作CAN設備,需要知道設備名。.

在100ask_IMX6ULL開發板上打開串口,使用“ifconfig -a”命令查看,知道當前CAN設備名稱為”can0”。

直接在linux命令行直接使用ip命令可以打開,設置,和關閉CAN設備,因此我們定義了三個宏ip_cmd_open, ip_cmd_close,ip_cmd_set_can_params, 這三個宏可以通過系統調用system()進行執行。

見第14章節代碼“02_socketcan_send_addline”中“can_controller.c”文件中宏定義。

29 /**************宏定義**************************************************/
30 
31 /* 將CAN0波特率設置為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 /* 關閉CAN0 */    
38 #define ip_cmd_close           "ifconfig can0 down"   

(2)配置CAN控制器

配置CAN控制器有3個部分:打開can0設備,CAN波特率配置,CAN過濾器配置。

見第14章節代碼“01_stm32f407_can_addline”中“can_controller.c”文件int CAN_Set_Controller( void )函數。

A.配置波特率,打開can0設備

使用(1)中的三個命令ip_cmd_open, ip_cmd_close,ip_cmd_set_can_params,通過system系統調用:具體代碼如下

77     /* 通過system調用ip命令設置CAN波特率 */
78     system(ip_cmd_close);               
79     system(ip_cmd_set_can_params);
80     system(ip_cmd_open);

B.創建套接口

因為linux應用操作設備均使用讀read寫write操作,linux一切皆文件,而socketcan又是一個特殊的文件,因此我們需要調用socket()函數創建一個socketcan接口,獲取sock_fd描述符。

具體代碼如下:

82   /*************************************************************/
83   /* 創建套接口 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設備與套接口

具體代碼如下:

92   //將套接字與 can0 綁定
93   strcpy(ifr.ifr_name, "can0");
94  ioctl(sock_fd, SIOCGIFINDEX,&ifr); // 設置設備為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.配置過濾器

具體代碼如下:

109     /*************************************************************/
110     //禁用過濾規則,本進程不接收報文,只負責發送
111  setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); 

D.配置非阻塞操作

Linux系統調用read和write函數有阻塞和非阻塞,我們在周期調用時,此采用非阻塞方式對CAN報文進行讀寫操作。

具體代碼實現如下:

114     //設置read()和write()函數設置為非堵塞方式
115     int flags;
116     flags = fcntl(sock_fd, F_GETFL);
117     flags |= O_NONBLOCK;
118     fcntl(sock_fd, F_SETFL, flags);  

E.返回sock_fd套接口

具體代碼實現如下:

int CAN_Set_Controller( void )函數直接結束后,返回值賦值給CAN_COMM_STRUCT的can_port成員。

后續應用層所訪問的sock_fd描述符即為can_port.

(3)創建CAN接收線程

在STM32中,接收使用的接收FIFO中斷進行處理,在linux應用中,我們則采用線程輪詢去讀取報文。

因此我們需要創建一個CAN接收線程,具體代碼實現如下:

127 /**********************************************************************
128 * 函數名稱: void CAN_Set_Interrupt(int can_port,  pCanInterrupt callback)
129 * 功能描述: 創建CAN接收線程,并傳入應用的的回調函數,回調函數主要處理應用層的功能
130 * 輸入參數: can_port,端口號
131 *          callback: 中斷具體處理應用功能的回調函數
132 * 輸出參數: 無
133 * 返 回 值: 無
134 * 修改日期             版本號        修改人           修改內容
135 * -----------------------------------------------
136 * 2020/05/13         V1.0             bert            創建
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 }

創建后的線程函數如下所示:

CAN1_RX0_IRQHandler是一個CAN接收線程函數,與CAN接收中斷功能相似,只這里采用輪詢方式讀取CAN報文。

253 /**********************************************************************
254 * 函數名稱: void CAN1_RX0_IRQHandler(void)
255 * 功能描述: CAN接收線程函數
256 * 輸入參數: 無  
257 * 輸出參數: 無
258 * 返 回 值: 無
259 * 修改日期             版本號        修改人           修改內容
260 * -----------------------------------------------
261 * 2020/05/13         V1.0             bert            創建
262 ***********************************************************************/
263 void *CAN1_RX0_IRQHandler(void *arg)
264 {
265     /* 接收報文定義 */
266     while( 1 )
267     {
268     /* 如果回調函數存在,則執行回調函數 */
269         if( g_pCanInterrupt != NULL)
270         {
271             g_pCanInterrupt();
272         }
273         usleep(10000);
274     }
275 }

(4)CAN報文讀取函數

161 /**********************************************************************
162 * 函數名稱: void CAN_Read(int can_port, CanRxMsg* recv_msg)
163 * 功能描述: CAN讀取接收寄存器,取出接收到的報文
164 * 輸入參數: can_port,端口號     
165 * 輸出參數: recv_msg:接收報文
166 * 返 回 值: 無
167 * 修改日期             版本號        修改人           修改內容
168 * -----------------------------------------------
169 * 2020/05/13         V1.0             bert            創建
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報文發送函數

206 /**********************************************************************
207 * 函數名稱: void CAN_Write(int can_port, CanTxMsg send_msg)
208 * 功能描述: CAN報文發送接口,調用發送寄存器發送報文
209 * 輸入參數: can_port,端口號     
210 * 輸出參數: send_msg:發送報文
211 * 返 回 值: 無
212 * 修改日期             版本號        修改人           修改內容
213 * -----------------------------------------------
214 * 2020/05/13         V1.0             bert            創建
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)); //發送 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抽象結構體框架初始化

與14.3章節STM32定義實例類似。

定義一個can1通信結構實例CAN_COMM_STRUCT can1_controller;

使用(1)~(5)步驟實現的函數,初始化can1_controller,構成與應用層關聯的一個連接點。

298 /**********************************************************************
299 * 名稱:     can1_controller
300 * 功能描述: CAN1結構體初始化
301 * 修改日期             版本號        修改人           修改內容
302 * -----------------------------------------------
303 * 2020/05/13         V1.0             bert            創建
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 編寫應用層代碼

根據14.4.3.1 已經將具體的linux下socketCAN硬件操作已經實現,并且已經抽象實例化了CAN編程框架。

但是我們現在還沒關聯到應用層,應用層并不知道調用哪個接口。

(1)CAN應用層注冊實例

在應用層編寫一個通用的實例化注冊函數。

見第14章節代碼“02_socketcan_send_addline”中“app_can.c”文件int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)函數。

代碼實現如下:(和STM32應用編程完全一樣,代碼幾乎不用更改)

73 /**********************************************************************
74 * 函數名稱: int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
75 * 功能描述: 應用層進行CAN1結構體注冊
76 * 輸入參數: p_can_controller,CAN控制器抽象結構體
77 * 輸出參數: 無
78 * 返 回 值: 無
79 * 修改日期             版本號        修改人           修改內容
80 * -----------------------------------------------
81 * 2020/05/13         V1.0             bert            創建
82 ***********************************************************************/
83 int register_can_controller(const pCAN_COMM_STRUCT p_can_controller)
84 {
85     /* 判斷傳入的p_can_controller為非空,目的是確認這個結構體是實體*/
86     if( p_can_controller != NULL )
87     {
88         /* 將傳入的參數p_can_controller賦值給應用層結構體gCAN_COMM_STRUCT */
89         
90         /*端口號,類比socketcan套接口*/
91         gCAN_COMM_STRUCT.can_port              = p_can_controller->can_port; 
92         /*CAN控制器配置函數*/
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報文讀函數*/
97         gCAN_COMM_STRUCT.can_read              = p_can_controller->can_read;
98         /*CAN報文發送函數*/
99         gCAN_COMM_STRUCT.can_write             = p_can_controller->can_write;
100         return 1;
101     }
102     return 0;
103 }

(2)CAN應用層初始化

CAN應用層代碼初始化如下:(和STM32 CAN應用代碼完全一樣)

105 /**********************************************************************
106 * 函數名稱: void app_can_init(void)
107 * 功能描述: CAN應用層初始化
108 * 輸入參數: 無
109 * 輸出參數: 無
110 * 返 回 值: 無
111 * 修改日期             版本號        修改人           修改內容
112 * -----------------------------------------------
113 * 2020/05/13         V1.0             bert            創建
114 ***********************************************************************/
115 void app_can_init(void)
116 {
117     /** 
118     * 應用層進行CAN1結構體注冊
119     */
120     CAN1_contoller_add();
121     
122     /*
123     *調用can_set_controller進行CAN控制器配置,
124     *返回can_port,類比linux socketcan中的套接口,單片機例程中作為自定義CAN通道 
125     */
126     gCAN_COMM_STRUCT.can_port = gCAN_COMM_STRUCT.can_set_controller();
127     /** 
128     * 調用can_set_interrput配置CAN接收中斷,類比socketcan中的接收線程,本例不用接收,因此回調函數傳入NULL
129     */
130     gCAN_COMM_STRUCT.can_set_interrput( gCAN_COMM_STRUCT.can_port, NULL );
131 }

(3)設計一個簡單的周期發送報文功能

我們需要先設計一個在10ms周期函數中調用的void app_can_tx_test(void)功能函數,這個函數在main主線程函數中進行調用。

CAN周期發送報文的功能函數代碼實現如下:

134 /**********************************************************************
135 * 函數名稱: void app_can_tx_test(void)
136 * 功能描述: CAN應用層報文發送函數,用于測試周期發送報文
137 * 輸入參數: 無
138 * 輸出參數: 無
139 * 返 回 值: 無
140 * 修改日期             版本號        修改人           修改內容
141 * -----------------------------------------------
142 * 2020/05/13         V1.0             bert            創建
143 ***********************************************************************/
144 void app_can_tx_test(void)
145 {
146     // 以10ms為基準,運行CAN測試程序
147     
148     unsigned char i=0;
149     
150     /* 發送報文定義 */
151     CanTxMsg TxMessage;
152     
153     /* 發送報文中用一個字節來作為計數器 */
154     static unsigned char tx_counter = 0;
155     
156     /* 以10ms為基準,通過timer計數器設置該處理函數后面運行代碼的周期為1秒鐘*/  
157     static unsigned int timer =0;
158     if(timer++>100)
159     {
160         timer = 0;
161     }
162     else
163     {
164         return ;
165     }
166     
167     /* 發送報文報文數據填充,此報文周期是1秒 */
168     TxMessage.StdId = TX_CAN_ID;          /* 標準標識符為0x000~0x7FF */
169     TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
170     TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
171     TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
172     TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
173     
174     /* 填充數據,此處可以根據實際應用填充 */
175     TxMessage.Data[0] = tx_counter++;       /* 用來識別報文發送計數器 */
176     for(i=1; i<TxMessage.DLC; i++)
177     {
178         TxMessage.Data[i] = i;            
179     }
180     
181     /*  調用can_write發送CAN報文 */
182     gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
183     
184 }
185 

然后將void app_can_tx_test(void)函數加入到main函數中,進行10ms周期執行,其代碼實現如下:

188 /**********************************************************************
189 * 函數名稱: int main(int argc, char **argv)
190 * 功能描述: 主函數
191 * 輸入參數: 無
192 * 輸出參數: 無
193 * 返 回 值: 無
194 * 修改日期             版本號        修改人           修改內容
195 * -----------------------------------------------
196 * 2020/05/13         V1.0             bert            創建
197 ***********************************************************************/
198 int main(int argc, char **argv)
199 {
200     /* CAN應用層初始化 */
201     app_can_init();
202     
203     while(1)
204     {
205         /* CAN應用層周期發送報文 */
206         app_can_tx_test();
207         
208         /* 利用linux的延時函數設計10ms的運行基準 */
209         usleep(10000);
210     }
211 }

14.4.3.3 案例測試驗證

當我們上面代碼完成編寫后,目錄文件如下:

Linux應用開發【第十四章】CAN編程應用開發

(1)編寫Makfile

Makefile文件內容如下:

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虛擬機環境中。

進入ubuntu虛擬機對應的socket_send目錄下

Linux應用開發【第十四章】CAN編程應用開發

輸入make命令:

Linux應用開發【第十四章】CAN編程應用開發

通過make命令編譯后,生成socket_send可執行文件。

(3)運行socket_send

注意:運行在100ask_imx6開發板上運行。

此處使用的是nfs文件進行運行。

先給100ask_imx6ull開發板上電,打開串口:

Linux應用開發【第十四章】CAN編程應用開發

輸入root用戶登錄進入開發板linux系統;

然后掛載nfs,操作如下:

Mount -t nfs -o nolock 192.168.1.100:/home/book  /mnt

Linux應用開發【第十四章】CAN編程應用開發

注意:目前我的開發板IP:192.168.1.101, Ubuntu虛擬機是192.168.1.100.

然后再運行./socketcan_send

如果運行時提示權限不允許,可以使用chmod命令設置權限:

Chmod 777 socketcan_send

運行后串口查看打印信息如下:

Linux應用開發【第十四章】CAN編程應用開發

然后再觀察Vehcile Spy3上位機測試結果如下:

報文按照時間1S的周期性發送報文ID為0x101的CAN報文。

Linux應用開發【第十四章】CAN編程應用開發

(4)測試總結

到此為止,我們已經通過socketcan建立起來了linux下應用編程的框架,并且成功的調試成功了CAN周期發送報文的功能編程。

后面將基于此框架,一步一步的了解linux下CAN應用編程;

對于相關案例章節的目的設置如下:

章節 目的
14.4.3 socket_can簡單發送實例 簡單直接的了解發送報文
14.4.4 socket_can簡單接收實例 簡單直接的了解接收報文
14.4.5 socket_can接收和發送實例 發送和接收報文的組合操作

14.4.4 socket_can 簡單接收實例

簡單接收實例代碼目錄:“03_socketcan_recv”

我們在14.4.3章節已經了解了發送報文發送的功能,而且已經建立起了linux下應用編程的框架;本節重點了解簡單接收功能。

案例描述:

1.實現接收報文0x201的報文。

14.4.4.1 編寫抽象框架的實現函數

(1)定義CAN設備

參考“14.4.3.1 編寫抽象框架的實現函數”中“(1)定義CAN設備”描述。

(2)配置CAN控制器

參考“14.4.3.1 編寫抽象框架的實現函數”中“(2)配置CAN控制器”描述。

因為在“14.4.3.1”中我們只發送,并且設置了過濾器為禁止所有報文。具體代碼如下:

109 /*************************************************************/
110 //禁用過濾規則,本進程不接收報文,只負責發送
111 setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);  

而本案例需要配置接收,過濾器配置會有相應差異,我們目前是配置僅僅接收報文ID為0x201的報文,

具體實現代碼如下:

110     //定義接收規則,只接收表示符等于 0x201 的報文
111     struct can_filter rfilter[1];
112     rfilter[0].can_id = 0x201;
113     rfilter[0].can_mask = CAN_SFF_MASK;
114     //設置過濾規則
115     setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

作為擴展,我們也可以設置多個過濾器:

定義過濾器:

struct can_filter rfilter[5];  /*定義10個過濾器*/
rfilter[0].can_id = 0x201;
rfilter[0].can_mask = 0x7FF;  /*過濾規則:can_id & mask = 0x201 & 0x7FF = 0x201*/
rfilter[1].can_id = 0x302;
rfilter[1].can_mask = 0x7FF;  /*過濾規則:can_id & mask = 0x302& 0x7FF = 0x302*/
rfilter[2].can_id = 0x403;
rfilter[2].can_mask = 0x7FF;  /*過濾規則:can_id & mask = 0x403& 0x7FF = 0x403*/
rfilter[3].can_id = 0x504;
rfilter[3].can_mask = 0x700;  /*過濾規則:can_id & mask = 0x504 & 0x700 = 0x500,即接收報文ID為0x5**的報文*/
rfilter[3].can_id = 0x605;
rfilter[3].can_mask = 0x700;  /*過濾規則:can_id & mask = 0x504 & 0x700 = 0x600*/
setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

(3)創建CAN接收線程

參考“14.4.3.1 編寫抽象框架的實現函數”中“(3)創建CAN接收線程”描述。

(4)CAN報文讀取函數

參考“14.4.3.1 編寫抽象框架的實現函數”中“(4)CAN報文讀取函數”描述。

(5)CAN報文發送函數

參考“14.4.3.1 編寫抽象框架的實現函數”中“(5)CAN報文發送函數”描述。

(6)CAN抽象結構體框架初始化

參考“14.4.3.1 編寫抽象框架的實現函數”中“(6)CAN抽象結構體框架初始化”描述。

14.4.4.2編寫應用層代碼

(1)CAN應用層注冊實例

參考“14.4.3.2 編寫應用層代碼”中“(1)CAN應用層注冊實例”描述。

(2)CAN應用層初始化

在本簡單接收實例中,我們需要將接收線程里的回調指針函數CAN_RX_IRQHandler_Callback傳入,在這個函數里,應用層可以自行進行讀取CAN報文等處理。

105 /**********************************************************************
106 * 函數名稱: void app_can_init(void)
107 * 功能描述: CAN應用層初始化
108 * 輸入參數: 無
109 * 輸出參數: 無
110 * 返 回 值: 無
111 * 修改日期             版本號        修改人           修改內容
112 * -----------------------------------------------
113 * 2020/05/13         V1.0             bert            創建
114 ***********************************************************************/
115 void app_can_init(void)
116 {
117     /** 
118     * 應用層進行CAN1結構體注冊
119     */
120     CAN1_contoller_add();
121     
122     /*
123     *調用can_set_controller進行CAN控制器配置,
124     *返回can_port,類比linux socketcan中的套接口,單片機例程中作為自定義CAN通道 
125     */
126     gCAN_COMM_STRUCT.can_port = gCAN_COMM_STRUCT.can_set_controller();
127     /** 
128     * 調用can_set_interrput配置CAN接收中斷,類比socketcan中的接收線程
129     */
130     gCAN_COMM_STRUCT.can_set_interrput( gCAN_COMM_STRUCT.can_port, CAN_RX_IRQHandler_Callback );
131 }

(3)設計一個簡單的接收報文功能

關于void CAN_RX_IRQHandler_Callback(void)的具體實現如下所示:

CAN_RX_IRQHandler_Callback是在接收線程中循環執行,應用層在CAN_RX_IRQHandler_Callback函數進行gCAN_COMM_STRUCT.can_read讀取CAN報文。

133 /**********************************************************************
134 * 函數名稱: void CAN_RX_IRQHandler_Callback(void)
135 * 功能描述: CAN1接收中斷函數;在linux中可以類比用線程,或定時器去讀CAN數據
136 * 輸入參數: 無
137 * 輸出參數: 無
138 * 返 回 值: 無
139 * 修改日期             版本號        修改人           修改內容
140 * -----------------------------------------------
141 * 2020/05/13         V1.0             bert            創建
142 ***********************************************************************/
143 void CAN_RX_IRQHandler_Callback(void)
144 {
145     /* 接收報文定義 */
146     CanRxMsg RxMessage; 
147     
148     /* 接收報文清零 */
149     memset( &RxMessage, 0, sizeof(CanRxMsg) );
150    
151     /* 通過can_read接口讀取寄存器已經接收到的報文 */
152     gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);
153 
154     /* 將讀取到的CAN報文存拷貝到全局報文結構體g_CAN1_Rx_Message */
155     memcpy(&g_CAN1_Rx_Message, &RxMessage, sizeof( CanRxMsg ) );
156     
157 }

本案例無發送報文功能,主線程中無代碼處理,只需要空跑運行即可。

159 /**********************************************************************
160 * 函數名稱: int main(int argc, char **argv)
161 * 功能描述: 主函數
162 * 輸入參數: 無
163 * 輸出參數: 無
164 * 返 回 值: 無
165 * 修改日期             版本號        修改人           修改內容
166 * -----------------------------------------------
167 * 2020/05/13         V1.0             bert            創建
168 ***********************************************************************/
169 int main(int argc, char **argv)
170 {
171     /* CAN應用層初始化 */
172     app_can_init();
173     
174     while(1)
175     {        
176         /* 利用linux的延時函數設計10ms的運行基準 */
177         usleep(10000);
178     }
179 }
180 

14.4.4.3 案例測試驗證

(1)編寫Makfile

Makefile文件內容如下:

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虛擬機環境中。

進入ubuntu虛擬機對應的socket_recv目錄下,執行make all進行編譯。

編譯過程如下:

Linux應用開發【第十四章】CAN編程應用開發

(3)運行socket_recv

注意:運行在100ask_imx6開發板上運行。

此處使用的是nfs文件進行運行。

Nfs掛載,請參考“14.4,3.3 案例測試驗證”。

在100ask_imx6開發板環境下,執行“./socket_recv”,運行程序;

然后通過Vhicle Spy3向100ask_imx6開發板CAN端發送報文ID未0x201的報文,報文trace如下:

Linux應用開發【第十四章】CAN編程應用開發

100ask_imx6開發板串口打印信息如下:

Linux應用開發【第十四章】CAN編程應用開發

(4)測試總結

到此為止,我們已經調試成功了CAN報文接收的功能編程。

14.4.5 socket_can 接收和發送實例

簡單接收實例代碼目錄:“04_socketcan_recv_send”

本案例整合了“14.4.3 簡單發送實例”和“14.4.3 簡單接收實例”,構建成一個發送和接收均有的組合案例。

案例描述:

  1. 實現周期1秒發送報文ID:0x101的報文;

  2. 實現接收報文0x201的報文,并將內容復制到報文0x301的報文,并發送出去;

14.4.5.1 編寫抽象框架的實現函數

(1)定義CAN設備

參考“14.4.3.1 編寫抽象框架的實現函數”中“(1)定義CAN設備”描述。

參考“14.4.4.1 編寫抽象框架的實現函數”中“(1)定義CAN設備”描述。

(2)配置CAN控制器

參考“14.4.3.1 編寫抽象框架的實現函數”中“(2)配置CAN控制器”描述。

參考“14.4.4.1 編寫抽象框架的實現函數”中“(2)配置CAN控制器”描述。

(3)創建CAN接收線程

參考“14.4.3.1 編寫抽象框架的實現函數”中“(3)創建CAN接收線程”描述。

參考“14.4.4.1 編寫抽象框架的實現函數”中“(3)創建CAN接收線程”描述。

(4)CAN報文讀取函數

參考“14.4.3.1 編寫抽象框架的實現函數”中“(4)CAN報文讀取函數”描述。

參考“14.4.4.1 編寫抽象框架的實現函數”中“(4)CAN報文讀取函數”描述。

(5)CAN報文發送函數

參考“14.4.3.1 編寫抽象框架的實現函數”中“(5)CAN報文發送函數”描述。

參考“14.4.4.1 編寫抽象框架的實現函數”中“(5)CAN報文發送函數”描述。

(6)CAN抽象結構體框架初始化

參考“14.4.3.1 編寫抽象框架的實現函數”中“(6)CAN抽象結構體框架初始化”描述。

參考“14.4.4.1 編寫抽象框架的實現函數”中“(6)CAN抽象結構體框架初始化”描述。

14.4.5.2 編寫應用層代碼

(1)CAN應用層注冊實例

參考“14.4.3.2 編寫應用層代碼”中“(1)CAN應用層注冊實例”描述。

參考“14.4.4.2 編寫應用層代碼”中“(1)CAN應用層注冊實例”描述。

(2)CAN應用層初始化

參考“14.4.4.2 編寫應用層代碼”中“(2)CAN應用層初始化”描述。

(3)設計一個簡單的周期發送報文功能

參考“14.4.3.2 編寫應用層代碼”中“(3)設計一個簡單的發送報文功能”描述。

(4)設計一個簡單的周期接收報文功能

參考“14.4.4.2 編寫應用層代碼”中“(3)設計一個簡單的接收報文功能”描述。

? 同時,我們此處需要將接收到的ID:0X201的報文,將內容復制給報文ID:0x301的報文,并發送出去。

我們在“14.4.4 簡單接收報文”的基礎上增加一個簡單的邏輯,在接收線程的回調函數CAN_RX_IRQHandler_Callback中,調用gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);接收到報文ID:0x201的報文后,設置標志g_CAN1_Rx_Flag = 1; 然后去主線程去判斷此標志是否被設置為1,標識已經接收到,則在void app_can_rx_test(void)中去拷貝報文ID:0X201的報文內容,然后賦值給報文ID:0x301的報文。

接收線程回調函數CAN_RX_IRQHandler_Callback的實現代碼如下:

231 /**********************************************************************
232 * 函數名稱: void CAN_RX_IRQHandler_Callback(void)
233 * 功能描述: CAN1接收中斷函數;在linux中可以類比用線程,或定時器去讀CAN數據
234 * 輸入參數: 無
235 * 輸出參數: 無
236 * 返 回 值: 無
237 * 修改日期             版本號        修改人           修改內容
238 * -----------------------------------------------
239 * 2020/05/13         V1.0             bert            創建
240 ***********************************************************************/
241 void CAN_RX_IRQHandler_Callback(void)
242 {
243     /* 接收報文定義 */
244     CanRxMsg RxMessage; 
245     
246     /* 接收報文清零 */
247     memset( &RxMessage, 0, sizeof(CanRxMsg) );
248    
249     /* 通過can_read接口讀取寄存器已經接收到的報文 */
250     gCAN_COMM_STRUCT.can_read(gCAN_COMM_STRUCT.can_port, &RxMessage);
251 
252     /* 將讀取到的CAN報文存拷貝到全局報文結構體g_CAN1_Rx_Message */
253     memcpy(&g_CAN1_Rx_Message, &RxMessage, sizeof( CanRxMsg ) );
254     
255     /* 設置當前接收完成標志,判斷當前接收報文ID為RX_CAN_ID,則設置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的接收觸發處理函數代碼如下:

187 /**********************************************************************
188 * 函數名稱: void app_can_rx_test(void)
189 * 功能描述: CAN應用層接收報文處理函數,用于處理中斷函數中接收的報文
190 * 輸入參數: 無
191 * 輸出參數: 無
192 * 返 回 值: 無
193 * 修改日期             版本號        修改人           修改內容
194 * -----------------------------------------------
195 * 2020/05/13         V1.0             bert            創建
196 ***********************************************************************/
197 void app_can_rx_test(void)
198 {
199     unsigned char i=0;
200     
201     /* 發送報文定義 */
202     CanTxMsg TxMessage;
203     
204     /* 發送報文中用一個字節來作為計數器 */
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         /* 發送報文報文數據填充,此報文周期是1秒 */
213         TxMessage.StdId = RX_TO_TX_CAN_ID;    /* 標準標識符為0x000~0x7FF */
214         TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
215         TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
216         TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
217         TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
218         
219         /* 填充數據,此處可以根據實際應用填充 */
220         TxMessage.Data[0] = rx_counter++;      /* 用來識別報文發送計數器 */
221         for(i=1; i<TxMessage.DLC; i++)
222         {
223             TxMessage.Data[i] = g_CAN1_Rx_Message.Data[i];            
224         }
225         
226         /*  調用can_write發送CAN報文 */
227         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
228     }
229 }

14.4.5.3 案例測試驗證

(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虛擬機環境中。

進入ubuntu虛擬機對應的socket_send目錄下,執行make all進行編譯。

編譯過程如下:

Linux應用開發【第十四章】CAN編程應用開發

(3)運行socket_recv_send

注意:運行在100ask_imx6開發板上運行。

此處使用的是nfs文件進行運行。

Nfs掛載,請參考“14.4,3.3 案例測試驗證”。

在100ask_imx6開發板環境下,執行“./socket_recv_send”,運行程序;

然后通過Vhicle Spy3向100ask_imx6開發板CAN端發送報文ID未0x201的報文,報文trace如下:

Linux應用開發【第十四章】CAN編程應用開發

然后觀察100ask_imx6開發串口打印信息如下:

Linux應用開發【第十四章】CAN編程應用開發

(4)測試總結

到此為止,我們已經調試成功了CAN報文接收和發送的功能編程。

14.5 汽車行業CAN總線應用

14.5.1 車廠CAN總線需求

CAN總線應用最廣泛的應該是汽車領域,幾乎所有的車均支持CAN總線,在這里就簡單介紹一些汽車相關的CAN總線需求。

14.5.1.1 網絡拓撲結構

下面這張網絡拓撲圖和我開發過的大部分實際車輛拓撲大致一致。一般是汽車廠商提供給零部件供應商的。

如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖14.5.1 整車網絡拓撲</p></center>

一般車身網絡分為如下6個局域CAN網絡:

車輛拓撲分組 描述
PT CAN <br/>(PowerTrain CAN ) <br/>動力總成CAN總線 主要負責車輛動力相關的ECU組網,是整車要求傳輸速率最高的一路CAN網絡;<br> 一般包括如下相關ECU單元: <br/> ECM(Engine Control Module)發動機控制模塊; <br/> SRS ( SupplementalRestraintSystem) 電子安全氣囊 <br/> BMS ( Battery Management System ) 電池管理系統 <br/>EPB Electronic Park Brake, 電子駐車系統
CH CAN <br/>(Chassis CAN) <br/>底盤控制CAN總線 CH CAN負責汽車底盤及4個輪子的制動/穩定/轉向,由于涉及整車制動/助力轉向等, <br/>所以其網絡信號優先級也是較高的。 <br/>一般包括如下相關ECU單元: <br/>ABS ( Antilock Brake System ) 防抱死制動系統 <br/> ESP(Electronic Stability Program)車身電子穩定系統 <br/>EPS(Electric Power Steering)電子轉向助力
Body CAN <br/>車身控制總線 Body CAN負責車身上的一些提高舒適性/安全性的智能硬件的管理與控制,其網絡信<br/>號優先級較低, 因為以上設備都是輔助設備。 <br/>一般包括如下相關ECU單元: <br/>AC ( Air Condition ) 空調 <br/>AVM(Around View Monitor) 360環視 <br/>BCM(Body Control Module) 天窗, 車窗, 霧燈, 轉向燈, 雨刮… <br/>IMMO(Immobilizer) 發動機防盜系統 <br/>TPMS(Tire Pressure Monitoring System) 胎壓監控系統
Info CAN <br/><br/> ( Infomercial CAN ) <br/>娛樂系統總線 Info CAN是輔助可選設備, 所以優先級也是較低的,主要負責車身上的一些提高娛樂性的智能硬件的管理與控制。 <br/>一般包括如下相關ECU單元: <br/>VAES( Video Audio Entertainment System) 車載娛樂系統(中控) <br/>IP(Instrument Pack) 組合儀表, 當今的數字儀表, 基本有音樂, 地圖, 通話等娛樂功能.
DiagCAN ( Diagnose CAN ) 診斷控制總線 DiagCAN總線主要提供遠程診斷功能,只有一個ECU: <br/> Tbox(Telematics BOX) 遠程控制模塊
OBD CAN OBD一般是提供外接診斷儀,基本是接在整車網關ECU上。

14.5.1.2 CAN 報文分類

在汽車CAN網絡里面,CAN報文主要分為三種:應用報文,網絡報文,和診斷報文。

不論是網絡報文,還是診斷報文,均是按照不同的功能需求劃分的,根據不同的需求,制定CAN報文數據的不同協議。

(1)CAN應用報文

CAN應用報文,主要用于車身網絡中不同ECU節點之間的數據信息的發送和接收,與具體應用功能相關;

汽車CAN應用報文,由車廠進行定義和發布“信號矩陣表(excel格式)”和“信號矩陣(DBC格式)”。

詳見“14.5.2 CAN應用報文應用分析及實例”。

(2)CAN網絡管理報文

汽車電子系統通過車載網絡對所有的ECU 進行配置管理和協調工作的過程稱之為網絡管理。

網絡管理可以通過對于網絡上的各個 ECU 的控制,發出一些命令規則,實現各個 ECU 的協同睡眠和喚醒,用于協同控制的CAN報文,就是網絡管理報文。

網絡管理有OSEK網絡管理和AUTOSAR網絡管理兩種。一般前裝車廠項目才會要求支持網絡管理。

(3)CAN診斷報文

CAN診斷主要是是實現車輛的功能監控,故障檢測,記錄/存儲故障信息,存儲/讀取數據,還有EOL下線檢測,ECU升級等功能。

基于CAN的通信分層模型:

OSI分層 車廠診斷標準 OBD標準
診斷應用 用戶定義 ISO15031-5
應用層 ISO15765-3 / ISO14229-1 ISO15031-5
表示層
會話層 ISO15765-3 ISO15765-4
傳輸層
網絡層 ISO15765-2 ISO15765-4
數據鏈路層 ISO11898-1 ISO15765-4
物理層 用戶定義 ISO15765-4

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖 CAN診斷服務OSI模型</p></center>

14.5.2 CAN 應用報文應用分析及實例

14.5.2.1 CAN 應用報文定義

當一個車廠項目啟動之后,根據項目的需求,車廠會提供CAN信號矩陣(excel),和DBC信號矩陣數據庫。

(1)CAN信號矩陣-excel格式

車廠提供的信號矩陣(excel)的文件格式,詳見第14章代碼目錄:CAN_Signal_Matrix.xlsx,

從CAN_Signal_Matrix.xlsx中截取報文定義,如下所示:

ECU_TX_MSG1: (周期發送報文,ID:0x123)

Linux應用開發【第十四章】CAN編程應用開發

ECU_TX_MSG2: (事件發送報文,ID:0x124)

Linux應用開發【第十四章】CAN編程應用開發

ECU_TX_MSG3: (周期&事件發送報文,ID:0x125)

Linux應用開發【第十四章】CAN編程應用開發

ECU_RX_MSG1:(事件接收報文,ID: 0X201)

Linux應用開發【第十四章】CAN編程應用開發

? 從上報文定義可以看出,車廠會定義報文的很多屬性,如報文名稱,報文ID,報文長度, 報文周期,報文發送類型, 以及報文中的信號名稱,信號起始字節,信號長度,排列格式(Intel或Motorala),信號的取值范圍,信號的發送方式等等。

(2)CAN信號矩陣-DBC

本章提供的示例CAN矩陣“CAN_Signal_Matrix.xlsx”對應的DBC文件,該DBC文件使用vector CANdb+ Editor編輯;如下圖所示為DBC文件所顯示的報文信息內容,和excel表格所展示內容是一致的,文件格式不是最關鍵的,只要理解車廠對CAN信號的要求即可。

Linux應用開發【第十四章】CAN編程應用開發

<center><p>圖 CAN信號矩陣DBC</p></center>

14.5.2.2 CAN應用報文發送規則

? 我們提到車廠會提供CAN信號矩陣表,會定義周期報文,事件報文,周期事件混合報文,那么定義這些信號的通用規則在哪里?一般車廠會提供關于CAN總線的通信規范,車廠根據通信規范才定義出CAN信號矩陣。

? 下面是某車廠的通信規范《XXX Communication Requirement Specification.pdf》,其規范目錄如下圖所示,從目錄可以看出,主要介紹CAN物理層,數據鏈路層,通信交互層等相關規則。

? 本小節,我們主要介紹應用報文相關的通信交互層“4 Interaction Layer”相關的內容:CAN報文發送類型(Message Send Type)。

Linux應用開發【第十四章】CAN編程應用開發

CAN報文發送類型按照之前矩陣表展示的逐一介紹如下:

(1)周期型報文(Cyclic Message)

周期報文,即為周期定時發送,周期為T。

如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

當系統運行后,ECU就按照周期T定時發送CAN報文。

(2)事件型報文(Event Message)

觸發事件時發送事件型消息,如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

當系統運行后,ECU并不主動發送事件型報文,而是當ECU被某一條件觸發(Event),則ECU會連續發送三幀事件報文。

當然車廠要求不僅僅如此,車廠還會有更多其他要求,

比如,

要求1,:觸發發送三幀報文后,要求信號恢復為默認值;

要求2:觸發發送三幀,幀與幀間間隔要求50ms;

(3)周期事件型報文(Cyclic And Event Message)

? 周期事件混合型報文(簡稱CE),當無事件觸發的情況下,按照周期T定時發送報文,當有事件觸發的情況下,按照event事件觸發方式發送報文。

如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

實際車廠定義的CAN報文發送類型并不僅僅是上面三種,但是這三種是最重要的發送方式。

14.5.2.3 汽車CAN應用報文發送應用實例

通過上一小節的描述,我們已經了解了車廠規范中三個應用報文發送類型,現在我們就開始在100ask_imx6開發板上進行試驗,實現車廠應用報文的需求。

關于linux socketcan的應用編程框架我們已經在“14.4 linux socketcan基礎應用編程”詳細講解了,我們現在就基于“14.4.5 socketcan接收和發送實例”進行本章案例應用編程,重點側重于app_can.c編程,can_controller.c可以完全沿用。

以下應用編程,我們使用14.5.2.1中介紹的CAN報文矩陣中的CAN報文。

(1)linux can編程框架準備

使用案例“04_socketcan_recv_send”代碼,復制文件夾改名為“06_socketcan_ecu_application”。

在app_can.c文件中定義報文ID:

30 /**************宏定義**************************************************/
31 /* 本例程中測試周期發送的CAN報文ID */
32 #define TX_CAN_CYCLIC_ID    0X123
33 #define TX_CAN_EVENT_ID     0X124
34 #define TX_CAN_CE_ID        0X125
35 
36 /* 本例程中測試接收的CAN報文ID */
37 #define RX_CAN_ID           0x201   

(2)周期型報文實現

實現功能:

A.編程實現周期發送報文ID:0x123, 周期T為1000ms。

代碼實現如下:

136 /**********************************************************************
137 * 函數名稱: void app_can_cyclicmsg_test(void)
138 * 功能描述: CAN應用層測試發送周期型報文(ID:0X123)
139 * 輸入參數: 無
140 * 輸出參數: 無
141 * 返 回 值: 無
142 * 修改日期             版本號        修改人           修改內容
143 * -----------------------------------------------
144 * 2020/05/13         V1.0             bert            創建
145 ***********************************************************************/
146 void app_can_cyclicmsg_test(void)
147 {
148     // 以10ms為基準,運行CAN測試程序
149     
150     unsigned char i=0;
151     
152     /* 發送報文定義 */
153     CanTxMsg TxMessage;
154     
155     /* 發送報文中用一個字節來作為計數器 */
156     static unsigned char tx_counter = 0;
157     
158     /* 以10ms為基準,通過timer計數器設置該處理函數后面運行代碼的周期為1秒鐘*/  
159     static unsigned int timer =0;
160     if(timer++>100)
161     {
162         timer = 0;
163     }
164     else
165     {
166         return ;
167     }
168     
169     /* 發送報文報文數據填充,此報文周期是1秒 */
170     TxMessage.StdId = TX_CAN_CYCLIC_ID;   /* 標準標識符為0x000~0x7FF */
171     TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
172     TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
173     TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
174     TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
175     
176     /* 填充數據,此處可以根據實際應用填充 */
177     TxMessage.Data[0] = tx_counter++;       /* 用來識別報文發送計數器 */
178     for(i=1; i<TxMessage.DLC; i++)
179     {
180         TxMessage.Data[i] = i;            
181     }
182     
183     /*  調用can_write發送CAN報文 */
184     gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
185     
186 }

(3)事件型報文實現

實現功能:

A. 編程實現當接收到一幀報文(ID:0x201)的信號ECU_RX_MSG1_signal1=1時,觸發發送事件型報文(ID:0x124),讓ECU_MSG2_signal2(Byte1字節)=2 且兩幀報文間時間間隔為50ms。

B. 事件觸發條件:接收到報文(ID:0x201),且ECU_RX_MSG1_signal1(Byte0字節bit0)為1

代碼實現如下:

188 /**********************************************************************
189 * 函數名稱: void app_can_eventmsg_test(void)
190 * 功能描述: CAN應用層測試發送事件型報文(ID:0X124)
191 * 輸入參數: 無
192 * 輸出參數: 無
193 * 返 回 值: 無
194 * 修改日期             版本號        修改人           修改內容
195 * -----------------------------------------------
196 * 2020/05/13         V1.0             bert            創建
197 ***********************************************************************/
198 void app_can_eventmsg_test(void)
199 {
200     unsigned char i=0;
201 
202     /* 發送報文中用一個字節來作為事件觸發計數器 */
203     static unsigned char tx_counter = 0;
204 
205     /* 發送報文定義 */
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         /* 發送報文報文數據填充,此報文周期是1秒 */
214         TxMessage.StdId = TX_CAN_EVENT_ID;    /* 標準標識符為0x000~0x7FF */
215         TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
216         TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
217         TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
218         TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
219         
220         /* 填充數據,此處可以根據實際應用填充 */
221         for(i=0; i<TxMessage.DLC; i++)
222         {
223             TxMessage.Data[i] = 0x00;            
224         }
225         /* 填充數據,此處可以根據實際應用填充 */
226     tx_counter = 0;
227     
228     /*更新第1幀數據*/
229     TxMessage.Data[1] = 0x02;
230     TxMessage.Data[7] = (++tx_counter);
231         /*  調用can_write發送CAN報文,第1幀 */
232         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
233     /*延時50ms,作為事件報文間隔*/
234     usleep(50000);
235 
236     /*更新第2幀數據*/
237     TxMessage.Data[1] = 0x02;
238     TxMessage.Data[7] = (++tx_counter);
239     /*  調用can_write發送CAN報文,第2幀 */
240         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
241     /*延時50ms,作為事件報文間隔*/
242     usleep(50000);
243 
244     /*更新第3幀數據*/
245     TxMessage.Data[1] = 0x02;
246     TxMessage.Data[7] = (++tx_counter);
247     /*  調用can_write發送CAN報文,第3幀 */
248         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
249     /*延時50ms,作為事件報文間隔*/
250     usleep(50000);
251     }
252 }

(4)周期事件型報文實現

實現功能:

A. 編程實現周期發送報文(ID:0x125);

B. 而當接收到一幀報文(ID:0x201)的信號ECU_RX_MSG1_signal2=1時,觸發發送周期事件型報文(ID:0x125), 讓ECU_MSG3_signal9(Byte1字節bit0)=1,且連續發送三幀,且兩幀報文間時間間隔為50ms,三幀發送完成后恢復成ECU_MSG3_signal5=0;

A. 事件觸發條件:接收到報文(ID:0x201),且ECU_RX_MSG1_signal2(Byte0字節bit1)為1

代碼實現如下:

255 /**********************************************************************
256 * 函數名稱: void app_can_cycliceventmsg_test(void)
257 * 功能描述: CAN應用層測試發送周期事件混合報文(ID:0X125)
258 * 輸入參數: 無
259 * 輸出參數: 無
260 * 返 回 值: 無
261 * 修改日期             版本號        修改人           修改內容
262 * -----------------------------------------------
263 * 2020/05/13         V1.0             bert            創建
264 ***********************************************************************/
265 void app_can_cycliceventmsg_test(void)
266 {
267     unsigned char i=0;
268     
269     /* 發送報文定義 */
270     CanTxMsg TxMessage;
271     
272     /* 發送報文中用一個字節來作為事件觸發計數器 */
273     static unsigned char tx_counter = 0;
274 
275     /* 以10ms為基準,通過timer計數器設置該處理函數后面運行代碼的周期為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     /* 發送報文報文數據填充,此報文周期是1秒 */
284         TxMessage.StdId = TX_CAN_CE_ID;      /* 標準標識符為0x000~0x7FF */
285         TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
286         TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
287         TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
288         TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
289         
290         /* 清零數據區 */
291         for(i=0; i<TxMessage.DLC; i++)
292         {
293             TxMessage.Data[i] = 0x00;            
294         }
295     /* 填充數據,此處可以根據實際應用填充 */
296     tx_counter = 0;
297 
298         /*更新第1幀數據*/
299     TxMessage.Data[1] = 0x01;
300     TxMessage.Data[7] = (++tx_counter);
301         /*  調用can_write發送CAN報文,第1幀 */
302         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
303     /*延時50ms,作為事件報文間隔*/
304     usleep(50000);
305 
306     /*更新第2幀數據*/
307     TxMessage.Data[1] = 0x01;
308     TxMessage.Data[7] = (++tx_counter);
309     /*  調用can_write發送CAN報文,第2幀 */
310         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
311     /*延時50ms,作為事件報文間隔*/
312     usleep(50000);
313 
314     /*更新第3幀數據*/
315     TxMessage.Data[1] = 0x01;
316     TxMessage.Data[7] = (++tx_counter);
317     /*  調用can_write發送CAN報文,第3幀 */
318         gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
319     /*延時50ms,作為事件報文間隔*/
320     usleep(50000);
321     }
322 
323     /* 以10ms為基準,通過timer計數器設置該處理函數后面運行代碼的周期為1秒鐘*/  
324     if(timer++>100)
325     {
326         timer = 0;
327     }
328     else
329     {
330         return ;
331     }
332 
333     /* 發送報文報文數據填充,此報文周期是1秒 */
334     TxMessage.StdId = TX_CAN_CE_ID;   /* 標準標識符為0x000~0x7FF */
335     TxMessage.ExtId = 0x0000;             /* 擴展標識符0x0000 */
336     TxMessage.IDE   = CAN_ID_STD;         /* 使用標準標識符 */
337     TxMessage.RTR   = CAN_RTR_DATA;       /* 設置為數據幀  */
338     TxMessage.DLC   = 8;                  /* 數據長度, can報文規定最大的數據長度為8字節 */
339         
340     /* 填充數據,此處可以根據實際應用填充 */
341     for(i=1; i<TxMessage.DLC; i++)
342     {
343     TxMessage.Data[i] = 0x00;            
344     }
345      
346     /*  調用can_write發送CAN報文 */
347     gCAN_COMM_STRUCT.can_write(gCAN_COMM_STRUCT.can_port, TxMessage);
348 }

(4)案例測試

第一步:測試周期報文

運行socket_ecu_test,串口打印信息如下所示:

Linux應用開發【第十四章】CAN編程應用開發

然后觀察Vehicle Spy3軟件獲取的報文trace,如下所示:

報文ID:0x123,0x125兩個報文均以1000ms的周期發送報文;

Linux應用開發【第十四章】CAN編程應用開發

第二步:測試事件型報文

在Vehicle Spy3軟件上Messages里面過濾出報文ID:0X201,0X124.

然后手動點擊右側的Tx Panel上的ID:0X201的報文,左側Messages的記錄為100ask_imx6開發板發出3幀ID:0x124的報文。

通過開發板串口打印信息看出:“Message:0x124 is Triggered!”,在這條打印信息之后,存在三幀ID:0x124的報文。

Linux應用開發【第十四章】CAN編程應用開發

觀察出左側的Messages的trace如下圖所示:

Linux應用開發【第十四章】CAN編程應用開發

第三步:測試周期事件型報文

在Vehicle Spy3軟件上Messages里面過濾出報文ID:0X201,0X125.

然后手動點擊右側的Tx Panel上的ID:0X201的報文,左側Messages的記錄為100ask_imx6開發板發出3幀ID:0x125的報文,但是報文數據與默認數據不同,數據內容Byte7依次為0x01,0x02,0x03。

通過開發板串口打印信息看出:“Message:0x125 is Triggered!”,在這條打印信息之后,存在三幀ID:0x124的報文。

Linux應用開發【第十四章】CAN編程應用開發

觀察出左側的Messages的trace如下圖所示:

ID:0X125正常情況下以1000ms的周期發送默認報文,當ID:201的報文觸發事件,引起ID:0X125發送事件報文。

(5)事件報文發送改進

通過前面步驟,我們已經了解應用報文的發送類型和實現不同發送類型的方式,但是上面事件處理有一個缺陷,就是當事件觸發時,發送時通過ucsleep()函數實現的報文間隔,這個延時會使得周期報文的周期變長,這個可以通過觀察CAN報文trace查找到。

這里對案例“06_socketcan_ecu_application”做了一個小小的改進,對觸發事件的處理采用周期計數來實現,具體請查看案例代碼“07_socketcan_ecu_application_new”。

Linux應用開發【第十四章】CAN編程應用開發

本文摘自 :https://blog.51cto.com/w

開通會員,享受整站包年服務
国产呦精品一区二区三区网站|久久www免费人咸|精品无码人妻一区二区|久99久热只有精品国产15|中文字幕亚洲无线码