本文将介绍如何使用Arduino来制造一个自动平衡机器人,并介绍其控制原理。
自平衡机器人的控制原理
自平衡机器人
自平衡机器人听名字比较高大上,但实际生活中其中已经可以看到很多成品了,像比较早期高端的赛格威,满大街跑的小米平衡车等,都属于自平衡机器人的范畴。那他是如何工作的呢?首先我们要知道,自平衡机器人为了让机器人保持平衡,电机的运动必须要能抵消机器人的自然重力导致的姿态变化,比如倾倒、后仰等。要实现这个抵消的动作平衡机器人就需要能反馈并纠正这些变化的因素。在本项目中,反馈元件是MPU6050及其内置的陀螺仪和加速度计,它在三个轴上都提供加速度和旋转反馈功能。
Arduino控制器通过它来知道机器人当前的姿态信息,从而通过这些信息控制电机和车轮的运动,使机器人能够保持平衡。
感知到机器人的倾斜,通过驱动车轮来保持机器人直立。
自平衡机器人的电路原理图
自平衡机器人的电路原理图
在电路连接方面,首先我们要将MPU6050连接到Arduino,可查阅:Arduino如何使用MPU6050,然后参照上图所示连接其余的组件。L298N模块可以为Arduino提供所需的+5V(详情见:Arduino直流电机控制教程中的相关内容)。本文中,我们选择了单独的电源为电机和电路供电。请注意,如果您计划为L298N模块使用大于+12V的电源电压,您需要移除刚好位于+12V输入上方的跳线。
构建机器人
机器人框架
机器人框架采用一些简单的材料黏贴完成,如果有3D打印机也可以尝试设计一款适合自己风格外壳框架。具体可参阅:设计自平衡机器人。
机器人主电路板
主电路板主要包括ArduinoNano和MPU6050和一块L298N电机驱动板。
自平衡机器人本质上是一个倒立钟摆。如果质量中心相对于轮轴较高,则可以更好地平衡。较高的质心意味着较高的质量惯性矩,这对应于较低的角加速度。这就是为什么我把电池组放在上面。然而,机器人的高度是根据材料的可用性来选择的。制作时不必过分强求。
PID控制原理
在控制中,保持某个变量的稳定需要一个称为PID的特殊控制器。这些参数都存在“增益”的问题,通常称为Kp、Ki和Kd。PID需要在期望值之间进行校正。输入和输出之间的差异称为“误差”。PID控制器通过不断调整输出将误差减小到最小值。在本文的Arduino自平衡机器人中,这些值的设置遵循以下原则:
使KpKiKd等于0。
Kp的调整:Kp值太低会使机器人摔倒,因为没有足够的校正值。太大的Kp会使机器人疯狂地来回移动。一个合适的Kp值会使机器人稍微来回移动或者稍微摆动即可。
设定好Kp值后,需要调整Kd值。适当的Kd值可以减小振动,直到机器人基本稳定。同时,即使它被推动,适当的Kd值也能让机器人始终保持站立。
最后就是设置Ki值。因为我们即使设定了Kp和Kd值,机器人在打开电源时也会发生振动,但它会在一定的时间后稳定下来。正确的Ki值可以缩短机器人稳定的时间。
Arduino自平衡机器人代码
要成功实现代码安装,需要四个外部库文件来让Arduino自平衡机器人正常工作。PID库的引入使得计算P、I和D值变得相对容易。LMotorController库文件用于L298N模块驱动两个电机,I2Cdev库和MPU6050_6_Axis_MotionApps20库用于从MPU6050读取数据。你可以在这里下载包含这些库文件的代码。
#include
#include
#include""
#include"MPU6050_6Axis_"
#ifI2CDEV_IMPLEMENTATION==I2CDEV_ARDUINO_WIRE
#include""
#endif
#defineMIN_ABS_SPEED20
MPU6050mpu;
//MPUcontrol/statusvars
booldmpReady=false;//settrueifDMPinitwassuccessful
uint8_tmpuIntStatus;//holdsactualinterruptstatusbytefromMPU
uint8_tdevStatus;//returnstatusaftereachdeviceoperation(0=success,!0=error)
uint16_tpacketSize;//expectedDMPpacketsize(defaultis42bytes)
uint16_tfifoCount;//countofallbytescurrentlyinFIFO
uint8_tfifoBuffer[64];//FIFOstoragebuffer
//orientation/motionvars
Quaternionq;//[w,x,y,z]quaternioncontainer
VectorFloatgravity;//[x,y,z]gravityvector
floatypr[3];//[yaw,pitch,roll]yaw/pitch/rollcontainerandgravityvector
//PID
doubleoriginalSetpoint=173;
doublesetpoint=originalSetpoint;
doublemovingAngleOffset=0.1;
doubleinput,output;
//adjustthesevaluestofityourowndesign
doubleKp=50;
doubleKd=1.4;
doubleKi=60;
PIDpid(input,output,setpoint,Kp,Ki,Kd,DIRECT);
doublemotorSpeedFactorLeft=0.6;
doublemotorSpeedFactorRight=0.5;
//MOTORCONTROLLER
intENA=5;
intIN1=6;
intIN2=7;
intIN3=8;
intIN4=9;
intENB=10;
LMotorControllermotorController(ENA,IN1,IN2,ENB,IN3,IN4,motorSpeedFactorLeft,motorSpeedFactorRight);
volatileboolmpuInterrupt=false;//indicateswhetherMPUinterruptpinhasgonehigh
voiddmpDataReady()
{
mpuInterrupt=true;
}
voidsetup()
{
//joinI2Cbus(I2Cdevlibrarydoesn'tdothisautomatically)
#ifI2CDEV_IMPLEMENTATION==I2CDEV_ARDUINO_WIRE
();
TWBR=24;//400kHzI2Cclock(200kHzifCPUis8MHz)
#elifI2CDEV_IMPLEMENTATION==I2CDEV_BUILTIN_FASTWIRE
Fastwire::setup(400,true);
#endif
();
devStatus=();
//supplyyourowngyrooffsetshere,scaledforminsensitivity
(220);
(76);
(-85);
(1788);//1688factorydefaultformytestchip
//makesureitworked(returns0ifso)
if(devStatus==0)
{
//turnontheDMP,nowthatit'sready
(true);
//enableArduinointerruptdetection
attachInterrupt(0,dmpDataReady,RISING);
mpuIntStatus=();
//setourDMPReadyflagsothemainloop()functionknowsit'sokaytouseit
dmpReady=true;
//getexpectedDMPpacketsizeforlatercomparison
packetSize=();
//setupPID
(AUTOMATIC);
(10);
(-255,255);
}
else
{
//ERROR!
//1=initialmemoryloadfailed
//2=DMPconfigurationupdatesfailed
//(ifit'sgoingtobreak,usuallythecodewillbe1)
(F("DMPInitializationfailed(code"));
(devStatus);
(F(")"));
}
}
voidloop()
{
//ifprogrammingfailed,don'ttrytodoanything
if(!dmpReady)return;
//waitforMPUinterruptorextrapacket(s)available
while(!mpuInterruptfifoCountpacketSize)
{
//nompudata-performingPIDcalculationsandoutputtomotors
();
(output,MIN_ABS_SPEED);
}
//resetinterruptflagandgetINT_STATUSbyte
mpuInterrupt=false;
mpuIntStatus=();
//getcurrentFIFOcount
fifoCount=();
//checkforoverflow(thisshouldneverhappenunlessourcodeistooinefficient)
if((mpuIntStatus0x10)||fifoCount==1024)
{
//resetsowecancontinuecleanly
();
(F("FIFOoverflow!"));
//otherwise,checkforDMPdatareadyinterrupt(thisshouldhappenfrequently)
}
elseif(mpuIntStatus0x02)
{
//waitforcorrectavailabledatalength,shouldbeaVERYshortwait
while(fifoCountpacketSize)fifoCount=();
//readapacketfromFIFO
(fifoBuffer,packetSize);
//trackFIFOcounthereincasethereis1packetavailable
//(thisletsusimmediatelyreadmorewithoutwaitingforaninterrupt)
fifoCount-=packetSize;
(q,fifoBuffer);
(gravity,q);
(ypr,q,gravity);
input=ypr[1]*180/M_PI+180;
}
}
本文中的Kp、Ki、Kd的值可能适用也可能不适用你的机器人。如果不适用,那么按照上面PID控制原理中提到的方法来设当调整。注意,代码中的倾斜值设置为173度。如果有必要,你可以更改这个值,这是机器人必须保持的倾斜角度。此外,如果你的电机速度太快,你可以适当调整motorSpeedFactorLeft和motorSpeedFactorRight值,一个成功的DIY是需要不断调整的,重点是要有耐心,最后希望大家都能完成自己的自平衡机器人。