14 CAN编程

14.4.3.2 编写应用层代码

但是我们现在还没关联到应用层,应用层并不知道调用哪个接口。

(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 案例测试验证

当我们上面代码完成编写后,目录文件如下:

(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目录下

输入make命令:

通过make命令编译后,生成socket_send可执行文件。

(3)运行socket_send

注意:运行在100ask_imx6开发板上运行。

此处使用的是nfs文件进行运行。

先给100ask_imx6ull开发板上电,打开串口:

输入root用户登录进入开发板linux系统;

然后挂载nfs,操作如下:

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

注意:目前我的开发板IP:192.168.1.101, Ubuntu虚拟机是192.168.1.100.

然后再运行./socketcan_send

如果运行时提示权限不允许,可以使用chmod命令设置权限:

chmod 777 socketcan_send

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

然后再观察Vehcile Spy3上位机测试结果如下:

报文按照时间1S的周期性发送报文ID为0x101的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进行编译。

编译过程如下:

(3)运行socket_recv

注意:运行在100ask_imx6开发板上运行。

此处使用的是nfs文件进行运行。

Nfs挂载,请参考“14.4,3.3 案例测试验证”。

在100ask_imx6开发板环境下,执行“./socket_recv”,运行程序;

然后通过Vhicle Spy3向100ask_imx6开发板CAN端发送报文ID未0x201的报文,报文trace如下:

100ask_imx6开发板串口打印信息如下:

(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进行编译。

编译过程如下:

(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如下:


然后观察100ask_imx6开发串口打印信息如下:

(4)测试总结

到此为止,我们已经调试成功了CAN报文接收和发送的功能编程。

14.5 汽车行业CAN总线应用

14.5.1 车厂CAN总线需求

CAN总线应用最广泛的应该是汽车领域,几乎所有的车均支持CAN总线,在这里就简单介绍一些汽车相关的CAN总线需求。

14.5.1.1 网络拓扑结构

下面这张网络拓扑图和我开发过的大部分实际车辆拓扑大致一致。一般是汽车厂商提供给零部件供应商的。

如下图所示:

一般车身网络分为如下6个局域CAN网络:

车辆拓扑分组 描述
PT CAN
(PowerTrain CAN )
动力总成CAN总线
主要负责车辆动力相关的ECU组网,是整车要求传输速率最高的一路CAN网络;
一般包括如下相关ECU单元:
ECM(Engine Control Module)发动机控制模块;
SRS ( SupplementalRestraintSystem) 电子安全气囊
BMS ( Battery Management System ) 电池管理系统
EPB Electronic Park Brake, 电子驻车系统
CH CAN
(Chassis CAN)
底盘控制CAN总线
CH CAN负责汽车底盘及4个轮子的制动/稳定/转向,由于涉及整车制动/助力转向等,
所以其网络信号优先级也是较高的。
一般包括如下相关ECU单元:
ABS ( Antilock Brake System ) 防抱死制动系统
ESP(Electronic Stability Program)车身电子稳定系统
EPS(Electric Power Steering)电子转向助力
Body CAN
车身控制总线
Body CAN负责车身上的一些提高舒适性/安全性的智能硬件的管理与控制,其网络信
号优先级较低, 因为以上设备都是辅助设备。
一般包括如下相关ECU单元:
AC ( Air Condition ) 空调
AVM(Around View Monitor) 360环视
BCM(Body Control Module) 天窗, 车窗, 雾灯, 转向灯, 雨刮…
IMMO(Immobilizer) 发动机防盗系统
TPMS(Tire Pressure Monitoring System) 胎压监控系统
Info CAN

( Infomercial CAN )
娱乐系统总线
Info CAN是辅助可选设备, 所以优先级也是较低的,主要负责车身上的一些提高娱乐性的智能硬件的管理与控制。
一般包括如下相关ECU单元:
VAES( Video Audio Entertainment System) 车载娱乐系统(中控)
IP(Instrument Pack) 组合仪表, 当今的数字仪表, 基本有音乐, 地图, 通话等娱乐功能.
DiagCAN ( Diagnose CAN ) 诊断控制总线 DiagCAN总线主要提供远程诊断功能,只有一个ECU:
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

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)

ECU_TX_MSG2: (事件发送报文,ID:0x124)

ECU_TX_MSG3: (周期&事件发送报文,ID:0x125)

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

​ 从上报文定义可以看出,车厂会定义报文的很多属性,如报文名称,报文ID,报文长度, 报文周期,报文发送类型, 以及报文中的信号名称,信号起始字节,信号长度,排列格式(Intel或Motorala),信号的取值范围,信号的发送方式等等。

(2)CAN信号矩阵-DBC

本章提供的示例CAN矩阵“CAN_Signal_Matrix.xlsx”对应的DBC文件,该DBC文件使用vector CANdb+ Editor编辑;如下图所示为DBC文件所显示的报文信息内容,和excel表格所展示内容是一致的,文件格式不是最关键的,只要理解车厂对CAN信号的要求即可。

14.5.2.2 CAN应用报文发送规则

​ 我们提到车厂会提供CAN信号矩阵表,会定义周期报文,事件报文,周期事件混合报文,那么定义这些信号的通用规则在哪里?一般车厂会提供关于CAN总线的通信规范,车厂根据通信规范才定义出CAN信号矩阵。

​ 下面是某车厂的通信规范《XXX Communication Requirement Specification.pdf》,其规范目录如下图所示,从目录可以看出,主要介绍CAN物理层,数据链路层,通信交互层等相关规则。

​ 本小节,我们主要介绍应用报文相关的通信交互层“4 Interaction Layer”相关的内容:CAN报文发送类型(Message Send Type)。

CAN报文发送类型按照之前矩阵表展示的逐一介绍如下:

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

周期报文,即为周期定时发送,周期为T。

如下图所示:

当系统运行后,ECU就按照周期T定时发送CAN报文。

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

触发事件时发送事件型消息,如下图所示:

当系统运行后,ECU并不主动发送事件型报文,而是当ECU被某一条件触发(Event),则ECU会连续发送三帧事件报文。

当然车厂要求不仅仅如此,车厂还会有更多其他要求,

比如,

要求1,:触发发送三帧报文后,要求信号恢复为默认值;

要求2:触发发送三帧,帧与帧间间隔要求50ms;

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

​ 周期事件混合型报文(简称CE),当无事件触发的情况下,按照周期T定时发送报文,当有事件触发的情况下,按照event事件触发方式发送报文。

如下图所示:


实际车厂定义的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!\n");
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!\n");
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,串口打印信息如下所示:

然后观察Vehicle Spy3软件获取的报文trace,如下所示:
报文ID:0x123,0x125两个报文均以1000ms的周期发送报文;

第二步:测试事件型报文
在Vehicle Spy3软件上Messages里面过滤出报文ID:0X201,0X124.
然后手动点击右侧的Tx Panel上的ID:0X201的报文,左侧Messages的记录为100ask_imx6开发板发出3帧ID:0x124的报文。
通过开发板串口打印信息看出:“Message:0x124 is Triggered!”,在这条打印信息之后,存在三帧ID:0x124的报文。

观察出左侧的Messages的trace如下图所示:

第三步:测试周期事件型报文
在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的报文。

观察出左侧的Messages的trace如下图所示:
ID:0X125正常情况下以1000ms的周期发送默认报文,当ID:201的报文触发事件,引起ID:0X125发送事件报文。
(5)事件报文发送改进
通过前面步骤,我们已经了解应用报文的发送类型和实现不同发送类型的方式,但是上面事件处理有一个缺陷,就是当事件触发时,发送时通过ucsleep()函数实现的报文间隔,这个延时会使得周期报文的周期变长,这个可以通过观察CAN报文trace查找到。
这里对案例“06_socketcan_ecu_application”做了一个小小的改进,对触发事件的处理采用周期计数来实现,具体请查看案例代码“07_socketcan_ecu_application_new”。

1 Like

请问:14章的源代码在哪里获取啊