1. 程式人生 > >基於51微控制器的六足仿生機器人

基於51微控制器的六足仿生機器人

大三上學期的期末設計,全程全手工DIY,歷時一個月,時間主要花在機器人步態的調整,為了讓機器人走得好一點,花了不少功夫,給出設計方案,僅供參考

先來看看視訊效果:連結:http://pan.baidu.com/s/1eR2d1Fc 密碼:8o8h

機器有點簡陋,當時是想要把電池做在機器人裡面的,無奈當初經費有限(100塊,買舵機就花了90多),買的舵機不給力,加上電池後太重,機器站不起來,所以就有了視訊中連著兩條電線。

一、整體框架:

(1)設計功能:

①能完成多方向行走以及其他的自定義的動作。(前進,後撤,左右轉,避障);

②可自動避障;

③通過手機藍芽下令他的下一步動作。

(2)功能框架:

 

(3)使用器材:

①STC89C52微控制器、74LS04(反相器);

②藍芽串列埠通訊模組;

③超聲波測距模組;

④9G舵機18個;

⑤PVC線槽若干(模具);

⑥PCB轉印板;

⑦螺絲螺母若干。

⑦keil3軟體

二、工作原理:

(1)藍芽串列埠通訊模組:

藍芽串列埠通訊模組接收手機藍芽軟體傳送字串訊號,微控制器通過串列埠通訊協議處理藍芽模組接收到的資訊,再根據資訊的內容來判斷機器人將進行的下一步行動。

(2)超聲波測距模組:

超聲波模組向某一方向發射超聲波,在發射時刻的同時開始計時(傳出低電平),超聲波在空氣中傳播,途中碰到障礙物就立即返回來,超聲波接收器收到反射波就立即停止計時(回到高電平),根據低電平的長短來計算測量距離。(超聲波在空氣中的傳播速度為340m/s,根據計時器記錄的時間t,就可以計算出發射點距障礙物的距離(s),即:s=340t/2)

(3)舵機控制:

控制電路板接受來自訊號線的控制訊號,控制電機轉動,電機帶動一系列齒輪組,減速後傳動至輸出舵盤。舵機的輸出軸和位置反饋電位計是相連的,舵盤轉動的同時,帶動位置反饋電位計,電位計將輸出一個電壓訊號到控制電路板,進行反饋,然後控制電路板根據所在位置決定電機轉動的方向和速度,從而達到目標停止。舵機的控制訊號週期為20MS的脈寬調製(PWM)訊號,其中脈衝寬度從0.5-2.5MS,相對應的舵盤位置為0-180度,呈線性變化。也就是說,給他提供一定的脈寬,它的輸出軸就會保持一定對應角度上,無論外界轉矩怎麼改變,直到給它提供一個另外寬度的脈衝訊號,它才會改變輸出角度到新的對應位置上。

在我們的作品中,18路舵機分成2組,分別用一個內部定時器來控制,產生對應舵機的PWM訊號(首先定時器1生成第一個舵機的脈寬,再生成第二個舵機的,到第9個舵機為止,然後定時器2以同樣方式生成剩餘的9個舵機的PWM訊號,以此往復)。

三、製作過程:

(1)模擬原理圖:

 

(2)PCB製作:

 

(3)硬體搭建:

<a>肢體制作:

材料:PVC線槽,PVC板

①模型製作:(純手工割出來的)

 

②舵機改造:

 

③整體:

 

四、除錯以及問題解決:

①結構問題:

我們認為,整體的外形結構是決定作品成敗的關鍵。經過多種材料的試驗,最終我們選擇了容易裁剪、硬度基本滿足的PVC線槽來改裝拼接肢體,軀體使用更厚的塑料板。經歷一週的純手工加工改造後,完成了整個模型的製作。

②供電問題:

由於我們使用的是9G舵機,效能較差,扭力不夠,無法支撐起我們設計的電源與穩壓模組,最後放棄了內嵌的電源,使用實驗室的可調電源箱通過電線來供電,無法獨立開來也是我們的唯一遺憾。

③機器抖動問題

由於89C52只有6個內部中斷,遠遠無法滿足18個舵機的控制,並且其他功能模組也要使用到內部中斷。所以我們將18路舵機分成了2組,初始時一個接一個舵機(每個舵機20ms週期)來發送PWM,但這也產生了傳送一次18路PWM的總週期長度太大(18*20=360ms),足以產生被人眼所察覺的抖動。經過反覆研究,讓當前舵機的PWM訊號在上一個PWM訊號的低電平處開始產生高電平(在上一個PWM的高電平結束後)如下圖,大大縮短了18路舵機一次動作的總週期長度(經過18路後,總週期長度為一個PWM的週期長度約20ms),使抖動無法被人眼所觀察。

程式碼挺多,給出主要的舵機控制程式碼,程式碼看不懂沒關係,後面有解釋:

#include<reg52.h>

#include<intrins.h>

#include<dongzuo.h>

#define ucharunsigned char

#define uintunsigned int

//PWM

sbit PWM0 = P1^0;

sbit PWM1 = P1^1;

sbit PWM2 = P1^2;

sbit PWM3 = P1^3;

sbit PWM4 = P1^4;

sbit PWM5 = P1^5;

sbit PWM6 = P3^4;

sbit PWM7 = P3^5;

sbit PWM8 = P3^6;

sbit PWM9 = P3^7;

sbit PWM10 = P2^0;

sbit PWM11 = P2^1;

sbit PWM12 = P2^2;

sbit PWM13 = P2^3;

sbit PWM14 = P2^4;

sbit PWM15 = P2^5;

sbit PWM16 = P2^6;

sbit PWM17 = P2^7;

//超聲波測距

sfr T2MOD = 0XC9;           //定時器2模式控制暫存器地址

sbit Trig    =P3^2;

sbit Echo   =P3^3;

unsigned intdistance;

uchar DZCS                =0x11;       //控制動作

uchar buf;

uchar sd=3;

bit flag=0;                             //是否傳送字元

bit CSB     =0;                                //超聲波啟動控制位

bit HZ=0;                              //後退後左轉控制位

uchar PWMscan =0;

uchar PWMscan1        =0;

uchar PWMval[]={//初始姿態

        0xf8,0x8f,0xf7,0x05,0xf9,0x8c,/*5*/                0xfa,0x0d,0xf8,0x0b,0xf9,0x67,/*b*/               0xfa,0xd4,0xf7,0x94,0xf9,0xcb,/*11*/

        0xfa,0xad,0xfc,0xdd,0xfb,0x58,/*17*/    0xfa,0xe9,0xfc,0xfc,0xfb,0x39,/*1d*/     0xfc,0x18,0xfc,0xca,0xfb,0x00/*23*/     

};

void delay(uint a)

{

        uchar j;

        for(a;a>0;a--)

                  for(j=0;j<112;j++)

                           ;

}

void task00()

{

        if(PWMscan==1)             //第1路PWM。

        {

                  PWM0=1;

                  TH0=PWMval[0];

                  TL0=PWMval[1];

        }

        else if(PWMscan==2)     //第2路PWM。

        {

                  PWM0=0;

                  PWM1=1;

                  TH0=PWMval[2];       

                  TL0=PWMval[3];

        }

        else if(PWMscan==3)     //第3路PWM。

        {

                  PWM1=0;

                  PWM2=1;

                  TH0=PWMval[4];

                  TL0=PWMval[5];

                  }

        else if(PWMscan==4)     //第4路PWM。

        {

                  PWM2=0;

                  PWM3=1;

                  TH0=PWMval[6];       

                  TL0=PWMval[7];

        }

        else if(PWMscan==5)     //第5路PWM。

        {

                  PWM3=0;

                  PWM4=1;

                  TH0=PWMval[8];       

                  TL0=PWMval[9];

        }

        else if(PWMscan==6)     //第6路PWM。

        {

                  PWM4=0;

                  PWM5=1;

                  TH0=PWMval[10];     

                  TL0=PWMval[11];

        }

        else if(PWMscan==7)     //第7路PWM。

        {

                  PWM5=0;

                  PWM6=1;

                  TH0=PWMval[12];

                  TL0=PWMval[13];

        }

        else if(PWMscan==8)     //第8路PWM。

        {

                  PWM6=0;

                  PWM7=1;

                  TH0=PWMval[14];     

                  TL0=PWMval[15];

        }

        else if(PWMscan==9)     //第9路PWM。

        {

                  PWM7=0;

                  PWM8=1;

                  TH0=PWMval[16];     

                  TL0=PWMval[17];

        }

        else if(PWMscan==10)                   //給一定低電平,將週期拉長

        {

                  PWM8=0;

                  TH0=0xFF;

                  TL0=0xd2;

                  PWMscan=0;

                  TR0  = 0;                             //關定時器0,開定時器1

                  TR1  = 1;

        }

        PWMscan++;

}

void task01()

{

        if(PWMscan1==1)             //第10路PWM。

        {

                  PWM9=1;

                  TH1=PWMval[18];

                  TL1=PWMval[19];

        }

        else if(PWMscan1==2)     //第11路PWM。

        {

                  PWM9=0;

                  PWM10=1;

                  TH1=PWMval[20];

                  TL1=PWMval[21];

        }

        else if(PWMscan1==3)     //第12路PWM。

        {

                  PWM10=0;

                  PWM11=1;

                 TH1=PWMval[22];

                  TL1=PWMval[23];

        }

        else if(PWMscan1==4)     //第13路PWM。

        {

                  PWM11=0;

                  PWM12=1;

                  TH1=PWMval[24];

                  TL1=PWMval[25];

        }

        else if(PWMscan1==5)     //第14路PWM。

        {

                  PWM12=0;

                  PWM13=1;

                  TH1=PWMval[26];

                  TL1=PWMval[27];

        }

        else if(PWMscan1==6)     //第15路PWM。

        {

                  PWM13=0;

                  PWM14=1;

                  TH1=PWMval[28];

                  TL1=PWMval[29];

        }

        else if(PWMscan1==7)     //第16路PWM。

        {

                  PWM14=0;

                  PWM15=1;

                  TH1=PWMval[30];

                  TL1=PWMval[31];

        }

        else if(PWMscan1==8)     //第17路PWM。

        {

                  PWM15=0;

                  PWM16=1;

                  TH1=PWMval[32];

                  TL1=PWMval[33];

        }

        else if(PWMscan1==9)     //第18路PWM。

        {

                  PWM16=0;

                  PWM17=1;

                  TH1=PWMval[34];

                  TL1=PWMval[35];

        }

        else if(PWMscan1==10)     //給一定低電平,將週期拉長

        {

                  PWM17=0;

                  TH1=0xFf;//b1//這是一個大概的值,由於每一組的PWMval的總和(PWMval中定時器的間隔的總和就是一個週期)不一致,

//所以會導致週期不一定是20ms,但大概可以控制在20ms左右,也是因為週期的不固定,所以才需要

                  TL1=0xd2;//e0//調整每一個舵機的實際的佔空比。

                  PWMscan1=0;

                  TR0  = 1;//開定時器0

                 TR1  = 0;//關定時器1

        }

        PWMscan1++;

}

void timer0()interrupt 1

{

         task00();//控制前9路PWM

}

void timer1()interrupt 3

{

        task01();//控制後9路PWM        

}


在實際過程中,或許是由於舵機的質量問題,又或者是其他問題,舵機的角度控制總是難以運用原理上的公式來控制角度,都是實際操作,手動調整高電平的寬度,當達到合適的值的時候,然後再把相應的程式碼記錄下來。

微控制器的高電平寬度是通過定時器的兩個暫存器控制的,所以操作舵機的轉動就變成操作定時器的暫存器,再具體一點就是要得到TH、TL兩個值。(定時器高低位的差值對應高電平的寬度)

在程式碼上,在控制第幾路舵機的時候,TH、TL的值已經定死了為哪一個PWMval[?],比如第18路:

TH1=PWMval[34];

 TL1=PWMval[35];

這將決定此時第18路舵機的轉動角度是多少,那麼怎麼控制下一次該舵機的轉動角度呢?答案很簡單,就是把PWMval[34];PWMval[35];的值修改一下就可以了,其他的舵機同樣是這個道理。所以,機器人的一個姿態就可以變為這樣:機器人姿態→18路舵機的角度→18個TH、TL的值→一個36個元素的陣列PWMval的值。

所以,一個動作姿態就可以用這樣一個函式來確定:

void DZ(ucharPWM[])//動作

{

        uchar i;

        for(i=0;i<36;i++)

                  PWMval[i]=PWM[i];

}

明白了這個之後,就是對每一個姿態收集資料了,在製作過程,我是把TH和TL的兩個值顯示在數碼管上,然後記錄下來的。

後面又加入了藍芽控制模組,超聲波測距,發現51微控制器的定時器不太夠用,改成了52系列的微控制器,還一個定時器即用藍芽模組,又用超聲波測距,現在想來真佩服自己。給出控制程式碼,大家自行研究:

//***************************中斷初始化************************** 

void Init()

{

        TMOD |= 0x11;//定時器0、1      

        ET0 = 1;//使能定時器0中斷

        TR0 = 1;//開啟定時器0,定時器1中斷在定時器0開始後才打開

        ET1   = 1;//使能定時器1中斷

        IT1      = 0;//外部中斷1,低電平觸發 (邊沿高變低)

        EX1  =   1;//開外部中斷1

//定時器2用於波特率的產生

        SCON=0x50;

        PCON=0x00;

        RCAP2H=0xFF;

        RCAP2L=0xDC;//設定波特率為9600

        T2CON=0x34;//將定時器2設定為波特率發生器(接收和傳送都用Timer2) //此處包括啟動T2

        ES=1;         //串列埠中斷

        EA      = 1;//開總中斷

}

void timer0()interrupt 1

{

         task00();//控制前9路PWM

}

void timer1()interrupt 3

{

        task01();//控制後9路PWM        

}

void  serial() interrupt 4

{

        EA=0;                //其餘中斷全停

        if(RI)

        {      

                  RI=0;                   //清除序列接受標誌位                 

                  flag=1;            

                  buf=SBUF;   //從串列埠緩衝區取得資料 (i-0x30)將ASCLL碼轉換成數字

                  switch(buf)

                  {

                           case 0x00:  DZCS=0x00;break;  //向前走

                           case 0x01:  DZCS=0x01;break;  //向後走

                           case 0x02:  DZCS=0x02;break;  //左轉

                           case 0x03:  DZCS=0x03;break;  //右轉

                           case 0x04:  DZCS=0x04;break;  //橫著左

                           case 0x05:  DZCS=0x05;break;  //橫著右

                           case 0x06:  DZCS=0x06;break;  //揮爪子

                           case 0x07:  sd++;break;         //減速,其實就是每個姿態中的延時不一樣

                           case 0x08:  sd--;break;           //加速

                           case 0xff:   CSB=!CSB;break;  //啟動關閉超聲波壁障

                           default:   

                                    DZCS=0x11;break;  // 

                  }      

        }

        EA = 1;    //開啟總中斷

}

void start()// 超聲波測距啟動函式

{

        uchar i;

        Trig=1;

        for(i=0;i<20;i++)

        {

                  _nop_();

        }

        Trig=0;

}

void count()// 超聲波測距函式

{

        unsigned int time,timeH,timeL;

        timeH=TH1;

        timeL=TL1;

        time=timeH*256+timeL;

        distance=time*1.7/100;

}

void Inter()interrupt 2//外部中斷1在次完成測距以及相應的後續操作

{     

        EA =0;               

        ET0=0;                                   //關定時器中斷0

        TH1=0;

        TL1=0;

        TR1  =1;                                //檢測到距離開啟定時器1

        while(!Echo);                //當echo為零時等待,中斷flag跳出等待           

        TR1  =0;                  //關閉定時器1

        count();                        //計算距離

        if(((10<distance)&&(distance<30))||HZ)  //當距離小於5cm時,變換動作哦(在中斷中變換平面感應

        {

                  DZCS=0x02;                         //向左

                  HZ=0;

        }

        if(distance<10)             //當距離小於10cm時,變換動作哦(在中斷中變換曲面感應

        {

                  DZCS=0x01;                //後退

                  HZ=1;                           //後退後左轉標誌

        }

        if(distance>30)             //當距離小於40cm時,變換動作哦(在中斷中變換

        {

                  DZCS=0x00;                         //向前

                  HZ=0;

        }

        TR1=1;

        ET0=1;

        EA = 1;

}

void main()

{

        Init();

        while(1)

        {

                  uchar DZCST;//,i;

                  if(CSB)

                           start();

                  if(DZCST!=DZCS)//動作發生改變,則回到平衡

                           DZ(PH1);

                  if(sd==0)

                           sd=1;

                  switch(DZCS)

                  {                         

                           case0x00:DZXQ(sd);break;

                           case0x01:DZXH(sd);break;

                           case0x02:DZXZ(sd);break;

                           case0x03:DZXY(sd);break;

                           case0x04:DZHZZ(sd);break;

                           case0x05:DZHZY(sd);break;

                           case0x06:DZZZ(sd);break;

                           default:

                                    DZ(PH1);           

                  }

                  DZCST=DZCS;

        }      

}