ARS_408毫米波雷达数据解析学习记录-SocketCan部分

因毫米波雷达通过CAN口进行数据传输,为便于在LInux系统下进行毫米波雷达数据的获取与解析,简单收集整理了一些CAN总线及CAN接口应用开发的资料。本文主要是记录学习利用SocketCAN接口进行CAN总线数据的收发。仍在学习过程中,难免存在理解误区,欢迎批评指正,共同进步。

1、基础:CAN总线简介
  • CAN 是控制器局域网络(Controller Area Network,CAN)的简称,由德国BOSCH公司开发,并最终成为国际标准(ISO 11898-1)。CAN总线主要应用于工业控制和汽车电子领域,是国际上应用最广泛的现场总线之一。

  • CAN 总线是一种串行通信协议,能有效地支持具有很高安全等级的分布实时控制。

  • CAN 总线规范从最初的CAN 1.2 规范(标准格式)发展为兼容CAN 1.2 规范的CAN 2.0 规范(CAN 2.0A为标准格式,CAN 2.0B为扩展格式),目前应用的CAN器件大多符合CAN 2.0规范

  • 当CAN 总线上的节点发送数据时,以报文形式广播给网络中的所有节点,总线上的所有节点都不使用节点地址等系统配置信息,只根据每组报文开头的11位标识符(CAN 2.0A规范)解释数据的含义来决定是否接收。这种数据收发方式称为面向内容的编址方案

2、Linux系统中CAN总线接口配置
  • Linux系统中CAN总线接口设备作为网络设备被系统进行统一管理。在控制台下, CAN总线的配置和以太网的配置使用相同的命令。

  • 可以在终端输入如下命令查看CAN接口信息

    ifconfig -a
    

img

图片来源,其中,eth0设备是以太网接口,can0和can1设备是两个CAN总线接口。

  • 使用 ip 命令来配置CAN总线的波特率:(下列命令均以can0为例)
ip link set can0 up type can bitrate 1000000	//设置can0的波特率为1Mbps
  • 显示can0设备的详细信息:
ip -details link show can0
  • 设置完成后,可以用下列命令来使用或取消can0设备:
ifconfig can0 up		//使用can0设备
ifconfig can0 down		//取消使用can0设备
  • 其它一些Linux下调试can0设备常用命令
ip link set can0 down	//关闭can0网络
ip link set can0 up		//打开can0网络
candump can0			//接收can0数据
canconfig can0 bitrate + 波特率
canconfig	can0 start  //启动can0设备
canconfig can0 ctrlmode loopback on	//回环测试
canconfig can0 restart	//重启can0设备
canconfig can0 stop		//停止can0设备
canecho can0			//查看can0设备总线状态
cansend can0 --identifer=ID+数据	//发送数据;
candump can0 --filter=ID:mask	//使用滤波器接受ID匹配的数据
3、Linux系统中CAN接口应用开发

由于系统将CAN设备作为网络设备进行管理,因此在CAN总线应用开发方面,Linux提供了SocketCAN接口,使得CAN总线通信近似于和以太网的通信。

此外,通过https://gitorious.org/linux-can/can-utils 网站发布的基于SocketCAN的can-utils工具套件,也可以实现CAN总线通信。

  • SocketCAN 中大部分的数据结构和函数在头文件 linux/can.h 中进行了定义。 CAN 总线套接字的创建采用标准的网络套接字操作来完成。网络套接字在头文件 sys/socket.h 中定义。套接字的初始化方法如下:
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); //创建 SocketCAN 套接字()
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr); //指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定

其中: ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。ifreq包含在ifconf结构中。而 ifconf结构通常是用来保存所有接口的信息的。

  • 在数据收发的内容方面,CAN 总线与标准套接字通信稍有不同,每一次通信都采用 can_ frame 结构体将数据封装成帧。 结构体定义如下:
struct can_frame{
    canid_t can_id;	//CAN标识符
    __u8 can_dlc;	//数据场的长度
    __u8 data[8];	//数据
}
  • 数据发送使用 write 函数来实现。
struct can_frame frame;
frame.can_id = 0x123; 
frame.can_dlc = 1; //数据长度为 1
frame.data[0] = 0xAB; //数据内容为 0xAB
int nbytes = write(s, &frame, sizeof(frame)); //发送数据
if(nbytes != sizeof(frame)) //如果 nbytes 不等于帧长度,就说明发送失败
  • 数据接收使用 read 函数来完成:
struct can_frame frame;
int nbytes = read(s, &frame, sizeof(frame));
4、自定义用于CAN口数据收发的SocketCAN类接口
  • 引入需要用到的头文件
#include <cstdint>

#include <errno.h>			//错误号头文件,包含系统中各种出错号。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>			//Linux 标准头文件,定义了各种符号常数和类型
#include <string.h>		

#include <net/if.h>			//用来配置和获取ip地址,掩码,MTU等接口信息的
#include <sys/types.h>		//类型头文件,定义了基本的系统数据类型。
#include <sys/socket.h>		//定义socket的头文件
#include <sys/ioctl.h>		//包含设备I/O通道管理的函数

#include <linux/can.h>		//包含了SocketCAN中大部分的数据结构和函数
#include <linux/can/raw.h>	
  • 声明命名空间及类的属性和成员函数
namespace socket_can{
class SocketCAN{
 public:
  SocketCAN(const char * ifname);	
  SocketCAN(const char * ifname, long timeout);
  ~SocketCAN();
  bool is_connected();		//用于确定是否建立连接
  bool write(uint32_t frame_id, uint8_t dlc, uint8_t * data);	//写入数据
  bool read(uint32_t * can_id, uint8_t * dlc, uint8_t * data);	//获取数据
 private:
  void init();
  const char * ifname_;		//用于指定网络设备的名称
  int socket_;				//用于接收创建的socket返回的描述符
  bool connected_;
  long timeout_;
};
}
  • 类的实现
#include <socket_can/socket_can.hpp>
namespace socket_can{
SocketCAN::SocketCAN(const char * ifname) :
  ifname_(ifname), 		//指定网络设备的名称
  connected_(false),	//创建socket默认设置为未连接状态
  timeout_(1000l)		//设置默认的套接字超时时间
{		
  init();				//默认调用SocketCAN类的初始化函数
}
SocketCAN::SocketCAN(const char * ifname, long timeout) :
  ifname_(ifname), 
  connected_(false),
  timeout_(timeout)		//从外部获取套接字超时时间
{
  init();
}
SocketCAN::~SocketCAN(){
  if (close(socket_) < 0) {
    perror("Closing: ");
    printf("Error: %d", errno);		//如果未成功关闭socket描述符对应的操作空间,则输出错误信息
  }
}
void SocketCAN::init(){
  if ((socket_ = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {  //其中:PF_CAN为所用的协议族;SOCK_RAW为所用的套接字类,这里采用的是原始套接字;CAN_RAW是指原始CAN协议
    perror("Error while opening socket");	//创建socket套接字,并对返回的描述符进行判断,失败(<0)则输出错误信息并返回
    return;
  }
  struct ifreq ifr{};		//定义ifeq结构体,用于配置和获取接口信息
  strcpy(ifr.ifr_name, ifname_);   //将从外部获取网络设备名称拷贝给ifr.ifr_name
  ioctl(socket_, SIOCGIFINDEX, &ifr);	//获取网络设备接口地址
    
  struct sockaddr_can addr{};	//通用地址结构
  addr.can_family = AF_CAN;		
  addr.can_ifindex = ifr.ifr_ifindex;	//设置CAN协议
  printf("%s at index %d\n", ifname_, ifr.ifr_ifindex);
  if (bind(socket_, (struct sockaddr *) &addr, sizeof(addr)) < 0) {	//将刚生成的套接字与网络地址进行绑定,并对bind()的返回值进行判断,失败则输出错误信息并返回
    perror("Error in socket bind");
    return;
  }

  int error = 0;	//用于保存错误代码
  socklen_t len = sizeof (error);	
  int retval = getsockopt (socket_, SOL_SOCKET, SO_ERROR, &error, &len);  //获取socket_的状态
  if (retval != 0) {
    /* there was a problem getting the error code */
    printf("Error getting socket error code: %s\n", strerror(retval));
    return;
  }
  if (error != 0) {
    /* socket has a non zero error status */
    printf("Socket error: %s\n", strerror(error));		//将error中保存的错误代码输出
    return;
  }
  struct timeval timeout{};		//设置超时时间
  timeout.tv_sec = (timeout_ / 1000);
  timeout.tv_usec = (timeout_ % 1000) * 1000;
  if (setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (char *) & timeout, sizeof(timeout)) < 0) {			//SO_RCVTIMEO参数表示设置socket接收超时时间
    perror("Setting timeout failed");
  }
  connected_ = true;
}
bool SocketCAN::is_connected(){		//获取套接字连接状态
  return connected_;
}
bool SocketCAN::write(uint32_t can_id, uint8_t dlc, uint8_t * data){	//数据写入函数
  struct can_frame frame{};
  frame.can_id = can_id;
  frame.can_dlc = dlc;
  memcpy(frame.data, data, dlc * sizeof(uint8_t));	//将要写入的数据保存到can_frame结构体中
  auto num_bytes = ::write(socket_, &frame, sizeof(struct can_frame));	//获取写入的字节数
  return num_bytes > 0;
}
bool SocketCAN::read(uint32_t * can_id, uint8_t * dlc, uint8_t * data){
  struct can_frame frame{};
  auto num_bytes = ::read(socket_, &frame, sizeof(struct can_frame));	//获取读到的字节数
  if (num_bytes != sizeof(struct can_frame)) {		
    return false;		如果返回的bytes不等于帧长度,则读取失败,并返回 false
  }
  (* can_id) = frame.can_id;
  (* dlc) = frame.can_dlc;
  memcpy(data, frame.data, sizeof(frame.data));		//将读取的数据保存到传出参数中
  return true;		
}
    
}

- SocketCAN类的使用

```cpp
//创建类对象及其初始化
socket_can::SocketCAN can_("can0");		
//类的构造函数中已经完成了socket的创建以及与本地网络地址绑定的工作,若无异常则说明连接建立成功
/*数据获取
bool SocketCAN::read(uint32_t * can_id, uint8_t * dlc, uint8_t * data)
自定义数据读取函数的参数均为指针类型,因此在调用前需要创建对应类型的变量
为确保数据读取成功,还需要对read()函数的返回值进行判断
*/
uint32_t frame_id;
uint8_t dlc;
uint8_t data[8] = {0};
bool read_status = can_.read(&frame_id, &dlc, data);
if (!read_status) {
  return false;
}
/*数据写入
bool SocketCAN::write(uint32_t can_id, uint8_t dlc, uint8_t * data)
*/
uint8_t raw_data[8]
can_.write(frame_id, 8, raw_data)
参考:
  • 20
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 24
    评论
ARS408毫米波雷达有提供ROS驱动。您可以通过私信获取更多相关信息,并扫描二维码了解详情。\[1\]此外,ARS408是大陆40X毫米波雷达传感器系列中的入门产品,适用于不同的应用场景,具有低成本、坚固耐用、高性能和操作可靠安全性高等特点,测试距离可达170米。\[2\]在安装环境方面,需要使用ROS Melodic版本和peak-linux-driver-8.12.0驱动程序,以及Ubuntu 18.04操作系统。\[2\]通过/ars_40X_ros监听的rosservice,可以对毫米波雷达进行配置,如设置雷达ID、设置最大探测距离等,设置后将转换为总线报文通过ROS topic发出。同时,/ars_40X_rviz可以将障碍物进行可视化,实时显示障碍物的位置、速度、类型等信息,方便与激光雷达、摄像头等数据进行融合观察。\[3\] #### 引用[.reference_title] - *1* *3* [ARS_408毫米波雷达ROS驱动](https://blog.csdn.net/zbzb00zbzb/article/details/119421853)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [ROS中进行大陆ARS408雷达点云的可视化及二次开发(一)](https://blog.csdn.net/weixin_43253464/article/details/121208924)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值