---
# System prepended metadata

title: '[2] Arduino講義: Environment與跑馬燈＋按鍵+多工七段顯示器＋鍵盤'
tags: [單晶片助教]

---

---
tags: 單晶片助教
---

{%youtube Nq6kP6FNg1c %}

# [2] Arduino講義: Environment與跑馬燈＋按鍵+多工七段顯示器＋鍵盤

# Install the Arduino
* 至Arduino官方網站下載開發環境： (https://www.arduino.cc/en/Main/Software)

* 開起「裝置管理員」，將 Arduino 接上，看一下是否有新增 USB 序列裝置(COM6)，**不一定為 COM6 喔**。
![](https://i.imgur.com/7n0TLcm.png)





* **若「USB序列裝置」出現無法辨識的裝置**

  **方法一 : 按滑鼠右鍵，選擇「更新驅動程式」，並點選「自動搜尋更新的驅動程式軟體」，可以排除問題。**
  
  **方法二 : 按滑鼠右鍵，選擇「更新驅動程式」，並點選「瀏覽電腦上的驅動程式軟體」，瀏覽下載Arduino的地方(arduino-1.8.8)，找到drivers檔，點選並安裝。**![](https://i.imgur.com/SSvHWRm.png)
  
  **方法三 : 搜尋COM port安裝檔，下載後執行 https://sparks.gogo.co.nz/ch340.html**
  ![](https://i.imgur.com/xDlQzCs.jpg)

* 開啟Arduino IDE
![](https://i.imgur.com/Dwc2efw.png)

  

* 按下工具→序列埠，同時將arduino接上，選擇Uno，即可以使用。

![](https://i.imgur.com/BPaazpM.png)
* 在「開發板」的部分記得確認是**Arduino/Genuino Uno**。
![](https://i.imgur.com/Z2JcGLV.png)


## Arduino Common Function

**1.setup()**
設定程式參數，如：指定接腳的輸入輸出狀態，setup()裡的程式碼只會執行一次。

**2.loop()**
在loop()函式中的程式碼將會不停地重複執行，直到電源關閉為止。
![](https://i.imgur.com/bJVIF0x.png)

---
> ## Introduction of  GPIO (General Purpose Input Output)
![](https://i.imgur.com/ukpB4Ih.png)

>通用輸入/輸出，每個 GPIO 端口可透過軟件分別配置成 INPUT 或者 OUTPUT ，其接腳可以供使用者由程式控制。
>* **Input mode：** MCU 透過 GPIO 腳從外面收資料/信號，可以獲取外接感測器(溫度、紅外線感測器等)狀態、高低電位等資訊。

>* **Output mode：** MCU 透過 GPIO 腳將資料/信號送出，可用於控制LED、蜂鳴器及伺服馬達…等。

>* GPIO 端口可以當 I2C(**LCD 16x2**)、SPI (10~13)、UART(**Bluetooth**)…等多種用途，也能作為 IRQ (中斷要求)(2、3)的信號。

---

**3.pinMode(pin,mode)**
用於設定接腳模式，該函數無返回值，pin代表所要配置的接腳，mode代表設置的模式-INPUT或OUTPUT。
  Ex: 
  ```C++=
  pinMode(13,OUTPUT)//將第13腳設定成「輸出」模式
  pinMode(12,INPUT)//將第12腳設定成「輸入」模式
  ```
  
**4.digitalWrite(pin,value)**
函數有兩個參數 digitalWrite(接腳,電壓值), 電壓值分為HIGH(高電位)與LOW(低電位)。
 Ex:
 ```C++=
 digitalWrite(13,HIGH)//第13腳位為輸出「高電位」
 ```
 
**5.digitalRead(pin)**
在接腳為輸入的情況下,可以獲取接腳的電壓為HIGH或LOW。

**6.delay(ms)**
設定程式運行停止時間長度，單位為ms，舉例來說delay(1000)就是延遲1000豪秒。

**7.millis()**
millis()函數會回傳Arduino從執行程式碼開始至目前為止的milliseconds數值，其資料型態為unsigned long，該數值大約在50天之後會溢位(overflow)，屆時會從 0 開始重新計數。
![](https://i.imgur.com/Iq2nZV6.png)
以上方程式碼為例，每一秒印出millis()的回傳值，利用delay(1000)產生1000毫秒的延遲。
然而，相鄰的兩個數值的差值可能並未完全為1000，則是由Serial.print()印出資料需要的時間差。

## **Serial Library**

![](https://i.imgur.com/uP3t3at.png)

**1. Serial.begin()**
    啟動Serial Port並設定鮑率(baud rate)，鮑率可以被理解為單位時間內傳輸符號的個數
    Ex:
    
``` C++=
Serial.begin(9600);//開啟Serial Port，且通訊速率為9600bps(Bits Per Second)   
```
    
**2. Serial.print()**
    將字串透過TX腳位傳遞出去，並且顯示在Serial Monitor中
    
**3. Serial.println()**
    傳送資料到外部電腦並切換至下一行
    
**4. Serial.write()**
    以二進位碼(byte)輸出
    ![](https://i.imgur.com/iIpMaef.png)

**5. Serial.read()**
    讀取進來的第一個位元組 (first incoming byte)。
    
**6. Serial.available()**
    取得 Serial Port 可讀取的資料位元組數目 (number of bytes)，如果 Serial port 有資料進來，Serial.available() 會回傳大於 0 的數值。

---
通常我們會使用 Serial.available() 來檢查是否有資料進來，在使用Serial.read()把資料讀取出來放到變數後供後續使用。例如：
```c++=
int incomingByte = 0; // 用來儲存收進來的 data byte

void setup() {
  Serial.begin(9600);
  Serial.println("Hello");
}


void loop() {
  // 檢查是否有資料可供讀取
  if (Serial.available() > 0) {
    // 讀取進來的 byte

    incomingByte = Serial.read();

    // 印出收到的資料
    Serial.print("data received: ");
    Serial.print(incomingByte, DEC);
    Serial.print(", ");
    Serial.print(incomingByte, HEX);
    Serial.println(" (HEX)");
  }
```

---

**<<Note>>
Arduino其他相關語法請參考：[Arduino語言手冊](https://h2maker.wordpress.com/arduino/)**

---

## **Arduino講義：跑馬燈 ＋ 按鍵 + 多工七段顯示器 ＋ 4 x 4鍵盤**

## **1.跑馬燈(Electronic Marquee)**
### 模組說明
![](https://i.imgur.com/cxsQfH9.png)
**<<Note>>**
**<font color = "red">需要限流電阻(大概 220Ω 或 330Ω)，使 LED 流進電流不超過20mA。</font>**


## Arduino  LED Blink
![](https://i.imgur.com/Rva87AE.png)
> ## 程式說明 
```C++
const int LedPin = 8;

void setup() {
  pinMode(LedPin, OUTPUT);  //將第8腳位設定成OUTPUT模式
}

void loop() {
  digitalWrite(LedPin, HIGH);  //第8腳位輸出高電位，點亮LED
  delay(500);  //等待500ms
  digitalWrite(LedPin, LOW);  //第8腳位輸出低電位，熄滅LED
  delay(500);  //等待500ms
}
```

---

## LAB 1-1
* 實驗目的：學習對Arduino的GPIO及LED基本控制方式。
* 實驗目標：設定5個GPIO來實現跑馬燈，模式如範例影片所示
* 參考程式碼（可任意新增改寫）
```c++=
int Led[5] = {8, 9, 10, 11, 12};

void setup() {
  for(int i=0; i<5; i++)
    pinMode(Led[i], OUTPUT);
}

void loop() {
  /*
  
  Do something ^~^
  
  */
}
```
* 範例影片 \\\^~^/
{%youtube 0es2kQqbLis %}

---

## LAB 1-2 (Bonus)
* 實驗目的：學習對Arduino的GPIO及LED基本控制方式。
* 實驗目標：設定5個GPIO來實現跑馬燈，模式如範例影片所示
* 參考程式碼（可任意新增改寫）
```c++=
#define ROW 30
#define COLUMN 5

int Led[COLUMN] = {8, 9, 10, 11, 12};
int pos[ROW][COLUMN] = {
                        /*
                        模式
                        */
                       };

void setup() {
  for(int i=0; i<COLUMN; i++)
    pinMode(Led[i], OUTPUT);
}

void loop() {
  /*
  
  Do smoething ^~^
  
  */
}
```
* 範例影片
{%youtube waivt8rJaHg %}

---

## 2.按鈕(Button)
![](https://i.imgur.com/jzfqQzY.png =50%x)
### **開關接法**
![](https://i.imgur.com/dUD0QDi.png)
若未按下開關，Arduino的接腳沒接地，但也沒有接到高電位。輸入的訊號會在０到１之間飄移，造成浮動訊號，Arduino就無法正確判斷輸入值。
![](https://i.imgur.com/oh3j52w.png)
若開關沒有被按下，Pin2將會透過10kΩ接地，讀取到低電位(LOW)；按下開關時，5V電源將流入Pin2，產生高電位(HIGH)。
![](https://i.imgur.com/RHULvQZ.png)

若將電阻接到電源，則此電阻稱為「**上拉(pull-up)電阻**」(在開關接通時，輸入為「低電位」)。
> ### 使用Arduino內部的上拉電阻
Arduino內部的pin腳考量到拿來做數位輸入，有內建上拉電阻（除了D13腳位），可取代外接上拉電阻。
```C++=
pinMode(pin,INPUT_PULLUP);
```



## LAB 2-1 Button & LED 

* 實驗目的：認識上拉電阻與下拉電阻的接法差別，並用Button控制LED
* 實驗目標：按上拉電阻的 Button 時，LED 向左一顆亮；按下拉電阻的Button時，LED 向右邊一顆亮。
* 參考程式碼（可任意新增改寫）
```c++=
const int Led[5] = {8, 9, 10, 11, 12};    //LED
const int sw_up = 2;    //按鈕1
const int sw_down = 3;    //按鈕2
int pos = 2;  //初始亮LED位置

void setup() {
  for(int i=0; i<5; i++){
    pinMode(Led[i], OUTPUT);
    digitalWrite(Led[i], LOW);
  }
  digitalWrite(Led[2], HIGH);
  pinMode(sw_up, INPUT_PULLUP);  //內建上拉電阻
  pinMode(sw_down,INPUT);
}

void loop() {
  bool swstate_up = digitalRead(sw_up);
  bool swstate_down = digitalRead(sw_down);
  if(swstate_up==LOW){
    /*
    Do something
    */
  }
  else if(swstate_down==HIGH){
    /*
    Do something
    */
  }
}
```
* 範例影片
{%youtube EuoREBGsaRU %}

---

### 彈跳問題
機械式開關在切換的過程中，訊號並非立即從1變成0或從0變成1，而是會經過如下圖一般有忽高忽低的情況。
雖然彈跳的時間很短暫，但arduino仍會讀取到其連續變化的開關訊號，而導致誤差。
![](https://i.imgur.com/wTXp74A.png)

> ### 消除彈跳(de-bouncing)方法
在發現訊號開始變化時先讀取，在經過一段時間後，若訊號仍為HIGH，則視為按下第二次按鈕。



## LAB 2-2 de-bouncing
* 實驗目的：解決Button彈跳問題
* 實驗目標：無論長按或短按，按一下按鈕改變LED亮暗，且沒有不規則閃爍的問題； 
    ==Hint：不能使用delay()函式喔~==
* 參考程式碼（可任意新增改寫）
```c++=
const int button = 2;
const int led = 13;  //UNO內建LED
bool led_state = HIGH;
bool tick_led_state = HIGH;

void setup() {
  pinMode(button, INPUT_PULLUP);  //上拉
  pinMode(led, OUTPUT);
  digitalWrite(led, led_state);
  digitalWrite(tick_led, tick_led_state);
}

void loop() {
  if(digitalRead(button)==HIGH){  //沒按按鈕
    /*
    
    Do something
    
    */
  }
  else if(digitalRead(button)!=HIGH){  //按按鈕
    /*
    
    Do something
    
    */
  }

}
```
* 範例影片：
{%youtube AZMKNSqHxz8 %}

---

# Arduino講義：多工七段顯示器與掃描鍵盤
## 3. 多工七段顯示器(Multiplexed Seven-Segment display)
> ### 七段顯示器
![](https://i.imgur.com/CL8AjzP.png)
![](https://i.imgur.com/jCHvCki.png)



**<<Note>>**
**注意!!需額外增加限流電阻，否則內部可能會燒毀!!**
![](https://i.imgur.com/bg7CQ4b.png)
依據電源連接方式的不同，七段顯示器可以分成「**共陽極**」與「**共陰極**」兩種：
共陰極代表所有的LED接地端都相連，故LED的另一端接「高電位」就會發光；相反地，共陽極則是輸入「低電位」來發光。
    
| 十進位數 | a | b |c|d|e|f|g|
| - | - | - | - | - | - | - | - | 
|0|1|1|1|1|1|1|0|
|1|0|1|1|0|0|0|0|
|2|1|1|0|1|1|0|1|
|3|1|1|1|1|0|0|1|
|4|0|1|1|0|0|1|1|
|5|1|0|1|1|0|1|1|
|6|1|0|1|1|1|1|1|
|7|1|1|1|0|0|0|0|
|8|1|1|1|1|1|1|1|
|9|1|1|1|1|0|1|1|


>  ### 多工七段顯示器
![](https://i.imgur.com/k4HT6lO.png)
![](https://i.imgur.com/NTsPOqR.png =50%x)![](https://i.imgur.com/d4zoQH0.png =50%x)



* 實際接腳位置：

| 多工七段顯示器 | Arduino | 
| :-----:  | :-: |
|    0     |Pin10|
|    1     |Pin11|
|    2     |Pin12|
|    3     |Pin13|
|    a     |Pin2 |
|    b     |Pin3 |
|    c     |Pin4 |
|    d     |Pin5 |
|    e     |Pin6 |
|    f     |Pin7 |
|    g     |Pin8 |
|   dp     |Pin9 |



## LAB 3-1 多工七段顯示器
* 實驗目的：學習對Arduino的GPIO與多工七段顯示器掃描基本控制方式
* 實驗目標：控制多工七段顯示器顯示＂**2023**＂
* 參考程式碼（可任意新增改寫）
```c++=
const int SEG_COM[4] = {10, 11, 12, 13};  //控制線
const int SEG_data[10][8] = {{1, 1, 1, 1, 1, 1, 0, 0},  //"0"  //資料線
                       {0, 1, 1, 0, 0, 0, 0, 0},  //"1"
                       {1, 1, 0, 1, 1, 0, 1, 0},  //"2"
                       {1, 1, 1, 1, 0, 0, 1, 0},  //"3"
                       {0, 1, 1, 0, 0, 1, 1, 0},  //"4"
                       {1, 0, 1, 1, 0, 1, 1, 0},  //"5"
                       {1, 0, 1, 1, 1, 1, 1, 0},  //"6"
                       {1, 1, 1, 0, 0, 0, 0, 0},  //"7"
                       {1, 1, 1, 1, 1, 1, 1, 0},  //"8"
                       {1, 1, 1, 0, 0, 1, 1, 0}};  //"9"

int disp[4] = {2, 0, 2, 3};  //欲顯示數字

void setup() {
  for(int i=2; i<=13; i++){
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH);  //共陽極
  }
}

void loop() {
  for(int i=0; i<=3; i++){  //個、十、百、千位數
    digitalWrite(SEG_COM[i], LOW);  //觸發第i位數顯示數字
    SEG_Drive(disp[i]);  //資料線寫入數值
    delay(5);
    digitalWrite(SEG_COM[i], HIGH);  //結束觸發第i位數
  }
}

void SEG_Drive(char number){  //將字元變數從SEG_data[][]找到相對應的位置，並寫入a~g中
  /*
  
  Do something
  
  */
}
```

* 範例影片：
{%youtube FioqrakR8O8 %}

---

## LAB 3-2 多工七段顯示器數(ㄕㄨˇ)數
* 實驗目的：學習對Arduino的GPIO與多工七段顯示器掃描基本控制方式
* 實驗目標：控制多工七段顯示器 每隔一秒計數加一
* 參考程式碼（可任意新增改寫）
```C++
const int SEG_COM[4] = {10, 11, 12, 13};  //控制線
const int SEG_data[10][8] = {{1, 1, 1, 1, 1, 1, 0, 0},  //"0"  //資料線
                       {0, 1, 1, 0, 0, 0, 0, 0},  //"1"
                       {1, 1, 0, 1, 1, 0, 1, 0},  //"2"
                       {1, 1, 1, 1, 0, 0, 1, 0},  //"3"
                       {0, 1, 1, 0, 0, 1, 1, 0},  //"4"
                       {1, 0, 1, 1, 0, 1, 1, 0},  //"5"
                       {1, 0, 1, 1, 1, 1, 1, 0},  //"6"
                       {1, 1, 1, 0, 0, 0, 0, 0},  //"7"
                       {1, 1, 1, 1, 1, 1, 1, 0},  //"8"
                       {1, 1, 1, 0, 0, 1, 1, 0}};  //"9"
int disp[4];  //顯示數字
int number = 0;
int timer = 0;

void setup() {
  Serial.begin(9600);
  for(int i=2; i<=13; i++){
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH);  //共陽極
  }
}

void loop() {
  number_transfer(number);  //數值轉換入陣列中
  /*
  
  Do something(改寫Lab3-1) 
  
  */
}

void number_transfer(int Num){  //四位數拆成四個獨立的數字，存入陣列disp[]中
  /*
  
  Do something
  
  */
}

void SEG_Drive(char number){  //將字元變數從SEG_data[][]找到相對應的位置，並寫入a~g中
  /*
  
  Do something
  
  */
}
```
* 範例影片：
{%youtube 0qCCoaZ-apg %}

---

## 4. 4x4鍵盤(Keypad)

![](https://i.imgur.com/9LfIBf6.png)


> ### 按鈕偵測與掃描原理（1x3簡化版）
---

![](https://i.imgur.com/1oR4N7l.png)
假設開關的「行１」～「行３」輸入端全部都輸出高電位，則無論開關是否被按下，Arduino都將會接收到**高電位**。因此，為了偵測到使用者按下的開關鍵，撰寫程式時必須依序將「行１」～「行３」設置成**低電位**來檢測。
* 輪到「行１」腳被掃描時，「行１」腳輸入低電位，但Ａ開關沒有被按下，所以Arduino的微控腳接收到高電位(１)
![](https://i.imgur.com/n9dBBz0.png)
* 在掃描到「行２」時，「行２」腳輸入低電位，且Arduino的微控腳也接收到低電位(0)，因此可以判斷出「行２」的Ｂ開關被按下。
![](https://i.imgur.com/lTn2icb.png)
* 輪到「行３」腳被掃描時，「行３」腳輸入低電位，但Ｃ開關沒有被按下，所以Arduino的微控腳接收到高電位(１)
![](https://i.imgur.com/crZsTYq.png)

---
實際上的使用需要運用到**雙重迴圈(4x4)**，才能夠分批掃描到每一列：
![](https://i.imgur.com/8YSHIj2.png)






>  ### 實際腳位配置
| 4x4 Keypad | Arduino   |
| :--------: | :-------: | 
| 1(ROW1)    | Pin A0    |
| 2(ROW2)    | Pin A1    |
| 3(ROW3)    | Pin A2    |
| 4(ROW4)    | Pin A3    |
| 5(COL1)    | Pin 2     |
| 6(COL2)    | Pin 3     |
| 7(COL3)    | Pin 4     |
| 8(COL4)    | Pin 5     |



## LAB 4-1 掃描式鍵盤
* 實作目的：學習如何使用掃描式鍵盤
* 實作目標：<font color = "red">**在不使用<Keypad.h>的情況**</font>，以Keypad鍵入資料，將資料透過UART傳輸至內建的Serial Monitor
* 參考程式碼（可自行改寫）
```C++
const byte colPins[4] = {2, 3, 4, 5};  //行腳位
const byte rowPins[4] = {A0, A1, A2, A3};  //列腳位
const char keymap[4][4] = {{'1', '2', '3', 'A'},  //Keypad對應符號
                           {'4', '5', '6' ,'B'},
                           {'7', '8', '9', 'C'},
                           {'*', '0', '#', 'D'}};

void setup() {
  Serial.begin(115200);
  for(int i=0; i<=3; i++){
    pinMode(rowPins[i], INPUT_PULLUP);
    pinMode(colPins[i], OUTPUT);
    digitalWrite(colPins[i], HIGH);
  }
}

void loop() {
  for(int i=0; i<=3; i++){
    for(int j=0; j<=3; j++){
      digitalWrite(colPins[i], LOW);
      if(analogRead(rowPins[j])<=512){
        Serial.println(keymap[j][i]);
        delay(300);
      }
    }
    digitalWrite(colPins[i], HIGH);
  }
}
```
* 範例影片：
{%youtube wBKy9NmiV2I %}

---

> ### 匯入Keypad函式庫
Keypad程式庫的運作方式與LAB 4-1 大致相同，且多了除彈跳(de-bouncing)的部分。LAB 4-1有助於了解掃瞄鍵盤的原理，實際使用上仍是Keypad程式庫為主。

[**Keypad Library下載**](https://www.arduinolibraries.info/libraries/keypad)

![](https://i.imgur.com/aIS1wcX.png)

![](https://i.imgur.com/z7ufiOA.png)

使用Keypad函式庫，程式碼需定義按鍵模組的**行(col)**、**列(row)**、**連接Arduino的腳位**以及**按鍵所代表的字元**。
![](https://i.imgur.com/MrWNGmx.png)

```c++=
#include <Keypad.h> //引用Keypad函式庫
#define KEY_ROWS 4 //按鍵模組的列數
#define KEY_COLS 4 //按鍵模組的行數

const byte colPins[4] = {2, 3, 4, 5};
const byte rowPins[4] = {A0, A1, A2, A3};
const char keymap[KEY_ROWS][KEY_COLS] = {
    {'1','2','3','A'}, 
    {'4','5','6','B'}, 
    {'7','8','9','C'},
    {'*','0','#','D'}
};
Keypad myKeypad = Keypad(makeKeymap(keymap),rowPins,colPins,KEY_ROWS,KEY_COLS);

void setup(){
    Serial.begin(115200);
}

void loop(){
    //透過Keypad函式庫裡的getkey()方法讀取按鍵的字元
    char key = myKeypad.getKey();
    if(key){    //若有按鍵按下則顯示按下的字元
        Serial.println(key);
    }
}
```
## LAB 4-2 Keypad 輸出字串
* 實驗目的：學習如何使用掃描式鍵盤及其應用
* 實驗目標：利用Keypad輸入按鍵，並且將值暫存在 buffer 中，按下 * 後會將之前輸入的字元依序Print到Serial Monitor上。
* 範例影片：
{%youtube nXlvsgAh54E %}

## LAB 4-3 Matrix keypad with 7 segment (Bonus)
* 實驗目的：學習如何使用掃描式鍵盤與七段顯示器之結合應用
* 實驗目標：利用Keypad輸入按鍵，按數字 1~9 會分別在七段顯示器上顯示。
    | 多工七段顯示器 | Arduino | 4x4 Keypad|Arduino|
    |  :-------:  | :-----: |:------:|:------:|
    |    0     |Pin9 |Row1|A0|
    |    a     |Pin2 |Row2|A1|
    |    b     |Pin3 |Row3|A2|
    |    c     |Pin4 |Col1|13|
    |    d     |Pin5 |Col2|12|
    |    e     |Pin6 |Col3|11|
    |    f     |Pin7 
    |    g     |Pin8 
  

* 實驗結果：
{%youtube AigN8GxwVDI %}





:::info
## 課後習題
1. 請描述機械式開關的彈跳現象，並舉出彈跳現象的例子。

2. 描述多工七段顯示器及鍵盤掃描的工作原理
    
3. 實驗心得
:::
:::success
## 作業繳交格式 

**W2結報_第X組.zip**
壓縮檔裡包含:
1.W2結報.pdf
2.資料夾:W2
    Lab1-1.ino
    Lab1-2.ino
    ...
:::