步驟1:
1。框架:我的框架只是將兩個(gè)鋁制伺服支架用螺栓固定到兩個(gè)垂直的膠合板上,并與伺服支架固定在一起。框架的構(gòu)成或配置方式實(shí)際上并不重要。您可能應(yīng)該將其調(diào)高一點(diǎn),然后將電池放在頂部-多少錢總是一個(gè)問題,太高了,電動(dòng)機(jī)將沒有足夠的扭矩來使車輪足夠快地旋轉(zhuǎn),過低又可能使電動(dòng)機(jī)太慢而無法轉(zhuǎn)動(dòng)抓住機(jī)器人的傾斜。一塊水平的膠合板底部裝有Arduino Uno和電機(jī)控制器。
2。馬達(dá):我使用了兩個(gè)無處不在的黃色齒輪馬達(dá)和車輪,每個(gè)到處都可以找到,價(jià)格分別為幾美元。它們的轉(zhuǎn)速約為110 rpm,足以平衡,但如果轉(zhuǎn)速約為200或300 rpm,那就太好了。它們的齒輪傾斜度很小,因此機(jī)器人總是會(huì)有點(diǎn)擺動(dòng)。在將它們連接到電機(jī)控制器之前,您可能應(yīng)該將兩個(gè)電機(jī)引線互相纏繞,以防止雜散電磁場(chǎng)干擾Arduino。在電動(dòng)機(jī)引線兩端連接幾個(gè)電容器也是一個(gè)好主意。我用幾個(gè)拉鏈把電動(dòng)機(jī)固定在車架上,效果很好。
3。馬達(dá)控制器:我使用了L293D迷你控制器,我非常喜歡它,因?yàn)槲铱梢允褂靡粋€(gè)2s鋰電池為控制器供電,該控制器還可以為Arduino Uno供電-無需第二個(gè)電池。輕巧的重量減輕器和輕巧的重量,意味著機(jī)器人更容易平衡。
4。 MPU6050陀螺儀/加速度計(jì):這是一個(gè)不錯(cuò)的小模塊,用于測(cè)量機(jī)器人的傾斜角度。調(diào)用函數(shù)非常簡(jiǎn)單。我將我的機(jī)器人安裝在arduino和機(jī)器人的傾斜軸上方。有些人說應(yīng)該更高些,有些人說應(yīng)該更低些,但是可以找到它在哪里。
5。 Arduino Uno:神經(jīng)網(wǎng)絡(luò)將輕松以2k運(yùn)行。
6。電源開關(guān):連接電源開關(guān)以打開和關(guān)閉電池真的很值得。使機(jī)器人的使用變得比每次都要插入電池更容易。
7。 LIPO電池:我使用800mah 2s電池為所有電池供電。電池壽命通常約為連續(xù)運(yùn)行20分鐘或更長(zhǎng)時(shí)間。足夠用于測(cè)試和玩耍。
8。原理圖:最后一張照片是我連接所有模塊和電機(jī)的示意圖。
步驟2:加載并運(yùn)行Arduino草圖
1。 MPU6050校準(zhǔn):在實(shí)際運(yùn)行機(jī)器人之前,首先需要進(jìn)行的是陀螺儀/加速度計(jì)的校準(zhǔn)。下載位于以下位置的校準(zhǔn)草圖:http://forum.arduino.cc/index.php?action = dlattach; 。..在執(zhí)行之前,將您的機(jī)器人筆直站立,并在校準(zhǔn)程序運(yùn)行時(shí)不要移動(dòng)它。除非您碰巧將MPU6050移動(dòng)到機(jī)器人上的新位置,否則您只需運(yùn)行一次校準(zhǔn)例程。
運(yùn)行時(shí),它將向Arduino串行監(jiān)視器輸出6個(gè)值需要三個(gè)才能放入草圖。
2。 NeuralNet-SelfBalancingRobot草圖:將以下草圖加載到Arduino Uno。您需要將GYRO/ACC參數(shù)更改為校準(zhǔn)運(yùn)行中的參數(shù)。然后運(yùn)行草圖,查看機(jī)器人是否平衡。我的機(jī)器人會(huì)在地毯或床上保持相當(dāng)不錯(cuò)的平衡,但會(huì)四處運(yùn)行,然后掉落在光滑的地板上。
我為我的機(jī)器人設(shè)置了PID代碼,其平衡與Neuro Net略有不同但是使用NN基本上沒有調(diào)整,只需加載草圖即可平衡。 PID例程需要大量的操作。
我可以將我的PID控制器上傳到SB機(jī)器人,而無需進(jìn)行任何修改即可比較PID與NN軟件。 NN會(huì)在平衡點(diǎn)附近以較小的振蕩獲勝,但會(huì)在受到干擾的情況下輸給PID。但是我還沒有真正調(diào)整NN。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//神經(jīng)網(wǎng)絡(luò)程序,使用S型函數(shù)并應(yīng)用于簡(jiǎn)單的自平衡機(jī)器人
//由商洛大學(xué)Jim Demello創(chuàng)建,2018年5月
//改編自Sean Hodgins神經(jīng)網(wǎng)絡(luò)代碼:https://www.instructables.com/id/Arduino-Neural-Ne。
/修改了midhun_s自平衡機(jī)器人代碼:https://www.instructables.com/id/Arduino-Self-Bala.。.
/構(gòu)建了我自己的自平衡機(jī)器人
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include“ MPU6050.h”
#包括“ math.h”
/**************************************** **********************************
網(wǎng)絡(luò)配置-為每個(gè)網(wǎng)絡(luò)自定義
************************************************** ****************/
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} ,//左右左右發(fā)光
//{0,1,0,0},//左右左右發(fā)光
//{1,1,1,0} ,//頂部,左側(cè)和右側(cè)的燈光
};
const float Target [PatternCount] [OutputNodes] = {
{0,},////左傾斜
{1,}//右傾斜
//{-1,}//左移動(dòng)
//{0.65, 0.55},//LEFT MOTOR SLOW
//{0.75,0.5},//LEFT MOTOR FASTER
};
/***** ************************************************** ***********
終端網(wǎng)絡(luò)配置
********************** ***************/
int i,j,p,q,r;
int ReportEvery1000;
int RandomizedIndex [PatternC ount];
長(zhǎng)時(shí)間訓(xùn)練周期;
浮動(dòng)Rando;
浮動(dòng)誤差= 2;
浮動(dòng)累積;
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;
字節(jié)數(shù)= 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設(shè)置為反向
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(“啟動(dòng)程序”);
randomSeed(analogRead(A1));//收集一個(gè)隨機(jī)ADC樣本以進(jìn)行隨機(jī)化。
ReportEvery1000 = 1;
for(p = 0; p
RandomizedIndex [ p] = p;
}
Serial.println(“ do train_nn”);
train_nn();
delay( 1000);
//將電動(dòng)機(jī)控制和PWM引腳設(shè)置為輸出模式
pinMode(leftMotorPWMPin,OUTPUT);
pinMode(leftMotorDirPin,OUTPUT);
pinMode(rightMotorPWMPin,OUTPUT);
pinMode(rightMotorDirPin,OUTPUT);
//初始化MPU6050并設(shè)置偏移值
mpu.initialize();
mpu.setYAccelOffset(2113);//通過校準(zhǔn)例程
mpu.setZAccelOffset(1122);
mpu.setXGyroOffset(7);
Serial.print(“ End在以下位置初始化MPU: “); Serial.println(米利斯());
}
///////////////
/主循環(huán)/
/////////////
void loop(){
drive_nn();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
/使用了訓(xùn)練有素的神經(jīng)網(wǎng)絡(luò)要驅(qū)動(dòng)機(jī)器人
void drive_nn()
{
Serial.println(“ Running NN Drive”);
while(Error 《成功){
currentMillis = millis();
float TestInput [] = {0,0};
if(currentMillis-previousMillis》 loopTimer) {//每5毫秒或更長(zhǎng)時(shí)間進(jìn)行一次計(jì)算
Serial.print(“ currentMillis =”); Serial.println(currentMillis);
/////////////////////////////////////
//計(jì)算incli的角度國(guó)家//
//////////////////////////////////////////
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;
///////////////////////////////////////////////////////////////////
//補(bǔ)充過濾器///////////////////////////////////////////
////////////////////////////////////////////////////////////////////
currentAngle = 0.9934 *(prevAngle + gyroAngle)+ 0.0066 *(accAngle);
//Serial.print(“currentAngle=“); Serial.print(currentAngle); Serial.print(“ error =”); Serial.println(error);
//錯(cuò)誤= 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;//轉(zhuǎn)換為0到1
//Serial.print(“ testinput =”); Serial.println(TestInput [0]);
InputToOutput(TestInput [0]) ;//輸入到ANN以獲取輸出
//Serial.print(”output =“); Serial.println(Output [0]);
///////////////////////////////////////////
//在之后設(shè)置電動(dòng)機(jī)功率約束它//
///////////////////////////////////////////
motorPower =輸出[0] * 100;//從0轉(zhuǎn)換為1
//如果(motorPower 《50)motorPower = motorPower * -1;
motorPower = map(motorPower,0,100,-300,300 );
motorPower = motorPower +(motorPower * 6.0);//需要乘數(shù)以使車輪在接近平衡點(diǎn)時(shí)足夠快地旋轉(zhuǎn)
//Serial.print(“motorPower =“); Serial.println(motorPower);
motorPower = constrain(motorPower,-255,255);
prevAngle = currentAngle;
previousMillis = currentMillis;
}//結(jié)束毫秒循環(huán)
//如果(abs(error)》 30)motorPower = 0;//如果跌落則關(guān)閉電動(dòng)機(jī)
//motorPower = motorPower + error;
setMotors(motorPower,motorPower);
}
}//drive_nn()函數(shù)的結(jié)尾
///在培訓(xùn)時(shí)顯示信息
無效到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(“”);
}
/********************* **************
計(jì)算隱藏層激活
***************************************** *********************************/
for(i = 0; i
Accum = HiddenWeights [InputNodes] [i];
for(j = 0; j
累計(jì)+ =輸入[p] [j] * HiddenWeights [j] [i];
}
隱藏[i] = 1.0/(1.0 + exp(-Accum));//激活功能
}
/****************************** ******************************************
計(jì)算輸出層激活并計(jì)算錯(cuò)誤
******************************************* ***************************/
用于(i = 0; i
累計(jì)= OutputWeights [HiddenNodes] [i];
for(j = 0; j 《隱藏節(jié)點(diǎn); 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;//不使用
/****************************************** ****************************
計(jì)算隱藏層激活
**** ************************************************** ************/
for(i = 0; i
Accum = HiddenWeights [InputNodes] [i];
for(j = 0; j
累計(jì)+ = TestInput [j] * HiddenWeights [j] [i];
}
隱藏[i] = 1.0/(1.0 + exp(-Accum));
}
/********* ************************************************** *******
計(jì)算輸出層激活并計(jì)算錯(cuò)誤
********************** ***************/
for(i = 0; i
Accum = OutputWeights [HiddenNodes] [i];
for(j = 0; j
累計(jì)+ =隱藏[j] * OutputWeights [j] [i];
}
輸出[i] = 1.0/(1.0 + exp(-Accum));
}
//#ifdef調(diào)試
Serial.print(“輸出”);
對(duì)于(i = 0 ;我
Serial.print(Output [i],5);
Serial.print(“”);
}
//#endif
}
//訓(xùn)練神經(jīng)網(wǎng)絡(luò)
void train_nn(){
/*** ************************************************** *************
初始化HiddenWeights和ChangeHiddenWeights
******************* ***************/
int prog_start = 0;
Serial.println(“開始培訓(xùn)。..”);
//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();
/****************************************** ****************************
開始訓(xùn)練
****** ************************************************** **********/
用于(TrainingCycle = 1; TrainingCycle 《2147483647; TrainingCycle ++){
/*********** ************************************************** *****
隨機(jī)分配訓(xùn)練模式的順序
************************** ********************************************/
用于( p = 0; p
q = random(PatternCount);
r = RandomizedIndex [p];
RandomizedIndex [p] = RandomizedIndex [q];
RandomizedIndex [q] = r;
}
錯(cuò)誤= 0.0;
/*************************************** **************************************
以隨機(jī)順序遍歷每種訓(xùn)練模式
》
************************************************** ********************/
為(q = 0; q
p = RandomizedIndex [q];
/************************* **********************************************
隱藏計(jì)算層激活
********************************************* *****************************/
//digitalWrite(LEDYEL,LOW);
表示(i = 0; i
累計(jì)= HiddenWeights [InputNodes] [i];
for(j = 0; j
累計(jì)+ =輸入[p] [j] *隱藏重量[j] [i];
}
隱藏[i] = 1.0/(1.0 + exp(-Accum));
}
//digitalWrite(LEDYEL,HIGH);
/*********** ************************************************** *****
計(jì)算輸出層激活并計(jì)算錯(cuò)誤
************************ *************/
//digitalWrite(LEDRED,LOW);
for(i = 0; i
Accum = OutputWeights [HiddenNodes] [i];
for(j = 0; j
累計(jì)+ =隱藏[j] * OutputWeights [j] [i];
}
Output [i] = 1.0/(1.0 + exp(-Accum));
OutputDelta [i] =(Target [p] [i]-Output [ i])*輸出[i] *(1.0-輸出[i]);
錯(cuò)誤+ = 0.5 *(目標(biāo)[p] [i]-輸出[i])*(目標(biāo)[p] [i]-Output [i]);
}
//Serial.println(Output [0] * 100);
//digitalWrite( LEDRED,HIGH);
/***************************************** *********************************
向后傳播到隱藏層的錯(cuò)誤
** ************************************************** **************/
//digitalWrite(LEDYEL,LOW);
for(i = 0;我
累計(jì)= 0.0;
對(duì)于(j = 0; j
累計(jì)+ = OutputWeights [i] [j ] * OutputDelta [j];
}
HiddenDelta [i] =累積*隱藏[i] *(1.0-隱藏[i]);
}
//digitalWrite(LEDYEL,HIGH);
/************************* **********************************************
更新內(nèi)部-》隱藏重量
****************************************** ********************************/
//digitalWrite(LEDRED,LOW);
for(i = 0; i
ChangeHiddenWeights [InputNodes] [i] = LearningRate * HiddenDelta [i] +動(dòng)量* ChangeHiddenWeights [InputNodes] [i];
HiddenWeights [InputNodes] [i] + = ChangeHiddenWeights [InputNodes] [i];
for(j = 0; j
ChangeHiddenWeights [ j] [i] =學(xué)習(xí)率*輸入[p] [j] * HiddenDelta [i] +動(dòng)量* ChangeHiddenWeights [j] [i];
HiddenWeights [j] [i] + = ChangeHiddenWeights [j ] [i];
}
}
//digitalWrite(LEDRED,HIGH);
/************************************************* *********************
隱藏更新-》輸出權(quán)重
******** ************************************************** ********/
//digitalWrite(LEDYEL,LOW);
for(i = 0;我
ChangeOutputWeights [HiddenNodes] [i] =學(xué)習(xí)率* OutputDelta [i] +動(dòng)量* ChangeOutputWeights [HiddenNodes] [i];
OutputWeights [HiddenNodes] [i] ] + = ChangeOutputWeights [HiddenNodes] [i];
for(j = 0; j
ChangeOutputWeights [j] [i] = LearningRate * Hidden [ j] * OutputDelta [i] +動(dòng)量* ChangeOutputWeights [j] [i];
OutputWeights [j] [i] + = ChangeOutputWeights [j] [i];
}
}
//digitalWrite(LEDYEL,HIGH);
}
/********** ************************************************** ******
每100個(gè)周期將數(shù)據(jù)發(fā)送到終端進(jìn)行顯示并在OLED上繪制圖形
*************** ************************************************** */
ReportEvery1000 = ReportEvery1000-1;
如果(ReportEvery1000 == 0)
{
int graphNum = TrainingCycle/100 ;
int graphE1 =錯(cuò)誤* 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;
}
}
/******* ************************************************** *********
如果錯(cuò)誤率小于預(yù)定閾值,則結(jié)束
*************** ************************************************** */
如果(錯(cuò)誤《成功)中斷;
}
}
步驟3:最終注釋
1。這些參數(shù)可能只需要一點(diǎn)點(diǎn)就可以播放,尤其是可以增加NN輸出值的乘法器。當(dāng)電動(dòng)機(jī)接近平衡時(shí),必須使用該倍增器來提高電動(dòng)機(jī)的轉(zhuǎn)速。事實(shí)證明,這幾乎迫使機(jī)器人成為爆炸式,平衡式機(jī)器人。如果在平衡點(diǎn)附近的電動(dòng)機(jī)的值不夠高,則機(jī)器人將在電動(dòng)機(jī)具有足夠的rpm來捕捉下降之前倒下。
2。也許可以使用比S形函數(shù)更好的激活函數(shù)。有人說tanf函數(shù)更有用。我認(rèn)為真正需要的只是一個(gè)簡(jiǎn)單的f(x)函數(shù)。對(duì)這個(gè)領(lǐng)域的任何人都會(huì)真正感興趣。
3。這是一個(gè)簡(jiǎn)單的單輸入,多個(gè)隱藏節(jié)點(diǎn)和單個(gè)輸出神經(jīng)網(wǎng)絡(luò),而且肯定會(huì)產(chǎn)生過大的殺傷力,因?yàn)镻ID控制器會(huì)更簡(jiǎn)單,并且您實(shí)際上可以使用僅一行代碼的簡(jiǎn)單P控制器來達(dá)到平衡。但是,我不必像PID控制器那樣對(duì)這個(gè)NN進(jìn)行調(diào)整,所以這很酷。使用更多的輸入將很有趣,您可以簡(jiǎn)單地將陀螺儀的值設(shè)置為兩個(gè)輸入,而將加速度計(jì)設(shè)置為三個(gè)輸入神經(jīng)網(wǎng)絡(luò)的另一個(gè)。然后,您將不需要補(bǔ)充過濾器,因?yàn)樯窠?jīng)網(wǎng)絡(luò)將充當(dāng)過濾器。不確定如何操作,但嘗試可能很有趣。
-
機(jī)器人
+關(guān)注
關(guān)注
210文章
28078瀏覽量
205776 -
Arduino
+關(guān)注
關(guān)注
187文章
6456瀏覽量
186473
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論