- 文/曹永忠、吳欣蓉、陳建宇
本篇是承續「物聯網開發」系列中「顯示技術技巧大探索」直譯式顯示技術應用:以貪吃蛇為例(上、中篇)(曹永忠, 許智誠, & 蔡英德, 2017a, 2017b)的文章,主要告訴讀者,美商律美股份有限公司台灣分公司(Lumex Inc. Taiwan Branch)發展了 EZDISPLAY 的產品,運用直譯式顯示技術,可以讓開發系統與顯示系統各自獨立開發,創造了軟硬體系統開發的彈性。
本文將應用 Ameba8195AM 開發版,搭配 APP Inventor 2 手機開發程式,實作貪吃蛇來告訴讀者如何運用大型顯示幕做一個互動遊戲(曹永忠, 2017a, 2017b, 2017c)。
AMEBA 8195AM 開發版之系統開發
我們將 AMEBA 開發板的驅動程式安裝好之後,打開 Arduino 開發板的開發工具:Sketch IDE 整合開發軟體(軟體下載請至此),攥寫開發程式,如下表所示之貪吃蛇伺服器暨顯示主程式,將之編譯完成後,上傳到 Ameba 8195 AM 開發板之上,就可以進行系統測試了。
(表 1)貪吃蛇伺服器暨顯示程式
貪吃蛇伺服器暨顯示主程式(Ameba_snakegame_APMode_Control_by_TCPIP): |
#include <String.h> // Use String object
#include <WiFi.h> // Use wifi #include “control.h” /* AP Mode */ //————————————- IPAddress Meip ,Megateway ,Mesubnet ; //get network parameter WiFiServer server(80);
void ShowMac() {
Serial.print(“MAC:”); Serial.print(MacAddress); Serial.print(“\n”);
}
String GetWifiMac() { String tt ; String t1,t2,t3,t4,t5,t6 ; WiFi.status(); //this method must be used for get MAC WiFi.macAddress(MacData);
Serial.print(“Mac:”); Serial.print(MacData[0],HEX) ; Serial.print(“/”); Serial.print(MacData[1],HEX) ; Serial.print(“/”); Serial.print(MacData[2],HEX) ; Serial.print(“/”); Serial.print(MacData[3],HEX) ; Serial.print(“/”); Serial.print(MacData[4],HEX) ; Serial.print(“/”); Serial.print(MacData[5],HEX) ; Serial.print(“~”);
t1 = print2HEX((int)MacData[0]); t2 = print2HEX((int)MacData[1]); t3 = print2HEX((int)MacData[2]); t4 = print2HEX((int)MacData[3]); t5 = print2HEX((int)MacData[4]); t6 = print2HEX((int)MacData[5]); tt = (t1+t2+t3+t4+t5+t6) ; tt.toUpperCase() ; Serial.print(tt); Serial.print(“\n”);
return (tt) ; }
String print2HEX(int number) { String ttt ; if (number >= 0 && number < 16) { ttt = String(“0”) + String(number,HEX); } else { ttt = String(number,HEX); } return ttt ; }
void printWifiData() { // print your WiFi shield’s IP address: Meip = WiFi.localIP(); Serial.print(“IP Address: “); Serial.println(Meip); Serial.print(“\n”);
// print your MAC address: byte mac[6]; WiFi.macAddress(mac); Serial.print(“MAC address: “); Serial.print(mac[5], HEX); Serial.print(“:”); Serial.print(mac[4], HEX); Serial.print(“:”); Serial.print(mac[3], HEX); Serial.print(“:”); Serial.print(mac[2], HEX); Serial.print(“:”); Serial.print(mac[1], HEX); Serial.print(“:”); Serial.println(mac[0], HEX);
// print your subnet mask: Mesubnet = WiFi.subnetMask(); Serial.print(“NetMask: “); Serial.println(Mesubnet);
// print your gateway address: Megateway = WiFi.gatewayIP(); Serial.print(“Gateway: “); Serial.println(Megateway); }
void ShowInternetStatus() {
if (WiFi.status()) { Meip = WiFi.localIP(); Serial.print(“Get IP is:”); Serial.print(Meip); Serial.print(“\n”);
} else { Serial.print(“DisConnected:”); Serial.print(“\n”); }
}
void initializeWiFi() { String SSIDName = String(“AMEBA”)+MacAddress.substring(6,11) ; // stringcpy(SSIDName.toUpperCase(),&ssid[0]) ; stringcpy(SSIDName,&ssid[0]) ; while (status != WL_CONNECTED) { Serial.print(“Attempting to connect to SSID: “); Serial.println(ssid); // Connect to WPA/WPA2 network. Change this line if using open or WEP network: status = WiFi.apbegin(ssid, pass, channel); // status = WiFi.begin(ssid);
// wait 10 seconds for connection: delay(10000); } Serial.print(“\n Success to connect AP:”) ; Serial.print(ssid) ; Serial.print(“\n”) ;
}
void stringcpy(String srcchar, char *tarchar) { for (int i = 0 ; i < srcchar.length(); i++) { *(tarchar+i) = srcchar.charAt(i);
}
}
void strcpy(char *srcchar, char *tarchar, int len) { for (int i = 0 ; i < len; i++) { *(tarchar+i) = *(srcchar+i);
}
} /* LED Matrix Command */ //——————————- void StringWrite_AT_Command(String string){ mySerial.print(string); while (mySerial.read() != ‘E’) {} delay(2);
}
void initATMode(){ mySerial.write(0xf6); mySerial.write(0x01); delay(200); }
/* Game function*/ void Score() { if (eatfood == true) { eatfood = false; GenFood();
score++; snake_len++; rychlost -= 20;
//Serial.print (“len: “); //Serial.println (snake_len); Serial.print (“score: “); Serial.println (score);
} }
void Display(int matrix[8][8], byte x, byte y) { for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if ((matrix[i][j] >= 1) || ((i == x) && (j == y) )) { StringWrite_AT_Command(“AT9e=(“+String(i+45)+”,”+String(j+13)+”)”); }
} } }
void VymazHada(int matice[8][8]) { for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if (matice[i][j] == (clocktick – score)) { matice[i][j] = 0; StringWrite_AT_Command(“AT9f=(“+String(i+45)+”,”+String(j+13)+”)”); } } } }
void Pohyb() { switch (smer) { case 0: xHad++; break; case 1: xHad–; break; case 2: yHad–; break; case 3: yHad++; break; } if (xHad == 8) xHad = 0; if (yHad == 8) yHad = 0; if (xHad == 255) xHad = 7; if (yHad == 255) yHad = 7; }
void GenFood() { StringWrite_AT_Command(“AT9f=(“+String(xPotrava+45)+”,”+String(yPotrava+13)+”)”); do { xPotrava = random(0, 8); yPotrava = random(0, 8); } while (had[xPotrava][yPotrava] != 0 ); }
void GameOver() { Serial.println (“Game over”);
StringWrite_AT_Command(“ATd0=()”); // clear display TextMove_to_right(“GAME OVER”); StringWrite_AT_Command(“AT81=(3,7,Score:”+String(score)+”)”); }
void ScreenAnimate(){
for (int row = 0; row < 30; row=row+5) { for (int col = 0; col < 96; col=col+10) { StringWrite_AT_Command(“AT92=(“+String(col)+”,”+String(row)+”,”+String(col+5)+”,”+String(row+5)+”,1)”); //print square 1 StringWrite_AT_Command(“AT92=(“+String(col+3)+”,”+String(row)+”,”+String(col+8)+”,”+String(row+5)+”,1)”); //print square 1
delay(15); } }
for (int row = 0; row < 30; row=row+5) { for (int col = 0; col < 96; col=col+10) { StringWrite_AT_Command(“AT92=(“+String(col)+”,”+String(row)+”,”+String(col+5)+”,”+String(row+5)+”,0)”); //print square 0 StringWrite_AT_Command(“AT92=(“+String(col+3)+”,”+String(row)+”,”+String(col+8)+”,”+String(row+5)+”,0)”); //print square 1
delay(15); } }
} //————main void setup() { Serial.begin(9600) ; MacAddress = GetWifiMac() ; // get MacAddress ShowMac() ; //Show Mac Address initializeWiFi(); server.begin(); printWifiData() ;
delay(2000) ; //wait 2 seconds
//–game Serial.println(“START Snake Game”); mySerial.begin(115200); initATMode(); StringWrite_AT_Command(“ATd0=()”); // clear display delay(20); ScreenAnimate(); TriangleFrame(); TextMove_to_right(” START “); StringWrite_AT_Command(“ATd0=()”); // clear display delay(20); StringWrite_AT_Command(“AT91=(44,12,53,21,1)”); //Draw a play zone (square) }
void Up(Pos *nowp) { if (nowp->y > bound_y1){ nowp->y –;
Serial.print(“y:”); Serial.println(nowp->y); nowp->isUpdated = true ; Serial.print(“nowp.isUpdated:”); Serial.println(nowp->isUpdated); //delay(20);
} }
void Down(Pos *nowp) { if (nowp->y < bound_y2){ nowp->y++; Serial.print(“y:”); Serial.println(nowp->y); nowp->isUpdated = true ; Serial.print(“nowp.isUpdated:”); Serial.println(nowp->isUpdated); //delay(20); } }
void Left(Pos *nowp) { if (nowp->x > bound_x1){ nowp->x–; Serial.print(“x:”); Serial.println(nowp->x); nowp->isUpdated = true ; Serial.print(“nowp.isUpdated:”); Serial.println(nowp->isUpdated); //delay(20); } }
void Right(Pos *nowp) { if (nowp->x < bound_x2){ nowp->x++; Serial.print(“x:”); Serial.println(nowp->x); nowp->isUpdated = true ; Serial.print(“nowp.isUpdated:”); Serial.println(nowp->isUpdated); //delay(20); } }
void Draw(Pos nowp) {
StringWrite_AT_Command(“AT9e=(“+String(nowp.x)+”,”+String(nowp.y)+”)”); Serial.print(“Draw:”); Serial.println(String(nowp.x)+”,”+String(nowp.y));
}
void Erase(Pos nowp) { StringWrite_AT_Command(“AT9f=(“+String(nowp.x)+”,”+String(nowp.y)+”)”); Serial.print(“Erase:”); Serial.println(String(nowp.x)+”,”+String(nowp.y)); }
void GenFood(Pos *nowp) { if (eatFood == true) { eatFood = false; randNum = random(bound_x1,bound_x2); nowp->x = randNum; nowp->y = randNum; } }
void loop() { WiFiClient client = server.available(); readok = false ; StringWrite_AT_Command(“AT81=(0,0,Wating for player)”); if (client) {
Serial.println(“Now Someone Access WebServer”); Serial.println(“new client”); // an http request ends with a blank line boolean currentLineIsBlank = true;
while (client.connected()) { StringWrite_AT_Command(“AT81=(0,0,Player1)”); if (client.available()>0) { btstring = client.readStringUntil(0x0a); //btchar = client.read(); delay(50); Serial.print(“btstring:”); Serial.println(btstring) ;
//tempPosition.x = nowPosition.x; //tempPosition.y = nowPosition.y; //test game //Serial.println(“before VymazHada, clocktick-score= “+String(clocktick – score));
//control snake if (btstring == “L”) { smer = 1; } else if (btstring == “R”) { smer = 0; } else if (btstring == “U”) { smer = 2; } else if (btstring == “D”) { smer = 3; } VymazHada(had); Score(); Pohyb(); //move had if (xHad == xPotrava && yHad == yPotrava) eatfood = true; if (had[xHad][yHad] != 0) GameOver();
//Serial.println(“clocktick=”+String(clocktick)); clocktick++; //clock tick //Serial.println(“ticked, clocktick=”+String(clocktick)); had[xHad][yHad] = clocktick; //Serial.println(“set had[“+String(xHad)+”][“+String(yHad)+”]=”+String(clocktick)); Display(had, xPotrava, yPotrava); //set led
//end test game } // end of client.available()
} //end of while (client.connected())
} //end of if (client)
// delay(800) ; // free(client);
}
/* Opening Animation */ void TriangleFrame() { for (int i = 0; i <= 95 ; i=i+10) { //StringWrite_AT_Command(“AT95=(“+String(i)+”,1,5,1)”); //StringWrite_AT_Command(“AT95=(“+String(i)+”,30,5,1)”); StringWrite_AT_Command(“AT98=(“+String(i)+”,5,5,1)”); //downtri, peak is origin StringWrite_AT_Command(“AT97=(“+String(i)+”,26,5,1)”); //uptri, peak is origin } }
void TextMove_to_right(String string) {
StringWrite_AT_Command(“AT83=(1,0,”+string+”!!)”); delay(100); StringWrite_AT_Command(“ATd9=(0,10,100,23, 80)”); } |
(表 2)貪吃蛇伺服器暨顯示程式
貪吃蛇伺服器暨顯示含括檔程式(control.h): |
#include <String.h>
char ssid[12] =”AMEBA” ; // your network SSID (name) char pass[9] = “12345678”; // your network password char channel[] = “1”; uint8_t MacData[6]; // get mac address String MacAddress ; int status = WL_IDLE_STATUS; // wifi status String ReadStr = “” ;// recive data int delayval = 500; // delay for half a second int count = 0 ; boolean readok = false ; unsigned long strtime ; //Game control unsigned char btchar ; String btstring; struct Pos { int x; int y; boolean isUpdated; } ; Pos nowPosition = { 48 , 15 , false }; Pos tempPosition = { 0 , 0 , false };
int bound_x1 = 45; int bound_y1 = 13; int bound_x2 = 52; int bound_y2 = 20; int HHeight = 8; int WWidth = 8;
long randNum = 5; Pos foodPosition = {randNum, randNum, false}; boolean eatFood = false; //—–Control Display Use—–
#include <SoftwareSerial.h> const byte rxPin = 0; const byte txPin = 1; SoftwareSerial mySerial (rxPin, txPin); String incomingByte; // for incoming serial data int had[8][8] = {0}; byte xHad = 4; byte yHad = 5; byte xPotrava = 0; byte yPotrava = 0; int snake_x = {0}; int snake_y = {0}; int snake_len = 1; byte smer = 0; int clocktick = 1; long CasZmeny = 0; int rychlost = 500; byte score = 0; boolean eatfood = false; boolean zvuk = true; boolean probehlo = false; // Screen Data int Cislo[11][7] = { {B00000000,B00000000,B00000000,B00000000,B00000001,B01111111,B00100001}, //1 {B00000000,B00000000,B00110001,B01001001,B01000101,B00100011},//2 {B00000000,B00000000,B01000110,B01101001,B01010001,B01000001,B01000010},//3 {B00000000,B00001000,B11111111,B01001000,B00101000,B00011000,B00001000},//4 {B00000000,B00000000,B00111110,B01000101,B01001001,B01010001,B00111110}, //5 {B00000000,B00000000,B00000110,B01001001,B01001001,B00101001,B00011110}, //6 {B00000000,B00000000,B01110000,B01001000,B01000111,B01000000,B01100000}, //7 {B00000000,B00000000,B00110110,B01001001,B01001001,B01001001,B00110110}, //8 {B00000000,B00000000,B00111100,B01001010,B01001001,B01001001,B00110000}, //9 {B00000000,B00000000,B00111110,B01010001,B01001001,B01000101,B00111110}, //0 {B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000}, //mezera }; |
讀者也可以在筆者的 YouTube 頻道中,看到整個遊戲進展的展示影片喔!
我們也可以看到下圖所示之畫面,看到遊戲開始、進行、結束的畫面抓圖。
開發手機端操控程式
在手機端操控端,我們為了可以達到無限手把的效果,並不開發任何遊戲的核心程式,這樣可以讓讀者更可以應用筆者的經驗,開發出雲端板之手機用戶端的應用介面,而之間使用 TCP/IP 通訊方式,將控制碼傳送到遠端,進行解譯後,根據雲端之伺服器來顯示。
所以我們使用 MIT App Inventor 2 攥寫這個用戶端程式,如下圖所示,我們可以將手機當成一個類似任天堂 NS 的無線控制搖桿,所以我們 U、D、L、R 各代表看上、下、左、右等方向鍵。
在程式之中,在使用者按下這些 U(上)、D(下)、L(左)、R(右)等方向鍵後,我們將對應的控制碼,就是 U、D、L、R 這幾個 ACCII 碼傳回到 Ameba 8195AM 的遊戲伺服器端,當遊戲伺服器端就會進行解譯後,變更遊戲貪吃蛇的位置,並在 Ameba 接收後便會將整個遊戲運行的畫面顯示在四合一 LDM-768-1LT-X4 產品之上(曹永忠, 2017a, 2017b, 2017c)。
- 讀者可以在這裡下載 MIT App Inventor 2 的原始碼後,再上傳到讀者的程式專案保管箱,也可以自行在 MIT App Inventor 2 專案畫面之介面區建立如上圖所示之 GUI 元件,在程式區建立如下圖所示之程式碼,再編譯成 APK 檔,安裝在手機上進行測試。
到此,我們已經完成直譯式顯示技術應用一個雲端版的應用,而且還是整合手機端與簡單小遊戲的方式來呈現這篇專欄,讓這篇的技術含量更具未來性與延伸性,相信讀者可以將這樣的設計技巧與技術經驗,發展成更具代表性的雲端版物聯網用途的顯示技術(曹永忠, 許智誠, & 蔡英德, 2018a, 2018b),更可以當成開發目前當紅的雲端顯示技術Display of Ethernet(DOE)的軟硬體技術入門。
後續
本篇是「物聯網開發」系列中「顯示技術技巧大探索」的完結篇:直譯式顯示技術應用:以貪吃蛇為例(下篇),主要告訴讀者,整合手機端與簡單小遊戲的方式來呈現目前當紅的雲端顯示技術 Display of Ethernet(DOE),未來可以讓大家使用這個直譯式顯示技術應用。
後續筆者還會繼續發表「物聯網開發」系列的文章,在未來我們可以創造出更優質,更具未來性的物聯網(Internet of Thing:IOT)產品開發相關技術。
敬請期待更多的文章。
作者介紹
曹永忠(Yung-Chung Tsao):目前為自由作家,專注於軟體工程、軟體開發與設計、物件導向程式設計、物聯網系統開發、Arduino開發、嵌入式系統開發,商品攝影及人像攝影。長期投入資訊系統設計與開發、企業應用系統開發、軟體工程、物聯網系統開發、軟硬體技術整合等領域,並持續發表作品及相關專業著作。
Email:prgbruce@gmail.com/Line ID:dr.brucetsao/作者網頁/臉書社群(Arduino.Taiwan)/Github 網站/Youtube
吳欣蓉(Jessie Wu):目前就讀於國立暨南國際大學電機工程學系,休閒時喜歡慢跑、打球、組裝模型、看美劇及日劇,騎車探索周遭的未知區域。曾對魔術方塊非常著迷,喜歡數學,亦對機器人、嵌入式系統、物聯網系統領域感興趣
Email:s104323018@mail1.ncnu.edu.tw
陳建宇(Chien-Yu Chen):目前就讀於國立暨南國際大學電機工程學系,休閒時喜歡打球、看美劇及日劇,騎車探索周遭的未知區域。曾對魔術方塊非常著迷,喜歡數學,亦對機器人、人工智慧、嵌入式系統、物聯網系統的領域感興趣。
Email:chenchienyu1104@gmail.com/Line ID:1996110406
參考文獻
- 曹永忠. (2017a). 【Tutorial】Quark SE C1000之GPIO腳位設定技巧. Retrieved from https://makerpro.cc/2017/10/quark-se-c1000-gpio-setup-techniques/
- 曹永忠. (2017b). 【Tutorial】如何用Intel SE C1000開發整合大型顯示裝置. Retrieved from https://makerpro.cc/2017/11/integrate-quark-se-c1000-with-display/
- 曹永忠. (2017c). 【Tutorial】如何用ISSM開發Intel SE C1000. Retrieved from https://makerpro.cc/2017/08/use-issm-to-develop-quark-se-c1000/
- 曹永忠, 許智誠, & 蔡英德. (2017a). 【物聯網開發系列】顯示技術技巧大探索-直譯式顯示技術應用:以貪吃蛇為例(上篇). 智慧家庭. Retrieved from https://vmaker.tw/archives/25153
- 曹永忠, 許智誠, & 蔡英德. (2017b). 【物聯網開發系列】顯示技術技巧大探索-直譯式顯示技術應用:以貪吃蛇為例(中篇). 智慧家庭. Retrieved from https://vmaker.tw/archives/25153
- 曹永忠, 許智誠, & 蔡英德. (2018a). Intel Quark SE C1000开发板程序设计基础篇:An Introduction to Programming by Using An Intel Quark SE C1000 (初版 ed.). 台湾、彰化: 渥瑪數位有限公司.
- 曹永忠, 許智誠, & 蔡英德. (2018b). Intel Quark SE C1000開發板程式設計基礎篇:An Introduction to Programming by Using An Intel Quark SE C1000 (初版 ed.). 台湾、彰化: 渥瑪數位有限公司.