
这次,让我们更深入地了解ARDUINO(AVR)的一些关键库方法的实现,这些方法负责移动MIRO机器人。 对于想知道如何在配备了最简单编码器电机的ARDUINO上如何控制机器人的线性和角速度的每个人,这部分都将很感兴趣。
目录:
第1部分 ,
第2 部分 ,
第3 部分 ,
第4 部分 ,
第5部分 。
就解释如何,什么以及为什么而言,负责里程表行驶的方法仍然很痛苦。 关于控制机器人的运动,您需要了解的第一件事是一个显而易见的事实,即机器人的集电器在没有额外调整的情况下永远不会以相同的速度旋转。 不同的离合器,不同的驱动通道输出特性,稍微不同的电动机和变速箱中的润滑。
您应该了解并知道的第二个事实是,即使齿轮比足够大,发动机中也会存在惯性。 即 当从电动机端子上去除电压时,即使没有加载,车轮也会移动几度。 额外旋转的大小取决于车轮上的加载力,缓解应力之前的转速以及相同的不可见因素,例如变速箱中的润滑剂类型和数量。
这些事实决定了与配备里程表传感器(在MIRO的情况下,每个车轮的数字编码器)的底盘运动相关的一组方法的实现。
正如我们在第四部分中发现的那样,在软件模型中有
Chassis类,该类实现单个底盘引擎的旋转控制。 我要强调-不是控制底盘,小车的运动,而是控制小车的引擎。 在
Robot和
Miro类中实现对手推车的直接控制。
让我们从上面开始。 以下是
Miro类的一种方法,该方法以给定的线性(
lin_speed ,m / s)和角(
ang_speed ,deg / s)速度实现机器人一定距离(
dist ,
meter )的运动。
我们尚未注意
en_break参数。
int Miro::moveDist(float lin_speed, float ang_speed, float dist, bool en_break) { float _wheelSetAngSpeed[WHEEL_COUNT]; _wheelSetAngSpeed[LEFT] = MIRO_PI2ANG * (lin_speed - (ROBOT_DIAMETER * ang_speed / (2 * MIRO_PI2ANG))) / WHEEL_RADIUS; _wheelSetAngSpeed[RIGHT] = MIRO_PI2ANG * (lin_speed + (ROBOT_DIAMETER * ang_speed / (2 * MIRO_PI2ANG))) / WHEEL_RADIUS; float _wheelSetAng[WHEEL_COUNT]; _wheelSetAng[RIGHT] = _wheelSetAngSpeed[RIGHT] * dist / lin_speed; _wheelSetAng[LEFT] = _wheelSetAngSpeed[LEFT] * dist / lin_speed; return this->chassis.wheelRotateAng(_wheelSetAngSpeed, _wheelSetAng, en_break); }
在这种方法中,首先计算左引擎和右引擎所需的角速度。 根据相当明显的公式,这不是要推论的问题。 仅需记住,该方法中的线速度以米/秒为单位,角速度以度/秒为单位(不是弧度)。 因此,我们预先计算常数
MIRO_PI2ANG = 57.29 = 180 / pi。 ROBOT_DIAMETER-机器人左右车轮之间的距离(以米为单位),
WHEEL_RADIUS-车轮半径(也以米为单位)。 此类情况下的所有数字常量都包含在defs.h文件中,而机械手和机箱自定义参数则包含在config.h文件中。
之后,计算每个车轮必须转动的角度,以便机器人行驶距离
dist (也以米为单位)。
因此,在此阶段,我们需要以何种速度和角度旋转机器人底盘的每个轮子。 然后,
调用 底盘对象的
wheelRotateAng()方法。
wheelRotateAng方法
(float *速度,float * ang,bool en_break)用于使机器人车轮以由
speed []数组指定的角速度(以m / s为单位)旋转由
ang []数组指定的角度(以度为单位)。 最后一个参数
en_break (我们之前已经见过)通过向车轮施加短期反向电压来设置车轮转弯后硬停止的要求。 这对于抑制机器人的惯性是必要的,以防止机器人在从电机上移除控制电压后已经移动超过所需距离。 当然,为了完全满足要求,可以使用
wheelRotateAngRad()方法,类似于
wheelRotateAng() ,不同之处在于,它以弧度和弧度每秒的旋转角度和角速度的值作为参数。
wheelRotateAng()方法的算法如下。
1.首先,检查
速度[]和
ang []的值与某些边界条件的对应关系。 显然,底盘在车轮的最大旋转角速度和最小旋转速度上都有物理限制。 同样,
ang []中的角度不能小于由编码器的精度确定的最小固定旋转角度。
2.接下来,计算每个车轮的旋转方向。 显然通过产品符号
ang [i] * speed [i] ;
3.计算每个车轮的“旋转距离”
Dw [i] -要旋转给定
ang [i]必须完成的编码器样本数量。
该值由以下公式确定:
Dw [i] = ang [i] * WHEEL_SEGMENTS / 360 ,
其中
WHEEL_SEGMENTS是编码器轮的段数(完整旋转)。
4.记录电机驱动器上的电压值。
关于发动机上的电压* PWM用于控制电动机的旋转,因此,为了知道提供给每个电动机的电压,必须知道电动机驱动器的供电电压。 在MIRO机器人中,驱动器直接连接到电池电源电路。 函数float getVoltage(); 从分压器返回电压,其分压系数为VOLTAGE_DIVIDER。 ADC参考电压:5V。 此刻,机器人中的VOLTAGE_DIVIDER的值为2,并且来自电池组(1S)的电压被提供给ADC输入(PIN_VBAT)。 由于电池组可能以不同的方式放电并失去平衡,因此这并不完全正确,但是,正如实践证明的那样,在电池不断充电且保持平衡的情况下,该解决方案非常有效。 将来,我们计划制造一个带有两罐电池的普通分隔器。
5.根据每个车轮的校准表,确定PWM信号的初始值,以确保车轮以所需的
速度[i]旋转。 什么样的校准表以及它来自何处-我们将进一步分析。
6.根据计算出的速度和旋转方向开始发动机的旋转。 在类实现的文本中,
_wheel_rotate_sync()私有方法对此负责。
我们更加深入。
_wheel_rotate_sync()方法根据以下算法工作:
1.在无限循环中,进行检查以实现每个车轮转弯距离
Dw [i]的编码器响应的计数。 如果达到了计数器
Dw [i]中的任何一个,则所有轮子都会停止并退出循环,然后退出功能(步骤5)。 这样做的原因如下。 由于测量旋转角度的不连续性,当通过将一个非整数值四舍五入到较小的一侧并将第二个车轮的
Dw [j]舍入到一个较大的整数来获得一个车轮的计算距离
Dw [i]时,这是一种非常常见的情况。 这导致以下事实:在停止一个车轮之后,第二个车轮继续转弯。 对于带有差速器驱动器的机箱(以及许多其他驱动器),这会导致机器人在任务结束时意外“转向”。 因此,在组织整个底盘的空间运动的情况下,必须立即停止所有发动机。
2.如果未达到
Dw [i] ,则在循环中检查编码器的下一个操作的事实(变量
_syncloop [w] ,从编码器中断更新并在此无限循环中复位)。 当下一个相交发生时,程序将根据显而易见的公式计算每个车轮当前角速度的模块(度/秒):
W [i] =(360 * tau [i])/ WHEEL_SEGMENTS ,
其中:
tau [i] -编码器的最后两个响应之间的时间平均值。 平均滤波器的“深度”由
MEAN_DEPTH确定,默认为8。
3.根据计算出的车轮速度,将绝对误差计算为设定角速度与实际角速度之间的差。
4.根据计算出的误差,对每个电机的控制动作(PWM信号值)进行校正。
5.达到
Dw [i]后 ,在激活
en_break的情况下,向电机施加反向的短期电压。 这种影响的持续时间由校准表(请参见下文)确定,通常为15到40毫秒。
6.引擎中的压力已完全释放,并退出
_wheel_rotate_sync() 。
我已经提到过两次校准表。 因此,库中有一个特殊的值表,该值表存储在机器人存储器的EEPROM中,并包含三个相关值的记录:
1.电机端子上的电压。 通过将PWM信号的值转换为实际电压来计算。 为此,在
wheelRotateAng()方法的步骤4中,记录了发动机驱动器上的实际电压。
2.与给定电压相对应的车轮旋转角速度(空载)。
3.硬停止信号的持续时间与该角速度相对应。
默认情况下,校准表的大小为10条记录(由
config.h文件中的常量
WHEEL_TABLE_SIZE确定)-值“电压-角速度-停止信号的持续时间”的10个三倍。
为了从该表的2和3项中确定值,使用了一种特殊方法
-wheelCalibrate(字节轮) 。
让我们来看看它。 此方法执行一系列操作,以确定发动机/车轮校准表中的缺失值,以及找出启动的最小角速度和车轮的最大角速度。
为了执行校准,将机器人安装在支架上;在校准过程中,所有车轮旋转均在无负载的情况下进行。
1.首先,您需要确定最小启动速度。 这非常简单。 在一个循环中,控制PWM从0开始以1的增量馈送给引擎。在每个步骤中,程序都会等待一段时间,该时间由常量
WHEEL_TIME_MAX (正常
延迟() )确定。 等待时间过去之后,它将检查启动是否已完成(通过更改编码器计数器的值)。 如果完成折叠,则将计算车轮的旋转角速度。 为了更加确定,将10值加到与该启动速度相对应的PWM值上,从而得出第一对值“电动机上的电压”-“角速度”。
2.找到启动速度后,将计算PWM阶跃以均匀填充校准表。
3.在该循环中,对于每个新的PWM值,将车轮旋转2整圈,并根据类似于
_wheel_rotate_sync()方法的算法测量角速度。 在同一周期中,也通过逐次逼近,测量硬停止信号持续时间的最佳值。 最初,一些明显的巨大价值。 然后在“转弯停止”模式下进行测试。 作为最佳选择,选择停止信号持续时间的最大值,在该最大值处不超过设置的“转弯距离”。 换句话说,这种信号持续时间的值一方面被抑制了,然后抑制了惯性,另一方面,没有短期的反向运动(由相同的编码器确定)。
4.校准完成后,将不再向被校准的电动机施加控制电压,并且该车轮的校准表记录在EEPROM中。
我省略了各种琐碎的实现方式,并试图说明其本质。 您可能会注意到,
wheelRotateAng()和
wheelRotateAngRad()方法是阻塞函数。 这是运动准确性和相当简单地集成到用户草图中的代价。 可以制作一个固定时间的小型任务管理器,但这将要求用户严格地将其功能嵌入到分配的时间配额中。
对于非阻塞应用程序,API具有功能
wheelRotate(float * speed) 。 从参数列表中可以看出,它仅以设定的速度执行车轮的旋转。 然后,通过机器人底盘的
Sync()方法调整旋转速度,该方法在同名的Miro类对象的
Sync()方法中调用。 并且根据对用户草图结构的要求,应将此方法称为ARDUINO草图的主
循环()循环的每次迭代。
在步骤4中,在
_wheel_rotate_sync()方法的描述中,我提到了引擎的“控制校正”。 你怎么猜的? 这是PID控制器)。 好吧,更准确地说是PD控制器。 如您所知(实际上-并非总是如此),确定调节器系数的最佳方法是选择。 config.h配置文件中有一个定义:
#define DEBUG_WHEEL_PID
如果取消注释,则在调用Miro类的
moveDist()方法时,将在机器人控制台中显示以下控制机器人轮子之一(左)的角速度时的相对误差的反向图形。

不一样吗? 向下是时间(每个小节是控制周期的一个步骤),错误值保存在右侧(保留符号)。 这是具有相同比例的两对图,其中PD控制器的系数不同。 “驼峰”只是超调的“浪潮”。 水平条上的数字是相对误差(保留符号)。 调节器的简单可视化,有助于手动调节系数。 随着时间的流逝,我希望可以进行自动设置,但是现在。
这是一个骗子:-)
好吧,最后,让我们看一个例子。 直接从API_Miro_moveDist库:
#include <Miro.h> using namespace miro; byte PWM_pins[2] = { 5, 6 }; byte DIR_pins[2] = { 4, 7 }; byte ENCODER_pins[2] = { 2, 3 }; Miro robot(PWM_pins, DIR_pins, ENCODER_pins); int laps = 0; void setup() { Serial.begin(115200); } void loop() { for (unsigned char i = 0; i < 4; i++) { robot.moveDist(robot.getOptLinSpeed(), 0, 1, true); delay(500); robot.rotateAng(0.5*robot.getOptAngSpeed(), -90, true); delay(500); } Serial.print("Laps: "); Serial.println(laps); laps++; }
从程序的文本来看,所有内容都应该清楚。 工作原理-在视频中。
600 x 600毫米瓷砖和5毫米瓷砖间隙。 从理论上讲,机器人应该绕着一个边长为1米的正方形。 当然,轨迹“漂浮”。 公平地说,值得一提的是,在我留给机器人进行测试的版本中,有相当多的旋转引擎很难缓慢驱动。 但是在高速和打滑的情况下,仍有一个地方,惯性并不容易应对。 齿轮比较高的发动机(例如即使在我们的MIRO机器人中,只是在测试过程中没有使用),其性能也应有所提高。
如果有无法理解的时刻-我很高兴澄清,讨论和改进。 反馈通常很有趣。