中文 | English

【物聯網開發系列】直譯式顯示技術應用:以貪吃蛇為例(下篇)

  • 文/曹永忠、吳欣蓉、陳建宇

本篇是「物聯網開發」系列中「顯示技術技巧大探索」的第二篇。前文在此:上篇中篇

本篇是承續「物聯網開發」系列中「顯示技術技巧大探索」直譯式顯示技術應用:以貪吃蛇為例(上、中篇)(曹永忠, 許智誠, & 蔡英德, 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 頻道中,看到整個遊戲進展的展示影片喔!

我們也可以看到下圖所示之畫面,看到遊戲開始、進行、結束的畫面抓圖。

圖 1:貪吃蛇遊戲主機端展示畫面。

開發手機端操控程式

在手機端操控端,我們為了可以達到無限手把的效果,並不開發任何遊戲的核心程式,這樣可以讓讀者更可以應用筆者的經驗,開發出雲端板之手機用戶端的應用介面,而之間使用 TCP/IP 通訊方式,將控制碼傳送到遠端,進行解譯後,根據雲端之伺服器來顯示。

所以我們使用 MIT App Inventor 2 攥寫這個用戶端程式,如下圖所示,我們可以將手機當成一個類似任天堂 NS 的無線控制搖桿,所以我們 U、D、L、R 各代表看上、下、左、右等方向鍵。

圖 2:貪吃蛇手機端畫面。

在程式之中,在使用者按下這些 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 檔,安裝在手機上進行測試。

圖 3:貪吃蛇手機端程式原始碼。

到此,我們已經完成直譯式顯示技術應用一個雲端版的應用,而且還是整合手機端與簡單小遊戲的方式來呈現這篇專欄,讓這篇的技術含量更具未來性與延伸性,相信讀者可以將這樣的設計技巧與技術經驗,發展成更具代表性的雲端版物聯網用途的顯示技術(曹永忠, 許智誠, & 蔡英德, 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.). 台湾、彰化: 渥瑪數位有限公司.

分享到社群

曹永忠

曹永忠 (Yung-Chung Tsao) ,目前為自由作家暨專業Maker,專研於軟體工程、軟體開發與設計、物件導向程式設計,商品攝影及人像攝影。長期投入創客運動、資訊系統設計與開發、企業應用系統開發、軟體工程、新產品開發管理、商品及人像攝影等領域,並持續發表作品及相關專業著作。 Email:prgbruce@gmail.com Line ID:dr.brucetsao 作者網站:https://www.cs.pu.edu.tw/~yctsao/ 臉書社群(Arduino.Taiwan):https://www.facebook.com/groups/Arduino.Taiwan/ Github網站:https://github.com/brucetsao/ Youtube:https://www.youtube.com/channel/UCcYG2yY_u0m1aotcA4hrRgQ

This site or product includes IP2Location LITE data available from https://lite.ip2location.com.