這篇文章來源于DevicePlus.com英語網(wǎng)站的翻譯稿。點(diǎn)擊此處閱讀本文第1部分 >
在第1部分中,我們討論了構(gòu)建一個(gè)探索者機(jī)器人的幾個(gè)重要步驟。我們通過Eagle設(shè)計(jì)并創(chuàng)建了我們自己的PCB。在第2部分中,我們將添加其他組件,并對(duì)程序進(jìn)行測(cè)試,以確保RF機(jī)器人能夠按照預(yù)期方式運(yùn)行。這里我們所設(shè)計(jì)的探索者機(jī)器人能夠自主行駛,感知周圍環(huán)境并無線傳輸收集到的數(shù)據(jù)。該項(xiàng)目的目標(biāo)是為探索者機(jī)器人制造原型,該原型將配備有一組傳感器,如溫度傳感器和壓力傳感器,這些傳感器能夠借助數(shù)字無線電通信模塊(RF)實(shí)時(shí)發(fā)送所收集的信息。
硬件
Arduino Uno
2x 收發(fā)器 NRF24l01+
2x Pololu 發(fā)動(dòng)機(jī) 100:1
4x 車輪
穩(wěn)壓器
水晶玻璃
L298
nRF24L01
氣壓傳感器 BMP085
距離傳感器 HC-SR04
軟件
Arduino IDE
GitHub
工具
Eagle CAD
圖1:第1部分中的RF機(jī)器人設(shè)置
步驟 1:結(jié)構(gòu)和機(jī)械組件
遠(yuǎn)程控制器將從筆記本電腦的USB端口獲取信息,并通過第二個(gè)nRF24l01+模塊進(jìn)行重定向。遠(yuǎn)程控制器由Arduino開發(fā)板和帶有NRF連接器和電源的板組成,這個(gè)板將被安裝在Uno上方,以避免使用電線。
圖2:遠(yuǎn)程控制器原理圖
圖3:遠(yuǎn)程控制器PCB
圖4:安裝有連接到Arduino Uno 的NRF24L01+模塊的遠(yuǎn)程控制器
您還會(huì)看到我們安裝的兩個(gè)LED燈,用來指示電路的功能和上述的其他組件。我們將會(huì)使用一些用于穩(wěn)壓器和NPN晶體管過熱保護(hù)的小冷卻器。電路板將會(huì)被放置于電池上方,如圖5所示。
圖5:安裝在電池上方的遠(yuǎn)程控制器
步驟 2: 模塊
我們正在使用的是nRF24L01+無線收發(fā)器模塊。nRF24L01+是一款超低功耗的無線射頻收發(fā)器。對(duì)于此類應(yīng)用程序,該模塊是最佳選擇。它以其出色的性能和低廉的價(jià)格成為最受歡迎的型號(hào)之一,同時(shí),該模塊通用的通信協(xié)議使其能夠與全球廣泛使用的軟件兼容。
nRF24L01+ 資料表
規(guī)格:
工作頻率 2.4GHz – 126 通道
速度: 250kbps, 1 和 2 Mbps
發(fā)射器: 輸出功率為0dBm 時(shí)是11.3mA
最大功率: 100mW (由于前置放大器)
接口: 4 引腳 SPI – 有效負(fù)載: 32 字節(jié)
圖6:NRF24L01+模塊
為了獲取壓力和溫度數(shù)據(jù),我們將使用Sparkfun BMP085氣壓傳感器。該傳感器提供300至110 kPa的測(cè)量范圍,誤差為0.03 kPa。BMP085還可以提供0至65 °C范圍內(nèi)的溫度測(cè)量功能。其承受的電壓必須在1.8-3.6V范圍內(nèi),并通過l2C直接與微控制器建立連接。
BMP085 資料表
規(guī)格:
接口(l2C)
寬氣壓范圍
寬電壓范圍
極低的電流損耗
低噪環(huán)境測(cè)量
完全較準(zhǔn)
含運(yùn)動(dòng)傳感器
圖7:BMP085氣壓傳感器
現(xiàn)在我們將要安裝HC-SR04超聲波傳感器。HC-SR04的檢測(cè)范圍為2-200厘米。微控制器向傳感器發(fā)送激發(fā)聲波的脈沖。當(dāng)傳感器感知到聲波已返回時(shí),將向微控制器發(fā)送回脈沖,計(jì)算發(fā)送脈沖的時(shí)間與接收到脈沖的時(shí)間之間的差值即可算出距離。
D=(t2?t1)×170
圖8:位于車體正面的HC-SR04距離傳感器
步驟 3: 數(shù)據(jù)路由
Arduino平臺(tái)提供了預(yù)定義的功能,使用者無需對(duì)一些寄存器進(jìn)行配置??梢酝ㄟ^一些庫來實(shí)現(xiàn)平臺(tái)與其他外圍設(shè)備的接口。使用一根Arduino-PC電纜就可以以非常簡(jiǎn)單地完成編程。該開發(fā)板配有USB-UART轉(zhuǎn)換器。
在本項(xiàng)目中,需要有兩個(gè)程序:一個(gè)用于機(jī)器人的功能,另一個(gè)用于遠(yuǎn)程控制器。這兩個(gè)程序通過nRF24101+模塊以無線電的方式相互通信。數(shù)據(jù)流程圖如圖9所示。有兩種方式。正向傳遞:首先,信息被應(yīng)用程序發(fā)送到Arduino。然后由收發(fā)器接管的信息將進(jìn)一步發(fā)送到第二個(gè)收發(fā)器,再返回到微控制器。這將用于控制機(jī)器人的機(jī)體運(yùn)動(dòng)。
圖9:數(shù)據(jù)流程圖
反向傳遞:控制器從傳感器獲取信息并進(jìn)行處理,然后將其發(fā)送到OTA(空中下載技術(shù)),使其最終到達(dá)PC端。
步驟 4: 應(yīng)用程序
該應(yīng)用程序是通過Microsoft Visual Studio 2010使用Visual C#編程語言來完成的。Visual Studio在編輯部分融入視覺元素,具有非常簡(jiǎn)潔直觀的界面。這有助于實(shí)現(xiàn)一些復(fù)雜的應(yīng)用程序。該應(yīng)用程序可以發(fā)送指令,接收數(shù)據(jù)并進(jìn)行顯示。機(jī)器人的控制由鍵盤上的箭頭按鍵來實(shí)現(xiàn)。當(dāng)您按下箭頭按鍵的時(shí)候,運(yùn)動(dòng)按鈕會(huì)亮起,表示指令已經(jīng)被接收到。這些指令其實(shí)是串行傳送的字母,最后會(huì)到達(dá)機(jī)器人控制器進(jìn)行解碼。例如,當(dāng)您按下“向上”鍵時(shí),應(yīng)用程序通過USB在串行端口發(fā)送“U”,指令的解碼表如下所示:
字母 | 動(dòng)作 |
U | 前進(jìn) |
R | 右轉(zhuǎn) |
D | 向后移動(dòng) |
L | 左轉(zhuǎn) |
N | 更新 |
B | 打開燈光 |
O | 發(fā)送PWM值 |
為了達(dá)到該效果,我們應(yīng)用了以下算法:應(yīng)用程序發(fā)送LED燈“打開”指令,然后遠(yuǎn)程控制器接收指令并將其發(fā)送給機(jī)器人。一旦機(jī)器人接收到指令,就會(huì)打開LED并向應(yīng)用程序發(fā)送指令打開指示燈。我們引入該算法來避免產(chǎn)生不同步的問題(LED熄滅但指示燈亮起的情況)。
另一個(gè)重要問題是界面中信息的導(dǎo)入和顯示。為了使應(yīng)用程序能夠區(qū)分信息并知道在哪里顯示,我們實(shí)施了以下方法:一旦機(jī)器人從遠(yuǎn)程控制器接收到信息,它不僅僅只是進(jìn)行簡(jiǎn)單的傳送,還會(huì)對(duì)其進(jìn)行處理并發(fā)送類似這樣的指令:“CMD: TE =” + val.marime。應(yīng)用程序就會(huì)在通過串行端口檢索到以“CMD”開頭的信息時(shí)刪除掉“:”并讀取之后的兩個(gè)字母,這兩個(gè)字母表示的是信息將會(huì)被寫入的區(qū)域?!皏al. marime”會(huì)在“=”之后被讀取,對(duì)于以上示例,程序?qū)?huì)把讀取到的數(shù)據(jù)分配到溫度區(qū)域中。
為了使應(yīng)用程序能夠識(shí)別同時(shí)按下的兩個(gè)按鍵,我們已經(jīng)為每個(gè)按鍵初始化了一個(gè)變量。當(dāng)按下一個(gè)按鍵時(shí),對(duì)應(yīng)事件就會(huì)其變量的值增加到1;當(dāng)釋放按鍵時(shí),對(duì)應(yīng)事件就會(huì)將其變量減少到0。計(jì)時(shí)器將會(huì)持續(xù)計(jì)算這些變量的值。
圖10:設(shè)計(jì)應(yīng)用程序
步驟 5: 關(guān)于程序
機(jī)器人使用的是在Arduino IDE中編譯的程序,比遠(yuǎn)程控制器的程序要復(fù)雜的多:
程序定義了向前/向后移動(dòng),轉(zhuǎn)彎,開燈,計(jì)算距離、溫度和壓力的函數(shù)。
主程序是一個(gè)無限循環(huán)程序,將會(huì)持續(xù)判斷無線電模塊是否接收到信息,如果有,就會(huì)對(duì)輸入的代碼進(jìn)行處理。
如果輸入信息與程序中的一條預(yù)設(shè)指令匹配,那么將根據(jù)收到的字母執(zhí)行相應(yīng)功能。
當(dāng)從傳感器接收到傳遞信息的指令時(shí),將會(huì)運(yùn)行多個(gè)函數(shù)來對(duì)信息進(jìn)行收集,并將其添加到將被傳遞的向量中。
如果我們要運(yùn)行的是有關(guān)運(yùn)動(dòng)的函數(shù),則當(dāng)收到“U”,“D”,“R”或“L”時(shí),程序?qū)?zhí)行一個(gè)指令20ms。盡管這個(gè)時(shí)間很短,但通過持續(xù)按下筆記本電腦上的按鍵,就可以非常快速地發(fā)送一連串的指令。
步驟 6: 代碼
1. 遠(yuǎn)程控制器
#include #include "nRF24L01.h" #include "RF24.h" char cmd[3]; RF24 radio(9,10); const uint64_t pipes[2] ={ 0xE8E8F0F0E1LL, 0xDEDEDEDEE7LL} ; float bar[7]; char arc[3]; int cifra[3]; int val; int a; void setup(void){ Serial.begin(9600); radio.begin(); radio.openWritingPipe(pipes[0]); radio.openReadingPipe(1,pipes[1]); radio.setDataRate(RF24_250KBPS); pinMode(6, OUTPUT); pinMode(5, OUTPUT); } void loop(void){ if(Serial) { digitalWrite(6, HIGH); digitalWrite(5, LOW); } else { digitalWrite(5, HIGH); digitalWrite(6, LOW); } cmd[0]=Serial.read(); if(cmd[0]=='O') { delay(20); arc[0]=Serial.read(); delay(20); arc[1]=Serial.read(); delay(20); arc[2]=Serial.read(); //Storing the values cifra[0]=arc[0]-'0'; cifra[1]=arc[1]-'0'; cifra[2]=arc[2]-'0'; val=10*cifra[0]+cifra[1]; Serial.println(val); Serial.println(sizeof(cmd)); cmd[0]='X'; cmd[1]=val; cmd[2]=cifra[2]; radio.write(cmd, sizeof(cmd)); } a=1; if(cmd[0]=='N') { radio.write(cmd, sizeof(cmd)); radio.startListening(); delay(1); while(a==1) { if (radio.available()){ a=2; bool done = false; while (!done){ done = radio.read(bar, 28); Serial.print("CMD:X1="); Serial.println(bar[0]); delay(5); Serial.print("CMD:D1="); Serial.println(bar[1]); delay(5); Serial.print("CMD:TE="); Serial.println(bar[2]); delay(5); Serial.print("CMD:PR="); Serial.println(bar[3]); delay(5); Serial.print("CMD:AT="); Serial.println(bar[4]); delay(5); Serial.print("CMD:AL="); Serial.println(bar[5]); Serial.print("CMD:LE="); Serial.println(bar[6]); } } } } else { radio.write(cmd, sizeof(cmd)); } }
2. 機(jī)器人
#include #include "nRF24L01.h" #include "RF24.h" #include #define BMP085_ADDRESS 0x77 // I2C address of BMP085 #define trigPin 2 #define echoPin 4 const unsigned char OSS = 0; int pwm, test1, test2; char cmd[3]; RF24 radio(9,10); const uint64_t pipes[2] ={ 0xE8E8F0F0E1LL, 0xDEDEDEDEE7LL} ; float bar[7]; int ac1; int ac2; int ac3; unsigned int ac4; unsigned int ac5; unsigned int ac6; int b1; int b2; int mb; int mc; int md; float temperature, pressure, atm, altitude,leduri; long b5; void bmp085Calibration() { ac1 = bmp085ReadInt(0xAA); ac2 = bmp085ReadInt(0xAC); ac3 = bmp085ReadInt(0xAE); ac4 = bmp085ReadInt(0xB0); ac5 = bmp085ReadInt(0xB2); ac6 = bmp085ReadInt(0xB4); b1 = bmp085ReadInt(0xB6); b2 = bmp085ReadInt(0xB8); mb = bmp085ReadInt(0xBA); mc = bmp085ReadInt(0xBC); md = bmp085ReadInt(0xBE); } // Calculate temperature in deg C float bmp085GetTemperature(unsigned int ut){ long x1, x2; x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15; x2 = ((long)mc << 11)/(x1 + md); b5 = x1 + x2; float temp = ((b5 + 8)>>4); temp = temp /10; return temp; } // Calculate pressure given up // calibration values must be known // b5 is also required so bmp085GetTemperature(...) must be called first. // Value returned will be pressure in units of Pa. long bmp085GetPressure(unsigned long up){ long x1, x2, x3, b3, b6, p; unsigned long b4, b7; b6 = b5 - 4000; // Calculate B3 x1 = (b2 * (b6 * b6)>>12)>>11; x2 = (ac2 * b6)>>11; x3 = x1 + x2; b3 = (((((long)ac1)*4 + x3)<>2; // Calculate B4 x1 = (ac3 * b6)>>13; x2 = (b1 * ((b6 * b6)>>12))>>16; x3 = ((x1 + x2) + 2)>>2; b4 = (ac4 * (unsigned long)(x3 + 32768))>>15; b7 = ((unsigned long)(up - b3) * (50000>>OSS)); if (b7 < 0x80000000) p = (b7<<1)/b4; else p = (b7/b4)<<1; x1 = (p>>8) * (p>>8); x1 = (x1 * 3038)>>16; x2 = (-7357 * p)>>16; p += (x1 + x2 + 3791)>>4; long temp = p; return temp; } // Read 1 byte from the BMP085 at 'address' char bmp085Read(unsigned char address) { unsigned char data; Wire.beginTransmission(BMP085_ADDRESS); Wire.write(address); Wire.endTransmission(); Wire.requestFrom(BMP085_ADDRESS, 1); while(!Wire.available()) ; return Wire.read(); } // Read 2 bytes from the BMP085 // First byte will be from 'address' // Second byte will be from 'address'+1 int bmp085ReadInt(unsigned char address) { unsigned char msb, lsb; Wire.beginTransmission(BMP085_ADDRESS); Wire.write(address); Wire.endTransmission(); Wire.requestFrom(BMP085_ADDRESS, 2); while(Wire.available()<2) ; msb = Wire.read(); lsb = Wire.read(); return (int) msb< } // Read the uncompensated temperature value unsigned int bmp085ReadUT(){ unsigned int ut; // Write 0x2E into Register 0xF4 // This requests a temperature reading Wire.beginTransmission(BMP085_ADDRESS); Wire.write(0xF4); Wire.write(0x2E); Wire.endTransmission(); // Wait at least 4.5ms delay(5); // Read two bytes from registers 0xF6 and 0xF7 ut = bmp085ReadInt(0xF6); return ut; } // Read the uncompensated pressure value unsigned long bmp085ReadUP(){ unsigned char msb, lsb, xlsb; unsigned long up = 0; // Write 0x34+(OSS<<6) into register 0xF4 // Request a pressure reading w/ oversampling setting Wire.beginTransmission(BMP085_ADDRESS); Wire.write(0xF4); Wire.write(0x34 + (OSS<<6)); Wire.endTransmission(); // Wait for conversion, delay time dependent on OSS delay(2 + (3<> (8-OSS); return up; } void writeRegister(int deviceAddress, byte address, byte val) { Wire.beginTransmission(deviceAddress); // start transmission to device Wire.write(address); // send register address Wire.write(val); // send value to write Wire.endTransmission(); // end transmission } int readRegister(int deviceAddress, byte address){ int v; Wire.beginTransmission(deviceAddress); Wire.write(address); // register to read Wire.endTransmission(); Wire.requestFrom(deviceAddress, 1); // read a byte while(!Wire.available()) { // waiting } v = Wire.read(); return v; } float calcAltitude(float pressure){ float A = pressure/101325; float B = 1/5.25588; float C = pow(A,B); C = 1 - C; C = C /0.0000225577; return C; } int distance2() { long duration, distance; digitalWrite(trigPin, LOW); // Added this line delayMicroseconds(2); // Added this line digitalWrite(trigPin, HIGH); delayMicroseconds(10); // Added this line digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = (duration/2) / 29.1; if (distance < 4) { // This is where the LED On/Off happens } else { } if (distance >= 200 || distance <= 0){ } else { } return distance; } void setup(void){ pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); bar[0] ='A'; Serial.begin(9600); radio.begin(); radio.openReadingPipe(1,pipes[0]); radio.openWritingPipe(pipes[1]); radio.startListening(); radio.setDataRate(RF24_250KBPS); pwm=255; pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(2, OUTPUT); pinMode(4, INPUT); pinMode(3, OUTPUT); Wire.begin(); bmp085Calibration(); } void mersF(int timp, int pwm) { analogWrite(5, pwm); digitalWrite(6, LOW); digitalWrite(8, LOW); digitalWrite(7, LOW); delay(timp); digitalWrite(5, LOW); } void mersS(int timp, int pwm) { analogWrite(6, pwm); digitalWrite(5, LOW); digitalWrite(8, LOW); digitalWrite(7, LOW); delay(timp); digitalWrite(6, LOW); } void VirareD(int timp, int pwm) { analogWrite(5, pwm); digitalWrite(6, LOW); digitalWrite(8, HIGH); digitalWrite(7, LOW); delay(timp); digitalWrite(5, LOW); digitalWrite(8, LOW); } void VirareS(int timp, int pwm) { analogWrite(5, pwm); digitalWrite(6, LOW); digitalWrite(7, HIGH); digitalWrite(8, LOW); delay(timp); digitalWrite(7, LOW); digitalWrite(5, LOW); } void VirareDspate(int timp, int pwm) { analogWrite(6, pwm); digitalWrite(5, LOW); digitalWrite(8, HIGH); digitalWrite(7, LOW); delay(timp); digitalWrite(6, LOW); digitalWrite(8, LOW); } void VirareSspate(int timp, int pwm) { analogWrite(6, pwm); digitalWrite(5, LOW); digitalWrite(7, HIGH); digitalWrite(8, LOW); delay(timp); digitalWrite(7, LOW); digitalWrite(6, LOW); } void stop(){ digitalWrite(5, LOW); digitalWrite(6, LOW); digitalWrite(7, LOW); digitalWrite(8, LOW); } void trimitere() { temperature = bmp085GetTemperature(bmp085ReadUT()); //MUST be called first pressure = bmp085GetPressure(bmp085ReadUP()); atm = pressure / 101325; // "standard atmosphere" altitude = calcAltitude(pressure); //Uncompensated caculation - in Meters leduri=digitalRead(3); radio.stopListening(); bar[0]=analogRead(A2); bar[1]=distance2(); bar[2]=temperature; bar[3]=pressure; bar[4]=atm; bar[5]=altitude; bar[6]=leduri; radio.write(bar, sizeof(bar)); delay(200); radio.startListening(); } void aprindere() { if(digitalRead(3)) digitalWrite(3,LOW); else digitalWrite(3,HIGH); delay(30); }
void loop(void){ if (radio.available()){ bool done = false; while (!done){ done = radio.read(cmd, 3); if (cmd[0] == 'U'){ Serial.println("Exec cmd: Up"); mersF(30,pwm); } else if (cmd[0] == 'E'){ Serial.println("Exec cmd: Right"); VirareD(30,pwm); } else if (cmd[0] == 'C'){ Serial.println("Exec cmd: Right"); VirareDspate(30, pwm); } else if (cmd[0] == 'Z'){ Serial.println("Exec cmd: Right"); VirareSspate(30,pwm); } else if (cmd[0] == 'D'){ Serial.println("Exec cmd: Down"); mersS(30,pwm); } else if (cmd[0] == 'Q'){ Serial.println("Exec cmd: Left"); VirareS(30, pwm); } else if (cmd[0] == 'N'){ delay(30); trimitere(); } else if (cmd[0] == 'B'){ delay(30); aprindere(); } else if (cmd[0] == 'J'){ stop(); delay(30); } else if (cmd[0] == 'X'){ test1=cmd[1]; test2=cmd[2]; pwm=test1*10+test2; delay(30); } else { } } } }
圖11:完成的RF機(jī)器人
圖12:組裝好的RF機(jī)器人(側(cè)視圖)
現(xiàn)在,我們已經(jīng)構(gòu)建了一個(gè)自主arduino機(jī)器人,該機(jī)器人能夠自主導(dǎo)航并從周圍環(huán)境中收集數(shù)據(jù)!這是一個(gè)非常具有挑戰(zhàn)性又很有意義的項(xiàng)目。這個(gè)原型還可以進(jìn)行進(jìn)一步的改善,例如添加用于在崎嶇地形/環(huán)境中提供保護(hù)的外殼。如果您有任何改進(jìn)的建議,請(qǐng)隨時(shí)與我分享!
Tiberia Todeila
Tiberia目前是羅馬尼亞布加勒斯特理工大學(xué)電氣工程學(xué)院的大四學(xué)生。她非常熱衷于設(shè)計(jì)和開發(fā)讓日常生活更輕松的智能居家設(shè)備。
審核編輯黃宇
-
機(jī)器人
+關(guān)注
關(guān)注
213文章
29737瀏覽量
212862
發(fā)布評(píng)論請(qǐng)先 登錄
【「# ROS 2智能機(jī)器人開發(fā)實(shí)踐」閱讀體驗(yàn)】視覺實(shí)現(xiàn)的基礎(chǔ)算法的應(yīng)用
【「# ROS 2智能機(jī)器人開發(fā)實(shí)踐」閱讀體驗(yàn)】+ROS2應(yīng)用案例
名單公布!【書籍評(píng)測(cè)活動(dòng)NO.58】ROS 2智能機(jī)器人開發(fā)實(shí)踐
開源項(xiàng)目!基于Arduino控制的六足機(jī)器人
【「具身智能機(jī)器人系統(tǒng)」閱讀體驗(yàn)】2.具身智能機(jī)器人的基礎(chǔ)模塊
《具身智能機(jī)器人系統(tǒng)》第10-13章閱讀心得之具身智能機(jī)器人計(jì)算挑戰(zhàn)
開源項(xiàng)目!能夠精確地行走、跳舞和執(zhí)行復(fù)雜動(dòng)作的機(jī)器人—Tillu
【「具身智能機(jī)器人系統(tǒng)」閱讀體驗(yàn)】1.初步理解具身智能
《具身智能機(jī)器人系統(tǒng)》第1-6章閱讀心得之具身智能機(jī)器人系統(tǒng)背景知識(shí)與基礎(chǔ)模塊
開源項(xiàng)目!可以自主演奏音樂的尤克里里機(jī)器人!
智能網(wǎng)聯(lián)汽車云控系統(tǒng)第2部分:車云數(shù)據(jù)交互規(guī)范
【開源項(xiàng)目】你準(zhǔn)備好DIY一款功能強(qiáng)大的機(jī)器人了嗎?
如何進(jìn)行電源設(shè)計(jì)–第2部分

電源設(shè)計(jì)方法-第2部分

電源設(shè)計(jì)方法-第1部分

評(píng)論