底盘四轮转向运动学解析(含代码)

目录

  • 写在前面的话
  • 四轮转向运动学解析
    • 四轮转向理论图解
    • robot_control.py 完整代码
      • 关键参数
      • 完整代码
    • 公式解析(根据代码)
      • 反相--模式1
        • 详细图解
      • 正相--模式2
      • 轴心--模式3

写在前面的话

网上找了很多资料,对于四轮转向运动学描述的很少,有部分涉及到的又将的很晦涩难懂。在论文研究大多是四轮转向动力学研究。很多文章中都会把四轮运动简化成二轮运行(自行车),但是都只讨论了前驱或者后驱的情况,把前驱或后驱中轴点的速度的速度当做整车的速度,跟我到的代码不对应。

本文是基于代码进行解析,一些前提条件本文忽略(什么刚体,侧滑之类的),把车子的中心作为运动中心,已知车速和角速度计算四轮的线速度和转向,希望大家可以看懂,主要难点是在反相运动的部分,需要着重注意。

四轮转向运动学解析

四轮转向理论图解

  • 三种模式
    • 同相运动(前后轮摆动方向相同)
    • 反相运动(前后轮摆动方法相反,如下图,前轮往右摆,那么后轮就往左摆)
    • 轴心旋转

在这里插入图片描述

下图中属于逆向运动学,也即是根据车子行驶的速度和角速度,求解车轮的速度和方向。图中在反相运动过程中示例是右转弯,前后轮应该都符合阿克曼角,属于四驱阿克曼(双阿克曼),图中不明显。

在这里插入图片描述

robot_control.py 完整代码

代码链接:humble/four_ws_ros2/src/four_ws_control/scripts/robot_control.py

创建了一个 joy 节点,监听 v e l m s g vel_{msg} velmsg, m o d e s e l e c t i o n mode_{selection} modeselection 两个参数
车子的速度设置: v e l m s g = [ l i n e a r x , l i n e a r y , l i n e a r z , a n g l e x , a n g l e y , a n g l e z ]  模式选择: m o d e s e l e c t i o n = [ 1 , 2 , 3 ] \begin{aligned} &\begin{gathered} &\text{车子的速度设置:}\\ &vel_{ msg} = [linear_x, linear_y, linear_z, angle_x, angle_y, angle_z]\\ \\ &\text{ 模式选择:}\\ &mode_{selection} = [1, 2, 3] \\ &\end{gathered} \end{aligned} 车子的速度设置:velmsg=[linearx,lineary,linearz,anglex,angley,anglez] 模式选择:modeselection=[1,2,3]

关键参数

  • 车轮间距(wheel_seperation): 同一车轴上左轮和右轮之间的距离。
  • 前后轮距(wheel_base):前后车轴之间的距离。
  • 车轮半径(wheel_radius):车轮的半径。
  • 车轮转向 y 偏移(wheel_steering_y_offset): 车轮转向装置偏离车轮中心线的横向偏移。 (可以理解转向器的转轴和车轮的垂直轴有偏移)
  • 转向轨迹 (steering_track): 计算公式为【车轮间距 - 2 * 车轮转向 y 偏移】。 这表示影响车辆操控性的有效轨道宽度。

在这里插入图片描述

完整代码

#!/usr/bin/python3
import math
import threading
import rclpy
import numpy as np
from rclpy.node import Node
from std_msgs.msg import Float64MultiArray
from geometry_msgs.msg import Twist
from sensor_msgs.msg import Joy

vel_msg = Twist()  # robot velosity
mode_selection = 0 # 1:opposite phase, 2:in-phase, 3:pivot turn 4: none

class Commander(Node):

    def __init__(self):
        super().__init__('commander')
        timer_period = 0.02
        self.wheel_seperation = 0.122
        self.wheel_base = 0.156
        self.wheel_radius = 0.026
        self.wheel_steering_y_offset = 0.03
        self.steering_track = self.wheel_seperation - 2*self.wheel_steering_y_offset

        self.pos = np.array([0,0,0,0], float)
        self.vel = np.array([0,0,0,0], float) #left_front, right_front, left_rear, right_rear

        self.pub_pos = self.create_publisher(Float64MultiArray, '/forward_position_controller/commands', 10)
        self.pub_vel = self.create_publisher(Float64MultiArray, '/forward_velocity_controller/commands', 10)
        self.timer = self.create_timer(timer_period, self.timer_callback)

    def timer_callback(self):
        global vel_msg, mode_selection

        # opposite phase
        if(mode_selection == 1):
            
            vel_steerring_offset = vel_msg.angular.z * self.wheel_steering_y_offset
            sign = np.sign(vel_msg.linear.x)

            self.vel[0] = sign*math.hypot(vel_msg.linear.x - vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) - vel_steerring_offset
            self.vel[1] = sign*math.hypot(vel_msg.linear.x + vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) + vel_steerring_offset
            self.vel[2] = sign*math.hypot(vel_msg.linear.x - vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) - vel_steerring_offset
            self.vel[3] = sign*math.hypot(vel_msg.linear.x + vel_msg.angular.z*self.steering_track/2, vel_msg.angular.z*self.wheel_base/2) + vel_steerring_offset

            self.pos[0] = math.atan(vel_msg.angular.z*self.wheel_base/(2*vel_msg.linear.x + vel_msg.angular.z*self.steering_track+0.001))
            self.pos[1] = math.atan(vel_msg.angular.z*self.wheel_base/(2*vel_msg.linear.x - vel_msg.angular.z*self.steering_track+0.001))
            self.pos[2] = -self.pos[0]
            self.pos[3] = -self.pos[1]

        # in-phase
        elif(mode_selection == 2):

            V = math.hypot(vel_msg.linear.x, vel_msg.linear.y)
            sign = np.sign(vel_msg.linear.x)
            
            if(vel_msg.linear.x != 0):
                ang = vel_msg.linear.y / vel_msg.linear.x
            else:
                ang = 0
            
            self.pos[0] = math.atan(ang)
            self.pos[1] = math.atan(ang)
            self.pos[2] = self.pos[0]
            self.pos[3] = self.pos[1]
            
            self.vel[:] = sign*V
            
        # pivot turn
        elif(mode_selection == 3):

            self.pos[0] = -math.atan(self.wheel_base/self.steering_track)
            self.pos[1] = math.atan(self.wheel_base/self.steering_track)
            self.pos[2] = math.atan(self.wheel_base/self.steering_track)
            self.pos[3] = -math.atan(self.wheel_base/self.steering_track)
            
            self.vel[0] = -vel_msg.angular.z
            self.vel[1] = vel_msg.angular.z
            self.vel[2] = self.vel[0]
            self.vel[3] = self.vel[1]

        else:

            self.pos[:] = 0
            self.vel[:] = 0

        pos_array = Float64MultiArray(data=self.pos) 
        vel_array = Float64MultiArray(data=self.vel) 
        self.pub_pos.publish(pos_array)
        self.pub_vel.publish(vel_array)
        self.pos[:] = 0
        self.vel[:] = 0

class Joy_subscriber(Node):

    def __init__(self):
        super().__init__('joy_subscriber')
        self.subscription = self.create_subscription(
            Joy,
            'joy',
            self.listener_callback,
            10)
        self.subscription

    def listener_callback(self, data):
        global vel_msg, mode_selection

        if(data.buttons[0] == 1):   # in-phase # A button of Xbox 360 controller
            mode_selection = 2
        elif(data.buttons[4] == 1): # opposite phase # LB button of Xbox 360 controller
            mode_selection = 1
        elif(data.buttons[5] == 1): # pivot turn # RB button of Xbox 360 controller
            mode_selection = 3
        else:
            mode_selection = 4

        vel_msg.linear.x = data.axes[1]*7.5
        vel_msg.linear.y = data.axes[0]*7.5
        vel_msg.angular.z = data.axes[3]*10

if __name__ == '__main__':
    rclpy.init(args=None)
    
    commander = Commander()
    joy_subscriber = Joy_subscriber()

    executor = rclpy.executors.MultiThreadedExecutor()
    executor.add_node(commander)
    executor.add_node(joy_subscriber)

    executor_thread = threading.Thread(target=executor.spin, daemon=True)
    executor_thread.start()
    rate = commander.create_rate(2)
    try:
        while rclpy.ok():
            rate.sleep()
    except KeyboardInterrupt:
        pass
    
    rclpy.shutdown()
    executor_thread.join()

公式解析(根据代码)

np.sign 是 NumPy 库中的一个函数,用于返回输入数组中每个元素的符号。它的主要作用是判断数值的正负,具体行为如下:

  1. 如果元素为正数,返回 1
  2. 如果元素为负数,返回 -1
  3. 如果元素为零,返回 0

反相–模式1

  • v x v_{x} vx 是车子前进的速度。
  • v z v_{z} vz 是车子旋转的角速度(也就是上图中的 w w w)。
  • t t t 是车轮间距。
  • b b b 是前后轮距。
  • y o f f s e t y_{offset} yoffset 是车轮和转向器的偏移。

v o f f s e t = v z ⋅ y o f f s e t 每个车轮的线速度(除以车轮半径就是角速度):  v l f = ( v x − v z ⋅ t 2 ) 2 + ( v z ⋅ b 2 ) 2 − v o f f s e t v r f = ( v x + v z ⋅ t 2 ) 2 + ( v z ⋅ b 2 ) 2 + v o f f s e t v l r = v l f v r r = v r f 每个车轮的转向:  p o s l f = tan ⁡ − 1 ( v z ⋅ b 2 ⋅ v x + v z ⋅ t + 0.001 ) pos ⁡ r f = tan ⁡ − 1 ( v z ⋅ b 2 ⋅ v x − v z ⋅ t + 0.001 ) 上式是介绍前轮的转向,后轮只要取负数即。 \begin{aligned}\\ &v_{offset} = v_z \cdot y_{offset} \\ \\ \\ &\text {每个车轮的线速度(除以车轮半径就是角速度): }\\ &\begin{gathered} v_{l f}= \sqrt{\left(v_x-\frac{v_z \cdot t}{2}\right)^2+\left(\frac{v_z \cdot b}{2}\right)^2}-v_{offset} \\ v_{r f}=\sqrt{\left(v_x+\frac{v_z \cdot t}{2}\right)^2+\left(\frac{v_z \cdot b}{2}\right)^2}+v_{offset} \\ v_{l r}=v_{l f} \\ v_{r r}=v_{r f} \end{gathered}\\ \\ &\text {每个车轮的转向: }\\ &\begin{gathered} & p o s_{l f}=\tan ^{-1}\left(\frac{v_z \cdot b}{2 \cdot v_x+v_z \cdot t + 0.001}\right) \\ & \operatorname{pos}_{r f}=\tan ^{-1}\left(\frac{v_z \cdot b}{2 \cdot v_x-v_z \cdot t +0.001}\right) \end{gathered}\\ &\text {上式是介绍前轮的转向,后轮只要取负数即。}\\ \end{aligned} voffset=vzyoffset每个车轮的线速度(除以车轮半径就是角速度)vlf=(vx2vzt)2+(2vzb)2 voffsetvrf=(vx+2vzt)2+(2vzb)2 +voffsetvlr=vlfvrr=vrf每个车轮的转向poslf=tan1(2vx+vzt+0.001vzb)posrf=tan1(2vxvzt+0.001vzb)上式是介绍前轮的转向,后轮只要取负数即。

详细图解

根据上述的控制代码,得到车子的中心瞬时线速度 V x V_x Vx和角速度 w w w,需要计算四个轮子的线速度
v l f = w ⋅ ( V x w + t 2 ) 2 + ( L 2 2 ) + w ⋅ s v r f = w ⋅ ( V x w − t 2 ) 2 + ( L 2 2 ) − w ⋅ s p o s l f = L 2 V x w + t 2 p o s r f = L 2 V x w − t 2 \begin{aligned} v_{lf} = w \cdot \sqrt{\left( \frac{V_x}{w}+\frac{t}{2} \right)^2+\left( \frac{L}{2}^2 \right)}+w\cdot s \\ v_{rf} = w \cdot \sqrt{\left( \frac{V_x}{w}-\frac{t}{2} \right)^2+\left( \frac{L}{2}^2 \right)}-w\cdot s \end{aligned} \\ pos_{lf} = \frac{\frac{L}{2}}{\frac{V_x}{w}+\frac{t}{2}} \\ pos_{rf} = \frac{\frac{L}{2}}{\frac{V_x}{w}-\frac{t}{2}} vlf=w(wVx+2t)2+(2L2) +wsvrf=w(wVx2t)2+(2L2) wsposlf=wVx+2t2Lposrf=wVx2t2L

在这里插入图片描述

正相–模式2

每个车轮的线速度(除以车轮半径就是角速度):  v = ( v x 2 + v y 2 ) 每个车轮的转向:  p o s = tan ⁡ − 1 ( v y v x ) \begin{aligned}\\ \\ &\text {每个车轮的线速度(除以车轮半径就是角速度): }\\ &\begin{gathered} v=\sqrt{\left(v_x^2 + v_y^2\right)} \\ \end{gathered}\\ \\ &\text {每个车轮的转向: }\\ &\begin{gathered} p o s=\tan ^{-1}\left(\frac{v_y }{v_x}\right) \\ \end{gathered}\\ \end{aligned} 每个车轮的线速度(除以车轮半径就是角速度)v=(vx2+vy2) 每个车轮的转向pos=tan1(vxvy)

轴心–模式3

每个车轮的线速度(除以车轮半径就是角速度):  v l f = − v z v r f = v z 后轮和前轮同相。 每个车轮的转向:  p o s l f = − tan ⁡ − 1 ( b t ) p o s r f = tan ⁡ − 1 ( b t ) 后轮和前轮反相,后轮取负数即可。 \begin{aligned}\\ &\text {每个车轮的线速度(除以车轮半径就是角速度): }\\ &\begin{gathered} v_{lf} = -v_z \\ v_{rf} = v_z \\ \end{gathered}\\ &\text {后轮和前轮同相。}\\ \\ &\text {每个车轮的转向: }\\ &\begin{gathered} p o s_{lf}=-\tan ^{-1}\left(\frac{b}{t}\right) \\ p o s_{rf}=\tan ^{-1}\left(\frac{b}{t}\right) \\ \end{gathered}\\ &\text {后轮和前轮反相,后轮取负数即可。}\\ \end{aligned} 每个车轮的线速度(除以车轮半径就是角速度)vlf=vzvrf=vz后轮和前轮同相。每个车轮的转向poslf=tan1(tb)posrf=tan1(tb)后轮和前轮反相,后轮取负数即可。

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

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

相关文章

爬虫过程 | 蜘蛛程序爬取数据流程(初学者适用)

蜘蛛程序(也称网络爬虫,是搜索引擎的重要组成部分) 主要功能:遍历互联网,抓取网站信息并建立索引,便于用户在搜索引擎中检索到最新的网页内容工作原理:从初始网站页面的URL开始,发送…

最适配达梦、人大金仓的sql工具是什么?

SQLynx是一款功能强大的数据库管理工具,它不仅支持Oracle、MySQL等国际主流数据库,还很好地支持了武汉达梦、人大金仓等国产数据库。这款工具具有以下几个特点: 1.广泛支持:SQLynx支持多种数据库系统,包括PostgreSQL、…

MySQL学习笔记(持续更新中)

1、Mysql概述 1.1 数据库相关概念 三个概念:数据库、数据库管理系统、SQL 名称全称简称数据库存储数据的仓库,数据是有组织的进行存储DataBase(DB)数据库管理系统操纵和管理数据库的大型软件DataBase Mangement System&#xf…

电子看板实时监控数据可视化助力工厂精细化管理

在当今竞争激烈的制造业领域,工厂的精细化管理成为提高竞争力的关键。而电子看板实时监控数据可视化作为一种先进的管理工具,正为工厂的精细化管理带来巨大的助力。 一、工厂精细化管理的挑战 随着市场需求的不断变化和客户对产品质量要求的日益提高&am…

Qt 模型视图(三):视图类QAbstractItemView

文章目录 Qt 模型视图(三):视图类QAbstractItemView1.基本概念1.1.使用现有视图1.2.使用模型1.3.使用模型的多个视图1.4.在视图之间共享选择 Qt 模型视图(三):视图类QAbstractItemView ​ 模型/视图结构是一种将数据存储和界面展示分离的编程方法。模型存储数据,视…

Ubuntu22.04关闭631端口的方法

何为631端口? 631端口主要用于访问和控制网络打印服务,是linux下CUPS(Common UNIX Printing System,通用UNIX打印系统)服务的端口。Linux中的CUPS(Common UNIX Printing System,通用UNIX打印系…

VisualStudio的“应用代码更改“按钮功能

无意发现这个按钮,因为开发这么多年也没专门尝试这个按钮,于是好奇它的功能。 光标放在按钮上面提示了“应用代码更改”,于是猜想应该是在调试不断开的情况下支持热应用更改。 经过验证,功能确实如同猜想的一样,具体验…

【Elasticsearch系列廿一】ES7 SQL 新特性

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于c++实现的简易shell

代码逻辑 核心思想 解析命令行,拆解命令及其选项创建子进程,在子进程中执行命令如果是前台执行命令,则父进程就阻塞等待子进程中命令执行结束后回收子进程的资源如果是后台执行命令,则父进程不进行阻塞等待,可继续向下…

【ArcGIS微课1000例】0123:数据库中要素类批量转为shapefile

除了ArcGIS之外的其他GIS平台,想要打开ArcGIS数据库,可能无法直接打开,为了便于使用shp,建议直接将数据库中要素类批量转为shapefile。 文章目录 一、连接至数据库二、要素批量转shp一、连接至数据库 打开ArcMap,或者打开ArcCatalog,找到数据库连接,如下图: 数据库为个…

Laravel邮件发送:从配置到发邮件的指南!

Laravel邮件发送功能如何实现?怎么使用Laravel发信? Laravel作为一款流行的PHP框架,提供了强大且易用的邮件发送功能。AokSend将详细介绍如何从配置到实际发送邮件的全过程,帮助你快速掌握Laravel邮件发送的技巧。 Laravel邮件发…

数据中台!企业的必备还是可有可无?(附数据中台构建完整脑图)

数据中台!企业的必备还是可有可无?(附数据中台构建完整脑图) 前言数据中台 前言 在这个数据爆炸的时代,数据已经成为了企业最宝贵的资产之一。然而,如何有效地管理和利用这些数据,却是许多企业…

Linux驱动开发 ——架构体系

只读存储器(ROM) 1.作用 这是一种非易失性存储器,用于永久存储数据和程序。与随机存取存储器(RAM)不同,ROM中的数据在断电后不会丢失,通常用于存储固件和系统启动程序。它的内容在制造时或通过…

Blender软件三大渲染器Eevee、Cycles、Workbench对比解析

Blender 是一款强大的开源3D制作平台,提供了从建模、雕刻、动画到渲染、后期制作的一整套工具,广泛应用于电影、游戏、建筑、艺术等领域。 渲染101云渲染云渲6666 相比于其他平台,如 Autodesk Maya、3ds Max 或 Cinema 4D,Blende…

Error when custom data is added to Azure OpenAI Service Deployment

题意:在向 Azure OpenAI 服务部署添加自定义数据时出现错误。 问题背景: I receive the following error when adding my custom data which is a .txt file (it doesnt matter whether I add it via Azure Cognitive Search, Azure Blob Storage, or F…

药用植物的空间多组学:从生物合成途径到工业应用-文献精读51

Spatial multi-omics in medicinal plants: from biosynthesis pathways to industrial applications 药用植物的空间多组学:从生物合成途径到工业应用 摘要 随着分子测序和成像技术的快速发展,药用植物的多组学研究进入了单细胞时代。我们讨论了空间多…

振弦式渗压计常见故障有哪些?怎么解决?

振弦式渗压计是一种用于测量结构物或土体内部渗透水压力的仪器,广泛应用于土木工程、水利工程及环境监测领域。在使用过程中,可能会遇到一些常见的故障,以下是一些故障及其解决方法: 1. 读数不稳定: - 确保渗压计安装在…

React18入门教程

React介绍 React由Meta公司开发,是一个用于 构建Web和原生交互界面的库 React的优势 相较于传统基于DOM开发的优势 组件化的开发方式 不错的性能 相较于其它前端框架的优势 丰富的生态 跨平台支持 React的市场情况 全球最流行,大厂必备 开发环境…

2024年数学建模比赛题目及解题代码

目录 一、引言 1. 1竞赛背景介绍 1.1.1数学建模竞赛概述 1.1.2生产过程决策问题在竞赛中的重要性 1.2 解题前准备 1.2.2 工具与资源准备 1.2.3 心态调整与策略规划 二、问题理解与分析 三、模型构建与求解 3.1 模型选择与设计 3.1.1 根据问题特性选择合适的数学模型类…

线程池的执行流程和配置参数总结

一、线程池的执行流程总结 提交线程任务;如果线程池中存在空闲线程,则分配一个空闲线程给任务,执行线程任务;线程池中不存在空闲线程,则线程池会判断当前线程数是否超过核心线程数(corePoolSize&#xff09…