Nvidia Jetson AGX Orin使用CAN与底盘通信(ROS C++ 驱动)

文章目录

  • 一、Nvidia Jetson AGX Orin使用CAN通信
    • 1.1 CAN使能配置修改GPIO口功能
    • 1.2 can收发测试
  • 二、通过CAN协议编写CAN的SocketCan ROS1驱动程序
    • 2.1 通讯协议
    • 2.2 接收数据节点
    • 2.3 发送数据节点
    • 2.4 功能包配置
  • 三、ROS2驱动程序


一、Nvidia Jetson AGX Orin使用CAN通信

参考:https://blog.csdn.net/vonct/article/details/129055892,使用杜邦线将Nvidia Jetson AGX Orin的CAN口(底盘CANL和CANH连接对应Orin的CANL和CANH,具体底盘CANL和CANH接口可能要看Nvidia Jetson AGX Orin说明书)接出来。

1.1 CAN使能配置修改GPIO口功能

由于默认的CAN引脚不是配置为CAN,因此需要修改4个寄存器的值。具体可以从文档看到:
在这里插入图片描述
以Orin为例,图中Addr就是寄存器地址,value就是需要写入的值
(1)使用busybox修改寄存器的值

sudo apt-get install busybox
sudo busybox devmem 0x0c303018 w 0xc458
sudo busybox devmem 0x0c303010 w 0xc400
sudo busybox devmem 0x0c303008 w 0xc458
sudo busybox devmem 0x0c303000 w 0xc400

(2)挂载CAN内核

sudo modprobe can
sudo modprobe can_raw
sudo modprobe mttcan

(3)CAN属性设置
例如将CAN0波特率设置成500k,注意,配置前需要先关闭can

sudo ip link set down can0
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0

将上诉代码写进shell文件,每次使用之前要运行:

sudo busybox devmem 0x0c303018 w 0xc458
sudo busybox devmem 0x0c303010 w 0xc400
sudo busybox devmem 0x0c303008 w 0xc458
sudo busybox devmem 0x0c303000 w 0xc400
sudo modprobe can
sudo modprobe can_raw
sudo modprobe mttcan
sudo ip link set down can0
sudo ip link set can0 type can bitrate 500000
sudo ip link set up can0

1.2 can收发测试

用can-utils进行简单的can收发测试,后续将用SocketCan结合ROS。

sudo apt-get install can-utils
cansend can0 0E2#05.00.00.00.00.00.00.00  #cansend can0/1 [can_id]#[八字节数据]
cangen -v can0  #随机发送
candump can0 #接受can帧

在这里插入图片描述
在这里插入图片描述

二、通过CAN协议编写CAN的SocketCan ROS1驱动程序

2.1 通讯协议

(1)小车反馈的整车数据
在这里插入图片描述
(2)线控指令
在这里插入图片描述
在这里插入图片描述
不同于UDP,类似于串口通信,可以参考我串口通信的文章
代码写好之后遇到的问题:
(1)接收数据的时候反馈慢
解决:去掉代码中的 spin频率控制;
(2)速度控制时无法换档
解决:每次发速度之前,发送速度指令清零;
(3)节点关闭时速度仍在
解决:在析构函数中发送退出线控指令;
(4)将Autoware参数(TwistStamped)写进Launch方便调参
优化并调试后的代码如下(其中协议中写的角度和速度指定bit与实际值的比例关系似乎不太对,需要实际调试的时候优化):

2.2 接收数据节点

can_receive_node.cpp

#include "can_ros1_driver/can_receive.hpp"

int main(int argc, char **argv)
{
    ros::init(argc, argv, "can_receive_node");
    canControl can_control;

    ros::spin();

    return 0;
}

can_receive.hpp

#ifndef CAN_H_
#define CAN_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>

#include <iostream>
#include <string>
#include <cmath>

#include "ros/ros.h"
#include "std_msgs/String.h"
#include "geometry_msgs/Twist.h"
#include "nav_msgs/Odometry.h"
#include <tf/transform_broadcaster.h>
#include <tf/tf.h>

class canControl
{
public:
    // 0 初始化
    canControl();

    // 1 接收CAN消息
    void receiveCanData();

    // 1.1 解析数据
    std::vector<int16_t> hexToShort(const std::vector<uint8_t> &hex_data);

    // 1.2 推算里程计并发布
    void publishOdometry(double linear_velocity, double angular_velocity, double Steer_Angle);

    // 2 析构函数
    ~canControl();

private:
    ros::NodeHandle nh_;
    // 轴距
    double wheel_base;
    // 套接字参数
    struct ifreq ifr;
    struct sockaddr_can can_addr;
    int sockfd;
    int ret;
    // 接收的数据
    struct can_frame frame;

    // 当前里程计
    double position_x_;
    double position_y_;
    double yaw;

    // 发布速度和里程计
    ros::Publisher pub_;
    ros::Publisher pub_Odom;
    tf::TransformBroadcaster tf_broadcaster_;
};

#endif // CAN_H_

can_receive.cpp

#include "can_ros1_driver/can_receive.hpp"

// 0 初始化
canControl::canControl() : nh_("~")
{
    // 1 初始化ROS节点
    nh_.param<double>("wheel_base", wheel_base, 1.0);
    position_x_ = 0.0;
    position_y_ = 0.0;
    yaw = 0.0;

    /* 打开套接字 */
    sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (0 > sockfd)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 指定can0设备 */
    ifr = {0};
    can_addr = {0};
    frame = {0};
    strcpy(ifr.ifr_name, "can0");
    ioctl(sockfd, SIOCGIFINDEX, &ifr);
    can_addr.can_family = AF_CAN;
    can_addr.can_ifindex = ifr.ifr_ifindex;

    /* 将can0与套接字进行绑定 */
    if (0 > bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr)))
    {
        perror("bind error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 创建用于反馈速度指令的发布者
    pub_ = nh_.advertise<geometry_msgs::Twist>("/feedback_twist", 1000);
    pub_Odom = nh_.advertise<nav_msgs::Odometry>("/panda_odom", 100);

    receiveCanData();
}

// 2 接收CAN串口消息
void canControl::receiveCanData()
{
    while (ros::ok())
    {
        /* 接收数据 */
        if (0 > read(sockfd, &frame, sizeof(struct can_frame)))
        {
            perror("read error");
            break;
        }

        /* 校验是否接收到错误帧 */
        if (frame.can_id & CAN_ERR_FLAG)
        {
            printf("Error frame!\n");
            break;
        }

        /* 校验帧格式 */
        // if (frame.can_id & CAN_EFF_FLAG) // 扩展帧
        //     printf("扩展帧 <0x%08x> ", frame.can_id & CAN_EFF_MASK);
        // else // 标准帧
        //     printf("标准帧 <0x%03x> ", frame.can_id & CAN_SFF_MASK);

        /* 校验帧类型:数据帧还是远程帧 */
        if (frame.can_id & CAN_RTR_FLAG)
        {
            printf("remote request\n");
            continue;
        }

        /* 打印数据长度 */
        // printf("[%d] ", frame.can_dlc);

        /* 打印数据 */
        std::vector<uint8_t> buff;
        for (int i = 0; i < frame.can_dlc; i++)
        {
            // 这里的%02x表示以两位十六进制数的形式输出,并且若不足两位则在前面补0。
            // printf("%02x ", frame.data[i]);
            // 一位一位字节地添加到队列
            buff.push_back(static_cast<int8_t>(frame.data[i]));
        }
        // printf("\n");

        /* 解析数据 */
        if (frame.can_id == 0x116 && frame.can_dlc == 8)
        {
            // printf("扩展帧 <0x%08x> ", frame.can_id & CAN_EFF_MASK);
            // printf("%02x ", frame.can_dlc);
            // printf("\n---------------------\n");
            // printf("Receive状态值为:%02x \n", buff[0]);
            // printf("Receive打角值L为:%02x \n", buff[2]);
            // printf("Receive打角值H为:%02x \n", buff[3]);
            // printf("Receive速度值L为:%02x \n", buff[4]);
            // printf("Receive速度值H为:%02x \n", buff[5]);
            // printf("Receive电量0x为:%02x \n", buff[6]);

            std::vector<int16_t> Velocity_feedback(4, 0);
            Velocity_feedback = hexToShort(buff);

            // 获取完一帧的标志位,可以输出数据
            geometry_msgs::Twist feedback_twist;

            // 1 获取档位
            int gear = static_cast<int>(Velocity_feedback[0]);

            // 3 获取车速
            // TODO  ± 5m/s 0.001(m/s)/bit  有符号 
            double DM_Speed = static_cast<double>(Velocity_feedback[2]) / 185;
            if (gear == 2)
                DM_Speed = -DM_Speed;
            feedback_twist.linear.x = DM_Speed;

            // 2 获取舵机转向角度(打角值)
            // TODO 524-1024-1524   0.1°/bit    0对应最左,1024对应中间角度
            double Steer_Angle = (static_cast<double>(Velocity_feedback[1]) - 1024) / 3.6;
            // 打角值满足:tan(打角值) = 前后轮轴距 / 转弯半径
            // 角速度 = 线速度 / 转弯半径 = 线速度 * tan(打角值) / 前后轮轴距
            double Velocity_Angle = DM_Speed * tan(fabs(M_PI * Steer_Angle / 180)) / wheel_base;
            feedback_twist.angular.z = Velocity_Angle;

            // 4 获取电池电量
            int power = static_cast<double>(Velocity_feedback[3]);
            if (power < 20)
                ROS_WARN("Battery power (%d) is less than 10%!", power);

            // std::cout << "Receive档位值为: " << gear << std::endl;
            // std::cout << "Receive角速度值为: " << Velocity_Angle << std::endl;
            // std::cout << "Receive速度值为: " << DM_Speed << std::endl;
            // std::cout << "Receive电量为: " << power << std::endl;

            // 5 发布速度
            pub_.publish(feedback_twist);

            // 6 发布里程计,左转是rZ正方向,而这个底盘最左是负,因此加-号
            publishOdometry(DM_Speed, Velocity_Angle, -M_PI * Steer_Angle / 180);
        }
    }
}

// 2.1 解析数据
std::vector<int16_t> canControl::hexToShort(const std::vector<uint8_t> &hex_data)
{
    std::vector<int16_t> short_data(4, 0);
    // 1 获取档位
    uint8_t byte = hex_data[0], mask = 0b00001100;
    // 使用位与操作符&来获取第3-4位的值,将结果向右位移3位
    uint8_t gear = (byte & mask) >> 3;
    short_data[0] = static_cast<int16_t>(gear);

    // 2 获取舵机转向角度
    // 将两个连续的字节(低位hex_data[i] 和 高位hex_data[i+1])组合为一个 int16_t 类型的数值:千万注意高低顺序,仔细看通讯协议
    // 高位hex_data[i+1]需要先强制转换为一个有符号的short类型的数据以后再移位
    short Steer_Angle_H = static_cast<int16_t>(hex_data[3]);
    // 左移运算符 << 将 high 的二进制表示向左移动 8 位。这样做是因为 int16_t 类型占用 2 个字节,而我们希望将 high 的数据放置在最高的 8 位上。
    // |: 按位或运算符 | 将经过左移的 high 数据和 hex_data[i] 数据进行按位或操作,将它们组合为一个 16 位的数值
    short_data[1] = static_cast<int16_t>((Steer_Angle_H << 8) | hex_data[2]);

    // 3 获取车速
    short DM_Speed_H = static_cast<int16_t>(hex_data[5]);
    short_data[2] = static_cast<int16_t>((DM_Speed_H << 8) | hex_data[4]);

    // 4 获取电池电量
    short_data[3] = static_cast<int16_t>(hex_data[6]);

    return short_data;
}

// 2.2 推算里程计并发布
void canControl::publishOdometry(double linear_velocity, double angular_velocity, double Steer_Angle)
{
    // 计算与上一时刻的时间差
    static double last_stamp = ros::Time::now().toSec();
    double dt = ros::Time::now().toSec() - last_stamp;
    last_stamp = ros::Time::now().toSec();

    // 1 推算里程
    double wz = angular_velocity, vx = 0.0, vy = 0.0;
    // 计算偏航角(注意是相对于初始0,要绝对的需要IMU)
    yaw += angular_velocity * dt;
    // 根据线速度以及航向角计算X,Y方向上的位移
    vx = linear_velocity * std::cos(Steer_Angle);
    vy = linear_velocity * (linear_velocity < 0.0 ? sin(-Steer_Angle) : sin(Steer_Angle));
    // 计算相对与上一时刻的位,根据偏航变换到初始坐标系
    position_x_ += cos(yaw) * vx * dt - sin(yaw) * vy * dt;
    position_y_ += sin(yaw) * vx * dt + cos(yaw) * vy * dt;

    // 2 发布tf
    geometry_msgs::Quaternion odom_quat = tf::createQuaternionMsgFromYaw(yaw);
    // std::cout<< "odom_quat:" << odom_quat<<std::endl;
    geometry_msgs::TransformStamped tf_msg;
    tf_msg.header.stamp = ros::Time::now();
    tf_msg.header.frame_id = "odom";
    tf_msg.child_frame_id = "base_link";
    tf_msg.transform.translation.x = position_x_;
    tf_msg.transform.translation.y = position_y_;
    tf_msg.transform.translation.z = 0.0;
    tf_msg.transform.rotation = odom_quat;
    tf_broadcaster_.sendTransform(tf_msg);

    // 3 发布odom
    nav_msgs::Odometry odom_msg;
    odom_msg.header.stamp = ros::Time::now();
    odom_msg.header.frame_id = "odom";
    odom_msg.child_frame_id = "base_link";

    odom_msg.pose.pose.position.x = position_x_;
    odom_msg.pose.pose.position.y = position_y_;
    odom_msg.pose.pose.position.z = 0.0;
    odom_msg.pose.pose.orientation = odom_quat;

    odom_msg.twist.twist.linear.x = vx;
    odom_msg.twist.twist.linear.y = vy;
    odom_msg.twist.twist.angular.z = wz;

    odom_msg.pose.covariance[0] = 0.1;
    odom_msg.pose.covariance[7] = 0.1;
    odom_msg.pose.covariance[14] = 0.1;
    odom_msg.pose.covariance[21] = 1.0;
    odom_msg.pose.covariance[28] = 1.0;
    odom_msg.pose.covariance[35] = 1.0;

    pub_Odom.publish(odom_msg);
}

// 3 析构函数
canControl::~canControl()
{
    /* 关闭套接字 */
    close(sockfd);
    exit(EXIT_SUCCESS);
}

2.3 发送数据节点

can_send_node.cpp

#include "can_ros1_driver/can_send.hpp"

int main(int argc, char **argv)
{
    ros::init(argc, argv, "can_send_node");
    canControl can_control;

    ros::spin();

    return 0;
}

can_send.hpp

#ifndef CAN_H_
#define CAN_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>

#include <iostream>
#include <string>
#include <cmath>

#include "ros/ros.h"
#include "std_msgs/String.h"
#include "geometry_msgs/Twist.h"
#include "geometry_msgs/TwistStamped.h"
#include "nav_msgs/Odometry.h"

class canControl
{
public:
    // 0 初始化
    canControl();

    // 1 下发CAN消息 速度控制节点消息的回调函数
    void velocityCallback(const geometry_msgs::Twist::ConstPtr &msg);
    void velocityCallback_Autoware(const geometry_msgs::TwistStamped::ConstPtr &msg);

    // 1.1 向底盘发送控制指令
    void publishCmd(float linear_vel, float angular_vel);

    // 2 析构函数
    ~canControl();

private:
    ros::NodeHandle nh_;
    // 是否为Autoware速度
    bool Autoware;
    // 接收的速度话题
    std::string twistTopic;
    // 轴距
    double wheel_base;
    // 套接字参数
    struct ifreq ifr;
    struct sockaddr_can can_addr;
    int sockfd;
    int ret;
    // 发送的数据
    struct can_frame frameE2;
    struct can_frame frame469;

    // 当前里程计
    double position_x_;
    double position_y_;
    double yaw;

    // 订阅速度控制
    ros::Subscriber sub_;
};

#endif // CAN_H_

can_send.cpp

#include "can_ros1_driver/can_send.hpp"

// 0 初始化
canControl::canControl() : nh_("~")
{
    // 1 初始化ROS节点
    nh_.param<double>("wheel_base", wheel_base, 1.0);
    nh_.param<bool>("Autoware", Autoware, 1.0);
    nh_.param<std::string>("twistTopic", twistTopic, "/cmd_vel");

    position_x_ = 0.0;
    position_y_ = 0.0;
    yaw = 0.0;

    /* 打开套接字 */
    sockfd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if (0 > sockfd)
    {
        perror("socket error");
        exit(EXIT_FAILURE);
    }

    /* 指定can0设备 */
    ifr = {0};
    can_addr = {0};
    strcpy(ifr.ifr_name, "can0");
    ioctl(sockfd, SIOCGIFINDEX, &ifr);
    can_addr.can_family = AF_CAN;
    can_addr.can_ifindex = ifr.ifr_ifindex;

    /* 将can0与套接字进行绑定 */
    if (0 > bind(sockfd, (struct sockaddr *)&can_addr, sizeof(can_addr)))
    {
        perror("bind error");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    /* 设置过滤规则 */
    setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);

    /* 发送数据格式 */
    frameE2.can_dlc = 8;    // 一次发送8个字节数据
    frameE2.can_id = 0x0E2; // 帧ID为0x0E2,标准帧
    frameE2.data[4] = 0x00;
    frameE2.data[5] = 0x00;
    frameE2.data[6] = 0x00;

    frame469.can_dlc = 8;    // 一次发送8个字节数据
    frame469.can_id = 0x469; // 帧ID为0x469,标准帧
    frame469.data[0] = 0x20;
    frame469.data[1] = 0x00;
    frame469.data[2] = 0x00;
    frame469.data[5] = 0x00;
    frame469.data[6] = 0x00;

    // 创建订阅控制底盘消息的订阅者
    if (Autoware)
        sub_ = nh_.subscribe(twistTopic, 1000, &canControl::velocityCallback_Autoware, this);
    else
        sub_ = nh_.subscribe(twistTopic, 1000, &canControl::velocityCallback, this);
}

// 1 下发CAN消息 根据接收到的速度消息生成控制指令并发送
// 其他节点消息的回调函数
void canControl::velocityCallback(const geometry_msgs::Twist::ConstPtr &msg)
{
    publishCmd(msg->linear.x, msg->angular.z);
}
// Autoware发送的
void canControl::velocityCallback_Autoware(const geometry_msgs::TwistStamped::ConstPtr &msg)
{
    publishCmd(msg->twist.linear.x, msg->twist.angular.z);
}

// 1.1 向底盘发送控制指令
void canControl::publishCmd(float linear_vel, float angular_vel)
{
    // 根据线速度和角速度生成控制指令的逻辑,将 linear_vel、angular_vel 转换为控制指令
    // 1. 计算挡位Gear
    int gear = 0; // 默认值
    if (linear_vel > 0)
    {
        gear = 1; // 前进挡
    }
    else if (linear_vel < 0)
    {
        gear = 2; // 倒挡
    }
    // 2. 线控驾驶标志位,通过CAN线控
    int line_control = 1;
    // 3. 驻车P标志位
    int parking_brake = 0;
    // 速度为0或速度反向的时候刹车
    static int last_gear = 0;
    if (linear_vel == 0.0 || last_gear + gear == 3)
        parking_brake = 1;
    last_gear = gear;
    // 4. 循环计数,默认为0
    int loop_count = 0;
    uint8_t gear_contrl = static_cast<uint8_t>(loop_count + parking_brake * 8 + line_control * 4 + gear);

    // 5 计算速度byte
    if (linear_vel > 5)
    {
        ROS_WARN("Speed is too fast!");
        linear_vel = 5;
    }
    // TODO 1000有待优化
    // printf("Send档位值为:%02x \n", gear_contrl);
    // std::cout << "Send速度值为: " << linear_vel << std::endl;
    uint8_t Speed_L = static_cast<uint8_t>((int)fabs(linear_vel) * 1000 % 256);
    uint8_t Speed_H = static_cast<uint8_t>((int)fabs(linear_vel) * 1000 / 256);

    // 6. 计算低压电器开关和速度校验和
    int brakeLight = 0;
    if (parking_brake)
        brakeLight = 1;
    int headlight = 0;
    int trafficIndicatorL = 0;
    int trafficIndicatorR = 0;
    if (angular_vel > 0)
        trafficIndicatorL = 1;
    else if (angular_vel < 0)
        trafficIndicatorR = 1;
    uint8_t LowVoltageEle = static_cast<uint8_t>(brakeLight + headlight * 2 + trafficIndicatorL * 4 + trafficIndicatorR * 8);

    uint8_t checksumSpeed = gear_contrl + Speed_L + Speed_H + LowVoltageEle;

    /* 发送数据 进入线控 协议需要每次先要发速度清空 */
    frameE2.data[0] = gear_contrl;
    frameE2.data[1] = 0x00;
    frameE2.data[2] = 0x00;
    frameE2.data[3] = LowVoltageEle;
    frameE2.data[7] = checksumSpeed;
    ret = write(sockfd, &frameE2, sizeof(frameE2)); // 发送数据

    // 7. 生成速度控制指令 发送数据
    frameE2.data[0] = gear_contrl;
    frameE2.data[1] = Speed_L;
    frameE2.data[2] = Speed_H;
    frameE2.data[3] = LowVoltageEle;
    frameE2.data[7] = checksumSpeed;
    ret = write(sockfd, &frameE2, sizeof(frameE2)); // 发送数据

    // 8. 计算角度byte
    // 转弯的角度 = arctan(( 角速度 / 线速度 ) * 轴距 )
    // 左转是rZ正方向,而这个底盘最左是负,因此加-号
    // TODO
    double angular = -atan((angular_vel / fabs(linear_vel)) * wheel_base);
    int angular_byte = 3.6 * angular * 180 / M_PI + 1024;
    // std::cout << "Send打角值为: " << angular_byte << std::endl;
    uint8_t angular_L = static_cast<uint8_t>((int)angular_byte % 256);
    uint8_t angular_H = static_cast<uint8_t>((int)angular_byte / 256);

    // 9. 计算角度校验和
    uint8_t checksumAng = 0x20 + angular_H + angular_L;

    // 10. 生成角度控制指令 发送数据
    frame469.data[3] = angular_H;
    frame469.data[4] = angular_L;
    frame469.data[7] = checksumSpeed;
    ret = write(sockfd, &frame469, sizeof(frame469)); // 发送数据
}

// 2 析构函数
canControl::~canControl()
{
    /* 发送数据 退出线控 */
    for (int i = 0; i < 8; i++)
        frameE2.data[i] = 0x00;
    ret = write(sockfd, &frameE2, sizeof(frameE2)); // 发送数据

    /* 关闭套接字 */
    close(sockfd);
    exit(EXIT_SUCCESS);
}

2.4 功能包配置

can_control.launch

<launch>
    <node name="can_receive_node" pkg="can_ros1_driver" type="can_receive_node" output="screen">
        <!-- 小车轴距 -->
        <param name="wheel_base" value="0.35"/>
    </node>
    <node name="can_send_node" pkg="can_ros1_driver" type="can_send_node" output="screen">
        <!-- 小车轴距 -->
        <param name="wheel_base" value="0.35"/>
        <!-- 是否为Autoware速度 -->
        <param name="Autoware" value="false"/>
        <!-- 接收的速度话题 -->
        <param name="twistTopic" value="/turtle1/cmd_vel"/>
    </node>
</launch>

cmakeLists

cmake_minimum_required(VERSION 3.0.2)
project(can_ros1_driver)

add_compile_options(-std=c++11)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  tf
  std_msgs
  nav_msgs
  geometry_msgs
)

catkin_package( CATKIN_DEPENDS roscpp tf std_msgs nav_msgs geometry_msgs)

include_directories(
  include
  ${catkin_INCLUDE_DIRS})

add_executable(can_receive_node src/can_receive.cpp src/can_receive_node.cpp)
# ROS相关库
target_link_libraries(can_receive_node ${catkin_LIBRARIES})

add_executable(can_send_node src/can_send.cpp src/can_send_node.cpp)
target_link_libraries(can_send_node ${catkin_LIBRARIES})

package.xml

<?xml version="1.0"?>
<package format="2">
  <name>can_ros1_driver</name>
  <version>0.0.0</version>
  <description>The can_ros1_driver package</description>

  <maintainer email="WangBin@todo.todo">WangBin</maintainer>

  <license>TODO</license>

  <buildtool_depend>catkin</buildtool_depend>
  <depend>roscpp</depend>
  <depend>tf</depend>
  <depend>std_msgs</depend>
  <depend>nav_msgs</depend>
  <depend>geometry_msgs</depend>
</package>

三、ROS2驱动程序

TODO

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

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

相关文章

python股票分析挖掘预测技术指标知识之蜡烛图指标(6)

本人股市多年的老韭菜&#xff0c;各种股票分析书籍&#xff0c;技术指标书籍阅历无数&#xff0c;萌发想法&#xff0c;何不自己开发个股票预测分析软件&#xff0c;选择python因为够强大&#xff0c;它提供了很多高效便捷的数据分析工具包。 我们已经初步的接触与学习其中数…

Java中的String类:深入分析与高级应用

Java中的String类&#xff1a;深入分析与高级应用 1. String类基础1.1 概述1.2 不可变性的好处1.3 字符串常量池 2. 创建String对象3. String类常用方法4. 内存管理4.1 字符串常量池4.2 intern方法 5. String与StringBuilder/StringBuffer6. 性能考虑7. 结论 Java中的String类是…

【Bootstrap学习 day14】

分页 分页是通过将内容分成单独的页面来组织内容的过程&#xff0c;分页导航一般用于文章列表页&#xff0c;下载列表、图片列表等&#xff0c;由于数据很多&#xff0c;不可能在一页显示&#xff0c;一般分页导航包括上一页&#xff0c;下一页、数字页码等。 基础的分页 要创…

【Python机器学习】线性模型——用于二分类的线性模型

线性模型也广泛用于分类问题&#xff0c;对于二分类问题&#xff0c;可以用以下公式进行预测&#xff1a; yw[0]*x[0]w[1]*x[1]…………w[p]*x[p]b>0 公式与现行回归的公式非常类似&#xff0c;但没有返回特征的加权求和&#xff0c;而是为预测设置了阈值。如果函数值小于…

Unity 欧盟UMP用户隐私协议Android接入指南

Unity 欧盟UMP用户协议Android接入指南 官方文档链接开始接入mainTemplate.gradle 中引入CustomUnityPlayerActivity 导入UMP相关的包java类中新增字段初始化UMPSDK方法调用![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d882171b068c46a1b956e80425f3a9cf.png)测…

Linux操作系统基础(06):Linux的文件类型和颜色

1.Linux文件类型 在Linux系统中&#xff0c;文件类型是指文件的种类或类型&#xff0c;它决定了系统对文件的处理方式&#xff0c;文件类型的作用在于告诉系统如何处理文件&#xff0c;不同类型的文件会有不同的默认行为和处理方式&#xff0c;Linux系统中常见的文件类型包括 …

轻松玩转书生·浦语大模型趣味Demo

轻松玩转书生浦语大模型趣味 Demo 轻松玩转书生浦语大模型趣味 Demo 1 大模型及 InternLM 模型简介 1.1 什么是大模型&#xff1f;1.2 InternLM 模型全链条开源 2 InternLM-Chat-7B 智能对话 Demo 2.1 环境准备2.2 模型下载2.3 代码准备2.4 终端运行2.5 web demo 运行 3 Lagen…

大数据 Hive - 实现SQL执行

文章目录 MapReduce实现SQL的原理Hive的架构Hive如何实现join操作小结 MapReduce的出现大大简化了大数据编程的难度&#xff0c;使得大数据计算不再是高不可攀的技术圣殿&#xff0c;普通工程师也能使用MapReduce开发大数据程序。 但是对于经常需要进行大数据计算的人&#xff…

QT5.14 实现ModbusTCP客户端 Demo

本文在QT5.14平台&#xff0c;基于QModbusClientTcp类&#xff0c;实现了客户端对单个寄存器的读写&#xff0c;用ModbusSlave做服务器做测试。 1.界面 (1)更改读按钮的名称为bt_Read (2)更改写按钮的名称为bt_Write 2.修改pro文件的第三行 greaterThan(QT_MAJOR_VERSION, 4)…

快速幂算法总结

知识概览 快速幂可以在O(logk)的时间复杂度之内求出来的结果。 例题展示 快速幂 题目链接 活动 - AcWing 系统讲解常用算法与数据结构&#xff0c;给出相应代码模板&#xff0c;并会布置、讲解相应的基础算法题目。https://www.acwing.com/problem/content/877/ 代码 #inc…

Rapberry Pi 4 安装VxWorks笔记

Rapberry Pi 4 安装VxWorks笔记 本文章发表与我的github page&#xff1a; Rapberry Pi 4 安装VxWorks笔记 | Hi, I am watershade. Welcome to my pages. 在github page会有更好体验和更多文章。 一、概述 ROS2推荐的操作系统是ubuntu,众所周知&#xff0c;linux并不是实时…

基于php应用的文件管理器eXtplorer部署网站并内网穿透远程访问

文章目录 1. 前言2. eXtplorer网站搭建2.1 eXtplorer下载和安装2.2 eXtplorer网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1. 前言 通过互联网传输文件&#xff0c;是互联网最重要的应用之一&#xff0c;无论是…

7 种常见的前端安全攻击

文章目录 七种常见的前端攻击1.跨站脚本&#xff08;XSS&#xff09;2.依赖性风险3.跨站请求伪造&#xff08;CSRF&#xff09;4.点击劫持5.CDN篡改6. HTTPS 降级7.中间人攻击 随着 Web 应用程序对业务运营变得越来越重要&#xff0c;它们也成为更有吸引力的网络攻击目标。但不…

test mutation-02-变异测试 mutate-test-kata入门介绍

拓展阅读 开源 Auto generate mock data for java test.(便于 Java 测试自动生成对象信息) 开源 Junit performance rely on junit5 and jdk8.(java 性能测试框架。性能测试。压测。测试报告生成。) test 系统学习-04-test converate 测试覆盖率 jacoco 原理介绍 mutate-te…

SkyWalking介绍和Docker环境下部署

一、Skywalking概述 1、Skywalking介绍 Skywalking是分布式系统的应用程序性能监视工具&#xff0c;专为微服务&#xff0c;云原生架构和基于容器&#xff08;Docker&#xff0c;K8S,Mesos&#xff09;架构而设计&#xff0c;它是一款优秀的APM&#xff08;Application Perfo…

RocketMQ5-02快速部署RocketMQ5.x(手动和容器部署)

RocketMQ5快速入门指南(含部署实践) 部署环境本机单机可执行包部署、Docker部署 Mac部署&#xff1a;下载源文件可执行包部署 NameServer 问题1&#xff1a;资源不足补充: 关于日志的输出 可执行包部署 Broker 对于Local模式对于Cluster模式 对于 ProxyDocker部署 NameServerD…

蒙特卡洛算法

通过随机数获得结果的算法。 当一个问题无法通过数学推导&#xff0c;计算机无法在有限时间求解时候。 就需要考虑蒙特卡洛方法了。 当无法求得精确解时候&#xff0c;进行随机抽样&#xff0c;根据统计试验求近似解。 可行域过大&#xff0c;没有通用方法求出精确解。 主…

OpenHarmony基于HDF简单驱动开发实例

背景 OpenHarmony-3.0-LTSqemu_small_system_demoliteos_aqemu 添加配置 device/qemu/arm_virt/liteos_a/hdf_config/device_info/device_info.hcs device_info 新增&#xff1a; sample_host :: host {hostName "sample_host";sample_device :: device {devic…

腾讯云免费服务器申请1个月攻略,亲测可行教程

腾讯云免费服务器申请入口 https://curl.qcloud.com/FJhqoVDP 免费服务器可选轻量应用服务器和云服务器CVM&#xff0c;轻量配置可选2核2G3M、2核8G7M和4核8G12M&#xff0c;CVM云服务器可选2核2G3M和2核4G3M配置&#xff0c;腾讯云服务器网txyfwq.com分享2024年最新腾讯云免费…

Python 常用数据类型

Python 常用数据类型有以下这些&#xff1a; 数据类型中文解析例子int整数&#xff0c;表示整数值1、2float浮点数&#xff0c;表示带有小数点的数值3.14、2.718complex复数&#xff0c;表示实部和虚部组成的复数12j、3-4jstr字符串&#xff0c;表示文本数据&#xff0c;用引号…