智能小車實踐教程

前言

先前做了一個外包app關(guān)于google的blockly二次開發(fā),文見Android 基于Android blockly和藍牙通信的機器人編程APP,于是乎對于硬件方面也產(chǎn)生了一些興趣,自己實現(xiàn)了軟件,但是硬件還不太懂,于是乎開始學(xué)習(xí)研究一下硬件方面的知識,也打算自己做一個智能小車。

如果你也是個軟件開發(fā)者,也希望自己多少能懂一點硬件知識,軟硬兼修的話,可以跟著我一起也來制作一個智能小車。期間會遇到很多硬件相關(guān)的術(shù)語或者專有名詞,優(yōu)秀的你,肯定會不懂就查不懂就學(xué)的,這是最重要的過程。

先上圖

有點丑 線太多了 有條件的可以綁起來
底面
斜面
側(cè)面
需要實現(xiàn)的功能

1、藍牙控制小車的行進
2、小車的速度可調(diào)
3、超聲波避障
4、循跡

準備硬件材料

我這里花了點時間把之前的采購清單整理了一下,我當時找了很多家店,那家的比較便宜,分享給大家,不是打廣告,只是為了讓大家省點錢。(當時前后購買了好幾次,運費給了不少,這里大家可以一次性買清)

1、紅外循跡傳感器 4個 [https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=43751274135&_u=aldqfnla542]
2、BLE藍牙hc-08(帶引腳)[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=36426439097&_u=aldqfnl3d00]
3、arduino uno [https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=525462013769&_u=aldqfnl5f3a]
4、智能小車底盤及其輪子 [https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=43735291557&_u=aldqfnl41dc]
5、L298n電機驅(qū)動模塊[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521709080904&_u=aldqfnl6aaa]
6、超聲波測距模塊[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=555002408091&_u=aldqfnlb0d6]
7、杜邦線 10cm 20cm 30cm 和 母對母 母對公 公對公 每樣一排40根 [https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=546979596255&_u=aldqfnla016]
8、18950電池盒[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521746780501&_u=aldqfnl8b94]
9、銅柱 各類長短 帶頭的 不帶頭的 都來個10根吧[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=558741728111&_u=aldqfnl0dc7]
10、螺絲 螺帽 若干 多數(shù)是3mm的 其他的也可以買點 15根+吧[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521771839187&_u=aldqfnl2beb]
11、螺絲刀[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521703689593&_u=aldqfnlead1]
12、mini面包板[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521761502825&_u=aldqfnl6f02]
13、黑色電工膠帶[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521702965521&_u=aldqfnle13c]
14、sg90舵機[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=43792233123&_u=aldqfnl3bf5]
15、hr-sr04超聲波模塊支架[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521750109177&_u=aldqfnlf46b]
16、小電鉆(可選)[https://item.taobao.com/item.htm?spm=a1z09.2.0.0.5b4e2e8dE025fh&id=521708346105&_u=aldqfnlac59]
17、充電電池18650 3.7v 2只(我這里是神火,較貴,可買其他的 注意:3.7v的 普通超市是沒有的,不是一般的7號或者5號電池 )[https://item.jd.com/5171888.html]
18、充電器[https://item.jd.com/4284307.html]

組裝小車

成型圖:(多圖預(yù)警?。。。?/p>


介紹圖

1、首先按照小車的說明書把小車的馬達和輪子都依次裝上去,如圖



2、然后把買來的杜邦線,可以直接拔掉接頭,露出線


使用杜邦線 如圖和馬達連接

3、然后接線,左邊兩排的馬達會有連接,右邊兩排的馬達會有連接。注意,這里我們先按照如圖所示的方法接線。
馬達上面的接口線和另一個馬達下面的接口線 接在一起

如圖

4、第一層底盤底部裝上3個循跡用的紅外傳感器,依次排列,9根線


現(xiàn)在裝,是因為,如果后面蓋上了上層底盤,就不太好裝了

裝上之后,第一層底盤的東西就裝的差不多了
如圖

5、第二層底盤上部,裝上電池盒


現(xiàn)在裝,是因為,如果后面蓋上了上層底盤,就不太好裝了

6、第二層底盤上部,裝上舵機。這里使用到了,舵機支架和舵機,有點不太好裝,耐心一點


現(xiàn)在裝,是因為,如果后面蓋上了上層底盤,就不太好裝了

7、把兩層底盤合并起來,OK,大功告成!?。?!鼓掌
最終如圖


這下小車就算拼裝好了

ps: 這樣一次性裝好,可以避免多次的拆裝底盤(我就是拆了好多次,好費神)

小車啟動

小車裝好了,激動了一會兒,但是還不能動,現(xiàn)在就要讓它能跑起來
跑起來的話,我們需要使用:電機驅(qū)動、電池、arduino的板子

先簡單介紹一下電機驅(qū)動:


電機驅(qū)動
  • 電機1接口 out1 out2 和 電機2接口out3 out4用于和馬達的交互,原理就是,給馬達的高低電平的不同,使得馬達往前轉(zhuǎn)動還是往后轉(zhuǎn)動
  • 供電正極:可接受5-12v的電壓,和電池盒接在一起,兩個3.7v的電池7.4伏,夠用
  • 地線:和電池盒的地線、arduino的地線連在一起
  • 5v:連接arduino的電源線
  • ENA、ENB:調(diào)速用的,默認情況下,有個蓋子,蓋起來的,相當于直接連接,滿壓5v就代表全速。后面會接arduino的pwm接口,來調(diào)速
    -In1~4: 接在arduino上

接入如圖:


1、把馬達對應(yīng)的 4個線接入到電機的out1 out2 out3 out4口,
注意對照一下前面接線的顏色,順序暫時可是弄成跟我的一樣(因為后面arduino的代碼里面要一致)

2、連接電池盒與電機(一般我們認為 紅色是電源、黑色為地線),連接arduino與電機

3、連接電機的in1-4口和arduino的19、18、17、16(有人問是哪里,A0~A5其實分別對應(yīng)的是14-19)

示意圖:

連接好,裝上電池,接下來解釋,寫arduino的程序了

1、去官網(wǎng)下載編譯器


長這樣,我是mac的,不過操作方法都差不多

2、通過電源接口連接到電腦


選擇這個uno

連接成功之后就有自己板子的信息了,如果沒有的話,去網(wǎng)上查一下,下載對應(yīng)操作系統(tǒng)的arduino的驅(qū)動就好


3、代碼
這里我們就簡單的讓小車 循環(huán)前后左右動。

#include <Servo.h>
//定義五中運動狀態(tài)
#define STOP      0
#define FORWARD   1
#define BACKWARD  2
#define TURNLEFT  3
#define TURNRIGHT 4
//定義需要用到的引腳
int leftMotor1 = 16;
int leftMotor2 = 17;
int rightMotor1 = 18;
int rightMotor2 = 19;

void setup() {
  // put your setup code here, to run once:
  //設(shè)置控制電機的引腳為輸出狀態(tài)
  pinMode(leftMotor1, OUTPUT);
  pinMode(leftMotor2, OUTPUT);
  pinMode(rightMotor1, OUTPUT);
  pinMode(rightMotor2, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  int cmd;
  for(cmd=0;cmd<5;cmd++)//依次執(zhí)行向前、向后、向左、想有、停止四個運動狀態(tài)
  {
    motorRun(cmd);  
    delay(2000);//每個命令執(zhí)行2s 
  } 
}
//運動控制函數(shù)
void motorRun(int cmd)
{
  switch(cmd){
    case FORWARD:
      digitalWrite(leftMotor1, LOW);
      digitalWrite(leftMotor2, HIGH);
      digitalWrite(rightMotor1, LOW);
      digitalWrite(rightMotor2, HIGH);
      break;
     case BACKWARD:
      digitalWrite(leftMotor1, HIGH);
      digitalWrite(leftMotor2, LOW);
      digitalWrite(rightMotor1, HIGH);
      digitalWrite(rightMotor2, LOW);
      break;
     case TURNLEFT:
      digitalWrite(leftMotor1, HIGH);
      digitalWrite(leftMotor2, LOW);
      digitalWrite(rightMotor1, LOW);
      digitalWrite(rightMotor2, HIGH);
      break;
     case TURNRIGHT:
      digitalWrite(leftMotor1, LOW);
      digitalWrite(leftMotor2, HIGH);
      digitalWrite(rightMotor1, HIGH);
      digitalWrite(rightMotor2, LOW);
      break;
     default:
      digitalWrite(leftMotor1, LOW);
      digitalWrite(leftMotor2, LOW);
      digitalWrite(rightMotor1, LOW);
      digitalWrite(rightMotor2, LOW);
  }
}

OK,不出意外的話,你的小車也可以動起來啦?。。?!鼓掌

代碼解析:

 case FORWARD:
      digitalWrite(leftMotor1, LOW);
      digitalWrite(leftMotor2, HIGH);
      digitalWrite(rightMotor1, LOW);
      digitalWrite(rightMotor2, HIGH);
      break;

可以看到 前進的時候,我們把引腳leftMotor1設(shè)置為了低電壓,leftMotor2設(shè)置為了高電壓


有人會問為啥 從圖上看,為啥兩個馬達的 高低電壓 不同,卻是相同的方向。那是因為,這兩個馬達 是這樣放的。


如果是你把后面那個擺成跟前面那個一樣,后面的下上就變成了上下,其實上下的高低電壓就一致了。所以,前后輪的轉(zhuǎn)動方向就一致了(這里根據(jù)我們的接線方法 我們是前進,如果說接線方法不同或者接反了,可以把代碼里面的LOW和HIGH進行互換)

轉(zhuǎn)彎:
我們這里的行為方式是跟坦克類似的,因為我們沒有轉(zhuǎn)向輪。
如果想要左轉(zhuǎn),就是左邊輪子向后,右邊輪子向前。這樣的話會是,原地的左轉(zhuǎn);如果想要,向左轉(zhuǎn)彎,保持前進的話,就應(yīng)該是,左邊保持低速前進,右邊保持高速前進;如果想要調(diào)整原地轉(zhuǎn)動的速率和轉(zhuǎn)幅,可以調(diào)整左輪的速度和右輪的速度差值來實現(xiàn)。后面的藍牙小車調(diào)速會講到。
右轉(zhuǎn)也是同理。

藍牙小車

我們會用到4個引腳

接線方法:

TX:接Arduino UNO開發(fā)板”RX”引腳
RX:接Arduino UNO開發(fā)板”TX”引腳
GND:接Arduino UNO開發(fā)板”GND”引腳
VCC:接Arduino UNO開發(fā)板”5V”或”3.3V”引腳

示意圖

這里,我們使用到了調(diào)速功能,因為前面說到我們?nèi)绻胍斑M轉(zhuǎn)彎,就需要設(shè)置左右兩排輪子不同的速度來實現(xiàn)。這里需要電機驅(qū)動接入ENA ENB的引腳接口到arduino的PWM接口(arduino上面的引腳號碼前有~的就是PWM接口,這里我們使用5、6)。

示意圖

寫入代碼注意?。?/h3>

我們這里如果接入了藍牙的RXD、TXD的時候,如果想要連接arduino和電腦進行代碼寫入,是不行的。因為,如果藍牙占用了RXD和TXD,沒有辦法寫入了。拔掉一個,然后就可以了。

代碼:

#include <Servo.h>


//定義五中運動狀態(tài)
String STOP    =  "D105FFFF";

String UP  = "D101FFFF";
String DOWN = "D102FFFF";
String LEFT = "D103FFFF";
String RIGHT = "D104FFFF";

String LEFT_UP  = "D111FFFF";
String LEFT_DOWN = "D112FFFF";
String RIGHT_UP = "D113FFFF";
String RIGHT_DOWN = "D114FFFF";

//定義需要用到的引腳
int leftMotor1 = 16;
int leftMotor2 = 17;
int rightMotor1 = 18;
int rightMotor2 = 19;

int leftPWM = 5;
int rightPWM = 6;

int inputPin = 7; // 定義超聲波信號接收接口
int outputPin = 8; // 定義超聲波信號發(fā)出接口

String comdata = "";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  //設(shè)置控制電機的引腳為輸出狀態(tài)
  pinMode(leftMotor1, OUTPUT);
  pinMode(leftMotor2, OUTPUT);
  pinMode(rightMotor1, OUTPUT);
  pinMode(rightMotor2, OUTPUT);
  //超聲波控制引腳初始化
  pinMode(inputPin, INPUT);
  pinMode(outputPin, OUTPUT);
  //調(diào)速
  pinMode(leftPWM, OUTPUT);
  pinMode(rightPWM, OUTPUT);
}

void loop() {
  while (Serial.available() > 0)
  {
    int i = Serial.read();
    char c = char(i);
    comdata += c;  //每次讀一個char字符,并相加
    delay(2);
  }

  if (comdata.length() > 0) {
    Serial.println(comdata); //打印接收到的字符
    motorRun(comdata, 250);
    comdata = "";
  }
}

//運動控制函數(shù)
void motorRun(String cmd, int value)
{
  analogWrite(leftPWM, value);  //設(shè)置PWM輸出,即設(shè)置速度
  analogWrite(rightPWM, value);
  if (cmd == UP) {
    Serial.println("UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == DOWN) {
    Serial.println("DOWN");
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == LEFT) {
    Serial.println("left");
    analogWrite(leftPWM, value - 90); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 70);
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == RIGHT) {
    Serial.println("right");
    analogWrite(leftPWM, value - 70); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 90);
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == LEFT_UP) {
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("LEFT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == LEFT_DOWN) {
    Serial.println("LEFT_DOWN");
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == RIGHT_UP) {
    analogWrite(rightPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("RIGHT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == RIGHT_DOWN) {
    analogWrite(rightPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("RIGHT_DOWN");
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else {
    Serial.println("stop");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, LOW);
  }
}

代碼解析:

  • 這里我們?yōu)榱丝梢灾苯邮褂梦易约旱腶pp,所以把指令直接定義為了和我app里面一樣的。APP下載地址
  • 一共有9個指令,這里的app里面對于小車行進做了兩種操作。一種是上下左右停,一種是搖桿(其實就是 上下左右 左上 右上 左下 右下)。如圖:


    上下左右
搖桿
  analogWrite(leftPWM, value);  //設(shè)置PWM輸出,即設(shè)置速度
  analogWrite(rightPWM, value);

是左右兩邊輪子的調(diào)速代碼

  • 默認我們的對于PWM寫入的最大速度值為255,按道理區(qū)間為1-255,但是,經(jīng)過測試發(fā)現(xiàn)(根據(jù)不同電池情況),如果低于一定值的,就帶不動馬達轉(zhuǎn)動了(我這里大約是100左右,如果電池沒電了可能最小值更高)

  • 原地左轉(zhuǎn)

  } else  if (cmd == LEFT) {
    Serial.println("left");
    analogWrite(leftPWM, value - 90); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 70);
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);

這里我的左轉(zhuǎn),我不想讓它原地的轉(zhuǎn)動的轉(zhuǎn)幅太大,所以我調(diào)小了兩邊的轉(zhuǎn)速

  • 左前
  } else  if (cmd == LEFT_UP) {
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("LEFT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);

這里,你會發(fā)現(xiàn)“左前”,其實代碼是在“前進”的代碼基礎(chǔ)上加了一句調(diào)速代碼,如我們先前所說,我們要保持前進的同時轉(zhuǎn)彎,就需要向前,并且減速左邊的輪子,就可以實現(xiàn)了。

ok,寫入了代碼,接好RX TX線,裝上電池,就可以運行了。藍牙的藍色的燈也會亮起來。


成型圖

這時候,可以使用下載好的app來連接藍牙小車了。連接成功之后,就可以通過控制界面的兩種控制方式來控制我們的小車啦!!!不出意外的話,鼓掌??!

超聲波避障

超神波避障的主要原理:
利用超聲波測距傳感器測量小車與障礙之間的距離,小于一定值的時候就停下,然后利用舵機來改變超聲波傳感器的方向繼續(xù)測距,如果哪邊沒有障礙,就往哪邊轉(zhuǎn)向來避開障礙,然后再前進。

我們這里需要用到的就是舵機和超聲波傳感器(及其支架)。舵機,之前裝底盤的時候就裝上去了,如果那時候沒有裝的話,現(xiàn)在要裝的話就比較困難了。

插線:

  • 超聲波有4個引腳,vcc、trig、echo、gnd,vcc和gnd插到arduino上,但是,現(xiàn)在來看,arduino上面已經(jīng)沒有足夠的 vcc和gnd的引腳口了,所以,我們要利用 mini面包板來 擴展一些。
mini面包板

擴展原理:


示意圖

這樣連接的話,一豎排都是相當于是vcc或者gnd線的接口。

  • trig和echo分別接到arduino的“8、7”號引腳上面(“Trig”引腳控制超聲波發(fā)出聲波,對應(yīng)int outputPin=8; “Echo”引腳反應(yīng)接收到返回聲波,對應(yīng)int inputPin=7;)。大致原理就是 發(fā)送一個超聲波,然后接受一個超聲波,然后通過時間來算得距離

  • 舵機一般為三根線:灰色——GND,紅色——VCC,橙色——控制信號。因此我們將灰色色線接到GND,紅色線接到“+5V”引腳,橙色線接到“9”號引腳

成型圖
成型圖
示意圖

代碼:

#include <Servo.h>


//定義五中運動狀態(tài)
String STOP    =  "D105FFFF";

String UP  = "D101FFFF";
String DOWN = "D102FFFF";
String LEFT = "D103FFFF";
String RIGHT = "D104FFFF";

String LEFT_UP  = "D111FFFF";
String LEFT_DOWN = "D112FFFF";
String RIGHT_UP = "D113FFFF";
String RIGHT_DOWN = "D114FFFF";


#define BIZHANG_START "D301FFFF"
#define BIZHANG_END "D302FFFF"

#define PIN_SERVO 9  //舵機信號控制引腳

//定義需要用到的引腳
int leftMotor1 = 16;
int leftMotor2 = 17;
int rightMotor1 = 18;
int rightMotor2 = 19;

int leftPWM = 5;
int rightPWM = 6;

int inputPin = 7; // 定義超聲波信號接收接口
int outputPin = 8; // 定義超聲波信號發(fā)出接口

Servo myServo;  //舵機

String lastCmd;

String comdata = "";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  //舵機引腳初始化
  myServo.attach(PIN_SERVO);
  //設(shè)置控制電機的引腳為輸出狀態(tài)
  pinMode(leftMotor1, OUTPUT);
  pinMode(leftMotor2, OUTPUT);
  pinMode(rightMotor1, OUTPUT);
  pinMode(rightMotor2, OUTPUT);
  //超聲波控制引腳初始化
  pinMode(inputPin, INPUT);
  pinMode(outputPin, OUTPUT);
  //調(diào)速
  pinMode(leftPWM, OUTPUT);
  pinMode(rightPWM, OUTPUT);
}

void loop() {
  while (Serial.available() > 0)
  {
    int i = Serial.read();
    char c = char(i);
    comdata += c;  //每次讀一個char字符,并相加
    delay(2);
  }

  if (comdata.length() > 0) {
    Serial.println(comdata); //打印接收到的字符
    if (comdata == BIZHANG_START) {
      avoidance();
    } else if (comdata == BIZHANG_END) {
      motorRun(STOP, 250);
    } else {
      motorRun(comdata, 250);
    }
    lastCmd = comdata;
    comdata = "";
  } else {
    if (lastCmd == BIZHANG_START) {
      Serial.println("現(xiàn)在是 避障");
      avoidance();
    }
  }
}

void avoidance()
{
  int pos;
  int dis[3];//距離
  motorRun(UP, 255);
  myServo.write(90);
  dis[1] = getDistance(); //中間
  Serial.println("mid dis:" + dis[1]);
  if (dis[1] < 30)
  {
    motorRun(DOWN,255);
    delay(300);                                                                                                                                                                           motorRun(STOP, 0);
    for (pos = 90; pos <= 150; pos += 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
    }
    dis[2] = getDistance(); //左邊
    Serial.println("left dis:" + dis[2]);
    for (pos = 150; pos >= 30; pos -= 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
      if (pos == 90)
        dis[1] = getDistance(); //中間
      Serial.println("mid dis:" + dis[1]);
    }
    dis[0] = getDistance(); //右邊
    Serial.println("right dis:" + dis[0]);
    for (pos = 30; pos <= 90; pos += 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
    }
    if (dis[0] < dis[2]) //右邊距離障礙的距離比左邊近
    {
      //左轉(zhuǎn)
      motorRun(LEFT, 250);
      delay(500);
    }
    else  //右邊距離障礙的距離比左邊遠
    {
      //右轉(zhuǎn)
      motorRun(RIGHT, 250);
      delay(500);
    }
  }
}

int getDistance()
{
  digitalWrite(outputPin, LOW); // 使發(fā)出發(fā)出超聲波信號接口低電平2μs
  delayMicroseconds(2);
  digitalWrite(outputPin, HIGH); // 使發(fā)出發(fā)出超聲波信號接口高電平10μs,這里是至少10μs
  delayMicroseconds(10);
  digitalWrite(outputPin, LOW); // 保持發(fā)出超聲波信號接口低電平
  int distance = pulseIn(inputPin, HIGH); // 讀出脈沖時間
  distance = distance / 58; // 將脈沖時間轉(zhuǎn)化為距離(單位:厘米)
  Serial.println("distance:" + distance); //輸出距離值

  if (distance >= 50)
  {
    //如果距離小于50厘米返回數(shù)據(jù)
    return 50;
  }//如果距離小于50厘米
  else
    return distance;
}


//運動控制函數(shù)
void motorRun(String cmd, int value)
{
  analogWrite(leftPWM, value);  //設(shè)置PWM輸出,即設(shè)置速度
  analogWrite(rightPWM, value);
  if (cmd == UP) {
    Serial.println("UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == DOWN) {
    Serial.println("DOWN");
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == LEFT) {
    Serial.println("left");
    analogWrite(leftPWM, value - 90); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 70);
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == RIGHT) {
    Serial.println("right");
    analogWrite(leftPWM, value - 70); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 90);
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == LEFT_UP) {
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("LEFT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == LEFT_DOWN) {
    Serial.println("LEFT_DOWN");
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == RIGHT_UP) {
    analogWrite(rightPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("RIGHT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == RIGHT_DOWN) {
    analogWrite(rightPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("RIGHT_DOWN");
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);   
    digitalWrite(rightMotor2, HIGH);
  } else {
    Serial.println("stop");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, LOW);
  }
}
代碼解析:

超聲波:

  1. 采用Trig引腳觸發(fā),給至少10us的高電平脈沖信號
  2. 模塊自動發(fā)送8個40kHz的方波,自動檢測是否有信號返回
  3. 有信號返回,通過Echo引腳輸出一個高電平脈沖,高電平脈沖持續(xù)的時間就是超聲波從發(fā)射到反射返回的時間。距離=(高電平脈沖時間*340)/2。(聲音在空氣中傳播速度為340m/s)

超聲波發(fā)出引腳“Trig”為高時對外發(fā)出超聲波,為保證發(fā)出10μs聲波,因此在發(fā)送之前需要將該引腳拉低,并給他一定反應(yīng)時間。

digitalWrite(outputPin, LOW); // 使發(fā)出發(fā)出超聲波信號接口低電平2μs
delayMicroseconds(2);

之后發(fā)送10μs超聲波

digitalWrite(outputPin, HIGH); // 使發(fā)出發(fā)出超聲波信號接口高電平10μs,這里是至少10μs

聲波發(fā)送之后禁止其繼續(xù)發(fā)送,同時開始檢測是否反射回來的聲波

digitalWrite(outputPin, LOW); // 保持發(fā)出超聲波信號接口低電平
  int distance = pulseIn(inputPin, HIGH); // 讀出脈沖時間

pulseIn()單位為微秒,聲速344m/s,所以距離cm=344100/1000000pulseIn()/2約等于pulseIn()/58.0
distance= distance/58; // 將脈沖時間轉(zhuǎn)化為距離(單位:厘米)

超聲波模塊工作受物體表面反射程度影響,并且在傳播過程中信號強度容易衰減,因此該模塊適用的檢測距離有限,一般在50cm以內(nèi)相對正確,而且我們在避障時不需要檢測太遠的距離,因此超過50cm以上的都按50cm計算

舵機:

 dis[1] = getDistance(); //中間
  Serial.println("mid dis:" + dis[1]);
  if (dis[1] < 30)
  {
    motorRun(DOWN,255);
    delay(300);                                                                                                                                                                           motorRun(STOP, 0);
    for (pos = 90; pos <= 150; pos += 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
    }
    dis[2] = getDistance(); //左邊
    Serial.println("left dis:" + dis[2]);
    for (pos = 150; pos >= 30; pos -= 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
      if (pos == 90)
        dis[1] = getDistance(); //中間
      Serial.println("mid dis:" + dis[1]);
    }
    dis[0] = getDistance(); //右邊
    Serial.println("right dis:" + dis[0]);
    for (pos = 30; pos <= 90; pos += 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
    }
    if (dis[0] < dis[2]) //右邊距離障礙的距離比左邊近
    {
      //左轉(zhuǎn)
      motorRun(LEFT, 250);
      delay(500);
    }
    else  //右邊距離障礙的距離比左邊遠
    {
      //右轉(zhuǎn)
      motorRun(RIGHT, 250);
      delay(500);
    }

先獲取中間時候的距離,小于30之后停下。向左邊轉(zhuǎn)60度左右,然后到達最左邊的時候,獲取左邊的距離,然后轉(zhuǎn)動到中間獲取中間的距離,然后轉(zhuǎn)動到右邊,獲取右邊的距離。
比較左右邊距離,哪邊長小車就往哪邊 轉(zhuǎn)動,然后繼續(xù)向前。

ok,如果線接好了、代碼錄入了,裝上電池就可以使用app的避障,“開始”和“結(jié)束”按鈕,來運行了。
不出意外,你的小車就有了簡單的避障功能了。故障!??!

ps:利用超聲波避障,總會有些不太精準的情況,比如障礙不是一個較為平整的能夠被很好測算距離的情況??傊?,我們追求的是過程,過程最重要嘛。

循跡

循跡的話由于循跡算法比較粗糙,有時候會有一些小問題,比如沖出黑線區(qū)域等。所以這里,簡單的介紹一下。

上一下所有的代碼

#include <Servo.h>


//定義五中運動狀態(tài)
String STOP    =  "D105FFFF";

String UP  = "D101FFFF";
String DOWN = "D102FFFF";
String LEFT = "D103FFFF";
String RIGHT = "D104FFFF";

String LEFT_UP  = "D111FFFF";
String LEFT_DOWN = "D112FFFF";
String RIGHT_UP = "D113FFFF";
String RIGHT_DOWN = "D114FFFF";


#define BIZHANG_START "D301FFFF"
#define BIZHANG_END "D302FFFF"
#define XUNJI_START "D201FFFF"
#define XUNJI_END "D202FFFF"

#define PIN_SERVO 9  //舵機信號控制引腳

//定義需要用到的引腳
int leftMotor1 = 16;
int leftMotor2 = 17;
int rightMotor1 = 18;
int rightMotor2 = 19;

int follow1 = 10;
int follow2 = 11;
int follow3 = 12;

int leftPWM = 5;
int rightPWM = 6;

int inputPin = 7; // 定義超聲波信號接收接口
int outputPin = 8; // 定義超聲波信號發(fā)出接口

int xunjiSpeed = 170;

Servo myServo;  //舵機

String lastCmd;

String comdata = "";

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  //舵機引腳初始化
  myServo.attach(PIN_SERVO);
  //設(shè)置控制電機的引腳為輸出狀態(tài)
  pinMode(leftMotor1, OUTPUT);
  pinMode(leftMotor2, OUTPUT);
  pinMode(rightMotor1, OUTPUT);
  pinMode(rightMotor2, OUTPUT);
  //超聲波控制引腳初始化
  pinMode(inputPin, INPUT);
  pinMode(outputPin, OUTPUT);
  //調(diào)速
  pinMode(leftPWM, OUTPUT);
  pinMode(rightPWM, OUTPUT);
  //尋跡模塊引腳初始化
  pinMode(follow1, INPUT);
  pinMode(follow2, INPUT);
  pinMode(follow3, INPUT);
}

void loop() {
  while (Serial.available() > 0)
  {
    int i = Serial.read();
    char c = char(i);
    comdata += c;  //每次讀一個char字符,并相加
    delay(2);
  }

  if (comdata.length() > 0) {
    Serial.println(comdata); //打印接收到的字符
    if (comdata == XUNJI_START) {
      follow();
    } else if (comdata == XUNJI_END) {
      motorRun(STOP, 250);
    } else if (comdata == BIZHANG_START) {
      avoidance();
    } else if (comdata == BIZHANG_END) {
      motorRun(STOP, 250);
    } else {
      motorRun(comdata, 250);
    }
    lastCmd = comdata;
    comdata = "";
  } else {
    if (lastCmd == XUNJI_START) {
      Serial.println("現(xiàn)在是 循跡");
      follow();
    } else if (lastCmd == BIZHANG_START) {
      Serial.println("現(xiàn)在是 避障");
      avoidance();
    }
  }
}

void follow()
{
  int data[3];
  data[0] = digitalRead(follow1);
  data[1] = digitalRead(follow2);
  data[2] = digitalRead(follow3);

  //data[x] == 0的時候說明檢測到了 黑線
  if (data[0] && data[1] && data[2])//3個都檢測到黑線說明 走到了T字  停止
  {
    Serial.println("我stop了現(xiàn)在-");
    motorRun(STOP, 0);
  } else if (!data[0] && data[1] && !data[2] ) { //中間監(jiān)測到黑線 就直線
    Serial.println("我UP了現(xiàn)在-");
    motorRun(UP, 100);
  } else if (data[0] && !data[1] && !data[2] ) {
Serial.println("我LEFT了現(xiàn)在-");    
    motorRun(LEFT, 180);
  } else if (!data[0] && !data[1] && data[2]) {
    Serial.println("我RIGHT了現(xiàn)在-");
    motorRun(RIGHT, 180);
  } else {
    motorRun(UP, 100);
    Serial.println("不知道干啥-");
  }

  Serial.println("00:" + data[0]);
  Serial.println("---");
  Serial.println("11:" + data[1]);
  Serial.println("---");
  Serial.println("22:" + data[2]);
}

void avoidance()
{
  int pos;
  int dis[3];//距離
  motorRun(UP, xunjiSpeed);
  myServo.write(90);
  dis[1] = getDistance(); //中間
  Serial.println("mid dis:" + dis[1]);
  if (dis[1] < 30)
  {
    motorRun(DOWN,255);
    delay(300);
    motorRun(STOP, 0);
    for (pos = 90; pos <= 150; pos += 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
    }
    dis[2] = getDistance(); //左邊
    Serial.println("left dis:" + dis[2]);
    for (pos = 150; pos >= 30; pos -= 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
      if (pos == 90)
        dis[1] = getDistance(); //中間
      Serial.println("mid dis:" + dis[1]);
    }
    dis[0] = getDistance(); //右邊
    Serial.println("right dis:" + dis[0]);
    for (pos = 30; pos <= 90; pos += 1)
    {
      myServo.write(pos);              // tell servo to go to position in variable 'pos'
      delay(10);                       // waits 15ms for the servo to reach the position
    }
    if (dis[0] < dis[2]) //右邊距離障礙的距離比左邊近
    {
      //左轉(zhuǎn)
      motorRun(LEFT, 250);
      delay(500);
    }
    else  //右邊距離障礙的距離比左邊遠
    {
      //右轉(zhuǎn)
      motorRun(RIGHT, 250);
      delay(500);
    }
  }
}

int getDistance()
{
  digitalWrite(outputPin, LOW); // 使發(fā)出發(fā)出超聲波信號接口低電平2μs
  delayMicroseconds(2);
  digitalWrite(outputPin, HIGH); // 使發(fā)出發(fā)出超聲波信號接口高電平10μs,這里是至少10μs
  delayMicroseconds(10);
  digitalWrite(outputPin, LOW); // 保持發(fā)出超聲波信號接口低電平
  int distance = pulseIn(inputPin, HIGH); // 讀出脈沖時間
  distance = distance / 58; // 將脈沖時間轉(zhuǎn)化為距離(單位:厘米)
  Serial.println("distance:" + distance); //輸出距離值

  if (distance >= 50)
  {
    //如果距離小于50厘米返回數(shù)據(jù)
    return 50;
  }//如果距離小于50厘米
  else
    return distance;
}


//運動控制函數(shù)
void motorRun(String cmd, int value)
{
  analogWrite(leftPWM, value);  //設(shè)置PWM輸出,即設(shè)置速度
  analogWrite(rightPWM, value);
  if (cmd == UP) {
    Serial.println("UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == DOWN) {
    Serial.println("DOWN");
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == LEFT) {
    Serial.println("left");
    analogWrite(leftPWM, value - 90); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 70);
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == RIGHT) {
    Serial.println("right");
    analogWrite(leftPWM, value - 70); //設(shè)置PWM輸出,即設(shè)置速度
    analogWrite(rightPWM, value - 90);
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == LEFT_UP) {
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("LEFT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == LEFT_DOWN) {
    Serial.println("LEFT_DOWN");
    analogWrite(leftPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else  if (cmd == RIGHT_UP) {
    analogWrite(rightPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("RIGHT_UP");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, HIGH);
    digitalWrite(rightMotor1, HIGH);
    digitalWrite(rightMotor2, LOW);
  } else  if (cmd == RIGHT_DOWN) {
    analogWrite(rightPWM, value - 150); //設(shè)置PWM輸出,即設(shè)置速度
    Serial.println("RIGHT_DOWN");
    digitalWrite(leftMotor1, HIGH);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, HIGH);
  } else {
    Serial.println("stop");
    digitalWrite(leftMotor1, LOW);
    digitalWrite(leftMotor2, LOW);
    digitalWrite(rightMotor1, LOW);
    digitalWrite(rightMotor2, LOW);
  }
}

循跡模塊的接線,也比較簡單,增加了10,11,12號引腳的接入,和藍牙控制命令
#define XUNJI_START "D201FFFF"
#define XUNJI_END "D202FFFF"

接線

這里可以看到3個循跡的模塊,一共9根線,其中每個模塊的一個input線就接到arduino對應(yīng)的引腳號上面,其余的就是電源和地線,接到面包板上面(面包板之前已經(jīng)講過原理,不懂的自己查一下)

最后循跡的線可以自己定一下,反正重點就是個T字


循跡

完結(jié)

ok! 大功告成

PS:

有些人買的藍牙型號不一致,只要是BLE就行。
不過如果有些CharacteristicSerial不一致的話,需要修改一下
修改地址

public static final String UUID_SERVICE = "0000ffe0-0000-1000-8000-00805f9b34fb";
public static final String UUID_INDICATE = "0000000-0000-0000-8000-00805f9b0000";
public static final String UUID_NOTIFY = "0000ffe1-0000-1000-8000-00805f9b34fb";
public static final String UUID_WRITE = "0000ffe1-0000-1000-8000-00805f9b34fb";
public static final String UUID_READ = "3f3e3d3c-3b3a-3938-3736-353433323130";

里面就有對應(yīng)的一些特征序列號。如果不知道的話,賣家那里問問,或者手上一些軟件可以查看(連上藍牙,手機上來看特征序列號)

主要就是READ,WRITE,NOTIFY這幾個

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容