這篇文章來源于DevicePlus.com英語網站的翻譯稿。
點擊此處閱讀本文第1部分 >
在智能寵物喂食機的第1部分“使用Arduino Uno制作智能自動寵物喂食機”中,我們建立了一個自動化平臺,可以判斷您的寵物是否被喂食以及計算下一次喂食的時間。在第2部分中,我們將嘗試通過MIT App Inventor開發一個應用程序并添加語音識別功能,使系統更加“智能”。在之前的教程中,我們已經使用過MIT App Inventor來創建應用程序。App Inventor是一種易于使用的基于塊的語言,用于設計Android應用程序。
硬件
? Arduino UNO
? RFID RC522
? HC-05 藍牙模塊
沿用第1部分的硬件:
? Arduino Uno
? 距離傳感器 Sharp GP2Y0A21YK
? RFID RC522
? 蜂鳴器
? 電機 SG90
? RTC DS1307
軟件
Arduino IDE
GitHub上的PetFeeder
MIT App Inventor
我們在第1部分中設置的用于EEPROM的標簽—在該存儲器中,標簽將被保存到我們將其清除為止。此功能有助于我們將自己的寵物與其他寵物分開來,只為帶有指定標簽的寵物提供食物。
我們在第1部分中設置了兩個標簽,并使用EEPROM來存儲數據。RFID標簽有助于識別您的寵物并將其與其他人的寵物區分開,從而僅向帶有指定標簽的寵物提供食物。使用EEPROM可以確保數據在系統重新啟動后也可以安全地存儲在內存中。您可以使用以下代碼從EEPROM更改標簽信息:
#include #include #include boolean match = false; boolean programMode = false; int isOurPet; byte storedCard[4]; byte readCard[4]; byte masterCard[4]; #define SS_PIN 10 #define RST_PIN 9 MFRC522 mfrc522(SS_PIN, RST_PIN); void setup() { Serial.begin(9600); SPI.begin(); mfrc522.PCD_Init(); if (EEPROM.read(1) != 143) { do { isOurPet = findOurPet(); } while (!isOurPet); for ( int j = 0; j < 4; j++ ) { EEPROM.write( 2 + j, readCard[j] ); } EEPROM.write(1, 143); } for ( int i = 0; i < 4; i++ ) { masterCard[i] = EEPROM.read(2 + i); } } void loop () { do { isOurPet = findOurPet(); } while (!isOurPet); if ( master(readCard)) { programMode = true; Serial.println(F("Our Pet - Green Tag")); int count = EEPROM.read(0); } else { Serial.println(F("Not our pet - Purple Tag")); } } int findOurPet() { if ( ! mfrc522.PICC_IsNewCardPresent()) { return 0; } if ( ! mfrc522.PICC_ReadCardSerial()) { return 0; } for (int i = 0; i < 4; i++) { readCard[i] = mfrc522.uid.uidByte[i]; } mfrc522.PICC_HaltA(); // Stop reading return 1; } void readCollar( int number ) { int start = (number * 4 ) + 2; for ( int i = 0; i < 4; i++ ) { storedCard[i] = EEPROM.read(start + i); } } boolean EEpromCheck ( byte a[], byte b[] ) { if ( a[0] != NULL ) match = true; for ( int k = 0; k < 4; k++ ) { if ( a[k] != b[k] ) match = false; } if ( match ) { return true; } else { return false; } } boolean master( byte test[] ) { if ( EEpromCheck( test, masterCard ) ) return true; else return false; }
圖1:我們在EEPROM中的寵物標簽
上次,我們將寵物指定為紅色標簽。這次,我們將標簽更改為綠色。
步驟2:怎樣控制伺服
伺服電機通過來自微控制器的PWM(脈沖寬度調節)來更改其位置。伺服需要進行校準,并將其閥門開度角度設置為90度。
為了控制伺服,我們將使用ArduinoSweep 代碼。該代碼中伺服電機軸可以旋轉180度。我們將把旋轉角度從0-180度更改為10-170度。
#include Servo myservo; // create servo object to control a servo int pos = 0; // variable to store the servo position void setup() { myservo.attach(9); // attaches the servo on pin 9 to the servo object } void loop() { for(pos = 10; pos <= 170; pos += 1) // goes from 10 degrees to 170 degrees // in steps of 1 degree { myservo.write(pos); // tell servo to go to position in variable ‘pos’ delay(15); // waits 15ms for the servo to reach the position } for(pos = 170; pos>=10; pos -= 1) // goes from 170 degrees to 10 degrees { myservo.write(pos); // tell servo to go to position in variable ‘pos’ delay(15); // waits 15ms for the servo to reach the position } }
關于伺服的注意事項:
您的伺服在某些時候可能會行為異常。當您由于Arduino自行重啟而無法執行指令時,可能是因為USB端口沒有提供足夠的電源來驅動伺服。在這種情況下,Arduino會進行重置,應用程序也無法工作。用以下兩種方法可以避免此問題的產生:
1. 您可以在面包板上的GND和5V之間添加一個大容值電容器(470uF或更大)。當Arduino沒有足夠的電源來維持電流時,該電容器可以用作臨時電源。較長的端子必須連接到VDD=5V,而較短的端子必須連接到GND。
2. 您可以通過USB對開發板進行編程,之后斷開連接。然后,您可以使用手機充電器通過插頭來為裝置供電,因為它具有更大的電流容量。
讓我們簡要地看一下 庫的工作方式。
#include
必須含有該指令才能使用Servo.h庫。Servo庫中給出的兩個示例是“Knob”和“Sweep”。這兩個控件對伺服的測試很有用。使用Knob,您就可以通過電位計將伺服轉動到特定角度。使用Sweep,您就可以在180度的范圍內來回掃動伺服軸。
Servo servo;
這是一個類型的聲明。它定義了servo類型的變量Servo。它與其他用于伺服的類型相似,如int(整型)和float(浮點型)。
servo.attach(servoPin);
在設置代碼塊時,您需要將伺服分配給特定的引腳。該指令用于將伺服變量分配引腳。
servo.write(angle);
此指令將設置伺服軸角度(在0到180度范圍內),然后將伺服轉動至該角度。
步驟3:添加HC-05藍牙模塊
———-
關于藍牙HC-05 – 用戶使用手冊
藍牙串行模塊的運行不需要進行驅動,并且可以與具有串行接口的其他藍牙設備進行通信。兩個藍牙模塊之間實現通信至少需要兩個條件:
(1) 必須在主設備和從設備之間進行。
(2) 密碼必須正確。
該模塊的屬性:
? 可以在主模式和從模式之間切換
? 藍牙名稱: HC-05
? 密碼: 1234
配對:主設備不僅可以與指定的藍牙地址配對(如手機、計算機適配器、從設備),還可以自動搜索從設備并與之配對。
典型方法:在某些特定條件下,主設備和從設備可以自動配對(這是默認方法)。
———-
在本項目中,我們選擇了藍牙連接,因為這種方式易于配置。藍牙模塊用作Arduino的串行端子,將被連接到TX和RX引腳。
想要通過藍牙進行數據傳輸需要遵循一些規則。我們需要有:
? (通常值為邏輯0)
? 數據位
? 校驗位(數據位的和;我們將會比較從頭到尾所有位的和)
? 停止位(大多數情況下值為邏輯1)
引腳連接:
1.將HC-05的TX引腳連接到Arduino的RX引腳。
2.將HC-05的RX引腳連接到Arduino的TX引腳。
3.將GND引腳連接在一起。
在我們之前的教程 制作您自己的Arduino RFID門鎖—第2部分:用智能手機解鎖中對HC-05設置進行了詳細說明。如果您在連接藍牙模塊時遇到問題,請參考上述教程。
圖2:HC-05和Arduino Uno之間的連接
藍牙傳輸代碼:
#include #include #include #include #include #include #include #include #include String voice; #define SS_PIN 10 #define RST_PIN 9 Servo myservo; boolean match = false; boolean programMode = false; boolean replaceMaster = false; int lightSensor = 0; int distanceSensor=1; int pos = 0; int successRead; byte storedCard[4]; byte readCard[4]; byte masterCard[4]; MFRC522 mfrc522(SS_PIN, RST_PIN); void setup() { pinMode(8, OUTPUT); setSyncProvider(RTC.get); myservo.attach(9); Serial.begin(9600); SPI.begin(); mfrc522.PCD_Init(); if (EEPROM.read(1) != 143) { do { successRead = getID(); } while (!successRead); for ( int j = 0; j < 4; j++ ) { EEPROM.write( 2 + j, readCard[j] ); } EEPROM.write(1, 143); } for ( int i = 0; i < 4; i++ ) { masterCard[i] = EEPROM.read(2 + i); Serial.print(masterCard[i], HEX); Serial.println(""); } } void loop() { int valueFromLightSensor = analogRead(lightSensor); //Serial.print("Light Value= "); //Serial.println(valueFromLightSensor); //Serial.println(""); //Serial.print("Distance Value= "); int valueFromDistanceSensor = analogRead(distanceSensor); int distance= 4800/(valueFromDistanceSensor - 20); //Serial.println(distance); //Serial.print("Hour= "); // Serial.println(hour()); while (Serial.available()) { delay(10); char c = Serial.read(); voice += c; } if (voice.length() > 0) { Serial.println(voice); if(voice == "feed") { myservo.write(130); delay(1000); myservo.write(50); delay(1000); myservo.write(130); delay(1000); myservo.write(50); delay(1000); digitalClockDisplay(); } if(voice == "feed2") { myservo.write(130); delay(1000); myservo.write(50); delay(1000); digitalClockDisplay(); } if(voice == "feed1") { myservo.write(130); delay(1000); myservo.write(50); delay(1000); myservo.write(130); delay(1000); myservo.write(50); delay(1000); myservo.write(130); delay(1000); myservo.write(50); delay(1000); digitalClockDisplay(); } ; } do { successRead = getID(); } while (!successRead); if (programMode) { if ( isMaster(readCard) ) { programMode = false; return; } else { if ( findID(readCard) ) { } } } else { if ( isMaster(readCard)) { programMode = true; int count = EEPROM.read(0); } else { if ( findID(readCard) ) { if ((hour()>=8) && (hour()<=12 )){ if (distance>=20){ // Serial.println(distance); myservo.write(130); delay(100); myservo.write(50); delay(100); myservo.write(130); delay(100); myservo.write(50); delay(100); digitalClockDisplay(); } delay(300); }
if ((hour()>=12) && (hour()<=16 )){ if (distance>=20){ // Serial.println(distance); myservo.write(130); delay(100); myservo.write(50); delay(100); myservo.write(130); delay(100); myservo.write(50); delay(100); digitalClockDisplay(); } delay(300); } if ((hour()>=16) && (hour()<=20 )){ if (distance>=20){ // Serial.println(distance); myservo.write(130); delay(100); myservo.write(50); delay(100); myservo.write(130); delay(100); myservo.write(50); delay(100); digitalClockDisplay(); } delay(300); } if ((hour()>=20) && (hour()<=8 )){ if (distance>=20){ // Serial.println(distance); myservo.write(130); delay(100); myservo.write(50); delay(100); myservo.write(130); delay(100); myservo.write(50); delay(100); digitalClockDisplay(); } delay(300); } } else { // If not, show that the ID was not valid Serial.println(F("You shall not pass")); } } } } int getID() { if ( ! mfrc522.PICC_IsNewCardPresent()) { return 0; } if ( ! mfrc522.PICC_ReadCardSerial()) { return 0; } // Serial.println(F("Scanned PICC's UID:")); for (int i = 0; i < 4; i++) { // readCard[i] = mfrc522.uid.uidByte[i]; // Serial.print(readCard[i], HEX); } // Serial.println(""); mfrc522.PICC_HaltA(); // Stop reading return 1; } void readID( int number ) { int start = (number * 4 ) + 2; for ( int i = 0; i < 4; i++ ) { storedCard[i] = EEPROM.read(start + i); } } boolean checkTwo ( byte a[], byte b[] ) { if ( a[0] != NULL ) match = true; for ( int k = 0; k < 4; k++ ) { if ( a[k] != b[k] ) match = false; } if ( match ) { return true; } else { return false; } } int findIDSLOT( byte find[] ) { int count = EEPROM.read(0); for ( int i = 1; i <= count; i++ ) { readID(i); if ( checkTwo( find, storedCard ) ) { return i; break; } } } boolean findID( byte find[] ) { int count = EEPROM.read(0); for ( int i = 1; i <= count; i++ ) { readID(i); if ( checkTwo( find, storedCard ) ) { return true; break; } else { } } return false; } boolean isMaster( byte test[] ) { if ( checkTwo( test, masterCard ) ) return true; else return false; } void digitalClockDisplay() { Serial.print(hour()); printDigits(minute()); //printDigits(second()); Serial.print(" "); Serial.print(day()); Serial.print(" "); Serial.print(month()); Serial.print(" "); Serial.print(year()); Serial.println(); } void printDigits(int digits){ // utility function for digital clock display: prints preceding colon and leading 0 Serial.print(":"); if(digits < 10) Serial.print('0'); Serial.print(digits); }
該代碼中的算法非常簡單:我們對串口進行初始化,然后等待端口打開。我們將通過該代碼發送指令。如果代碼不可用,程序將不會執行,“喂食”指令將不會發送到微控制器進行相應處理。
該程序還將比較來自“voice(聲音)”變量的字符串和串口讀取的字符串。如果兩者相同,則會向電機發送一個指令來觸發SG90伺服電機。
圖3:連接到第1部分裝置中的HC-05藍牙模塊
步驟3:設計應用程序
現在,讓我們來創建一個應用程序吧!和以前一樣,我們將使用MIT App Inventor。我們的最終目標是創建一個對所連接的多種設備進行集成的組合型應用程序(例如,集成了多個所連接設備的智能家居應用程序)。
有關MIT App Inventor的設置指南,請參考上一教程制作您自己的Arduino RFID門鎖—第2部分:用智能手機解鎖 (步驟3:應用程序)。本教程將分步指導您使用App Inventor創建自己的應用程序。
我們所創建的應用程序將會具有一個簡單的界面,其中包含以下功能:
? 連接到藍牙
? 使您可以遠程喂食寵物
? 存儲:將數據(以文件形式)存儲在手機中
? 顯示日期:在手機屏幕上顯示日期信息
圖4:寵物喂食器應用程序的簡單用戶界面
該程序的模塊圖非常簡單易懂:
?第一個模塊:第一個模塊用于藍牙按鈕
? ListPicker – MIT App Inventor
單擊該按鈕,將顯示文本列表供用戶選擇。可以通過Designer或Blocks Editor來指定文本內容,方法是將ElementsFromString屬性設置為文本的拆分字符串級聯(例如,選擇1,選擇2,選擇3)或者將Elements屬性設置為一個Blocks editor中的列表。
? ListPicker 顯示所有可用的藍牙;該功能在您選擇某一個藍牙前有效。
圖5:第一個模塊
? 第二個模塊:通過調用BluetoothClient1.Connect address現應用程序與客戶端之間的連接。您手機中的藍牙將搜索附近的設備,并將其顯示在ListPicker中。您可以選擇要配對的設備。
圖6:第二個模塊
同時還有一個標簽,在建立連接后標簽上會顯示相關消息。如果設備已經成功連接,您將在屏幕上看到“已連接(Connected)”的消息。
? 第三個模塊:僅用于連接完成時通過客戶端發送一個消息。該文本以串行通信的方式,通過藍牙從一個設備發送到另一個設備。這就像在Arduino的串行監視器中鍵入文本一樣。
當我們從串行讀取數據時,就是在對用戶的輸入與Arduino內存中存儲的字符串進行比較。這就是算法的工作原理。
圖7:第三個模塊
我們接下來看看另一組模塊圖:
? 第一個模塊:將藍牙傳輸的日期保存到存儲在手機內存里的.txt文件中。
圖8:第一個模塊
? 第二個模塊:當按下按鈕時,將會讀取保存在文本文件中的數據。
圖9:第二個模塊
? 第三個模塊:將喂食的時間和標簽寫入屏幕。這個信息很有用,因為它可以幫助我們對喂食時間進行記錄,如果我們不想對寵物喂食過多,可以查看時間來確認。
圖10:第三個模塊
? 第四個模塊:發生錯誤時,該模塊將刪除.txt文件中的所有數據。這一功能很重要,因為一旦執行,將不再顯示以前的信息。
圖11:第四個模塊
該應用程序的第二部分提供了不同的喂食模式:正常喂食模式,用于寵物寶寶的喂食模式和用于成年寵物的喂食模式。這也為您提供了需要為寵物喂食多少食物量的有關信息。其中最酷的功能之一是語音識別模式。我們將在下文中討論有關該功能的更多內容。
圖12:應用程序上顯示的非正確時間和日期
如果想要查找喂食的日期和時間,可以按“顯示日期”按鈕。該應用程序是以精簡模式制作的,因為并不是每個人都希望看到所有信息。如圖所示,日期和時間顯示不正確。為了獲得確切的日期和時間,我們需要使用Arduino IDE中的Set Time 示例。現在,RTC模塊將指示正確的日期和時間。
圖13:如何找到Arduino IDE中的SetTime
Set Time code: #include #include #include const char *monthName[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; tmElements_t tm; void setup() { bool parse=false; bool config=false; // get the date and time the compiler was run if (getDate(__DATE__) && getTime(__TIME__)) { parse = true; // and configure the RTC with this info if (RTC.write(tm)) { config = true; } } Serial.begin(9600); while (!Serial) ; // wait for Arduino Serial Monitor delay(200); if (parse && config) { Serial.print("DS1307 configured Time="); Serial.print(__TIME__); Serial.print(", Date="); Serial.println(__DATE__); } else if (parse) { Serial.println("DS1307 Communication Error :-{"); Serial.println("Please check your circuitry"); } else { Serial.print("Could not parse info from the compiler, Time=""); Serial.print(__TIME__); Serial.print("", Date=""); Serial.print(__DATE__); Serial.println("""); } } void loop() { } bool getTime(const char *str) { int Hour, Min, Sec; if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false; tm.Hour = Hour; tm.Minute = Min; tm.Second = Sec; return true; } bool getDate(const char *str) { char Month[12]; int Day, Year; uint8_t monthIndex; if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false; for (monthIndex = 0; monthIndex < 12; monthIndex++) { if (strcmp(Month, monthName[monthIndex]) == 0) break; } if (monthIndex >= 12) return false; tm.Day = Day; tm.Month = monthIndex + 1; tm.Year = CalendarYrToTm(Year); return true; }
圖14顯示了該應用程序的最終版本:
圖14:應用程序的最終版本
應用程序概述:
? 需要連接到藍牙。
? 根據寵物的年齡采用不同的喂食模式。
? 要激活語音識別模式,只需單擊“語音識別喂食”按鈕并講話即可。
? 僅當正確說出指令時,喂食器才能運行。如果關鍵字不正確(無法正確識別),則只會在標簽上顯示而不執行指令。
? 在語音識別的模式下,當兩次說出/重復“喂食”指令(比如您沒有等待幾秒鐘的時間讓系統對信息進行處理)時,指令就會變成“喂食喂食”,這不是有效的指令。它將保留在標簽上而不執行。
? 如果語音識別上的“喂食”指令正常工作,標簽上將會打印出時間。
? 還包括一個喂食指南,您可以根據寵物的體重查找有關所應提供食物量的信息。
圖15:寵物喂食指南/ ?Fish4Dogs
所有指令都可以在Arduino IDE的串行監視器上找到。這有助于我們在必要時對應用程序進行調試。
圖16:串行監視器中顯示的喂食數據
對于語音識別,我們需要一個按鈕來激活該模式。我們可以使用App Inventor中已經提供的SpeechRecognizer組件。
圖17:在MIT App Interventor上添加SpeechRecognizer組件
有了這兩個組件后,將它們連接起來非常簡單。您需要處理來自講話者的文本。這是通過調用SpeechRecognizer.GetText來完成的。之后,您需要有一個標簽來顯示所說的內容,也可以沒有這個標簽,但是如果沒有標簽您將無法看到自己是否說了正確的指令。在程序循環中,您還需要通過藍牙將語音指令傳輸到Arduino,需要使用SentText text程序。
圖18:語音識別模塊
對于每種模式,您都需要有相對應的按鈕。每個按鈕對應不同的指令,該指令將會被發送到Arduino,然后據此喂食不同量的食物。
圖19:寵物寶寶模式,正常喂食模式和成年寵物模式模塊
審核編輯黃宇
-
RFID
+關注
關注
388文章
6171瀏覽量
238090 -
語音識別
+關注
關注
38文章
1742瀏覽量
112712 -
程序
+關注
關注
117文章
3791瀏覽量
81156 -
Arduino
+關注
關注
188文章
6472瀏覽量
187320
發布評論請先 登錄
相關推薦
評論