电子说
步骤1:
1。框架:我的框架只是将两个铝制伺服支架用螺栓固定到两个垂直的胶合板上,并与伺服支架固定在一起。框架的构成或配置方式实际上并不重要。您可能应该将其调高一点,然后将电池放在顶部-多少钱总是一个问题,太高了,电动机将没有足够的扭矩来使车轮足够快地旋转,过低又可能使电动机太慢而无法转动抓住机器人的倾斜。一块水平的胶合板底部装有Arduino Uno和电机控制器。
2。马达:我使用了两个无处不在的黄色齿轮马达和车轮,每个到处都可以找到,价格分别为几美元。它们的转速约为110 rpm,足以平衡,但如果转速约为200或300 rpm,那就太好了。它们的齿轮倾斜度很小,因此机器人总是会有点摆动。在将它们连接到电机控制器之前,您可能应该将两个电机引线互相缠绕,以防止杂散电磁场干扰Arduino。在电动机引线两端连接几个电容器也是一个好主意。我用几个拉链把电动机固定在车架上,效果很好。
3。马达控制器:我使用了L293D迷你控制器,我非常喜欢它,因为我可以使用一个2s锂电池为控制器供电,该控制器还可以为Arduino Uno供电-无需第二个电池。轻巧的重量减轻器和轻巧的重量,意味着机器人更容易平衡。
4。 MPU6050陀螺仪/加速度计:这是一个不错的小模块,用于测量机器人的倾斜角度。调用函数非常简单。我将我的机器人安装在arduino和机器人的倾斜轴上方。有些人说应该更高些,有些人说应该更低些,但是可以找到它在哪里。
5。 Arduino Uno:神经网络将轻松以2k运行。
6。电源开关:连接电源开关以打开和关闭电池真的很值得。使机器人的使用变得比每次都要插入电池更容易。
7。 LIPO电池:我使用800mah 2s电池为所有电池供电。电池寿命通常约为连续运行20分钟或更长时间。足够用于测试和玩耍。
8。原理图:最后一张照片是我连接所有模块和电机的示意图。
步骤2:加载并运行Arduino草图
1。 MPU6050校准:在实际运行机器人之前,首先需要进行的是陀螺仪/加速度计的校准。下载位于以下位置的校准草图:http://forum.arduino.cc/index.php?action = dlattach; 。..在执行之前,将您的机器人笔直站立,并在校准程序运行时不要移动它。除非您碰巧将MPU6050移动到机器人上的新位置,否则您只需运行一次校准例程。
运行时,它将向Arduino串行监视器输出6个值需要三个才能放入草图。
2。 NeuralNet-SelfBalancingRobot草图:将以下草图加载到Arduino Uno。您需要将GYRO/ACC参数更改为校准运行中的参数。然后运行草图,查看机器人是否平衡。我的机器人会在地毯或床上保持相当不错的平衡,但会四处运行,然后掉落在光滑的地板上。
我为我的机器人设置了PID代码,其平衡与Neuro Net略有不同但是使用NN基本上没有调整,只需加载草图即可平衡。 PID例程需要大量的操作。
我可以将我的PID控制器上传到SB机器人,而无需进行任何修改即可比较PID与NN软件。 NN会在平衡点附近以较小的振荡获胜,但会在受到干扰的情况下输给PID。但是我还没有真正调整NN。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//神经网络程序,使用S型函数并应用于简单的自平衡机器人
//由商洛大学Jim Demello创建,2018年5月
//改编自Sean Hodgins神经网络代码:https://www.instructables.com/id/Arduino-Neural-Ne。
/修改了midhun_s自平衡机器人代码:https://www.instructables.com/id/Arduino-Self-Bala.。.
/构建了我自己的自平衡机器人
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include“ MPU6050.h”
#包括“ math.h”
/**************************************** **********************************
网络配置-为每个网络自定义
************************************************** ****************/
const int PatternCount = 2;
const int InputNodes = 1;
const int Hidd enNodes = 3;
const int OutputNodes = 1;
const float LearningRate = 0.3;
const float Momentum = 0.9;
const float InitialWeightMax = 0.5;
const float Success = 0.0015;
float Input [PatternCount] [InputNodes] = {
{0},//左倾斜
{1}//倾斜
//{-1}//倾斜
//{0,1,1,0} ,//左右左右发光
//{0,1,0,0},//左右左右发光
//{1,1,1,0} ,//顶部,左侧和右侧的灯光
};
const float Target [PatternCount] [OutputNodes] = {
{0,},////左倾斜
{1,}//右倾斜
//{-1,}//左移动
//{0.65, 0.55},//LEFT MOTOR SLOW
//{0.75,0.5},//LEFT MOTOR FASTER
};
/***** ************************************************** ***********
终端网络配置
********************** ***************/
int i,j,p,q,r;
int ReportEvery1000;
int RandomizedIndex [PatternC ount];
长时间训练周期;
浮动Rando;
浮动误差= 2;
浮动累积;
float Hidden [HiddenNodes];
float Output [OutputNodes];
float HiddenWeights [InputNodes + 1] [HiddenNodes];
float OutputWeights [HiddenNodes + 1] [OutputNodes];
float HiddenDelta [HiddenNodes];
float OutputDelta [OutputNodes];
float ChangeHiddenWeights [InputNodes + 1] [HiddenNodes] ;
float ChangeOutputWeights [HiddenNodes +1] [OutputNodes];
#define leftMotorPWMPin 6
#define leftMotorDirPin 7
#define rightMotorPWMPin 5
#define rightMotorDirPin 4
#define sampleTime 0.005
MPU6050 mpu;
int16_t accY,accZ,gyroX;
int motorPower,gyroRate;
float accAngle,gyroAngle,currentAngle,prevAngle = 0,error,prevError = 0,errorSum = 0;
字节数= 0;
long previousMillis = 0;
unsigned long currentMillis;
long loopTimer = 4;
void setMotors(int leftMotorSpeed,int rightMotorSpeed){
//串行.print(“ leftMotorSpeed =”); Serial.print(leftMotorSpeed); Serial.print(“ rightMotorSpeed =”); Serial.println(rightMotorSpeed);
if(leftMotorSpeed》 = 0){
AnalogWrite(leftMotorPWMPin,leftMotorSpeed);
digitalWrite(leftMotorDirPin,LOW);
}
else {//如果leftMotorSpeed为《0,则将dir设置为反向
AnalogWrite(leftMotorPWMPin, 255 + leftMotorSpeed);
digitalWrite(leftMotorDirPin,HIGH);
}
if(rightMotorSpeed》 = 0){
AnalogWrite (rightMotorPWMPin,rightMotorSpeed);
digitalWrite(rightMotorDirPin,LOW);
}
else {
AnalogWrite(rightMotorPWMPin,255 + rightMotorSpeed);
digitalWrite(rightMotorDirPin,HIGH);
}
}
void setup(){
Serial.begin(115200);
Serial.println(“启动程序”);
randomSeed(analogRead(A1));//收集一个随机ADC样本以进行随机化。
ReportEvery1000 = 1;
for(p = 0; p
RandomizedIndex [ p] = p;
}
Serial.println(“ do train_nn”);
train_nn();
delay( 1000);
//将电动机控制和PWM引脚设置为输出模式
pinMode(leftMotorPWMPin,OUTPUT);
pinMode(leftMotorDirPin,OUTPUT);
pinMode(rightMotorPWMPin,OUTPUT);
pinMode(rightMotorDirPin,OUTPUT);
//初始化MPU6050并设置偏移值
mpu.initialize();
mpu.setYAccelOffset(2113);//通过校准例程
mpu.setZAccelOffset(1122);
mpu.setXGyroOffset(7);
Serial.print(“ End在以下位置初始化MPU: “); Serial.println(米利斯());
}
///////////////
/主循环/
/////////////
void loop(){
drive_nn();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
/使用了训练有素的神经网络要驱动机器人
void drive_nn()
{
Serial.println(“ Running NN Drive”);
while(Error 《成功){
currentMillis = millis();
float TestInput [] = {0,0};
if(currentMillis-previousMillis》 loopTimer) {//每5毫秒或更长时间进行一次计算
Serial.print(“ currentMillis =”); Serial.println(currentMillis);
/////////////////////////////////////
//计算incli的角度国家//
//////////////////////////////////////////
accY = mpu.getAccelerationY();
accZ = mpu.getAccelerationZ();
gyroX = mpu.getRotationX();
accAngle = atan2(accY,accZ)* RAD_TO_DEG;
gyroRate = map(gyroX,-32768,32767 ,-250,250);
gyroAngle =(float)gyroRate * sampleTime;
///////////////////////////////////////////////////////////////////
//补充过滤器///////////////////////////////////////////
////////////////////////////////////////////////////////////////////
currentAngle = 0.9934 *(prevAngle + gyroAngle)+ 0.0066 *(accAngle);
//Serial.print(“currentAngle=“); Serial.print(currentAngle); Serial.print(“ error =”); Serial.println(error);
//错误= currentAngle-targetAngle;//不使用
float nnInput = currentAngle;
//Serial.print(“ nnInput =”); Serial.println(nnInput);
nnInput = map(nnInput,-30,30,0,100);//将倾斜角度范围映射到0到100
TestInput [0] = float(nnInput)/100;//转换为0到1
//Serial.print(“ testinput =”); Serial.println(TestInput [0]);
InputToOutput(TestInput [0]) ;//输入到ANN以获取输出
//Serial.print(”output =“); Serial.println(Output [0]);
///////////////////////////////////////////
//在之后设置电动机功率约束它//
///////////////////////////////////////////
motorPower =输出[0] * 100;//从0转换为1
//如果(motorPower 《50)motorPower = motorPower * -1;
motorPower = map(motorPower,0,100,-300,300 );
motorPower = motorPower +(motorPower * 6.0);//需要乘数以使车轮在接近平衡点时足够快地旋转
//Serial.print(“motorPower =“); Serial.println(motorPower);
motorPower = constrain(motorPower,-255,255);
prevAngle = currentAngle;
previousMillis = currentMillis;
}//结束毫秒循环
//如果(abs(error)》 30)motorPower = 0;//如果跌落则关闭电动机
//motorPower = motorPower + error;
setMotors(motorPower,motorPower);
}
}//drive_nn()函数的结尾
///在培训时显示信息
无效到Terminal()
{
for(p = 0; p
Serial.println();
Serial.print(“ Training Pattern:”);
Serial.println(p);
Serial.print(“ Input”);
for(i = 0; i
Serial.print(Input [p] [i],DEC);
Serial.print(“”);
}
Serial.print (“ Target”);
for(i = 0; i
Serial.print(Target [p] [i],DEC);
Serial.print(“”);
}
/********************* **************
计算隐藏层激活
***************************************** *********************************/
for(i = 0; i
Accum = HiddenWeights [InputNodes] [i];
for(j = 0; j
累计+ =输入[p] [j] * HiddenWeights [j] [i];
}
隐藏[i] = 1.0/(1.0 + exp(-Accum));//激活功能
}
/****************************** ******************************************
计算输出层激活并计算错误
******************************************* ***************************/
用于(i = 0; i
累计= OutputWeights [HiddenNodes] [i];
for(j = 0; j 《隐藏节点; j ++){
累计+ =隐藏[j] * OutputWeights [j] [i];
}
输出[i] = 1.0/(1.0 + exp(-Accum));
}
Serial.print(“ Output”);
for(i = 0; i
Serial.print(Output [i],5);
Serial.print(“”);
}
}
}
无效InputToOutput(float In1 )
{
float TestInput [] = {0};
TestInput [0] = In1;
//TestInput [ 1] = In2;//未使用
//TestInput [2] = In3;//未使用
//TestInput [3] = In4;//不使用
/****************************************** ****************************
计算隐藏层激活
**** ************************************************** ************/
for(i = 0; i
Accum = HiddenWeights [InputNodes] [i];
for(j = 0; j
累计+ = TestInput [j] * HiddenWeights [j] [i];
}
隐藏[i] = 1.0/(1.0 + exp(-Accum));
}
/********* ************************************************** *******
计算输出层激活并计算错误
********************** ***************/
for(i = 0; i
Accum = OutputWeights [HiddenNodes] [i];
for(j = 0; j
累计+ =隐藏[j] * OutputWeights [j] [i];
}
输出[i] = 1.0/(1.0 + exp(-Accum));
}
//#ifdef调试
Serial.print(“输出”);
对于(i = 0 ;我
Serial.print(Output [i],5);
Serial.print(“”);
}
//#endif
}
//训练神经网络
void train_nn(){
/*** ************************************************** *************
初始化HiddenWeights和ChangeHiddenWeights
******************* ***************/
int prog_start = 0;
Serial.println(“开始培训。..”);
//digitalWrite(LEDYEL,LOW);
for(i = 0; i
for(j = 0; j 《= InputNodes; j ++){
ChangeHiddenWeights [j] [i ] = 0.0;
Rando = float(random(100))/100;
HiddenWeights [j] [i] = 2.0 *(Rando-0.5)* InitialWeightMax;
}
}
//digitalWrite(LEDYEL,HIGH);
/************ ************************************************** ****
初始化OutputWeights和ChangeOutputWeights
**************************** ******************************************/
//digitalW rite(LEDRED,LOW);
for(i = 0;我
for(j = 0; j 《= HiddenNodes; j ++){
ChangeOutputWeights [j] [i] = 0.0;
Rando = float(random(100))/100;
OutputWeights [j] [i] = 2.0 *(Rando-0.5)* InitialWeightMax;
}
}
//digitalWrite(LEDRED,HIGH);
//SerialUSB.println(”Initial/Untrained Outputs:“);
//toTerminal();
/****************************************** ****************************
开始训练
****** ************************************************** **********/
用于(TrainingCycle = 1; TrainingCycle 《2147483647; TrainingCycle ++){
/*********** ************************************************** *****
随机分配训练模式的顺序
************************** ********************************************/
用于( p = 0; p
q = random(PatternCount);
r = RandomizedIndex [p];
RandomizedIndex [p] = RandomizedIndex [q];
RandomizedIndex [q] = r;
}
错误= 0.0;
/*************************************** **************************************
以随机顺序遍历每种训练模式
》
************************************************** ********************/
为(q = 0; q
p = RandomizedIndex [q];
/************************* **********************************************
隐藏计算层激活
********************************************* *****************************/
//digitalWrite(LEDYEL,LOW);
表示(i = 0; i
累计= HiddenWeights [InputNodes] [i];
for(j = 0; j
累计+ =输入[p] [j] *隐藏重量[j] [i];
}
隐藏[i] = 1.0/(1.0 + exp(-Accum));
}
//digitalWrite(LEDYEL,HIGH);
/*********** ************************************************** *****
计算输出层激活并计算错误
************************ *************/
//digitalWrite(LEDRED,LOW);
for(i = 0; i
Accum = OutputWeights [HiddenNodes] [i];
for(j = 0; j
累计+ =隐藏[j] * OutputWeights [j] [i];
}
Output [i] = 1.0/(1.0 + exp(-Accum));
OutputDelta [i] =(Target [p] [i]-Output [ i])*输出[i] *(1.0-输出[i]);
错误+ = 0.5 *(目标[p] [i]-输出[i])*(目标[p] [i]-Output [i]);
}
//Serial.println(Output [0] * 100);
//digitalWrite( LEDRED,HIGH);
/***************************************** *********************************
向后传播到隐藏层的错误
** ************************************************** **************/
//digitalWrite(LEDYEL,LOW);
for(i = 0;我
累计= 0.0;
对于(j = 0; j
累计+ = OutputWeights [i] [j ] * OutputDelta [j];
}
HiddenDelta [i] =累积*隐藏[i] *(1.0-隐藏[i]);
}
//digitalWrite(LEDYEL,HIGH);
/************************* **********************************************
更新内部-》隐藏重量
****************************************** ********************************/
//digitalWrite(LEDRED,LOW);
for(i = 0; i
ChangeHiddenWeights [InputNodes] [i] = LearningRate * HiddenDelta [i] +动量* ChangeHiddenWeights [InputNodes] [i];
HiddenWeights [InputNodes] [i] + = ChangeHiddenWeights [InputNodes] [i];
for(j = 0; j
ChangeHiddenWeights [ j] [i] =学习率*输入[p] [j] * HiddenDelta [i] +动量* ChangeHiddenWeights [j] [i];
HiddenWeights [j] [i] + = ChangeHiddenWeights [j ] [i];
}
}
//digitalWrite(LEDRED,HIGH);
/************************************************* *********************
隐藏更新-》输出权重
******** ************************************************** ********/
//digitalWrite(LEDYEL,LOW);
for(i = 0;我
ChangeOutputWeights [HiddenNodes] [i] =学习率* OutputDelta [i] +动量* ChangeOutputWeights [HiddenNodes] [i];
OutputWeights [HiddenNodes] [i] ] + = ChangeOutputWeights [HiddenNodes] [i];
for(j = 0; j
ChangeOutputWeights [j] [i] = LearningRate * Hidden [ j] * OutputDelta [i] +动量* ChangeOutputWeights [j] [i];
OutputWeights [j] [i] + = ChangeOutputWeights [j] [i];
}
}
//digitalWrite(LEDYEL,HIGH);
}
/********** ************************************************** ******
每100个周期将数据发送到终端进行显示并在OLED上绘制图形
*************** ************************************************** */
ReportEvery1000 = ReportEvery1000-1;
如果(ReportEvery1000 == 0)
{
int graphNum = TrainingCycle/100 ;
int graphE1 =错误* 1000;
int graphE = map(graphE1,3,80,47,0);
Serial.print(“ TrainingCycle:“);
Se rial.print(TrainingCycle);
Serial.print(“ Error =”);
Serial.println(Error,5);
toTerminal() ;
if(TrainingCycle == 1)
{
ReportEvery1000 = 99;
}
否则
{
ReportEvery1000 = 100;
}
}
/******* ************************************************** *********
如果错误率小于预定阈值,则结束
*************** ************************************************** */
如果(错误《成功)中断;
}
}
步骤3:最终注释
1。这些参数可能只需要一点点就可以播放,尤其是可以增加NN输出值的乘法器。当电动机接近平衡时,必须使用该倍增器来提高电动机的转速。事实证明,这几乎迫使机器人成为爆炸式,平衡式机器人。如果在平衡点附近的电动机的值不够高,则机器人将在电动机具有足够的rpm来捕捉下降之前倒下。
2。也许可以使用比S形函数更好的激活函数。有人说tanf函数更有用。我认为真正需要的只是一个简单的f(x)函数。对这个领域的任何人都会真正感兴趣。
3。这是一个简单的单输入,多个隐藏节点和单个输出神经网络,而且肯定会产生过大的杀伤力,因为PID控制器会更简单,并且您实际上可以使用仅一行代码的简单P控制器来达到平衡。但是,我不必像PID控制器那样对这个NN进行调整,所以这很酷。使用更多的输入将很有趣,您可以简单地将陀螺仪的值设置为两个输入,而将加速度计设置为三个输入神经网络的另一个。然后,您将不需要补充过滤器,因为神经网络将充当过滤器。不确定如何操作,但尝试可能很有趣。
全部0条评论
快来发表一下你的评论吧 !