无人驾驶虚拟仿真(十)--车辆姿态控制(PID)

简介:在上一节内容中,我们估算出了车辆姿态,主要是位置偏差d和航向角偏差phi,出现偏差就需要矫正,根据物理常识我们知道不可能马上矫正,需要一个矫正的过程,图像采集处理又有一个时间间隔,在这个时间间隔里还会出现矫正过度,需要反向矫正的情况,再加上车辆本身的动力偏差,道路的影响等,我们无法通过简单计算来让车辆达到误差为0的状态,因此我们引入PID控制算法,目的是实现车辆姿态控制参数自动化调整。

比例积分微分控制(proportional-integral-derivative control),简称PID控制,是最早发展起来的控制策略之一,由于其算法简单、鲁棒性好和可靠性高,被广泛应用于工业过程控制。其原理简单来说,就是根据给定值和实际输出值构成控制偏差,将偏差按比例积分微分通过线性组合构成控制量,对被控对象进行控制。

PID控制器各校正环节的作用如下:

比例环节:即时成比例地反应控制系统的偏差信号e(t),偏差一旦产生,控制器立即产生控制作用以减小误差。当偏差e=0时,控制作用也为0。因此,比例控制是基于偏差进行调节的,即有差调节。

积分环节:能对误差进行记忆,主要用于消除静差,提高系统的无差度,积分作用的强弱取决于积分时间常数Ti,Ti越大,积分作用越弱,反之则越强。

微分环节:能反映偏差信号的变化趋势(变化速率),并能在偏差信号值变得太大之前,在系统中引入一个有效的早期修正信号,从而加快系统的动作速度,减小调节时间。

从时间的角度讲,比例作用是针对系统当前误差进行控制,积分作用则针对系统误差的历史,而微分作用则反映了系统误差的变化趋势,这三者的组合是“过去、现在、未来”的完美结合。

详细的PID控制原理不再多说,大家可以到网上查看,接下来我们来说明如何在我们的系统中实现车辆姿态控制,由于PID控制也是一个多帧数据迭代计算的过程,不方便单独拆开来看效果,与上一节相似,还是以源码注解的方式来说明:

1、新建功能包

进入工作空间目录:

$ cd ~/myros/catkin_ws/src

创建新功能包:

$ catkin_create_pkg car_control rospy duckietown_msgs std_msgs

创建配置文件:

$ mkdir -p car_control/config/car_control_node

$ touch car_control/config/car_control_node/default.yaml

新建启动脚本:

$ mkdir -p car_control/launch

$ touch car_control/launch/start.launch

新建源码文件

$ touch car_control/src/car_control_node.py

修改编译配置文件

$ gedit car_control/CMakeLists.txt

修改为:


2、编辑配置文件

$ gedit car_control/config/car_control_node/default.yaml

v_bar: 0.4
k_d: -10
k_theta: -5
k_Id: 0.3
k_Iphi: 0.1
theta_thres: 0.523
d_thres: 0.2615
d_offset: 0.02
omega_ff: 0
integral_bounds: {'d': {'top': 0.3,'bot': -0.3},'phi': {'top': 1.2, 'bot': -1.2}}
d_resolution: 0.011
phi_resolution: 0.051
stop_line_slowdown: {'start': 0.6,'end': 0.15}

3、编辑源码文件

$ gedit car_control/src/car_control_node.py

#!/usr/bin/env python3

import rospy
import numpy as np
import time

from std_msgs.msg import Float32,Float32MultiArray, Bool, Header
from duckietown_msgs.msg import Twist2DStamped, LanePose, ButtonEvent, WheelsCmdStamped
from sensor_msgs.msg import Range

class CarControlNode:
    def __init__(self):
        rospy.init_node("car_control_node", anonymous=False)
        #车辆速度基数
        self.v_bar = rospy.get_param('~v_bar', default=0.4)
        #横向位置误差控制比例参数
        self.k_d = rospy.get_param('~k_d', default=-10)
        #航向角控制比例参数
        self.k_theta = rospy.get_param('~k_theta', default=-5)
        #横向位置误差控制积分参数
        self.k_Id = rospy.get_param('~k_Id', default=0.3)
        #航向角控制积分参数
        self.k_Iphi = rospy.get_param('~k_Iphi', default=0.1)
        #航向角偏差上限
        self.theta_thres = rospy.get_param('~theta_thres', default=0.523)
        #横向位置误差积分偏差上限
        self.d_thres = rospy.get_param('~d_thres', default=0.2615)
        #横向位置偏移量(米)
        self.d_offset = rospy.get_param('~d_offset', default=0.02)
        #角度偏移量
        self.omega_ff = rospy.get_param('~omega_ff', default=0.0)
        #积分项界限
        self.integral_bounds = rospy.get_param('~integral_bounds', default={'d': {'top': 0.3,'bot': -0.3},'phi': {'top': 1.2, 'bot': -1.2}})
        #横向位置估计的分辨率
        self.d_resolution = rospy.get_param('~d_resolution', default=0.011)
        #航向角估计的分辨率
        self.phi_resolution = rospy.get_param('~phi_resolution', default=0.051)
        #停车线处减速的起点和终点距离
        self.stop_line_slowdown = rospy.get_param('~stop_line_slowdown', default={'start': 0.6,'end': 0.15})
        
        #横向位置误差
        self.d_I = 0.0
        #当前航向角误差
        self.phi_I = 0.0
        #上一次横向位置误差
        self.prev_d_err = 0.0
        #上一次航向角误差
        self.prev_phi_err = 0.0
        
        self.last_s = None
        self.action = np.array([0.0, 0.0])
        #停车线检测结果--检测到为true
        self.red_line_sotp_falg = False
        #红绿灯检测结果--红灯为true
        self.traffic_light_flag = False
        #距离雷达检测结果--距离小于限值为true
        self.tof_stop_flag = False
        #按键检测,状态反向切换
        self.button_flag = False
        self.tof_count = 0
        self.wheels_cmd_executed = WheelsCmdStamped()
        self.wheels_cmd_executed.vel_left = 0.2
        self.wheels_cmd_executed.vel_right = 0.2
        
        rospy.Subscriber("~err", LanePose, self.cb_err)
        rospy.Subscriber('~wheels_cmd', WheelsCmdStamped, self.cd_wheels_cmd)
        rospy.Subscriber("~range", Range, self.cb_tof_info)
        rospy.Subscriber("~detected", Bool, self.cb_stop_line)
        rospy.Subscriber("~tlState", Bool, self.cb_traffic_light)
        rospy.Subscriber('~event', ButtonEvent, self.cd_button_event)
        self.pub_action = rospy.Publisher("~car_cmd", Twist2DStamped, queue_size=10)
    
    def cb_err(self, msg):
        current_s = time.time()
        dt = None
        #计算时间差
        if self.last_s is not None:
            dt = (current_s - self.last_s)
        #计算实际横向位置误差
        d_err = msg.d - self.d_offset
        #航向角误差
        phi_err = msg.phi
        #横向积分误差太大时限制一下
        if np.abs(d_err) > self.d_thres:
            print("d_err too large, thresholding it!", 'error')
            d_err = np.sign(d_err) * self.d_thres
        wheels_cmd_exec = [self.wheels_cmd_executed.vel_left, self.wheels_cmd_executed.vel_right]
        #计算控制参数
        self.action = self.compute_control_action(d_err, phi_err, dt, wheels_cmd_exec)
        self.action[1] += self.omega_ff
        self.last_s = current_s
        action_msg = Twist2DStamped()
        #判断是否需要停车
        if((self.red_line_sotp_falg and self.traffic_light_flag) or self.tof_stop_flag or self.button_flag):
            action_msg.v = 0
            action_msg.omega = 0
        else:
            action_msg.v = self.action[0]
            action_msg.omega = self.action[1]
        header = msg.header
        header.frame_id = 'car_control_frame'
        action_msg.header = header
        self.pub_action.publish(action_msg)
    #停车线检测处理
    def cb_stop_line(self, msg):
        self.red_line_sotp_falg = msg.data
    #红绿灯检测处理
    def cb_traffic_light(self, msg):
        self.traffic_light_flag = msg.data
    #距离传感器数据处理,连续5次检测到距离小于0.15米,停车
    def cb_tof_info(self, msg):
        dist = msg.range
        if(dist>0 and dist<0.15):
            self.tof_count += 1
            if self.tof_count>=5:
                self.tof_stop_flag = True
        else:
            self.tof_count = 0
            self.tof_stop_flag = False
    #按钮事件处理,按一次启动,再按一次停止
    def cd_button_event(self, msg):
        if self.button_flag:
            self.button_flag = False
        else:
            self.button_flag = True
    #车轮控制结果反馈
    def cd_wheels_cmd(self, msg):
        self.wheels_cmd_executed = msg
    #计算控制参数
    def compute_control_action(self, d_err, phi_err, dt, wheels_cmd_exec):
        if dt is not None:
            #横向位置和航向角误差进行积分计算
            self.integrate_errors(d_err, phi_err, dt)

        #调整横向位置和航向角积分误差
        self.d_I = self.adjust_integral(d_err, self.d_I, self.integral_bounds["d"], self.d_resolution)
        self.phi_I = self.adjust_integral(phi_err,self.phi_I,self.integral_bounds["phi"],self.phi_resolution,)

        #误差值符号变化或者车辆停止,消除误差积分
        self.reset_if_needed(d_err, phi_err, wheels_cmd_exec)

        #缩放线性参数,使其速度实际值为固定值
        omega = (self.k_d * d_err+ self.k_theta * phi_err+ self.k_Id * self.d_I+ self.k_Iphi * self.phi_I)

        self.prev_d_err = d_err
        self.prev_phi_err = phi_err

        return [self.v_bar, omega]

    #横向位置和航向角误差进行积分计算
    def integrate_errors(self, d_err, phi_err, dt):
        self.d_I += d_err * dt
        self.phi_I += phi_err * dt
    #如果误差正负符号改变或机器人完全停止,则重置“d”和“phi”中的积分错误
    def reset_if_needed(self, d_err, phi_err, wheels_cmd_exec):
        if np.sign(d_err) != np.sign(self.prev_d_err):
            self.d_I = 0
        if np.sign(phi_err) != np.sign(self.prev_phi_err):
            self.phi_I = 0
        if wheels_cmd_exec[0] == 0 and wheels_cmd_exec[1] == 0:
            self.d_I = 0
            self.phi_I = 0

    #调整积分误差,使其保持在定义的范围内,如果误差小于误差估计的分辨率,则将其取消
    @staticmethod
    def adjust_integral(error, integral, bounds, resolution):
        if integral > bounds["top"]:
            integral = bounds["top"]
        elif integral < bounds["bot"]:
            integral = bounds["bot"]
        elif abs(error) < resolution:
            integral = 0
        return integral

if __name__=='__main__':
    node = CarControlNode()
    rospy.spin()

4、编辑启动脚本

$ gedit car_control/launch/start.launch

<launch>
  <arg name="veh"/>
  <arg name="pkg_name" value="car_control"/>
  <arg name="node_name" value="car_control_node"/>
  <arg name="param_file_name" default="default" doc="Specify a param file. ex:megaman"/>
  <arg name="required" default="false" />

  <group ns="$(arg veh)">
    <remap from="car_control_node/err" to="lane_filter_node/err"/>
    <remap from="car_control_node/stopFlag" to="red_line_node/stopFlag"/>
    <remap from="car_control_node/range" to="front_center_tof_driver_node/range"/>
    <remap from="car_control_node/car_cmd" to="duckiebot_node/car_cmd"/>
    <remap from="car_control_node/detected" to="stop_line_node/detected"/>
    <remap from="car_control_node/tlState" to="traffic_light_node/tlState"/>
    <remap from="car_control_node/event" to="button_driver_node/event"/>
    <node name="$(arg node_name)" pkg="$(arg pkg_name)" type="$(arg node_name).py" respawn="true" respawn_delay="10" output="screen" required="$(arg required)">
    </node>
  </group>
</launch>

5、编译

$ cd ~/myros/catkin_ws

$ catkin_make


6、编辑多节点启动文件

$ gedit start.launch

<launch>
  <arg name="veh" default="duckiebot"/>
  <group>
    <include file="$(find duckiebot)/launch/duckiebot.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find anti_instagram)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find line_detect)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find lane_filter)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
    <include file="$(find car_control)/launch/start.launch">
      <arg name="veh" value="$(arg veh)"/>
    </include>
  </group>
</launch>

修改duckiebot_node,源码中:

修改为:

配置文件map-name更换loop_empty,这样程序运行时将打开车辆行驶画面。


7、启动程序

$ source devel/setup.bash

$ roslaunch start.launch

到此为止,我们完成了无人驾驶虚拟仿真从图像采集到姿态控制所有流程,形成了一个闭环的系统,后续章节我们将在这个闭环控制的基础上,增加一些新的功能。

若有收获,就点个赞吧

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
仿真模拟-pid.rar是一个压缩文件,其中包含了使用PID控制算法进行仿真模拟的相关代码和文件。PID控制算法是一种常用的控制算法,可以在工业自动化领域中应用于各种控制系统中。 该压缩文件中可能包含了一些仿真模拟的软件或者代码,用于实现PID控制算法的仿真模拟。通过仿真模拟,可以在计算机中对真实的控制系统进行模拟,通过调整PID参数等参数,观察系统的响应和性能,以此来优化和改进控制系统的设计。 PID控制算法基于对系统输出和期望输出之间的差别进行计算,并通过调整控制信号来减小这个差别,从而使系统的输出接近期望输出。PID控制算法由比例控制、积分控制和微分控制三个部分组成,通过调整这三个部分的比例系数、积分系数和微分系数,可以得到不同的控制效果。 通过这个压缩文件,我们可以学习和了解PID控制算法的原理和实现方式,可以进行一些简单的仿真模拟实验,并根据实验结果来调整和优化PID参数。这对于学习和研究控制系统以及优化工业自动化过程非常有帮助。同时,该压缩文件也可能包含一些使用其他仿真软件进行仿真模拟的代码或文件,具体内容需要解压后进行查看。 总之,仿真模拟-pid.rar是一个用于实现PID控制算法的仿真模拟工具,通过它可以学习和实践PID控制算法,进一步提升对控制系统的理解和应用能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

溪风沐雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值