Linux 底软开发——对CAN的详细操作(周期发送,异常检测,过滤报文)

Linux底软开发—对CAN发送接收详细操作

文章目录

  • Linux底软开发—对CAN发送接收详细操作
    • 1.保证多条CAN数据发送的周期性
    • 2.解析CAN报文数据
    • 3.CAN总线异常机制应对
    • 4.对CAN报文进行过滤操作
    • 5.完整的接收报文代码(过滤,心跳检测,解析)

1.保证多条CAN数据发送的周期性

如果想同时发送多条CAN,在Linux下可以使用多线程操作,一条线程对应一条CAN报文

 // 启动线程发送CAN消息for (int i = 0; i < 3; ++i){pthread_create(&tid[i], NULL, send_data, (void *)&data_args[i]);}// 等待所有线程结束for (int i = 0; i < 3; ++i){pthread_join(tid[i], NULL);}

保证每条CAN报文的周期性,可以使用Linux的时间函数—usleep() ,精确到微秒,同样每个线程可以接受时间间隔参数

示例代码:循环发送多条CAN报文,自定义时间周期,每一条CAN报文都可以自定义自己的周期

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <pthread.h>#define CAN_INTERFACE "can0"
#define CAN_ID_1 0x123
#define CAN_ID_2 0x124
#define CAN_ID_3 0x125
#define CAN_DATA_LEN 8struct send_data_args
{int sockfd;struct can_frame frame;int64_t interval; // 发送周期
};void send_can_message(int sockfd, struct can_frame *frame)
{int nbytes;nbytes = write(sockfd, frame, sizeof(struct can_frame));if (nbytes < 0){perror("Write failed");exit(EXIT_FAILURE);}
}void *send_data(void *args)
{struct send_data_args *data_args = (struct send_data_args *)args;while (1){send_can_message(data_args->sockfd, &(data_args->frame));printf("Sent CAN message with ID 0x%X\n", data_args->frame.can_id);usleep(data_args->interval);}pthread_exit(NULL);
}int main(int argc, char *argv[])
{struct sockaddr_can addr;struct ifreq ifr;int sockfd;int64_t time_stval; // 时间间隔周期if (argc < 3){// 如果未提供时间周期参数,则默认为100毫秒time_stval = 100 * 1000;}else{char *str = argv[2]; // 一个数字字符串int64_t result;// 使用 strtoll 函数将 char* 转换为 int64_tchar *endptr;result = strtoll(str, &endptr, 10);// 检查转换是否成功if (*endptr != '\0'){printf("Conversion failed. Not a valid number.\n");return 1;}time_stval = result;}printf("调试信息如下:\n");printf("接收参数个数:%d\n", argc);printf("can接口为:%s\n", argv[1]);printf("时间间隔周期为:%ld\n", time_stval);pthread_t tid[3];struct send_data_args data_args[3];// 创建socketif ((sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0){perror("Socket creation failed");return EXIT_FAILURE;}// 设置CAN接口 从外部输入指定strcpy(ifr.ifr_name, argv[1]);ioctl(sockfd, SIOCGIFINDEX, &ifr);addr.can_family = AF_CAN;addr.can_ifindex = ifr.ifr_ifindex;if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0){perror("Binding failed");close(sockfd);return EXIT_FAILURE;}// 准备CAN消息data_args[0].sockfd = sockfd;data_args[0].frame.can_id = CAN_ID_1;data_args[0].frame.can_dlc = CAN_DATA_LEN;memset(data_args[0].frame.data, 0, CAN_DATA_LEN); // 清空数据data_args[0].interval = 200 * 1000; // 第一个CAN报文的发送周期为200毫秒data_args[1].sockfd = sockfd;data_args[1].frame.can_id = CAN_ID_2;data_args[1].frame.can_dlc = CAN_DATA_LEN;memset(data_args[1].frame.data, 0, CAN_DATA_LEN); // 清空数据data_args[1].interval = 300 * 1000; // 第二个CAN报文的发送周期为300毫秒data_args[2].sockfd = sockfd;data_args[2].frame.can_id = CAN_ID_3;data_args[2].frame.can_dlc = CAN_DATA_LEN;memset(data_args[2].frame.data, 0, CAN_DATA_LEN); // 清空数据data_args[2].interval = 400 * 1000; // 第三个CAN报文的发送周期为400毫秒// 启动线程发送CAN消息for (int i = 0; i < 3; ++i){pthread_create(&tid[i], NULL, send_data, (void *)&data_args[i]);}// 等待所有线程结束for (int i = 0; i < 3; ++i){pthread_join(tid[i], NULL);}close(sockfd);return EXIT_SUCCESS;
}

测试结果

在这里插入图片描述

上图中定义0x123 每隔200ms 发送数据,时间误差为0.0001s,

2.解析CAN报文数据

CAN报文的数据格式通常由CAN帧的数据域(Data Field)和数据长度码(Data Length Code,DLC)组成。具体格式如下:

  1. CAN标识符(CAN Identifier):用于标识CAN消息的ID。在标准CAN帧中,ID为11位;在扩展CAN帧中,ID为29位。ID可以表示消息的优先级、消息类型等信息。
  2. 远程传输请求位(Remote Transmission Request,RTR):用于标识消息是数据帧还是远程帧。数据帧包含实际的数据,而远程帧则不包含数据,仅用于请求数据。RTR位为0表示数据帧,为1表示远程帧。
  3. 数据长度码(Data Length Code,DLC):指示了CAN帧数据域中包含的数据字节数。DLC的取值范围通常为0到8。
  4. 数据域(Data Field):包含了CAN消息的实际数据。数据域的大小由DLC决定,最大为8个字节。
  5. CRC校验码(Cyclic Redundancy Check,CRC):用于检测CAN帧在传输过程中的错误。CRC通常由CAN控制器自动生成和验证。
  6. 确认位(ACK):用于确认CAN消息是否被成功接收。CAN总线上的所有节点都可以接收CAN消息,并通过ACK位来确认消息是否被正确接收。
  7. 结束位(End of Frame,EOF):指示了CAN帧的结束。
--------------------------------------------------------------------
| Bit Position |  0-1  |  2-12  |  13   |  14-17  |  18-25  |  26-31 |
|--------------|-------|--------|-------|---------|---------|--------|
|      Field   |  SOF  |   ID   |   RTR |   DLC   |  Data   |   CRC  |
--------------------------------------------------------------------

在LinuxC编程中,接收CAN报文,系统API 已经封装好了结构体,对于开发者来讲,取到数据之后,读取can_frame结构体即可

结构体定义如下:

struct can_frame {canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */__u8    can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */__u8    __pad;   /* padding */__u8    __res0;  /* reserved / padding */__u8    __res1;  /* reserved / padding */__u8    data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};

读取示例:

  printf("Received CAN frame:\n");printf("ID: %03X\n", frame.can_id);printf("Length: %d\n", frame.can_dlc);printf("Data: ");for (int i = 0; i < frame.can_dlc; i++){printf("%02X ", frame.data[i]);}printf("\n");

具体功能需要解析frame.data 数据,根据通信矩阵,十六进制字节数据转为二进制 逐一分析即可

3.CAN总线异常机制应对

解决方案:引入心跳机制,即固定发送时间间隔的固定CAN_ID ,每一次得到CAN数据之后,就更新最后一次获取时间,引入心跳检测线程,一直判断是否超时,如果超时,说明CAN发送异常,数据未及时发送,或者掉线等其他原因,超时之后,再根据业务进行捕获异常。

示例代码: 在接受线程中,检测ID是否为心跳包,如果是心跳包,更新最后一次接收时间

// 检查是否为心跳包if (frame.can_id == HEARTBEAT_ID){// 更新心跳包接收时间gettimeofday(&last_heartbeat_time, NULL);}

注意:需要将last_heartbeat_time设置为全局变量,方便检测线程获取最新的时间

// 心跳包检测线程函数
void *heartbeat_checker(void *arg)
{struct timeval current_time;double elapsed_time;while (1){// 获取当前时间gettimeofday(&current_time, NULL);// 计算与上次心跳包的时间间隔elapsed_time = difftime(current_time.tv_sec, last_heartbeat_time.tv_sec);// 检查是否超时if (elapsed_time > TIMEOUT_SEC){online = -1;printf("CAN node is offline.\n");// 清空接收缓冲区 实现逻辑// ioctl(can_socket, SIOCINQ, 0);}// 休眠1秒sleep(1);}return NULL;
}

4.对CAN报文进行过滤操作

可以设置过滤表,将需要的ID放入过滤表中,并且设置当前状态是否可用

例如在运行目录读取过滤表设置文件

filter_table.ini 文件内容

#ID STATUS 录入数据请用空格隔开
0x123 enable
0x122 enable
0x111 disable

在程序初始化之后,读取过滤表,一定要在读取CAN报文线程之前运行。判断哪些可以接收并放入数组中,为下一步过滤做判断

代码实现:

//得到过滤表中可用的ID
void getEnableIds()
{FILE *file;char line[MAX_LINE_LENGTH];FilterEntry entries[MAX_IDS];int count = 0;int ptr = 0;// 打开过滤表文件file = fopen("filter_table.ini", "r");if (file == NULL){printf("filter_table.ini");perror("文件不存在,请检查路径\n");return 1;}// 逐行读取文件内容while (fgets(line, sizeof(line), file) != NULL){// 跳过以 "#" 开头的注释行if (line[0] == '#'){continue;}// 解析每行,提取CAN信号ID和状态unsigned int id;char status[10];if (sscanf(line, "%x %s", &id, status) == 2){// 将CAN信号ID和状态存储到数组中entries[count].id = id;entries[count].enabled = (strcmp(status, "enable") == 0) ? 1 : 0;if (strcmp(status, "enable") == 0){// 可用IDenable_id[ptr] = id;ptr++;enable_count++;}count++;if (count >= MAX_IDS){printf("Maximum number of entries reached. Aborting.\n");break;}}}// 关闭文件fclose(file);printf("Read enable %d filter table entries:\n", ptr);for (int i = 0; i < ptr; i++){printf("Entry %d: ID = 0x%X\n", i + 1, enable_id[i]);}
}

在读取过程中,判断报文是否可用,是否存在可用数组中。

 // 判断是否是可用的ID 不是可用的直接passif(isIdExists(frame.can_id,enable_id,enable_count)==-1) continue;// 打印接收到的CAN帧数据printf("Received CAN frame:\n");printf("ID: %03X\n", frame.can_id);printf("Length: %d\n", frame.can_dlc);printf("Data: ");for (int i = 0; i < frame.can_dlc; i++){printf("%02X ", frame.data[i]);}printf("\n");

5.完整的接收报文代码(过滤,心跳检测,解析)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/time.h>#define CAN_INTERFACE "can0" // 替换为你的CAN接口名称
#define HEARTBEAT_ID 0x166   // 心跳包的CAN消息ID
#define TIMEOUT_SEC 3        // 等待时长struct timeval last_heartbeat_time; // 设为全局变量
int can_socket;
int online = 1;
// 过滤表相关 定义
#define MAX_IDS 100         // 最大支持的CAN信号ID数量
#define MAX_LINE_LENGTH 100 // 最大行长度
typedef struct
{unsigned int id;int enabled; // 0为disable,1为enable
} FilterEntry;unsigned int enable_id[MAX_IDS];
int enable_count=0;//得到过滤表中可用的ID
void getEnableIds()
{FILE *file;char line[MAX_LINE_LENGTH];FilterEntry entries[MAX_IDS];int count = 0;int ptr = 0;// 打开过滤表文件file = fopen("filter_table.ini", "r");if (file == NULL){printf("filter_table.ini");perror("文件不存在,请检查路径\n");return 1;}// 逐行读取文件内容while (fgets(line, sizeof(line), file) != NULL){// 跳过以 "#" 开头的注释行if (line[0] == '#'){continue;}// 解析每行,提取CAN信号ID和状态unsigned int id;char status[10];if (sscanf(line, "%x %s", &id, status) == 2){// 将CAN信号ID和状态存储到数组中entries[count].id = id;entries[count].enabled = (strcmp(status, "enable") == 0) ? 1 : 0;if (strcmp(status, "enable") == 0){// 可用IDenable_id[ptr] = id;ptr++;enable_count++;}count++;if (count >= MAX_IDS){printf("Maximum number of entries reached. Aborting.\n");break;}}}// 关闭文件fclose(file);printf("Read enable %d filter table entries:\n", ptr);for (int i = 0; i < ptr; i++){printf("Entry %d: ID = 0x%X\n", i + 1, enable_id[i]);}
}// 判断十六进制数值是否存在于数组中的函数
int isIdExists(unsigned int id, unsigned int *enable_id, int size) {for (int i = 0; i < size; i++) {if (enable_id[i] == id) {return 1; // 存在}}return -1; // 不存在
}// 心跳包检测线程函数
void *heartbeat_checker(void *arg)
{struct timeval current_time;double elapsed_time;while (1){// 获取当前时间gettimeofday(&current_time, NULL);// 计算与上次心跳包的时间间隔elapsed_time = difftime(current_time.tv_sec, last_heartbeat_time.tv_sec);// 检查是否超时if (elapsed_time > TIMEOUT_SEC){online = -1;printf("CAN node is offline.\n");// 清空接收缓冲区 实现逻辑// ioctl(can_socket, SIOCINQ, 0);}// 休眠1秒sleep(1);}return NULL;
}// CAN数据接收线程函数
void *can_receiver(void *arg)
{struct can_frame frame;int nbytes;while (1){nbytes = read(can_socket, &frame, sizeof(struct can_frame));if (nbytes < 0){perror("read");break;}else if (nbytes < sizeof(struct can_frame)){fprintf(stderr, "read: incomplete CAN frame\n");break;}// 检查是否为心跳包if (frame.can_id == HEARTBEAT_ID){// 更新心跳包接收时间gettimeofday(&last_heartbeat_time, NULL);}// 判断是否是可用的ID 不是可用的直接passif(isIdExists(frame.can_id,enable_id,enable_count)==-1) continue;// 打印接收到的CAN帧数据printf("Received CAN frame:\n");printf("ID: %03X\n", frame.can_id);printf("Length: %d\n", frame.can_dlc);printf("Data: ");for (int i = 0; i < frame.can_dlc; i++){printf("%02X ", frame.data[i]);}printf("\n");}return NULL;
}int main(int argc, char *argv[])
{int s;struct sockaddr_can addr;struct ifreq ifr;pthread_t checker_thread;pthread_t receiver_thread;getEnableIds();printf("正在等待%s的数据.....\n", argv[1]);// 创建sockets = socket(PF_CAN, SOCK_RAW, CAN_RAW);if (s == -1){perror("socket");return 1;}can_socket = s; // 将socket赋给全局变量// 绑定CAN接口strcpy(ifr.ifr_name, argv[1]);ioctl(s, SIOCGIFINDEX, &ifr);addr.can_family = AF_CAN;addr.can_ifindex = ifr.ifr_ifindex;if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1){perror("bind");close(s);return 1;}// 创建心跳包检测线程if (pthread_create(&checker_thread, NULL, heartbeat_checker, NULL) != 0){perror("pthread_create");close(s);return 1;}// 创建CAN数据接收线程if (pthread_create(&receiver_thread, NULL, can_receiver, NULL) != 0){perror("pthread_create");close(s);return 1;}// 等待线程结束pthread_join(checker_thread, NULL);pthread_join(receiver_thread, NULL);// 关闭socketclose(s);return 0;
}

测试效果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/3794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java之复制图片

从文件夹中复制图片 从这个文件夹&#xff1a; 复制到这个空的文件夹&#xff1a; 代码如下&#xff1a; import java.io.*; import java.util.Scanner;/*** 普通文件的复制*/public class TestDome10 {public static void main(String[] args) {// 输入两个路径// 从哪里(源路…

MySQL随便聊-----之认识MySQL

本专栏&#xff0c;用作复习&#xff0c;不建议小白&#xff0c;建议者勿看 一、了解MySQL 二、MySQL的客户端/服务器架构 以我们平时使用的微信为例&#xff0c;它其实是由两部分组成的&#xff0c;一部分是客户端程序&#xff0c;一部分是服务器程序。客户端可 能有很多种形…

适合弱电行业用的项目管理系统,找企智汇项目管理系统!

弱电行业&#xff0c;是指通信、计算机、监控、安防、智能家居等一系列与现代生活息息相关的行业。在这个行业&#xff0c;项目管理的重要性不言而喻。企智汇项目管理系统在弱电行业的应用中&#xff0c;展现出了其独特的优势和价值。该系统能够充分满足弱电工程项目的复杂需求…

基于arcpro3.0.2版的使用深度学习检测对象之椰子树

基于arcpro3.0.2版的使用深度学习检测对象之椰子树 GPU显卡Nivda 1080 训练模型图 (四)检测对象之椰子树 使用深度学习检测对象 打开 detect objects using deep learning,参数 输入栅格为要检测的影像 模型定位为上一步输出的.emd文件 cpu模式Max Overlap Ratio0.4 运行时间…

C语言----单链表的实现

前面向大家介绍了顺序表以及它的实现&#xff0c;今天我们再来向大家介绍链表中的单链表。 1.链表的概念和结构 1.1 链表的概念 链表是一种在物理结构上非连续&#xff0c;非顺序的一种存储结构。链表中的数据的逻辑结构是由链表中的指针链接起来的。 1.2 链表的结构 链表…

ROM修改进阶教程------如何去除安卓机型系统的开机向导 几种操作步骤解析

在和很多工作室定制化系统中。手机在第一次启动的时候系统都会进入设置向导,虽然可以设置手机的基本配置。但有很多客户需要去除手机的开机向导来缩短开机时间。确保手机直接进入工作状态。那么今天的教程针去除对开机向导的几种方法做个解析。机型很多版本不同。操作也有不同…

配置jupyter的启动路径

jupyter的安装参考&#xff1a;python环境安装jupyter-CSDN博客 1&#xff0c;背景 继上一篇python环境安装jupyter&#xff0c;里面有一个问题&#xff0c;就是启动jupyter&#xff08;命令jupyter notebook&#xff09;之后&#xff0c;页面默认显示的是启动时候的路径。 …

AI 边缘计算平台 - 嘉楠堪智 CanMV K230 开箱

CanMV-K230 开发板采用的是嘉楠科技 Kendryte 系列 AIoT 芯片中的最新一代 SoC 芯片 K230。该芯片采用全新的多异构单元加速计算架构&#xff0c;最新高性能 RISC-V CPU 内置双核玄铁 C908 CPU, 主频高达 1.6GHz&#xff1b;是全球首款支持 RISC-V Vector 1.0 标准的商用 SoC&a…

python中如何用matplotlib写饼图

#代码 import matplotlib.pyplot as plt# 设置绘图的主题风格 plt.style.use(ggplot) # 中文乱码和坐标轴负号的处理 plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]False plt.rcParams[figure.figsize][10,8] # 构造数据 x [0.2515,0.3724,0.3336…

靠这套 Pytest 接口自动化测试框架,击败了99%的人

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Pytest 的下载安装 1、Python3 使用 pip install -U pytest 安装 2、查看 pytest 版本信息 py…

七天速记前端八股文(重点)

for in和正常for循环的区别 在遍历数组时&#xff0c;正常的 for 循环通常比 for...in 循环更适合。虽然 for...in 循环可以用于遍历数组&#xff0c;但它有一些潜在的问题和限制。 下面是一些使用 for 循环相对于 for...in 循环的优势&#xff1a; 顺序遍历&#xff1a;for…

【nodejs状态库mobx之computed规则】

The above example nicely demonstrates the benefits of a computed value, it acts as a caching point. Even though we change the amount, and this will trigger the total to recompute, it won’t trigger the autorun, as total will detect its output hasn’t been …

泛微E9开发 如何自定义流程标题

1、功能背景 主表中有“选择类别”下拉框字段&#xff0c;用户可以根据需求来选择申请类别&#xff0c;一般多个相似流程的申请可以合并成一个&#xff0c;但是为了区分&#xff0c;我们可以通过将标题修改的方式来使整个显示页面更明确。 2、展示效果 3、实现方法 注意&…

吴恩达深度学习笔记:深度学习的 实践层面 (Practical aspects of Deep Learning)1.6-1.8

目录 第一门课&#xff1a;第二门课 改善深层神经网络&#xff1a;超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第一周&#xff1a;深度学习的 实践层面 (Practical aspects of Deep Learning)…

knife4j 空指针异常

knife4j 空指针异常 一开始正常访问&#xff0c;但是改着改着&#xff0c;就无法访问了&#xff0c;百度了一圈没找到原因&#xff0c;最后对比了之前版本的区别发现这里有问题。最后把这个注解去掉就好了。 只是我本人遇到的问题是这样的&#xff0c;仅供参考

C++对象的初始化和处理

生活中我们买的电子产品都基本会有出厂设置!在某一天我们不用时候也会删除一些自己信息数据保证安全。 C中的面向对象来源于生活&#xff0c;每个对象也都会有初始设置以及对象销毁前的清理数据的设置。 构造函数和析构函数 对象的初始化和清理也是两个非常重要的安全问题 一…

Android Studio布局

文章目录 LinearLayout线性布局排列方向排列位置行列权重 LinearLayout线性布局 从行开始&#xff0c;顶格 排列方向 android:orientation“horizontal”android:orientation“vertical”排列位置 注意layout_width和layout_height的值是match_parent还是wrap_content&…

03_Scala变量和数据类型

文章目录 [toc] **变量和数据类型****1.注释****2.变量和常量****3. 标识符的命名规范****4.scala的字符串****5.键盘输入****5.1 StdIn.readLine()****5.2 从文件中读取数据****5.3 Scala向外写数据** 变量和数据类型 1.注释 和Java完全一样 ** ** 2.变量和常量 var name…

Shell脚本学习记录

0.理解Linux文件权限 0.1 Linux安全性 用户的权限是通过创建用户时分配的用户ID(UID)来追踪的&#xff0c;UID是个数值&#xff0c;每个用户都有一个唯一的UID 0.1.1 /etc/passwd文件 Linux系统使用一个专门的文件/etc/passwd来匹配登录名与对应的UID值&#xff0c;该文件包…

力扣37题:回溯算法之解数独

编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff09; 数独部分空…