# 遊戲機大補帖 記得填[課程回饋表單](https://forms.gle/c6DdpBGFdV44s7s47)喔~ 上次(3/16)還沒寫的也記得喔[表單](https://forms.gle/ozp9otgCKG8PwMVW6)喔 ## 0. 複習 Q&A > 先不看答案嘗試回答一下~有問題再上網用問題中的關鍵字找資料,如果還是不行才看答案~ --- ==**Q1 : Arduino是什麼?**== ***ANS:*** Arduino是整合微控制晶片及燒錄功能在一塊開發板上,就可以它的作用就好比人類的大腦,可以設計判斷不同條件,來去決定身體要做甚麼? 眼睛看到前方有障礙物>大腦判斷>控制雙腳移動避開。 ![](https://i.imgur.com/3QYbrfI.png) 所以在遊戲中,按鍵、搖桿等等就是我們的「感測」,而螢幕就是處理資訊並輸出~ --- ==**Q2 : Arduino上的接腳有什麼功用?**== ***ANS:*** Arduino上的接腳可以 1. 讀入訊號 2. 輸出訊號 3. 與電腦溝通 4. LED顯示(D13) ![](https://i.imgur.com/ndKDiaO.png) 而你會看到圖中有許多的資訊,那其實我們只要注意幾個關鍵就好,看到 - 哪些是數位輸出訊號? (紫色label) - 哪些是類比輸出腳位? (with A開頭的) 懂這些就夠寫遊戲機程式了~ --- ==**Q3 : Arduino主程式分為哪2種區塊?(基本架構)**== ***ANS:*** ```cpp= void setup(){ //Arduino開機時只會執行一次。 } void loop(){ //跑完setup後就會以一行一行的方式循環直到關機 } ``` --- ==**Q4 : 想要讓LED一閃一閃,需要哪些程式?**== ***ANS:*** ```cpp= void setup(){ pinMode(LED接腳,OUTPUT); } void loop(){ digitalWrite(LED接腳,HIGH); delay(1000); //milliseconds digitalWrite(LED接腳,HIGH); delay(1000); } ``` :::info :bulb: delay中填的值為毫秒!也就是 1000毫秒 = 1秒 ::: --- ==**Q5 : PULL DOWN(下拉電阻)以及PULL UP(上拉電阻)電阻有什麼意義?**== ***ANS:*** 「上拉電阻」以及「下拉電阻」以結論來說是為了「穩定邏輯電位」,不然會程式讀取的訊號會不穩。 好,多了一個名詞,什麼是「邏輯電位」?? :::info :book: 「邏輯電位」: 以Arduino為例,他是吃5V的電,所以假如我在INPUT接腳輸入5V的電就是「讀入1,HIGH」 ::: ![](https://i.imgur.com/7gcjTkX.png =70%x) [關於上下拉電阻更詳細介紹點我](http://www.cjwind.idv.tw/pull-up-and-pull-down-resistor/) --- ==**Q6 : 本次的遊戲機中按下按鈕時,Arduino會讀取什麼訊號?**== ***ANS:*** 因為遊戲機設計中「按鈕接腳」直接接到Arduino接腳上,沒有再添加其他電阻(回想一下,焊接時是不是只焊一個電阻而已?),所以使用Arduino內建在IC中的PULL_UP電阻^註[1]^,所以讀入的訊號為「0」就是「LOW」。 :::info :bulb: 註[1] : Arduino中只有內建「上拉電阻」! ::: --- ==**Q7 : 接續第六題,遊戲機上讀取按鈕值需要哪些程式?**== ***ANS:*** ```cpp= void setup() { pinMode(腳位名稱,INPUT_PULLUP); //使用內建pull_up 電阻 } void loop() { bool value_1 = digitalRead(腳位名稱); // true or false int value_2 = digitalRead(腳位名稱); // 1 or 0 } ``` --- ==**Q8 : 什麼是「類比訊號」以及「數位訊號」?**== **ANS:** ![](https://i.imgur.com/FCA78Uz.png =70%x) --- ==**Q9 : 如何讀取搖桿的值?**== **ANS:** ```cpp= //以下程式會把搖桿的X軸Raw Data讀入 void setup(){ //以下程式只跑一次 Serial.begin(9600); //打開和電腦的橋樑,並設速度為9600 pinMode(腳位名稱,INPUT); //告訴Arduino我pinNumber “joy_x”的角色為「INPUT」 } void loop(){ //這邊的程式會一直循環 float value = analogRead(腳位名稱); //用變數value存下讀入的值 Serial.println(value); } ``` :::info :bulb: 使用「analogRead(接腳)」回傳類比值。(0~1024) ::: --- ==**Q10 : 川普跌倒會變成什麼?**== **AND:** 三普 --- ==**Q11 : 有一天皮卡丘要向傑尼龜借1000萬,傑尼龜會說什麼?**== **ANS:** 不要 ###### [笑話解釋] 原本是「傑尼傑尼」(借你借你) --- ==**Q12 : 上面的笑話好笑嗎?**== **ANS:** 好笑啦,哪次不好笑。 --- ## A. 基礎C++/Arduino教學 (進階) ### 1. Preprocess (預處理器) > Preprocess在程式編譯前先跑過,不佔程式記憶體,且字首會有‘#’,句尾不必加分號 ```cpp= //使程式更加易讀的一種方式,這樣以後寫程式只要管名稱就好 #define 「名稱」 「值」 //Ex. 只要看到「WEIGHT」就代表「75」 #define WEIGHT 75 ``` ```cpp= //許多程式相當複雜,所以我們站在巨人的肩膀上,使用別人寫的Code //而那個巨人稱作「library」,而使用前需要告訴電腦「我們要使用它」,並使用「#include」。 #include 「Library名字」 //Ex. 使用「Adafruit_GFX.h」程式庫 #include "Adafruit_GFX.h" ``` ### 2. F(String) ```cpp= //首先,寫出範例用法 Serial.println(F("Hello World !!")); //F(字串)的功用是把字串存在Arduino IC中的記憶體,可以減少「運行中程式」的記憶體負荷量 ``` ### 3. pinMode()補充 ```cpp= //假如我要對「D13」pinMode宣告,需要用以下打法 //注意到是打「13」而不是「D13」 pinMode(13,HIGH); //但是假如是「A0」~「A7」,就要保留A pinMode(A0,INPUT); ``` ## B. Arduino燒入程式注意事項 > 使用Arduino IDE 1. 開發板選擇「Arduino Nano」 2. 在「工具」選單中,選取「Old Bootloader」 3. 燒錄器選取「AVR ISP mkll」(通常已經預設為此) ## C. 遊戲機前置作業 ### Step 1. 新增一個tab,命名為「config.h」並把以下程式碼貼上,並「Crtl + S」存檔 ![](https://i.imgur.com/lyq5uXV.png =80%x) ```cpp= //螢幕接腳設定 #define TFT_CS 10 #define TFT_DC 9 #define TFT_MOSI 11 #define TFT_CLK 13 #define TFT_RST 8 #define TFT_MISO 12 #define TS_CS 7 #define ROTATION 3 //周邊裝置設定 #define joy_x A7 #define joy_y A6 #define joy_sw A3 #define start A2 #define select A1 #define LED 5 #define buzzer 6 //Screen & Joystick's Coordinate /*(0,0)---------[+x] * | | * | | * | | * | | * | | * [+y]------------ */ //Serial Buadrate #define BUADRATE 9600 //Libraries #include "SPI.h" #include "Adafruit_GFX.h" #include "Adafruit_ILI9341.h" #include "XPT2046_Touchscreen.h" ``` ### Step 2. 回到主程式(.ino檔),並incldue config.h ```cpp= #include "config.h" void setup(){ Serial.begin(BUADRATE); } void loop(){ } ``` ## D. 螢幕使用教學 > 遊戲機上,我們使用ILI9341螢幕,且透過SPI通訊連接,大小2.4",螢幕分辨率 320x240 ### 1. 螢幕使用準備工作 #### Step 0. 下載Library Tools > Manage Libraries > Search for them~ 1. Adafruit_GFX.h (by Adafruit) 2. Adafruit_ILI9341.h (by Adafruit) 3. XPT2046_Touchscreen.h (by Paul Stoffregen) --- #### Step 1. #include libraries ```cpp=1 #include "config.h" //接腳設定檔 ``` --- #### Step 2. 建立螢幕(TFT),觸控(TS)物件 ```cpp=6 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); XPT2046_Touchscreen ts(TS_CS); ``` --- #### Step 3. 初始化tft ```cpp=8 void setup(){ tft.begin(); } ``` --- #### Step 4. 總結 ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); XPT2046_Touchscreen ts(TS_CS); void setup() { Serial.begin(BUADRATE); tft.begin(); } void loop() { } ``` ### 2. 螢幕顯示常用function > 此區塊整理許多函式庫的知識,有興趣看官方文件會更清楚~ > [官方Doc](https://learn.adafruit.com/adafruit-gfx-graphics-library/coordinate-system-and-units) 1. 螢幕顏色定義 ![](https://i.imgur.com/OVPWoLZ.png) ```cpp= ILI9341_BLACK ILI9341_RED ILI9341_GREEN ILI9341_BLUE ILI9341_BLACK ILI9341_WHITE ILI9341_YELLOW ILI9341_MAGENTA tft.color(R,G,B) ``` --- 2. 螢幕座標系 > 遊戲機的螢幕為 : 320px * 240px ![](https://i.imgur.com/XNS3C24.png) --- 3. 填滿螢幕(顏色) ```cpp= //Fill Screen Example #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); } void loop() { tft.fillScreen(ILI9341_BLACK); yield(); tft.fillScreen(ILI9341_MAGENTA); yield(); tft.fillScreen(ILI9341_RED); yield(); } ``` --- 4. 文字 ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(3); } void loop() { tft.setCursor(0, 0); tft.setTextColor(ILI9341_RED); tft.setTextSize(2); tft.println("Here's the Joke : "); tft.println(""); tft.setTextColor(ILI9341_WHITE); tft.setTextSize(2); tft.println("Why wouldn't the shrimp"); tft.println("share his treasure?"); tft.println(""); tft.println("Because he was a little shellfish."); } ``` --- 5. 畫Pixel(畫點) ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(3); } void loop() { //void drawPixel(x,y,color); tft.drawPixel(20,20,ILI9341_WHITE); } ``` --- 6. 畫線 ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(3); } void loop() { //void drawLine(x1,y1,x2,y2,color) tft.drawLine(50,50,20,20,ILI9341_WHITE); } ``` --- 7. 畫方形 ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(3); } void loop() { //void drawRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color); //void fillRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color); tft.drawRect(20,20,50,50,ILI9341_WHITE); tft.fillRect(50,50,30,50,ILI9341_RED); } ``` --- 8. 畫圓形 ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(3); } void loop() { //tft.drawCicle(x , y, radius, color); tft.drawCicle(20,20,10,ILI9341_RED); } ``` ###### 更多請看[官網](https://learn.adafruit.com/adafruit-gfx-graphics-library/graphics-primitives) ### 3. 讓螢幕動起來吧! (Animation) #### Step 1. 畫個小紅吧 ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(ROTATION); } int x = 160 ; int y = 120 ; void loop() { x += vx ; y += vy ; tft.fillRect(x,y,squareWidth,squareWidth,ILI9341_RED); } ``` --- #### Step 2. 讓它動起來~ ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(ROTATION); } int x = 160 ; //原始座標 int y = 120 ; int vx = 1 ; //速度變數 int vy = 1 ; int squareWidth = 10 ; void loop() { x += vx ; y += vy ; //讓他反彈的地方 //假如碰到牆,速度為反方向,所以反射角度為45度 if((x <= 0) || (x >= tft.width() - squareWidth)) vx = -vx ; if((y <= 0) || (y >= tft.height() - squareWidth)) vy = -vy ; //畫出新的東西 tft.fillRect(x,y,squareWidth,squareWidth,ILI9341_RED); } ``` 是不是發現他只是瘋狂填滿畫面而已..... 該怎麼辦?? --- #### Step 4. 更新螢幕 一般來說直觀想法是使用fillScreen清空螢幕,但Arduino負荷不了如此龐大的運算量,所以使用下列的方法!! ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(ROTATION); } int x = 160 ; int y = 120 ; int vx = 1 ; int vy = 1 ; int squareWidth = 20 ; void loop() { //記住上次的(舊)的座標 int oldX = x ; int oldY = y ; //改變座標 x += vx ; y += vy ; //判斷邊界 if((x <= 0) || (x >= tft.width() - squareWidth)) vx = -vx ; if((y <= 0) || (y >= tft.height() - squareWidth)) vy = -vy ; //用黑色蓋掉上次畫的東西 tft.fillRect(oldX,oldY,squareWidth,squareWidth,ILI9341_BLACK); //重新畫上新的東西 tft.fillRect(x,y,squareWidth,squareWidth,ILI9341_RED); } ``` 不是誒,怎麼還是一閃一閃的QQ 來到Step 5. !! --- #### Step 5. 調整fps 首先加個 ```cpp=32 delay(50); ``` 在LOOP在最尾端,希望紅色畫出的時間足夠長,我們有時間觀察,上傳並觀察一下... 好點吧~但還是差強人意... 這時候就需要**調整FPS(Frame Per Second)**!! ```cpp= #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(ROTATION); } int x = 160 ; int y = 120 ; int vx = 1 ; int vy = 1 ; int squareWidth = 10 ; unsigned long lastFrame = millis(); void loop() { while((millis() - lastFrame) < 20); lastFrame = millis(); int oldX = x ; int oldY = y ; x += vx ; y += vy ; if((x <= 0) || (x >= tft.width() - squareWidth)) vx = -vx ; if((y <= 0) || (y >= tft.height() - squareWidth)) vy = -vy ; tft.fillRect(oldX,oldY,squareWidth,squareWidth,ILI9341_BLACK); tft.fillRect(x,y,squareWidth,squareWidth,ILI9341_RED); tft.fillRect(0,0,90,15,ILI9341_BLACK); tft.setCursor(0,0); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE); tft.print(x); tft.setCursor(40,0); tft.print(y); } ``` ## E. 實作練習!! ### 1. 搖桿讀取+動畫顯示 ```cpp= //practice_1 #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_WHITE); tft.setRotation(ROTATION); pinMode(joy_x,INPUT); pinMode(joy_y,INPUT); } int old_x , old_y ; int x , y ; unsigned long lastFrame = millis(); void loop() { while((millis() - lastFrame) < 20); lastFrame = millis(); x = analogRead(joy_x); y = analogRead(joy_y); tft.setCursor(0,0); tft.setTextSize(2); tft.println("JOYSTICK : "); if( (old_x != x) || (old_y != y) ){ tft.fillRect(0,20,180,15,ILI9341_WHITE); tft.setTextColor(ILI9341_BLACK); tft.setCursor(0,20); tft.print("x: "); tft.print(x); tft.setCursor(90,20); tft.print("y: "); tft.print(y); } old_x = x; old_y = y; } ``` ### 2. 搖桿讀取 + 控制 + 螢幕動畫 ```cpp= //practice 2 - Integrated Practice~ #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); XPT2046_Touchscreen ts(TS_CS); enum direction{UP,DOWN,LEFT,RIGHT,STAY}; void setup() { Serial.begin(9600); tft.begin(); tft.setRotation(3); tft.fillScreen(ILI9341_BLACK); pinMode(joy_x,INPUT); pinMode(joy_y,INPUT); } int direction = STAY; int squareWidth = 10 ; int raw_x,raw_y ; int vx = 3 ; int vy = 3; int x = 160 ; int y = 120 ; unsigned long lastFrame = millis(); void loop() { while((millis() - lastFrame) < 20); lastFrame = millis(); raw_x = analogRead(joy_x); raw_y = analogRead(joy_y); int old_x = x ; int old_y = y ; if(raw_x < 100){ direction = RIGHT ; }else if(raw_x > 900){ direction = LEFT ; }else if(raw_y < 100){ direction = UP ; }else if(raw_y > 900){ direction = DOWN ; }else{ direction = STAY ; } switch(direction){ case UP: y -= vy ; break; case DOWN: y += vy; break; case LEFT: x -= vx; break; case RIGHT: x += vx; break; } if(direction == STAY){ tft.fillRoundRect(x,y,squareWidth,squareWidth,2,ILI9341_RED); }else{ tft.fillRoundRect(old_x,old_y,squareWidth,squareWidth,2,ILI9341_BLACK); tft.fillRoundRect(x,y,squareWidth,squareWidth,2,ILI9341_RED); } } ``` ### 3. 類snake ```cpp= //practice_2 #include "config.h" Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); enum direction{UP,DOWN,LEFT,RIGHT,STAY}; // 0 , 1 , 2 , 3 ,4 //#define UP 0 void setup() { Serial.begin(BUADRATE); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setRotation(ROTATION); pinMode(joy_x,INPUT); pinMode(joy_y,INPUT); } int direction = STAY ; int squareWidth = 20 ; int raw_x , raw_y ; int x = 160; int y = 120; int vx = 3 ; int vy = 3 ; unsigned long lastFrame = millis(); void loop() { while((millis() - lastFrame) < 20); lastFrame = millis(); raw_x = analogRead(joy_x); raw_y = analogRead(joy_y); int old_x = x ; int old_y = y ; //read in joystick, change vara direction if(raw_x < 100){ direction = RIGHT ; }else if(raw_x > 900){ direction = LEFT ; }else if(raw_y < 100){ direction = UP; }else if(raw_y > 900){ direction = DOWN ; } //direction -> movement switch(direction){ case UP: y -= vy ; break ; case DOWN : y += vy ; //y = y + vy break; case LEFT: x = x - vx ; break; case RIGHT: x += vx; break ; } if(direction == STAY){ tft.fillRect(x,y,squareWidth,squareWidth,ILI9341_RED); }else{ tft.fillRect(old_x,old_y,squareWidth,squareWidth,ILI9341_BLACK); tft.fillRect(x,y,squareWidth,squareWidth,ILI9341_RED); } } ``` --- ## [點我填寫課程回饋表單~](https://forms.gle/c6DdpBGFdV44s7s47) ## [3/23上次還沒填寫的也點我](https://forms.gle/ozp9otgCKG8PwMVW6) ###### tags: `創客社`