---
# System prepended metadata

title: 遊戲機大補帖
tags: [創客社]

---

# 遊戲機大補帖

記得填[課程回饋表單](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: `創客社`