# 遊戲機大補帖
記得填[課程回饋表單](https://forms.gle/c6DdpBGFdV44s7s47)喔~
上次(3/16)還沒寫的也記得喔[表單](https://forms.gle/ozp9otgCKG8PwMVW6)喔
## 0. 複習 Q&A
> 先不看答案嘗試回答一下~有問題再上網用問題中的關鍵字找資料,如果還是不行才看答案~
---
==**Q1 : Arduino是什麼?**==
***ANS:***
Arduino是整合微控制晶片及燒錄功能在一塊開發板上,就可以它的作用就好比人類的大腦,可以設計判斷不同條件,來去決定身體要做甚麼?
眼睛看到前方有障礙物>大腦判斷>控制雙腳移動避開。

所以在遊戲中,按鍵、搖桿等等就是我們的「感測」,而螢幕就是處理資訊並輸出~
---
==**Q2 : Arduino上的接腳有什麼功用?**==
***ANS:***
Arduino上的接腳可以
1. 讀入訊號
2. 輸出訊號
3. 與電腦溝通
4. LED顯示(D13)

而你會看到圖中有許多的資訊,那其實我們只要注意幾個關鍵就好,看到
- 哪些是數位輸出訊號? (紫色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」
:::

[關於上下拉電阻更詳細介紹點我](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:**

---
==**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」存檔

```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. 螢幕顏色定義

```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

---
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: `創客社`