[其他ST产品] 基于MATLAB+PID算法实现小车巡线功能

[复制链接]
 楼主| 结合国际经验 发表于 2023-2-28 17:43 | 显示全部楼层 |阅读模式
资源下载地址:https://download.csdn.net/download/sheziqiong/85737921
资源下载地址:https://download.csdn.net/download/sheziqiong/85737921

一、实验原理
本次实验我们采用比例、积分、微分控制, 即 PID 控制, 来对小车的巡线功能进行调节, 更好地控制小车转向时左右两个车轮的速度。该算法的原理如下:

1.1 比例控制 §
比例控制是一种最简单的控制方式。其控制器的输出与输入误差信号成比例关系, 当仅有比例控制时系统输出存在稳态误差。

1.2 积分控制 (I)
在积分控制中, 控制器的输出与输入误差信号的积分成正比关系。对一个自动控制系统, 如果在进入稳态后存在稳态误差, 则称这个控制系统是有稳态误差的。为了消除稳态误差, 在控制器中必须引入“积分项”。积分项对误差取决于时间的积分, 随着时间的增加, 积分项会增大。这样, 即便误差很小, 积 分项也会随着时间的增加而加大, 它推动控制器的输出增大使稳态误差进一步减小, 直到接近于零。因此, 比例+积分 (PI) 控制器, 可以使系统在进入稳态后几乎无稳态误差。

1.3 微分控制 (D)
在微分控制中, 控制器的输出与输入误差信号的微分(即误差的变化率)成正比关系。自动控制系统在克服误差的调节过程中可能会出现振荡甚至失稳。其原因是由于存在有较大惯性组件或有滞后组件, 具有抑制误差的作用, 其变化总是落后于误差的变化。解决的办法是使抑制误差的作用的变化“超前”, 即在误差接近零时, 抑制误差的作用就应该是零。这就是说, 在控制器中仅引入 “比例”项往往是不够的, 比例项的作用仅是放大误差的幅值, 而需要增加的是“微分项”, 它能预测误差变化的趋势, 这样, 具有比例 + 微分的控制器, 就能 够提前使抑制误差的控制作用等于零, 甚至为负值, 从而避免了被控量的严重超调。所以对有较大惯性或滞后的被控对象, 比例+微分 (PD) 控制器能改善系统在 调节过程中的动态特性。

PID 中比例控制 P 是主要的控制方法, 承担了 PID 控制中的大部分任务, 为了消除比例控制 P 留下的静态偏差, 增加了积分控制 I, 而微分控制 D 只为稳定而存在, 其稳定效果应该大于积分控制 I 的失稳效果, 在有大量噪音的系统中, 不适用微分控制 D。PID 控制器是一个完整的三项控制, 能够减少上升空间, 消除静态误差, 减少超调。
————————————————
版权声明:本文为CSDN博主「biyezuopinvip」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/newlw/article/details/124916268

 楼主| 结合国际经验 发表于 2023-2-28 17:46 | 显示全部楼层
代码实现
2.1 准备工作
本次实验我们使用了V-REP提供的matlab接口实现巡线小车。

V-REP会默认造在本地地址的19997端口开放。在matlab代码中实现对该端口的连接:

  1. % 初始化v-rep接口组件
  2. vrep = remApi('remoteApi');     % using the prototype file (remoteApiProto.m)
  3. vrep.simxFinish(-1);            % just in case, close all opened connections
  4. clientID = vrep.simxStart('127.0.0.1',19997,true,true,5000,5);
  5. if clientID < 0
  6.     disp('Failed connecting to remote API server');   
 楼主| 结合国际经验 发表于 2023-2-28 17:46 | 显示全部楼层
同时将V-REP的接口文件remApi.m、remoteApiProto.m和动态链接库remoteApi.dll放置在matlab代码文件LineFollowingBot.m的同一文件夹下,则可以用matlab控制V-REP的仿真。
 楼主| 结合国际经验 发表于 2023-2-28 17:46 | 显示全部楼层
使用matlab启动仿真:
  1. vrep.simxStartSimulation(clientID,vrep.simx_opmode_oneshot);
 楼主| 结合国际经验 发表于 2023-2-28 17:46 | 显示全部楼层
要实现巡线小车,需要获取小车前置摄像头的画面,依据画面进行速度的调整。小车有左、右两边的轮子可以控制。要先获取两边速度和摄像头的句柄,从而之后能对它们进行读取和修改:
  1. % 获取速度和图片的句柄
  2. [res, motorLeft] = vrep.simxGetObjectHandle(clientID,'Pioneer_p3dx_leftMotor',vrep.simx_opmode_blocking);
  3. [res, motorRight] = vrep.simxGetObjectHandle(clientID,'Pioneer_p3dx_rightMotor',vrep.simx_opmode_blocking);
  4. [res, camera] = vrep.simxGetObjectHandle(clientID,'Vision_sensor',vrep.simx_opmode_blocking);
 楼主| 结合国际经验 发表于 2023-2-28 17:47 | 显示全部楼层
控制小车使用PID算法,在这里,我们只考虑当前的误差比例和误差微分。先将当前误差初始化为0:
  1. err = 0;
 楼主| 结合国际经验 发表于 2023-2-28 17:47 | 显示全部楼层
接下来,在无限循环中,获取小车的画面并通过画面进行调整。获取画面的代码如下:
  1. code = 1;
  2. while code
  3.         [code, size, img] = vrep.simxGetVisionSensorImage2(clientID, camera, 1, vrep.simx_opmode_oneshot);
  4. end
 楼主| 结合国际经验 发表于 2023-2-28 17:47 | 显示全部楼层
为了便于操作,调用simxGetVisionSensorImage2函数,使用句柄camera,直接返回黑白画面。如果图像获取成功,则返回值code为0,退出循环,否则获取失败,重新获取。
 楼主| 结合国际经验 发表于 2023-2-28 17:47 | 显示全部楼层
2.2 当前的误差分析
调用getErr函数获取当前误差和转向方向:

  1. [new_err, direct_tmp] = getErr(img);
 楼主| 结合国际经验 发表于 2023-2-28 17:48 | 显示全部楼层
当前误差new_err为利用PD算法控制速度的依据,而direct_tmp为之后的转向提供方向判断,之后会详细说明。
 楼主| 结合国际经验 发表于 2023-2-28 17:48 | 显示全部楼层
getErr的具体实现如下。函数原型为:
  1. function [err, direct] = getErr(img)
 楼主| 结合国际经验 发表于 2023-2-28 17:48 | 显示全部楼层
输入黑白图像img,返回小车当前和预期位置的误差和转向判断值direct。
 楼主| 结合国际经验 发表于 2023-2-28 17:48 | 显示全部楼层
考虑到小车的画面中会出现很多条路,而我们只期望小车走它脚下的路,因此我们希望对所有画面中的路进行一个误差分析:路的位置和小车脚下(即画面中点)的距离作为误差值,如此一来,依据路离中点的远近,就能判断它是否是小车要走的路。需要注意的是,路可能在中点位置的左边或右边,因此误差有正有负。要取误差的绝对值作为判断依据。在所有的路中,有两条路是我们值得关注的(如果画面只有一条路或者没有路,会采取单独的策略):一是离中点最近的路,也就是误差的绝对值最小的路。期望中,小车正在走的路往往正在脚下,因此这条路就是小车在走的路,我们希望小车继续沿着这条路走。二是离中点第二近的路。试想:如果出现了急转弯(角度大于90度的转弯),在逐渐接近转弯点时,小车会看见两条路:转弯前要走的路和转完后要走的路。到达转弯点,两条路合并为一条,再往前走则会偏移当前道路。因此需要判断什么时候进行急转弯、急转弯是左转还是右转。通过分析不难得到:转弯的方向就是转完后要走的路的位置,在左边则左拐,在右边则右拐。而正在转弯前的路和转完后的路迟早会汇合,因此转弯后的路迟早会变为距离中点第二近的路,以此就能判断急转弯的转弯方向。
 楼主| 结合国际经验 发表于 2023-2-28 17:48 | 显示全部楼层
那么如何计算路到画面中点的距离呢?考虑到路是有宽度的,可以找路的最左边和最右边的点的坐标,取均值作为路的位置。而画面大小为480*640,因此取640的一半320作为画面的中点。画面中越靠下的部分离小车越近,因此依据下面画面调整小车状态的优先级更高。我们考虑从下往上一行行遍历,如果当前行找到了路则进行误差分析,否则看上一行是否有路。这样一来就能有效处理间断路的问题。
 楼主| 结合国际经验 发表于 2023-2-28 17:49 | 显示全部楼层
首先初始化以下几个变量:

  1. err = 1000000;
  2. err2 = 1000000;
  3. flag = 0;
  4. direct = 0;
  5. distance = 480;
 楼主| 结合国际经验 发表于 2023-2-28 17:49 | 显示全部楼层
其中err表示离中点最近的路的误差,err2表示离中点第二近的路的误差。注意误差可正可负,其绝对值表示到终点的距离。direct用于记录下一个急转弯预期的转向方向,为1或-1。如果没有转弯倾向则为0。因为找到一条路的当前位置,要找到路的左边和右边两个端点的坐标,flag用来表示下一个是寻找左边端点(值为0)还是右边端点(值为1)。distance表示当前遍历的画面的行号,从最后一行480行开始,依次往上遍历。
 楼主| 结合国际经验 发表于 2023-2-28 17:49 | 显示全部楼层
如果当前行没有找到路,则误差err不会更新,则需要找画面上一行,因此对err的值做一个循环判断:
  1. while err == 1000000
 楼主| 结合国际经验 发表于 2023-2-28 17:49 | 显示全部楼层
  1. for i = 1:640
  2.         if img(distance, i) < 20 && flag == 0
  3.                 l = i;
  4.                 flag = 1;
  5.         end
  6.         if img(distance, i) >20 && flag == 1
  7.                 r = i;
  8.                 flag = 0;
  9.                 dis = (r + l) / 2 - 320;
 楼主| 结合国际经验 发表于 2023-2-28 17:49 | 显示全部楼层
接下来依据误差值dis对err和err2进行更新。如果dis的绝对值小于err,说明新找到的路径就是离中点最近的路径。如果err还是初值1000000,则是找到的第一条路,还没有找到第二条最近的路,direct为0。
 楼主| 结合国际经验 发表于 2023-2-28 17:50 | 显示全部楼层
  1. if abs(dis) < abs(err)
  2.         if err == 1000000
  3.                 direct = 0;
您需要登录后才可以回帖 登录 | 注册

本版积分规则

64

主题

773

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部