简单硬件在环搭建(ROS+Prescan+Carsim+simulink)

本文通过ROS+Prescan+Carsim+simulink搭建简单的硬件在环仿真测试平台。

系统架构如下:

在Windows中运行prescan场景仿真软件,在jetson Nano中运行ROS,硬件上两台电脑通过一根网线相连传输信息;

1.prescan与carsim的集成

在C:\carsimworkspace\prescan\Extensions\Simulink的路径下,找到如下模型:

将carsim模型发送到simulink中如下:

打开prescan,建议使用prescan8.5,放置主车与一段道路:

在主车驾驶员模型中选择Path Follower,以便于录制跟踪的路径数据。在prescan中选择车辆动力学模型如下,点击browse,选择之间extension文件夹下的carsim动力学模型:

在道路设置中,用黄线将道路的参考线绘制出来,并将其添加到车辆的运动轨迹中:

添加路径示意图如下:

这样就将道路的参考线数据添加到了车辆的运动路径里面了。

2.ROS与prescan通讯配置

将prescan的模型发送到simulink,打开模型得到:

左边打红框的地方是carsim到prescan的坐标转换模块,右边是to workspace模块,用于将数据录制到matlab的工作区间:

打红框部分展开如下:

X0,Y0的数据录制模块如下图所示:

然后先运行仿真工程,让车辆沿着道路先走一次,录制得到的.mat格式的数据如下图所示:

在Windows这台PC上,点击Win+R打开,输入CMD打开终端:

找到本机的IP地址,如果设置IP地址与Ubuntu不在同一网段的话,点击网络设置中的以太网设置,对IP进行重新配置:

打开jetson nano,输入ifconfig查看IP:

在Ping一下jetson nano的IP地址,以确保通信是没有问题的:

注意,在ROS中,需要修改etc/home文件夹下的设置,将IP与用户名相绑定,这一步必须做,否则可能导致通讯链路是通的,但是ROS收不到simulink发布的ROS消息:

3.matlab/simulink与ROS通讯的接口代码

在matlab中建立与ros的通讯,代码如下:

setenv('ROS_MASTER_URI','http://10.120.175.3:11311') 
%setenv('ROS_MASTER_URI','http://10.120.175.3:13637')
setenv('ROSIP', '10.120.175.22');
rosinit

其中ROS_MASTER_URI为Ubuntu端IP,ROSIP为主机(WINDOWS)的IP地址,这段代码主要是用于在matlab中初始化一个ROS节点。

编写代码,将.mat文件储存为txt文件:

X=global_x.Data;
Y=global_y.Data;
save('globalx.mat',"X");
save('globaly.mat',"Y");
% 1. 加载 .mat 文件
load('globalx.mat');
% 假设 data 是你要保存的变量
% 提取某一列数据(例如第1列)
global_X = X(:, 1); 
% 2. 打开一个新的文本文件来写入数据
fileID = fopen('global_X.txt', 'w');
% 3. 将数据写入文本文件
fprintf(fileID, '%f\n', global_X);
% 4. 关闭文件
fclose(fileID);

% 1. 加载 .mat 文件
load('globaly.mat');
% 假设 data 是你要保存的变量
% 提取某一列数据(例如第1列)
global_Y = Y(:, 1); 
% 2. 打开一个新的文本文件来写入数据
fileID = fopen('global_Y.txt', 'w');
% 3. 将数据写入文本文件
fprintf(fileID, '%f\n', global_Y);
% 4. 关闭文件
fclose(fileID);

打开FileZilla,建立连接后将TXT文件传输到nano中: 

在ROS中编写PID控制以及ROS节点的代码,这里建议先不要写PID算法,可以给执行器简单的输入进行开环测试:

#include <ros/ros.h>
#include <geometry_msgs/Pose2D.h>
#include <std_msgs/Float64.h>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <cmath>
#include <limits>

// 创建一个算法类
class Algorithm {
public:
    double lambda = 0; // 积分权重参数
    double u = 1.0; // 微分权重参数

    // 距离计算函数
    double calculateDistance(double X, double Y, double X1, double Y1) {
        return sqrt((X - X1) * (X - X1) + (Y - Y1) * (Y - Y1));
    }

    // 找出最近的点
    int findClosestPoint(const std::vector<double>& X, const std::vector<double>& Y, double currentX, double currentY) {
        int closestIndex = -1;
        double minDistance = std::numeric_limits<double>::max();
        for (size_t i = 0; i < X.size(); ++i) {
            double distance = calculateDistance(X[i], Y[i], currentX, currentY);
            if (distance < minDistance) {
                minDistance = distance;
                closestIndex = i;
            }
        }
        return closestIndex+15;
    }

    // PID速度控制代码
    double PIDControl(double target_speed, double current_speed, double& integral, double& previous_error) {
        // PID增益
        double Kp = 14;
        double Ki = 0.4;
        double Kd = 0.06;

        // 计算误差
        double error = target_speed - current_speed;

        // 计算积分和微分项
        integral += error;
        double derivative = error - previous_error;

        // PID输出
        double output = Kp * error + Ki * integral + Kd * derivative;
        // 更新前一误差
        previous_error = error;
        return output;
    }

    // 二维查找表插值
    double lookupControlValue(const std::vector<std::vector<double>>& matrix, double control_input, double current_speed) {
        // 假设横坐标是控制输入,纵坐标是当前车速
        int row_index = std::min(static_cast<int>(current_speed), static_cast<int>(matrix.size() - 1));
        int col_index = std::min(static_cast<int>(control_input), static_cast<int>(matrix[0].size() - 1));

        // 插值输出
        return matrix[row_index][col_index];
    }

    // PID横向控制
    double PID(double target_X, double target_Y, double Phik, double X, double Y) {
        double Kp = 17;  
        double Ki = 0.05;  
        double Kd = 0.04;
        double Phi= Phik *3.1415/180;
        static double prev_err = 0; // 改为静态变量,保持上次调用的状态
        static double integral_err = 0; // 改为静态变量,保持上次调用的状态
        // 计算目标方向与当前位置的角度
        double alpha =atan2(target_Y - Y,target_X - X)-Phi;
        // 计算比例误差
        double proportional_err = alpha;
        // 计算积分误差
        integral_err += proportional_err;
        // 计算微分误差
        double derivative_err = proportional_err - prev_err;
        // 计算前轮转角 Delta
        double Delta = Kp * proportional_err + Ki * integral_err+ Kd *derivative_err;
        // 更新前一误差
        prev_err = proportional_err;

       
        return Delta; // 返回前轮转角
    }

    // 横向LQR控制
    double LQR() {
        // 实现横向LQR控制
    }

    // 横向PP控制
    double PureP() {
        // 实现横向PP控制
    }
};

// 全局变量声明
std::vector<double> gloabel_X;
std::vector<double> gloabel_Y;
std::vector<double> globel_Angel;
Algorithm algorithm;
std::vector<std::vector<double>> matrix;
double integral = 0.0;
double previous_error = 0.0;
double target_speed = 30.0; // 目标速度 30 km/h
double current_speed = 0.0;
double output = 0;

// 位置回调函数
void chatterCallback(const geometry_msgs::Pose2D& Pose_msg) {
    // 寻找索引值
    int index = algorithm.findClosestPoint(gloabel_X, gloabel_Y, Pose_msg.x, Pose_msg.y);
    ROS_INFO("索引: %f", gloabel_Y[index]);
    // 添加有效性检查
    if (index < 0 || index >= gloabel_X.size() || index >= gloabel_Y.size() || index >= globel_Angel.size()) {
        ROS_ERROR("索引越界: %d", index);
        return; // 如果索引无效,则返回
    }
    
    // 更新全局的 output
    output = algorithm.PID(gloabel_X[index], gloabel_Y[index],Pose_msg.theta, Pose_msg.x, Pose_msg.y);
}

// 速度回调函数
void velocityCallback(const geometry_msgs::Pose2D& Vel_msg) {
    current_speed = Vel_msg.x; // 假设当前速度在 x 轴
}

// 从文件读取数据
std::vector<double> readData(const std::string& filePath) {
    std::ifstream file(filePath);
    std::vector<double> data;

    if (!file.is_open()) {
        ROS_ERROR("无法打开文件: %s", filePath.c_str());
        return data; // 返回空的vector
    }

    std::string line;
    while (std::getline(file, line)) {
        std::istringstream iss(line);
        double value;
        if (iss >> value) {
            data.push_back(value);
        } else {
            ROS_WARN("读取数据失败: %s", line.c_str());
        }
    }

    file.close();
    return data;
}

// 从文件读取矩阵
std::vector<std::vector<double>> readMatrix(const std::string& filePath, int rows, int cols) {
    std::ifstream file(filePath);
    std::vector<std::vector<double>> matrix(rows, std::vector<double>(cols));

    if (!file.is_open()) {
        ROS_ERROR("无法打开文件: %s", filePath.c_str());
        return matrix;
    }

    std::string line;
    for (int i = 0; i < rows; ++i) {
        if (!std::getline(file, line)) {
            ROS_ERROR("读取文件时出错或文件行数不足");
            return matrix;
        }
        std::istringstream iss(line);
        for (int j = 0; j < cols; ++j) {
            if (!(iss >> matrix[i][j])) {
                ROS_ERROR("读取数据时出错或文件列数不足: 行 %d 列 %d", i, j);
                return matrix;
            }
        }
    }

    file.close();
    return matrix;
}

int main(int argc, char **argv) {
    ros::init(argc, argv, "Communicate");
    ros::NodeHandle n;
    ros::Publisher chatter_pub = n.advertise<geometry_msgs::Pose2D>("control", 1000);
    ros::Subscriber sub = n.subscribe("simulink_pose", 1000, chatterCallback);
    ros::Subscriber sub1 = n.subscribe("vel_cmd", 1000, velocityCallback);
    ros::Rate loop_rate(10);

    // 读取X轴坐标轨迹值
    std::string filePathX = "/home/jetson/ROS/R2/yahboomcar_ws/src/ROS_Prescan/prescanpp/globel_X.txt"; // 替换为实际文件路径
    gloabel_X = readData(filePathX);
    // for (const double& value : gloabel_X) {
    //     ROS_INFO("读取到的数据: %f", value);
    // }

    // 读取Y轴坐标轨迹值
    std::string filePathY = "/home/jetson/ROS/R2/yahboomcar_ws/src/ROS_Prescan/prescanpp/globel_Y.txt"; // 替换为实际文件路径
    gloabel_Y = readData(filePathY);
    // for (const double& value : gloabel_Y) {
    //     ROS_INFO("读取到的数据: %f", value);
    // }

    // 读取航向角值
    std::string filePathAngel = "/home/jetson/ROS/R2/yahboomcar_ws/src/ROS_Prescan/prescanpp/globel_Angel.txt"; // 替换为实际文件路径
    globel_Angel = readData(filePathAngel);
    // for (const double& value : globel_Angel) {
    //     ROS_INFO("读取到的航向角: %f", value);
    // }

    // 读取油门刹车查找表
    std::string filePathMatrix = "/home/jetson/ROS/R2/yahboomcar_ws/src/ROS_Prescan/prescanpp/src/tablebr.txt";
    matrix = readMatrix(filePathMatrix, 1001, 261);

    while (ros::ok()) {
        // 计算PID控制器输出
        double control_output = algorithm.PIDControl(target_speed, current_speed, integral, previous_error);
        
        // 查找表获取控制值
        double control_value = algorithm.lookupControlValue(matrix, control_output, current_speed);

        // 确定油门和刹车
        double throttle = (control_value > 0) ? control_value : 0;
        double brake = (control_value < 0) ? -control_value : 0;

        

        // 发布控制消息
        geometry_msgs::Pose2D msg;
        msg.x = throttle; // 油门开度
        msg.y = brake; // 刹车压力
        msg.theta = output-3.14/2; // 方向盘扭矩
        chatter_pub.publish(msg);
        
        // ROS_INFO("Throttle: %f, Brake: %f, Steering Torque (msg.theta): %f", throttle, brake, msg.theta);
        
        ros::spinOnce();
        loop_rate.sleep();
    }

    return 0;
}

编写CmakeList.txt文件如下:

cmake_minimum_required(VERSION 3.0.2)
project(prescanpp)

## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
)

## System dependencies are found with CMake's conventions
# find_package(Boost REQUIRED COMPONENTS system)


## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup()

################################################
## Declare ROS messages, services and actions ##
################################################

## To declare and build messages, services or actions from within this
## package, follow these steps:
## * Let MSG_DEP_SET be the set of packages whose message types you use in
##   your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
## * In the file package.xml:
##   * add a build_depend tag for "message_generation"
##   * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
##   * If MSG_DEP_SET isn't empty the following dependency has been pulled in
##     but can be declared for certainty nonetheless:
##     * add a exec_depend tag for "message_runtime"
## * In this file (CMakeLists.txt):
##   * add "message_generation" and every package in MSG_DEP_SET to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * add "message_runtime" and every package in MSG_DEP_SET to
##     catkin_package(CATKIN_DEPENDS ...)
##   * uncomment the add_*_files sections below as needed
##     and list every .msg/.srv/.action file to be processed
##   * uncomment the generate_messages entry below
##   * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)

## Generate messages in the 'msg' folder
# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

## Generate services in the 'srv' folder
# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

## Generate actions in the 'action' folder
# add_action_files(
#   FILES
#   Action1.action
#   Action2.action
# )

## Generate added messages and services with any dependencies listed here
# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )

################################################
## Declare ROS dynamic reconfigure parameters ##
################################################

## To declare and build dynamic reconfigure parameters within this
## package, follow these steps:
## * In the file package.xml:
##   * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
## * In this file (CMakeLists.txt):
##   * add "dynamic_reconfigure" to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * uncomment the "generate_dynamic_reconfigure_options" section below
##     and list every .cfg file to be processed

## Generate dynamic reconfigure parameters in the 'cfg' folder
# generate_dynamic_reconfigure_options(
#   cfg/DynReconf1.cfg
#   cfg/DynReconf2.cfg
# )

###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES prescanpp
#  CATKIN_DEPENDS roscpp std_msgs
#  DEPENDS system_lib
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

## Declare a C++ library
# add_library(${PROJECT_NAME}
#   src/${PROJECT_NAME}/prescanpp.cpp
# )

## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/prescanpp_node.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
#   ${catkin_LIBRARIES}
# )

#############
## Install ##
#############

# all install targets should use catkin DESTINATION variables
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
# catkin_install_python(PROGRAMS
#   scripts/my_python_script
#   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark executables for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
# install(TARGETS ${PROJECT_NAME}_node
#   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark libraries for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
# install(TARGETS ${PROJECT_NAME}
#   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )

## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
#   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
#   FILES_MATCHING PATTERN "*.h"
#   PATTERN ".svn" EXCLUDE
# )

## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
#   # myfile1
#   # myfile2
#   DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )

#############
## Testing ##
#############

## Add gtest based cpp test target and link libraries
# catkin_add_gtest(${PROJECT_NAME}-test test/test_prescanpp.cpp)
# if(TARGET ${PROJECT_NAME}-test)
#   target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()

## Add folders to be run by python nosetests
# catkin_add_nosetests(test)

#add_library(${PROJECT_NAME}
#   src/${PROJECT_NAME}/prescanpp.cpp
# )


add_executable(Communicate src/Communicate.cpp)
add_dependencies(Communicate ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
 target_link_libraries(Communicate
   ${catkin_LIBRARIES}
 )


 #----------------------------------------------------------------------#


在simulink中搭建ROS的发送与接收接口如下:

ROS_Prescan位置更新模块如下,如果前面的步骤均没有问题,这个地方应该可以自动读取出识别到的ROS节点,这个模块主要是将Prescan中车辆的数据接收并发送到ROS中:

ROS_Prescan位置信息更新模块如下,此模块主要用于接收ROS计算得到的执行器数值,并发送到场景软件中:

最后点击运行,如果没有问题,车辆应该能实现简单的轨迹跟踪。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值