當(dāng)前位置:首頁>新聞資訊>行業(yè)新聞

arduino pid麥克納姆輪小車程序詳解

發(fā)表時間:2021-09-14 10:00:00 人氣:6808

之前發(fā)過幾個帖子,大家可以參考,但經(jīng)過多次嘗試,整套系統(tǒng)升級了,優(yōu)化了很多地方目錄:

(1)程序定義部分

(2)程序初始化部分

(3)AB霍爾傳感器對電機轉(zhuǎn)速,正反的讀取

(4)sbus接收機信號讀取與處理

(5)電機驅(qū)動與控制

(6)PID調(diào)制

機甲大師代碼詳解:

一 定義部分

#include 
#include 
#include
FutabaSBUS sbus;
Servo yaw;
Servo roll;
int yawval,rollval;
int rcsig[25];
const int ma1=9;//電機A接口9
const int ma2=2;//電機A接口2
const int mb1=3;//電機B接口1
const int mb2=4;//電機B接口2
const int mc1=5;//以此類推
const int mc2=6;
const int md1=7;
const int md2=8;//電機D接口2
//-----------------上面定義電機PWM信號輸出口
const int ma_in=20;//電機A轉(zhuǎn)速輸入口
const int mb_in=19;//電機B轉(zhuǎn)速輸入口
const int mc_in=18;//以此類推
const int md_in=21;
const int Q1=28;//B相輸入
const int Q2=26;
const int Q3=22;
const int Q4=24;
bool rev1,rev2,rev3,rev4;//方向
//-----------------上面是中斷讀取霍爾傳感器轉(zhuǎn)速端口
int mafor,mago,maturn;
int mbfor,mbgo,mbturn;
int mcfor,mcgo,mcturn;
int mdfor,mdgo,mdturn;
//-----------------4電機的旋轉(zhuǎn),平移,前后值
const int L=1015,R=1035;
double M1PWMOUT,M2PWMOUT,M3PWMOUT,M4PWMOUT;//M1輸出pwm,M2輸出pwm,以此類推
double ref1,ref2,ref3,ref4;//四個電機的參考轉(zhuǎn)速
double in1,in2,in3,in4,M1S,M2S,M3S,M4S;//in1-4是脈沖的個數(shù),M1-4S是轉(zhuǎn)換成轉(zhuǎn)速(沒有單位)后的值
double Kp=6, Ki=1.3, Kd=0.1;  //PID系數(shù)
unsigned long t;
PID M1PID(&M1S,&M1PWMOUT,&ref1,Kp, Ki, Kd, DIRECT); //定義PID類
PID M2PID(&M2S,&M2PWMOUT,&ref2,Kp, Ki, Kd, DIRECT);
PID M3PID(&M3S,&M3PWMOUT,&ref3,Kp, Ki, Kd, DIRECT);
PID M4PID(&M4S,&M4PWMOUT,&ref4,Kp, Ki, Kd, DIRECT); '


以上是引腳定義等部分

首先我們要包括讀取遙控sbus信號的頭文件,pid的頭文件,以及舵機的頭文件

接著定義電機驅(qū)動板的引腳,一個電機在驅(qū)動板上對應(yīng)兩個pwm輸入的引腳,所以對于M1有ma1,ma2兩個引腳輸入pwm信號,同理有mb1,mb2,mc1,mc2,md1,md2分別對應(yīng)其他三個帶電機的引腳


而每個電機有霍爾傳感器,霍爾傳感器有AB兩相輸出,我們用A相發(fā)生電平變化觸發(fā)的外部中斷判斷速度,而用A相觸發(fā)時判斷B的高低,高就代表B在A前面,反之在A后面,從而判斷正反轉(zhuǎn)向

于是我們定義M1電機的A相輸入為ma_in,M2為mb_in,注意這里的引腳需要是單片機上支持外部中斷的引腳

而B相定義為Q1,Q2,Q3,Q4,電機轉(zhuǎn)動的方向定義為rev1,rev2,rev3,rev4

int mafor,mago,maturn;
int mbfor,mbgo,mbturn;
int mcfor,mcgo,mcturn;
int mdfor,mdgo,mdturn;

代表4電機的旋轉(zhuǎn),平移,前后值

后面一坨是Pid的變量以及pid的類

這里采用arduino自帶的PID

二 初始化部分

void setup()
{
   pinMode(ma1,OUTPUT);
   pinMode(ma2,OUTPUT);
   pinMode(mb1,OUTPUT);
   pinMode(mb2,OUTPUT);
   pinMode(mc1,OUTPUT);
   pinMode(mc2,OUTPUT);
   pinMode(md1,OUTPUT);
   pinMode(md2,OUTPUT);
   pinMode(33,OUTPUT);
   pinMode(Q1,INPUT);
   pinMode(Q2,INPUT);
   pinMode(Q3,INPUT);
   pinMode(Q4,INPUT);
   pinMode(35,OUTPUT);
   /************/
   Serial.begin(9600);
   attachInterrupt(digitalPinToInterrupt(ma_in), macount, FALLING);//觸發(fā)信號必須是變化的,上升或下降皆可(外部中斷讀轉(zhuǎn)速)
   attachInterrupt(digitalPinToInterrupt(mb_in), mbcount, FALLING);//觸發(fā)信號必須是變化的,上升或下降皆可
   attachInterrupt(digitalPinToInterrupt(mc_in), mccount, FALLING);//觸發(fā)信號必須是變化的,上升或下降皆可
   attachInterrupt(digitalPinToInterrupt(md_in), mdcount, FALLING);//觸發(fā)信號必須是變化的,上升或下降皆可
   M1PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M1PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M2PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M2PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M3PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M3PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M4PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M4PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M1PID.SetOutputLimits(-255,255);//PID輸出值設(shè)置為-255~255
   M2PID.SetOutputLimits(-255,255);
   M3PID.SetOutputLimits(-255,255);
   M4PID.SetOutputLimits(-255,255);
   yaw.attach(25);
   roll.attach(23);
   buzzer(100);
   delay(100);
   buzzer(100);
   delay(100);
   buzzer(200);
  sbus.begin(Serial3);
   sbus.attachDataReceived(dataReceived);
   t=millis();
}

定義外部中斷,設(shè)置PID,sbus初始化

三 轉(zhuǎn)速讀取

首先,我們要寫外部中斷的觸發(fā)要做的操作,將計數(shù)變量+1,并判斷B相來賦值1,0給rev代表電機轉(zhuǎn)向


這里肯定有人要問 :為什么需要讀取轉(zhuǎn)向,一般PID調(diào)制只需要速率不就行了嗎 [以下幾行看不懂可以不看,不影響后續(xù)閱讀,別把心態(tài)看炸了]

其實是因為這里的速度需要是一個[矢量].因為我用的算法不是先判斷遙控信號對應(yīng)的運動應(yīng)該是前進還是后退再控制電機,而是直接將遙控信號映射到三個運動方向(偏航,平移,前后),這樣可以使運動更平滑,而且可以做出組合動作.但缺點就是控制電機的時候我不知道到底小車要做的是什么運動,所以可能會出現(xiàn)這種情況:一開始某電機需要以-20的轉(zhuǎn)速旋轉(zhuǎn)(即以20的速率反向旋轉(zhuǎn)),后來這臺電機又需要以+30的速度旋轉(zhuǎn)(即以正向的30速率旋轉(zhuǎn)),那么這時[PID算法]中的[誤差值]怎么算?是|30-20|=10還是|30-(-20)|=50?實際上,誤差值應(yīng)該是50,但是如果這里的速率只是個標(biāo)量的話就會算出來10,這樣這臺電機的調(diào)速就會出問題.從另一方面解釋,如果采用了先通過遙控信號判斷運動方向,那么讀取的速度其實[方向]已經(jīng)固定了,所以需要處理的只有[數(shù)值].比如遙控的信號是俯仰+200,那么我就會進入if(俯仰通道有值且為正)這個程序段,這時候速度的方向其實就固定為+了.所以我們就發(fā)現(xiàn)這樣的算法方向改變很生硬,不夠靈活.

void macount()//轉(zhuǎn)速加一
{
  rev1=digitalRead(Q1);
  in1++;
}

然后我們有了總共的脈沖數(shù),但我們要的是速度,也就是單位時間的脈沖數(shù),于是我們就有了這樣的思路:用一個t變量,t=系統(tǒng)時間,每過了30ms將in1賦值給M1S(速度變量),并將in1清零,用于下一個30ms的讀取

if(millis()>t)
  {
    Speed();
    t=millis()+30;
  }


于是有了這樣的代碼

那么Speed()函數(shù)呢?其實也很簡單

void Speed()
{
  GetM1Speed();
  GetM2Speed();
  GetM3Speed();
  GetM4Speed();
}
void GetM1Speed()//刷轉(zhuǎn)速
{
    if(rev1)
      M1S =in1;//M1S變成in1
    else
      M1S=-in1;
    in1 = 0;//輸出速度結(jié)果后清零,記錄下一秒的觸發(fā)次數(shù)
}


如果rev1=1代表電機正轉(zhuǎn),那么轉(zhuǎn)速為正,否則為負

四 遙控器數(shù)據(jù)讀取

我這里采用了天地飛9的遙控和sbus接收機,sbus是一種協(xié)議,它與uart類似,可以與單片機通過串口通信(Serial)

(別看這個點很簡單,我花了很多時間研究這個東西)

有懂航模的朋友就要問了 為什么不直接讀取pwm輸出的接收機 實話告訴你,我一開始就是用的pwm接收機,因為它代碼簡單,讀取方便

但是它有個致命缺點:會被外部中斷干擾,而且讀取一下要2ms,并且精度低

因為pwm接收機是直接插舵機的,相當(dāng)于接收機先接受信號,再轉(zhuǎn)換成pwm輸出,單片機再計時讀取,繞了個大圈

于是我們選擇sbus接收機,雖然代碼量大一些,復(fù)雜一些,但它耗時少,精度高

軟件:我們首先要下載并安裝futabasbus(雖然名字是futaba但其他品牌的sbus也可以用)的庫和streaming庫(sbus示例程序中要用到,但是不包含也問題不大)文末會有下載鏈接

第一步:在setup()中寫這兩句話,初始化sbus

注意! 這里我們發(fā)現(xiàn)有一個Serial3,為什么要用Serial3,為什么呢?因為Serial(針腳0,1)是用來與電腦通信的,如果占用它下載程序和串口監(jiān)視器都會出問題,所以盡量用其他幾個Serial

sbus.begin(Serial3);
   sbus.attachDataReceived(dataReceived);


第二步:在loop()中寫這句話,每次讀取一下信號

sbus.receive();


第三步:然后有這行代碼,代表我們要把讀取的遙控數(shù)據(jù)放到rcsig數(shù)組里面,17,18是兩個數(shù)字通道(我也不知道有什么用)

void dataReceived(ChannelData channels) {
        // do something with the data
        for(int i=1;i<=16;i++)
        {
          rcsig[i]=channels.data[i-1];
        }
        rcsig[17]=channels.channels.channel17;
        rcsig[18]=channels.channels.channel18;
}


硬件: 硬件部分會復(fù)雜一些,因為sbus的信號其實是反的,我們要手工做一個電平取反器

  如上圖,使用S8050三極管反向信號

做好取反器后將輸入連到接收機輸出,取反器輸出連接單片機的rx3(注意要連到rx3,初始化寫的是哪個串口就連到哪個)


上圖是反向前的sbus信號


經(jīng)過反向的sbus信號

可以看到反向后噪波有點大,但經(jīng)過測試不影響讀取

五 電機控制

電機控制是一個比較重要的部分,這里我會介紹一種比較新穎,高級,順滑的(劃掉)控制策略,與一般的控制策略不同

常見的控制方法是判斷遙控數(shù)據(jù)如果低于某個值就后退,高于某個值就前進,但是這樣并不順滑,不適合用于機甲大師.  這種方法的好處不需要知道電機的轉(zhuǎn)向,因為既然我已經(jīng)分類前進后退了,就自然知道轉(zhuǎn)向了

那么它既然不順滑,為了追求完美,自然要找一種新的方法

我們想象一下,第一種控制方法是以遙控器的數(shù)據(jù)分類,那么能不能以電機分類?

于是就想到了這樣的方法,每個電機對應(yīng)一個前進后退方向的值,左右平移方向的值,旋轉(zhuǎn)的值,三個值由遙控器的數(shù)據(jù)算出,疊加后輸出給電機,這樣電機轉(zhuǎn)的會更加平滑!因為換向,變速不需要再經(jīng)過一個遙控數(shù)據(jù)的判斷

轉(zhuǎn)換成代碼如下

if(!((rcsig[2]>L)&&(rcsig[2]L)&&(rcsig[1]L)&&(rcsig[4]L)&&(rcsig[1]L)&&(rcsig[2]L)&&(rcsig[4]


注意這里的ref是有上限和下限的,需要判斷一下然后moving()函數(shù)怎么寫呢?其實就是把pwm輸出給電機

void moving()//移動
{
  if(M1PWMOUT>0)
  {
      analogWrite(ma2,M1PWMOUT);
      digitalWrite(ma1,0);
  }
  else
  {
      analogWrite(ma1,abs(M1PWMOUT));
      digitalWrite(ma2,0);
  }
  if(M2PWMOUT>0)
  {
     analogWrite(mb1,M2PWMOUT);
     digitalWrite(mb2,0);
  }
  else
  {
     analogWrite(mb2,abs(M2PWMOUT));
     digitalWrite(mb1,0);
  }
  if(M3PWMOUT>0)
  {
     analogWrite(mc1,M3PWMOUT);
     digitalWrite(mc2,0);
  }
  else
  {
    analogWrite(mc2,abs(M3PWMOUT));
    digitalWrite(mc1,0);
  }
  if(M4PWMOUT>0)
  {
     analogWrite(md2,M4PWMOUT);
     digitalWrite(md1,0);
  }
  else
  {
     analogWrite(md1,abs(M4PWMOUT));
     digitalWrite(md2,0);
  }
  //Serial.println("moved");
}

復(fù)制代碼


六 PID調(diào)制


看了上面的第五部分,有人肯定有疑問了,這個65是什么?PWMOUT又是怎么算出來的?

且慢,這都是PID的事

想讓小車走的直可以一定程度上轉(zhuǎn)化成讓電機轉(zhuǎn)速一樣,那么現(xiàn)在速度知道了,我們自然想到PID控制

其實PID本身不難,而且arduino有pid庫(文末下載)

第一步:初始化&定義

這一部分放在setup()中


M1PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M1PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M2PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M2PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M3PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M3PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M4PID.SetMode(AUTOMATIC);//設(shè)置PID為自動模式
   M4PID.SetSampleTime(30);//設(shè)置PID采樣頻率為100ms
   M1PID.SetOutputLimits(-255,255);//PID輸出值設(shè)置為-255~255
   M2PID.SetOutputLimits(-255,255);
   M3PID.SetOutputLimits(-255,255);
   M4PID.SetOutputLimits(-255,255);
double M1PWMOUT,M2PWMOUT,M3PWMOUT,M4PWMOUT;//M1輸出pwm,M2輸出pwm,以此類推
double ref1,ref2,ref3,ref4;//四個電機的參考轉(zhuǎn)速
double in1,in2,in3,in4,M1S,M2S,M3S,M4S;//in1-4是脈沖的個數(shù),M1-4S是轉(zhuǎn)換成轉(zhuǎn)速(沒有單位)后的值
double Kp=6, Ki=1.3, Kd=0.1;  //PID系數(shù)
unsigned long t;
PID M1PID(&M1S,&M1PWMOUT,&ref1,Kp, Ki, Kd, DIRECT); //定義PID類
PID M2PID(&M2S,&M2PWMOUT,&ref2,Kp, Ki, Kd, DIRECT);
PID M3PID(&M3S,&M3PWMOUT,&ref3,Kp, Ki, Kd, DIRECT);
PID M4PID(&M4S,&M4PWMOUT,&ref4,Kp, Ki, Kd, DIRECT);


上面這一段是定義PID需要一個參考轉(zhuǎn)速,也就是電機我希望它達到這個轉(zhuǎn)速,p,i,d這三個參數(shù),M1S,M2S,M3S,M4S這四個實際速度,和pwmout,代表輸出的pwm

而65是什么?其實是讓電機達到最大轉(zhuǎn)速讀取它的轉(zhuǎn)速,這就是一個速度的最大值了 注意,正反兩方向的最大轉(zhuǎn)速需要取小的那個---------------------------------------------------------------------------------------------------------------------------------------


此文關(guān)鍵字: pcb行業(yè)

工廠展示

工廠展示 工廠展示

聯(lián)系我們

成都子程新輝電子設(shè)備有限公司

聯(lián)系人:文先生

手機:13183865499

QQ:1977780637

地址:成都市金牛區(qū)星輝西路2號附1號(臺誼民生大廈)407號