欢迎光临
我们一直在努力

jetson orin nano+ubuntu22.04+ros2+宇树L2激光雷达+fastlio2实现px4定点模式

最近做课题要实现使用激光雷达作为px4外部定点数据,ros1的配置很多而ros2少的感人。踩了很多坑后终于搞定了,这篇文章用来记录(自用),可供参考。

使用jetson orin nano作为实现的平台,ubuntu版本为22.04 humble。以下内容默认ubuntu22.04安装完成。

1.ROS2安装

直接使用鱼香ros一键安装即可

wget http://fishros.com/install -O fishros && . fishros

根据对话框自己选择安装,这一块不多赘述 ,网上教程很多。

2.构建Micro XRCE-DDS

由于ros2和px4连接采用Micro XRCE-DDS,所以需要下载这玩意(通过串口连接jetson和px4之后用来创建通讯的中间交互部分)。

详细的下载步骤见:ROS 2 用户指南 | PX4 Guide (main)

核心程序:

git clone https://github.com/eProsima/Micro-XRCE-DDS-Agent.git #不用官方的代码了,那个容易出错
cd Micro-XRCE-DDS-Agent
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig /usr/local/lib/

如果之前在ros2中做过px4飞行仿真,这个应该是配置过的,使用这个代码查看是否配置成功:

MicroXRCEAgent udp4 -p 8888

照着px4的官网配置即可,有空的话可以照着px4的官网做一遍仿真。 

3.jetson和px4的串口通讯

可以参考Jetson Nano利用ROS2通过MicroDDS与PX4通讯_jetson nano ros2-CSDN博客,写的比较详细。

配置好Micro XRCE-DDS之后,就可以开始尝试通过串口连接jetson和px4了。使用usb转ttl即可,接口我这边选择的是TELEM2,自己焊接一下线缆连接即可(接口的引脚自己网上找找)连接图:

有一个关键点,为了使用Micro XRCE-DDS,需要将px4通过usb连接到电脑,通过qgc地面站配置参数。需要将DDS配置为你对应连接的接口,以及配置波特率。

 如果你的jetson安装过对应的串口驱动,就可以检测到(串口一般都显示为ttyUSB)

之后可以使用这个代码连接(使用qgc中配置的波特率):

MicroXRCEAgent serial -D /dev/ttyUSB0 -b 921600

之后另开一个终端,查看ros2话题(第一次是每使用dds,第二次是使用dds):

发现topic list中多了许多px4的话题,这就证明通过dds连接jetson和px4成功了,之后就可以通过这些topic进行数据的交互 。

当然这里存在一些问题,有的时候你的系统中没有安装对应的串口驱动将找不到串口。如果出现这个问题,只需要安装对于的串口驱动即可。可以参考:

Linux-ubuntu22.04串口驱动安装(CH34X)_ubuntu安装ch340驱动-CSDN博客ubuntu安装串口驱动(找不到ttyUSB*)_ubuntu usb转串口找不到-CSDN博客

如果报错

[ 7033.078452] usb 1-13: usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1

可以参考 :

Ubuntu22.04 CH340系列串口驱动(没有ttyUSB)问题解决方案_usbfs: interface 0 claimed by ch341 while 'brltty'-CSDN博客

一定要安装上对应的驱动才行。

4.宇树L2激光雷达配置与fastlio2配置

由于px4定点信息需要通过fastlio2输出的里程计转换获得,由于我采用宇树L2激光雷达,所以二者都需要进行配置。

首先激光雷达的配置看L2雷达使用教学_哔哩哔哩_bilibili,里面说的很详细,照着弄就行,需要完成视频说的对应有线ip配置。

至于fastlio2,可以通过这里下载https://github.com/newkillerwhale/unilidar_fastlio_ros2/tree/ros2

这个项目里面配置好了宇树L2对应的文件,可以直接使用,配置流程照着项目里面写的就行,如果不太清楚照着这个顺序:

1.首先建一个fastlio2使用的工作空间,我的名称为fast_lio2,在里面建一个src文件夹(这是建ros工作空间的基础部分),然后把上面在github中下的功能包放在src中。

2.在编译fast_lio2工作空间之前,需要先完成Livox ROS Driver 2和Livox SDK2的编译(这两个功能包是驱动fastlio2工作的必要组成部分),下面是这个两个包的地址,流程可以根据项目一步步做:

https://github.com/Livox-SDK/livox_ros_driver2
https://github.com/Livox-SDK/Livox-SDK2/blob/master/README.md

总结一下,具体的配置代码:

#首先新建一个工作空间,下载两个功能包到src中

git clone https://github.com/Livox-SDK/livox_ros_driver2.git ws_livox/src/livox_ros_driver2
cd ~/wslivox/src
git clone https://github.com/Livox-SDK/Livox-SDK2.git

#下载完成后,先进行sdk2的编译

cd ./Livox-SDK2/
mkdir build
cd build
cmake .. && make -j2 #这里github中写是cmake .. && make -j,建议改为j2不容易报错
sudo make install

#完成之后,进行livox_ros_driver2的编译

cd ~/ws_livox/src/livox_ros_driver2
source /opt/ros/humble/setup.sh #由于我的ros2为humble所以这么用,版本不一样的去上面的网站里面查
./build.sh humble

最后一步可能会报错,根据报错内容修改再编译即可。比如我编译的时候报错如下(图找不到了):

kkkitten@ubuntu:~/ws_livox/src/livox_ros_driver2$ ./build.sh humble
Working Path: /home/kkkitten/ws_livox/src/livox_ros_driver2
ROS version is: ROS2
Starting >>> livox_ros_driver2
Starting >>> livox_sdk2
— stderr: livox_ros_driver2
Traceback (most recent call last):
File "/home/kkkitten/ws_livox/build/livox_ros_driver2/ament_cmake_python/livox_ros_driver2/setup.py", line 4, in <module>
setup(
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/__init__.py", line 115, in setup
return distutils.core.setup(**attrs)
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 186, in setup
return run_commands(dist)
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 202, in run_commands
dist.run_commands()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 1002, in run_commands
self.run_command(cmd)
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/dist.py", line 1102, in run_command
super().run_command(command)
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 1021, in run_command
cmd_obj.run()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/command/egg_info.py", line 312, in run
self.find_sources()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/command/egg_info.py", line 320, in find_sources
mm.run()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/command/egg_info.py", line 548, in run
self.prune_file_list()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/command/sdist.py", line 162, in prune_file_list
super().prune_file_list()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_distutils/command/sdist.py", line 386, in prune_file_list
base_dir = self.distribution.get_fullname()
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_core_metadata.py", line 275, in get_fullname
return _distribution_fullname(self.get_name(), self.get_version())
File "/home/kkkitten/.local/lib/python3.10/site-packages/setuptools/_core_metadata.py", line 293, in _distribution_fullname
canonicalize_version(version, strip_trailing_zero=False),
TypeError: canonicalize_version() got an unexpected keyword argument 'strip_trailing_zero'
gmake[2]: *** [CMakeFiles/ament_cmake_python_build_livox_ros_driver2_egg.dir/build.make:70:CMakeFiles/ament_cmake_python_build_livox_ros_driver2_egg] 错误 1
gmake[1]: *** [CMakeFiles/Makefile2:451:CMakeFiles/ament_cmake_python_build_livox_ros_driver2_egg.dir/all] 错误 2
gmake[1]: *** 正在等待未完成的任务….
gmake: *** [Makefile:146:all] 错误 2

Failed <<< livox_ros_driver2 [25.0s, exited with code 2]
Aborted <<< livox_sdk2 [59.8s]

Summary: 0 packages finished [1min 0s]
1 package failed: livox_ros_driver2
1 package aborted: livox_sdk2
2 packages had stderr output: livox_ros_driver2 livox_sdk2
kkkitten@ubuntu:~/ws_livox/src/livox_ros_driver2$

这是因为Python setuptools版本不兼容,把它降级即可:

pip install setuptools==58.2.0 # 降级到兼容版本

然后再编译就通过了。编译通过之后需要将 livox_ros_driver2 的环境变量添加到 ~/.bashrc 中,以便在启动fastlio2时能自动加载第二步安装的Livox 驱动,避免频繁source。以下是具体步骤:

ls ~/ws_livox/install # 确认有 livox_ros_driver2 文件夹
echo "source ~/ws_livox/install/setup.bash" >> ~/.bashrc
source ~/.bashrc # 使更改生效
ros2 pkg list | grep livox # 应输出 livox_ros_driver2

3.完成上面的配置之后,回到fast_lio2工作空间中,进行编译

rosdep install –from-paths src –ignore-src -y
colcon build –symlink-install
. ./install/setup.bash # use setup.zsh if use zsh

有的时候rosdep不好用,所以可以使用rosdepc,可以通过我这篇文章最开始的鱼香ros安装rosdepc,之后使用rosdepc安装依赖:

rosdepc install –from-paths src –ignore-src -y

编译通过之后,通过下面这两个命令依次启动L2激光雷达以及fastlio2的建图(要在对应的工作空间中),不同的激光雷达去找对应的yaml文件:

ros2 launch unitree_lidar_ros2 launch.py
ros2 launch fast_lio mapping.launch.py config_file:=unilidar_l2.yaml

fastlio2即可正常进行建图: 

 这是对应的话题:

其中/Odometry是后续给px4提供定点数据的原始数据来源。

成功之后,如果你的px4版本比较新(我的固件版本是v1.15),需要在fastlio2功能包中找到laserMapping.cpp添加一个函数(大约在626行):

template<typename T>
void set_posestamp(T & out)
{
out.pose.position.x = state_point.pos(0);
out.pose.position.y = state_point.pos(1);
out.pose.position.z = state_point.pos(2);
out.pose.orientation.x = geoQuat.x;
out.pose.orientation.y = geoQuat.y;
out.pose.orientation.z = geoQuat.z;
out.pose.orientation.w = geoQuat.w;

}
//以下是新添加的部分
template<typename T>
void set_twistamp(T & out)
{
out.twist.linear.x = state_point.vel(0);
out.twist.linear.y = state_point.vel(1);
out.twist.linear.z = state_point.vel(2);
// angular xyz
out.twist.angular.x = Measures.imu.back()->angular_velocity.x;
out.twist.angular.y = Measures.imu.back()->angular_velocity.y;
out.twist.angular.z = Measures.imu.back()->angular_velocity.z;
}
//以上是新添加的部分
void publish_odometry(const rclcpp::Publisher<nav_msgs::msg::Odometry>::SharedPtr pubOdomAftMapped, std::unique_ptr<tf2_ros::TransformBroadcaster> & tf_br)
{
odomAftMapped.header.frame_id = "camera_init";
odomAftMapped.child_frame_id = "odom";
odomAftMapped.header.stamp = get_ros_time(lidar_end_time);
set_posestamp(odomAftMapped.pose);
//以下是新添加的部分
set_twistamp(odomAftMapped.twist);
//以上是新添加的部分
pubOdomAftMapped->publish(odomAftMapped);
auto P = kf.get_P();

这个新的添加部分是使 /Odometry输出中增加速度的输出,这是因为新版本px4的视觉融合相较于旧版本增加了速度,所以需要通过这个函数获得速度的输出。方法参考于:https://zhuanlan.zhihu.com/p/492496857

5.px4定点数据发送

在前面通过DDS连接之后,可以观察到其中一个px4话题为/fmu/in/vehicle_visual_odometry,这个话题就是我们需要发布从fastlio2中获取位置数据的地方。为了使用这个话题对应的数据格式,需要去px4官网下载px4_msgs功能包。注意,vehicle_visual_odometry使用vehicleodometry数据格式!!!同时也需要在qgc地面站中进行配置,把EKF2_EV_CTRL配置为11,EKF2_HGT_MODE设置为视觉模式。

所以实现px4定点模式,只需要将前面获得的fastlio2的/Odometry话题数据进行一定处理,之后发布到/fmu/in/vehicle_visual_odometry中即可实现,以下是通过ai写的程序,可以参考一下(工作空间怎么搭建自行百度,我把px4_msgs包也放进同一个工作空间中了,作为依赖):

 注意,这个程序只是用来测试px4能不能进入定点模式,实机使用的话是需要按照自己的需求更改的!!!,没弄清楚原理之前不要随便上实机!!!

fastlio2_to_px4.py

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from nav_msgs.msg import Odometry
from px4_msgs.msg import VehicleOdometry, TimesyncStatus # 使用正确的VehicleOdometry类型
import numpy as np
from collections import deque
def quaternion_matrix(q):
x, y, z, w = q
return np.array([
[1-2*y*y-2*z*z, 2*x*y-2*z*w, 2*x*z+2*y*w],
[2*x*y+2*z*w, 1-2*x*x-2*z*z, 2*y*z-2*x*w],
[2*x*z-2*y*w, 2*y*z+2*x*w, 1-2*x*x-2*y*y]
])

def euler_from_quaternion(q):
x, y, z, w = q
t0 = +2.0 * (w * x + y * z)
t1 = +1.0 – 2.0 * (x * x + y * y)
roll = np.arctan2(t0, t1)

t2 = +2.0 * (w * y – z * x)
t2 = np.clip(t2, -1.0, 1.0)
pitch = np.arcsin(t2)

t3 = +2.0 * (w * z + x * y)
t4 = +1.0 – 2.0 * (y * y + z * z)
yaw = np.arctan2(t3, t4)

return roll, pitch, yaw
import threading
import time
import logging

# 配置日志记录
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('FastLIOToPX4')

class FastLIOToPX4(Node):
def __init__(self):
super().__init__('fastlio_to_px4')

logger.info("Initializing FastLIO to PX4 converter…")

# 声明参数
self.declare_parameters(
namespace='',
parameters=[
('fastlio_topic', '/Odometry'),
('visual_odom_topic', '/fmu/in/vehicle_visual_odometry'),
('publish_rate', 30.0),
('position_variance', [0.01, 0.01, 0.01]),
('orientation_variance', [0.0001, 0.0001, 0.0001]),
('velocity_variance', [0.01, 0.01, 0.01]),
('quality_value', 80) # int8类型,0-127
]
)

# 获取参数值
self.fastlio_topic = self.get_parameter('fastlio_topic').value
self.visual_odom_topic = self.get_parameter('visual_odom_topic').value
self.publish_rate = self.get_parameter('publish_rate').value
self.quality_value = int(self.get_parameter('quality_value').value)

# 确保质量值在有效范围内
self.quality_value = max(0, min(127, self.quality_value))
logger.info(f"Using quality value: {self.quality_value}")

# 初始化变量
self.prev_position = np.zeros(3)
self.prev_time = None
self.current_position = np.zeros(3)
self.estimated_velocity = np.zeros(3)
self.q_mav = np.array([0.0, 0.0, 0.0, 1.0])

# 时间同步变量
self.TimesyncStatus_offset = 0
self.last_TimesyncStatus_received = self.get_clock().now()

# 线程安全锁
self.lock = threading.Lock()

# 创建订阅者
self.fastlio_sub = self.create_subscription(
Odometry,
self.fastlio_topic,
self.fastlio_callback,
10
)

# 创建时间同步订阅者
self.TimesyncStatus_sub = self.create_subscription(
TimesyncStatus,
'/fmu/out/TimesyncStatus',
self.TimesyncStatus_callback,
10
)

# 创建发布者 – 使用VehicleOdometry消息类型
self.visual_odom_pub = self.create_publisher(
VehicleOdometry,
self.visual_odom_topic,
10
)
logger.info(f"Publisher created for topic: {self.visual_odom_topic}")

# 创建定时器
self.timer = self.create_timer(1.0/self.publish_rate, self.publish_visual_odometry)
self.start_time = time.time()

logger.info("FastLIO to PX4 converter initialized successfully")

def TimesyncStatus_callback(self, msg):
"""处理时间同步消息"""
ros_time = self.get_clock().now()
px4_time_us = msg.timestamp
ros_time_ns = ros_time.nanoseconds
self.TimesyncStatus_offset = (px4_time_us * 1000) – ros_time_ns
self.last_TimesyncStatus_received = ros_time

def get_px4_timestamp(self):
"""获取PX4兼容的系统时间戳(微秒)"""
if self.TimesyncStatus_offset == 0:
return int(self.get_clock().now().nanoseconds / 1000)
ros_time = self.get_clock().now()
px4_time_us = (ros_time.nanoseconds + self.TimesyncStatus_offset) // 1000
return int(px4_time_us)

def fastlio_callback(self, msg):
with self.lock:
current_time = self.get_clock().now()
self.current_position = np.array([
msg.pose.pose.position.x,
msg.pose.pose.position.y,
msg.pose.pose.position.z
])

# 估算速度
if self.prev_time is not None:
dt = (current_time – self.prev_time).nanoseconds * 1e-9
if dt > 0:
displacement = self.current_position – self.prev_position
self.estimated_velocity = displacement / dt

self.prev_position = self.current_position.copy()
self.prev_time = current_time

self.q_mav = np.array([
msg.pose.pose.orientation.x,
msg.pose.pose.orientation.y,
msg.pose.pose.orientation.z,
msg.pose.pose.orientation.w
])

def publish_visual_odometry(self):
with self.lock:
try:
# 创建VehicleOdometry消息对象
visual_odom = VehicleOdometry()

px4_time = self.get_px4_timestamp()
visual_odom.timestamp = px4_time
visual_odom.timestamp_sample = px4_time

# 坐标系设置 – 根据您提供的定义
visual_odom.pose_frame = VehicleOdometry.POSE_FRAME_FRD # FRD world-fixed frame = 2
visual_odom.velocity_frame = VehicleOdometry.VELOCITY_FRAME_BODY_FRD # BODY_FRD = 3

# 位置转换 (ENU to FRD)
# FRD: X=Forward, Y=Right, Z=Down
# 所以我们需要将ENU转换为FRD
visual_odom.position[0] = np.float32(self.current_position[1]) # Y (East) -> X (Forward)
visual_odom.position[1] = np.float32(self.current_position[0]) # X (North) -> Y (Right)
visual_odom.position[2] = np.float32(-self.current_position[2]) # Z (Up) -> -Z (Down)

# 速度设置 – 在机体坐标系中
visual_odom.velocity[0] = np.float32(self.estimated_velocity[0] + 0.001) # 添加微小扰动
visual_odom.velocity[1] = np.float32(self.estimated_velocity[1] + 0.001)
visual_odom.velocity[2] = np.float32(self.estimated_velocity[2] + 0.001)

# 方向 – 保持原样
visual_odom.q[0] = np.float32(self.q_mav[0])
visual_odom.q[1] = np.float32(self.q_mav[1])
visual_odom.q[2] = np.float32(self.q_mav[2])
visual_odom.q[3] = np.float32(self.q_mav[3])

# 角速度
visual_odom.angular_velocity[0] = np.float32(0.001)
visual_odom.angular_velocity[1] = np.float32(0.001)
visual_odom.angular_velocity[2] = np.float32(0.001)

# 方差设置
visual_odom.position_variance[0] = np.float32(0.1) # X
visual_odom.position_variance[1] = np.float32(0.1) # Y
visual_odom.position_variance[2] = np.float32(0.2) # Z

visual_odom.orientation_variance[0] = np.float32(0.05) # Roll
visual_odom.orientation_variance[1] = np.float32(0.05) # Pitch
visual_odom.orientation_variance[2] = np.float32(0.1) # Yaw

visual_odom.velocity_variance[0] = np.float32(0.5) # Vx
visual_odom.velocity_variance[1] = np.float32(0.5) # Vy
visual_odom.velocity_variance[2] = np.float32(0.5) # Vz

# 设置质量值 (int8)
visual_odom.quality = int(self.quality_value)

# 重置计数器
visual_odom.reset_counter = 0

# 发布消息
self.visual_odom_pub.publish(visual_odom)

# 定期日志
now = time.time()
if now – self.start_time > 1.0:
self.start_time = now
logger.info(
f"Published VO: quality={visual_odom.quality}, "
f"position=[{visual_odom.position[0]:.2f}, {visual_odom.position[1]:.2f}, {visual_odom.position[2]:.2f}]"
)
except Exception as e:
logger.error(f"Publish error: {str(e)}", exc_info=True)

def main(args=None):
rclpy.init(args=args)
node = FastLIOToPX4()
try:
rclpy.spin(node)
except KeyboardInterrupt:
logger.info("Node shutdown by user")
except Exception as e:
logger.error(f"Node crashed: {str(e)}", exc_info=True)
finally:
node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

 注意,这个程序只是用来测试px4能不能进入定点模式,实机使用的话是需要按照自己的需求更改的!!!,没弄清楚原理之前不要随便上实机!!!

如果这个程序能正常运行,就全部关闭另开四个终端依次运行以下程序(我把这些程序运行的文件夹地址也贴出来了,仅供参考):

kkkitten@kkkitten-desktop:~$ MicroXRCEAgent serial -D /dev/ttyUSB0 -b 921600
kkkitten@kkkitten-desktop:~/unilidar_sdk2-2.0.4/unitree_lidar_ros2$ ros2 launch unitree_lidar_ros2 launch.py

kkkitten@kkkitten-desktop:~/fast_lio2$ ros2 launch fast_lio mapping.launch.py config_file:=unilidar_l2.yaml
kkkitten@kkkitten-desktop:~/new_ros2_ws$ ros2 run fastlio2_px4_bridge fastlio2_to_px4

后续如果嫌需要开四个窗口太麻烦,可以自己写一个launch文件一次性启动全部节点,这里就不说具体怎么实现了。

如果运行无报错,可以使用这个代码查看实际的发布信息:

ros2 topic echo /fmu/in/vehicle_visual_odometry

 有数据就成功了,这个时候我们再将px4通过usb连接到电脑上,打开qgc地面站,使用一下代码查看数据是否通过DDS发送过来了:

listener vehicle_visual_odometry

此时可以看到local_position_ned显示了对应的数据。

尝试使用遥控器切换到定点模式,成功!也能正常解锁。(由于硬件还没安装到无人机上所以没有尝试飞行)

——————————————————————————————————————————-

这段时间在实机上试了试,效果还可以(我实机的程序不是上面那个,实机不要用上面那个,请自己更改!!!)

px4定点

 

 

赞(0)
未经允许不得转载:171主机测评 » jetson orin nano+ubuntu22.04+ros2+宇树L2激光雷达+fastlio2实现px4定点模式
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址