(六)CAN总线通讯

文章目录

  • CAN总线回环测试
    • 第一种基于板载CAN测试
      • 第一步确认板载是否支持
      • 第二步关闭 CAN 接口将 CAN 接口置于非活动状态
      • 第三步 配置 CAN 接口
        • 第一步 设置 CAN 接口比特率
        • 第二步 设置 CAN 启用回环模式
        • 第三步 启用 CAN 接口
      • 第四步 测试CAN总线回环
        • 捕获 CAN 消息
        • 发送 CAN 消息
    • 第二种基于Linux库的原生开发
      • 1. 确认硬件支持
      • 2. 配置 CAN 接口
      • 3. 使用 SocketCAN API 编写应用程序
        • 创建 CAN 套接字
        • 绑定 CAN 接口
        • 发送和接收 CAN 消息
        • 示例代码
      • 4. 应用程序测试


CAN总线回环测试

第一种基于板载CAN测试

第一步确认板载是否支持

确保你的硬件支持 CAN,并且已经正确安装了驱动程序。可以通过以下命令检查是否检测到了 CAN 设备:

ip link show type can

可以看到板载自带了can0和can1.

请添加图片描述

can0 和 can1:是接口的名称,分别表示系统中的第一个和第二个 CAN 接口。

<NOARP,ECHO>:
NOARP:表示该接口不使用 ARP(地址解析协议)。CAN 总线不需要 ARP 因为它不是基于 IP 的网络。
ECHO:可能启用了回环模式,发送的消息会被自己的 CAN 控制器重新接收。这里不确定是否启用了,可以用指令ip -details link show 查看到:

请添加图片描述

或者单看can0,can1的状态,以can0为例子:

ip -details link show can0

请添加图片描述

mtu 16:最大传输单元(Maximum Transmission Unit),对于 CAN 接口来说MTU 通常是 16 字节,这是因为 CAN 消息的最大有效载荷是 8 字节,加上一些额外的头部信息。

qdisc noop:队列调度算法(Queueing Discipline),这里使用的是 noop,即空操作队列调度器。这意味着没有任何包调度策略被应用,所有数据包将直接传递而不会排队。

state DOWN:当前接口状态为关闭(DOWN),意味着接口未激活,无法进行通信。要激活接口,可以使用 ip link set can0 up 或 ip link set can1 up。

mode DEFAULT:表示接口的工作模式,默认情况下是标准模式。

group default:指明这个接口属于哪个组,default 是默认组。

qlen 10:队列长度(Queue Length),表示可以排队等待处理的数据包数量,在这里是 10。

链路类型
link/can:表明这是一个 CAN 类型的接口。


第二步关闭 CAN 接口将 CAN 接口置于非活动状态

如果没有激活的情况,直接跳过此步

ip link set can0 down

请添加图片描述


第三步 配置 CAN 接口

第一步 设置 CAN 接口比特率
ip link set can0 type can bitrate 500000

ip link set:这是一个用来配置网络接口的命令。
can0:指定要配置的网络接口名称,这里是 can0。
type can:指明该接口是 CAN 类型的。
bitrate 500000:设置 CAN 总线的数据传输速率(比特率)为 500 kbps。这个值应该根据你的硬件支持和需求进行调整。

设置完可以借助指令查看:

ip -details link show can0

请添加图片描述


第二步 设置 CAN 启用回环模式

指令:

ip link set can0 type can loopback on

loopback on:启用回环模式。在这种模式下,所有从 CAN 控制器发出的消息都会被重新送回到同一个控制器,而不会真正发送到物理总线上。这对于测试非常有用,因为它允许你在不连接任何其他设备的情况下验证软件是否正常工作。

请添加图片描述

设置完同样可以借助指令查看:

ip -details link show can0

请添加图片描述


第三步 启用 CAN 接口

指令:

ip link set can0 up

请添加图片描述

启动成功后同样可以借助指令查看:

ip -details link show can0

请添加图片描述


第四步 测试CAN总线回环

捕获 CAN 消息

使用 nohup 和 candump 捕获消息并记录到文件

nohup candump can0 > log.txt &

nohup:使程序在用户退出终端后继续运行。这对于长时间运行的任务很有用。
candump:这是 Linux 下的一个工具,用于捕获并显示 CAN 消息。它会实时监听指定的 CAN 接口,并输出接收到的消息。
can0:指定要监听的 CAN 接口。
log.txt:将 candump 的输出重定向到一个名为 log.txt 的文件中,而不是打印到屏幕上。
&:将命令放入后台执行,这样可以在同一终端窗口中继续输入其他命令。

请添加图片描述

发送 CAN 消息

标准数据帧发8位数据:

cansend can0 0B000123#00.00.00.00.00.00.00.01

#后面每一位数据不加.也行,注意别发错了

cansend:这是 Linux 下的一个工具,用于向指定的 CAN 接口发送 CAN 消息。
can0:指定要使用的 CAN 接口。

0B000123#00.00.00.00.00.00.00.01:这是要发送的 CAN 消息格式。
具体来说:0B000123是 CAN ID,其中 0B 表示标准帧格式(11位ID),000123是具体的 ID 值。
#分隔符,后面跟着的是数据字段。00.00.00.00.00.00.00.01 是数据字段的内容,表示 8 字节的数据。每个字节用两位十六进制数表示。

请添加图片描述

其实都用默认,就是标准数据帧:

cansend can0 123#00.00.00.00.00.00.00.02

请添加图片描述

数据也可以发0-8任意字节:

cansend can0 234#88

请添加图片描述

第二种基于Linux库的原生开发

在 Linux 下进行 CAN 总线应用开发时,通常需要通过系统调用和特定的套接字 API 来与 CAN 接口交互。Linux 内核提供了一个叫做 SocketCAN 的子系统,它使得 CAN 通信可以像普通的网络编程一样使用标准的 BSD 套接字接口来实现。下面是进行 CAN 应用开发的基本步骤:

1. 确认硬件支持

ip link show type can

加粗样式
可以看到板载自带了can0和can1.

请添加图片描述


2. 配置 CAN 接口

先关闭can接口

ip link set can0 down

配置波特率:

ip link set can0 type can bitrate 500000

如果使用回环如下操作,不使用或者使用正常模式就跳过此步,我这里采用回环测试因此执行此步骤:

ip link set can0 type can loopback on

启动can接口

ip link set can0 up

至此所有配置完成


3. 使用 SocketCAN API 编写应用程序

创建 CAN 套接字

在 C/C++ 中,可以通过 socket() 函数创建一个 CAN 套接字。例如:

#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>

int s;
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
    perror("Socket creation failed");
    return -1;
}

绑定 CAN 接口

接下来,将套接字绑定到具体的 CAN 接口(如 can0)。这可以通过 bind() 函数完成:

struct ifreq ifr;
struct sockaddr_can addr;

addr.can_family = AF_CAN;
strcpy(ifr.ifr_name, "can0");

if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
    perror("Interface index get failed");
    close(s);
    return -1;
}

addr.can_ifindex = ifr.ifr_ifindex;

if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    perror("Bind to CAN interface failed");
    close(s);
    return -1;
}

发送和接收 CAN 消息
struct can_frame frame;

// 构造要发送的 CAN 帧
frame.can_id = 0x123; // CAN ID
frame.can_dlc = 8;    // 数据长度码(DLC),表示数据域的字节数
memset(frame.data, 0x01, frame.can_dlc); // 设置数据域内容

// 发送 CAN 帧
if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
    perror("Write CAN frame failed");
    close(s);
    return -1;
}

// 接收 CAN 帧
if (read(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
    perror("Read CAN frame failed");
    close(s);
    return -1;
}

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

示例代码

下面是一段完整的示例代码,展示了如何创建 CAN 套接字、绑定接口、发送和接收消息:

这里我开启了线程,父线程用来写,子线程用来读;代码被屏蔽掉的那部分是,禁止回环模式的,为了防止配置的时候关闭回环失败,因此软件上再关闭一次。

#include "can_config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <libgen.h>
#include <getopt.h>
#include <limits.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <net/if.h>

#include <linux/can.h>
#include <linux/can/raw.h>

int main()
{
	int s; // 就是fd
	int n_read = 0; // 读取到的数据个数
	//创建 CAN 套接字
	if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
		perror("Socket creation failed");
		return -1;
	}
	printf("socket ok ========================\n");

	/*
	// 禁用回环模式(如果需要)
    int loopback = 0; // 0 表示关闭回环模式
    if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)) < 0) {
        perror("Failed to disable loopback mode");
        close(s);
        return -1;
    }

    // 禁用监听自己的消息(可选)
    int recv_own_msgs = 0; // 0 表示关闭接收自己的消息
    if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &recv_own_msgs, sizeof(recv_own_msgs)) < 0) {
        perror("Failed to disable receiving own messages");
        close(s);
        return -1;
    }
	*/

	// 绑定 CAN 接口
	struct ifreq ifr;
	struct sockaddr_can addr;

	addr.can_family = AF_CAN;
	strcpy(ifr.ifr_name, "can0");
	if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
		perror("Interface index get failed");
		close(s);
		return -1;
	}
	addr.can_ifindex = ifr.ifr_ifindex;
	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		perror("Bind to CAN interface failed");
		close(s);
		return -1;
	}
	printf("bind ok ========================\n");
	// 构造要发送的 CAN 帧
	struct can_frame wirte_frame;
	struct can_frame recive_frame;
	wirte_frame.can_id = 0x123; // CAN ID
	wirte_frame.can_dlc = 8;    // 数据长度码(DLC),表示数据域的字节数
	memset(wirte_frame.data, 0x01, wirte_frame.can_dlc); // 设置数据域内容
	
	int data = 0x01;
	//开启线程 一个接收一个发送;
	__pid_t pid;
	pid = fork();//返回的pid号 父进程是正数id号码,子进程是0
	// 父进程 write  子进程接收
	if(pid>0){
		printf("in farther ok ========================\n");
		while(1){
			// 发送 CAN 帧  间隔3秒写一次
			if (write(s, &wirte_frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
				perror("Write CAN frame failed");
				close(s);
				return -1;
			}
			sleep(3);
			memset(wirte_frame.data,++data, wirte_frame.can_dlc); // 设置数据域内容
		}	
	}else if(pid==0){
		printf("in son ok ========================\n");
		while(1){
			// 接收 CAN 帧
			if ((n_read = read(s, &recive_frame, sizeof(struct can_frame)) )!= sizeof(struct can_frame)) {
				perror("Read CAN frame failed");
				close(s);
				return -1;
			}

			if(n_read>0){
				printf("Received CAN ID=%x, DLC=%d\n", recive_frame.can_id, recive_frame.can_dlc);
				for (int i = 0; i < recive_frame.can_dlc; ++i)
					printf("Data[%d]: %02x\n", i, recive_frame.data[i]);

			}
		}
	}else{
		perror("fork error\n");
	}
	close(s);

	return 0 ;
}

4. 应用程序测试

先用交叉编译工具编译:

arm-linux-gnueabi-gcc mycan.c -o mycan

请添加图片描述


远程发送到板子:

scp ./mycan root@192.168.1.101:/root/zhua

请添加图片描述

执行:

请添加图片描述

请添加图片描述

请添加图片描述

适当改一改代码:

请添加图片描述

再测试:

请添加图片描述

这里有个小问题就是write一直写,FIFO文件队列会满,然后就是溢出报错,因此要学习错误处理等操作。

所以最后关于帧格式,位同步,仲裁,错误处理等可以深入了解。推荐在学习stm32的时候学习寄存器配置开发,可以深入的了解到。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

任务调度之Quartz(二):Quartz体系结构

1、Quartz 体系结构 由上一篇的Quartz基本使用可以发现&#xff0c;Quartz 主要包含一下几种角色&#xff1a; 1&#xff09;Job&#xff1a;也可以认为是JobDtetail&#xff0c;表示具体的调度任务 2&#xff09;Trigger&#xff1a;触发器&#xff0c;用于定义任务Job出发执行…

基于Springboot + vue实现的小型养老院管理系统

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论 &#x1f525;&#x1f525;&…

初学Linux电源管理

学习文档出处&#xff1a; 万字整理 | 深入理解Linux电源管理&#xff1a;万字整理 | 深入理解Linux电源管理-CSDN博客 电源管理 因为设备需要用电&#xff0c;而且设备中的各个硬件所需要的电压是不一样的。故计算机需要对硬件的电源状态管理。但是电能并不是免费的&#x…

React(二)——Admin主页/Orders页面/Category页面

文章目录 项目地址一、侧边栏1.1 具体实现 二、Header2.1 实现 三、Orders页面3.1 分页和搜索3.2 点击箭头显示商家所有订单3.3 页码按钮以及分页 四、Category页面4.1 左侧商品添加栏目4.2 右侧商品上传栏 五、Sellers页面六、Payment Request 页面&#xff08;百万数据加载&a…

刚体变换矩阵的逆

刚体运动中的变换矩阵为&#xff1a; 求得变换矩阵的逆矩阵为&#xff1a; opencv应用 cv::Mat R; cv::Mat t;R.t(), -R.t()*t

IDEA中Maven依赖包导入失败报红的潜在原因

在上网试了别人的八个问题总结之后依然没有解决&#xff1a; IDEA中Maven依赖包导入失败报红问题总结最有效8种解决方案_idea导入依赖还是报红-CSDN博客https://blog.csdn.net/qq_43705131/article/details/106165960 江郎才尽之后突然想到一个原因&#xff1a;<dep…

UVM:uvm_component methods configure

topic UVM component base class uvm_config_db 建议使用uvm_config_db代替uvm_resource_db uvm factory sv interface 建议&#xff1a;uvm_config_db 以下了解 建议打印error

基于时间维度水平拆分的多 TiDB 集群统一数据路由/联邦查询技术的实践

导读 在大数据时代&#xff0c;金融行业面临着日益增长的数据量和复杂的查询需求&#xff0c;尤其是跨库、跨集群的场景。在这种背景下&#xff0c;如何在保证数据一致性、高可用性的同时&#xff0c;实现业务的快速扩展与高效查询&#xff0c;成为了企业数字化转型的关键挑战…

概率论 期末 笔记

第一章 随机事件及其概率 利用“四大公式”求事件概率 加法公式 减法 条件概率公式 全概率公式与贝叶斯公式 伯努利概型求概率 习题 推导 一维随机变量及其分布 离散型随机变量&#xff08;R.V&#xff09;求分布律 利用常见离散型分布求概率 连续型R.V相关计算 利用常见连续…

把vue项目或者vue组件发布成npm包或者打包成lib库文件本地使用

将vue项目发布成npm库文件&#xff0c;第三方通过npm依赖安装使用&#xff1b;使用最近公司接了一个项目&#xff0c;这个项目需要集成到第三方页面&#xff0c;在第三方页面点击项目名称&#xff0c;页面变成我们的项目页面&#xff1b;要求以npm库文件提供给他们&#xff1b;…

《空舞的巨兽》官方学习版

一个以被遗忘之地为背景的原创故事&#xff0c;这是一个充满悲剧的没落王国。扮演外地战士雷恩猎人&#xff08;玩家&#xff09;&#xff0c;踏上危险的任务&#xff0c;结束困扰你自己和村庄的诅咒。你唯一的希望就是杀死不可杀死的可怕巨兽。 《空舞的巨兽》官方版 https:/…

go-zero框架快速入门

文章目录 go-zero 简介安装goctl安装go-zero启动go-zero API语言定义结构体API定义路由API格式化对齐 生成代码生成基本逻辑代码生成数据库model文件 go-zero 简介 go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性&#xff0c;经…

切忌 SELECT *,就算表只有一列

原文地址 尽量避免 SELECT *&#xff0c;即使在单列表上也是如此 – 如果你现在不同意这一点&#xff0c;读完这篇文章&#xff0c;你可能就要动摇了。 2012年的一个故事 这是我 12 年前&#xff08;约 2012-2013 年&#xff09;在客户后台应用程序中遇到的一个真实故事。 当…

了解RabbitMQ中的Exchange:深入解析与实践应用

在分布式系统设计中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色&#xff0c;而RabbitMQ作为开源消息代理软件的佼佼者&#xff0c;以其高性能、高可用性和丰富的功能特性&#xff0c;成为了众多开发者的首选。在RabbitMQ的核心组件中&…

【linux系统之redis6】redis的基础命令使用及springboot连接redis

redis的基础命令很多&#xff0c;大部分我们都可以在官网上找到&#xff0c;真的用的时候可以去官网找&#xff0c;不用全部记住这些命令 redis通用的基础命令的使用 代码测试 string类型常见的命令 key值的结构&#xff0c;可以区分不同的需求不同的业务名字 hash类型 创建…

基于FPGA的交通信号灯实现 (verilog极简实现)

本文分享利用FPGA实现的交通信号灯&#xff0c;FPGA型号为野火征途Pro开发板&#xff0c;具体功能如下&#xff1a; 此项目旨在模拟东西和南北两路口交通信号灯&#xff0c;初始态两路口均为红灯亮&#xff0c;接着&#xff0c;东西路口绿灯亮&#xff0c;南北路口红灯亮&…

在K8S上部署OceanBase的最佳实践

在K8S上部署OceanBase的最佳实践 目录 1. 背景与选型 1.1 为什么选择OB1.2 为什么选择ob-operator实现OB on K8S 2. 部署实操 2.1 环境准备2.2 安装 ob-operator2.3 配置 OB 集群2.4 配置 OBProxy 集群2.5 Headless Service 和 CoreDNS 配置2.6 监控与运维 2.6.1 Promethues部…

unity开发之shader 管道介质流动特效

效果 shader graph 如果出现下面的效果&#xff0c;那是因为你模型的问题&#xff0c;建模做贴图的时候没有设置好UV映射&#xff0c;只需重新设置下映射即可

【JavaWeb】2. 通用基础代码

以下内容来源&#xff1a;编程导航。 无论在任何后端项目中&#xff0c;都可以复用的代码。 1、自定义异常 自定义错误码&#xff0c;对错误进行收敛&#xff0c;便于前端统一处理。 &#x1f4a1; 这里有 2 个小技巧&#xff1a; 自定义错误码时&#xff0c;建议跟主流的错…