---
# System prepended metadata

title: 08 使用陣列展現不規律的亮度變化
tags: [Arduino, CPP]

---

###### tags: `CPP` `Arduino`

# 08 使用陣列展現不規律的亮度變化

目前實作的呼吸燈是以規律漸變的方式變化 LED 亮度, 如果我們希望可以不規律的變化亮度, 例如先很暗 0.5 秒、再一半亮 0.5 秒、全亮 0.5 秒, 最直接的寫法就是如下這樣：

```cpp=
const int total = 5000;   // 5000*100us = 0.5s 
const int duration = 100; // 單次亮/熄時長 (us)
const int led_pin = 5;    // 內建 LED 腳位 

void twinkle(int led_pin, int duration, int duration_on, int total) {
  for(int count = 0;count < total; count++) {
    digitalWrite(led_pin, LOW);
    delayMicroseconds(duration_on);
    digitalWrite(led_pin, HIGH);
    delayMicroseconds(duration - duration_on);
  }
}
 
void setup() {
  pinMode(led_pin, OUTPUT);
}

void loop() {
  twinkle(led_pin, duration, 10, total);
  twinkle(led_pin, duration, 30, total);
  twinkle(led_pin, duration, 100, total);
}
```

1. 由於每次切換亮/熄時長 100us, 所以第 1 行將切換次數改成 5000, 總時長就會是 0.5 秒。
2. 第 19~21 就依序以亮燈時長 10、30、100us 亮燈 0.5 秒。

你可以上傳程式測試看看, 現在就會變成 3 種亮度輪流 0.5 秒。

上面的做法雖然結果正確, 但是 3 種亮度變化就要重複 3 次呼叫函式, 如果有 50 種亮度變化, 不就要重複 50 次呼叫函式？

## 陣列 (array)

為了簡化程式, 我們可以採用[**陣列 (array)**](https://www.arduino.cc/reference/en/language/variables/data-types/array/)。陣列是一種特殊的資料儲存機制, 可以讓你使用單一個變數儲存多項資料, 並且可以透過**序號索引**取出儲存在其中的個別資料, 例如以下就是定義一個內含 3 項資料的陣列：

```cpp
int levels[3] = {10, 30, 100};
```

1. 陣列內的資料都是**同一種型別**, 像是上例中就都是整數 (int)。

2. 陣列名稱之後要用**方括號**標註陣列內的資料數量, 像是上例就是 3 項資料。

3. 在宣告陣列時可以直接用等號接著**大括號**定義陣列內含的資料, 像是上例, 就是 10、30、100 這 3 項資料。在這種情況下, 也可以不用標註資料數量, C++ 會自動計算, 像是這樣：

    ```cpp
    int levels[] = {10, 30, 100};
    ```

    :::info
    這裡的大括號是表示陣列內容的符號, **並不是區塊**, 所以**大括號後敘述結尾處仍然要加上分號**。
    :::

    如果宣告時沒有定義內含的資料, 就一定要指明資料數量, 例如：

    ```cpp
    int levels[3];
    ```

6. 在程式中可以用**從 0 算起的序號索引**來取用陣列內的個別資料, 就像是有多個變數一樣, 例如：

    ```cpp
    levels[0] // ---->  10
    levels[1] // ---->  30
    levels[2] // ----> 100
    ```
    
    也可以用同樣的方式設定陣列內的值, 例如：
    
    ```cpp
    levels[0] = 0
    ```
    
你發現了嗎？因為陣列是採用序號索引來取值, 所以如果是要循序取出陣列中的個別資料, 就可以利用迴圈遞增變數當成索引, 一一取出資料了。利用這樣的想法, 就可以改寫剛剛變化 3 種亮度的程式了：

```cpp=
const int total = 5000;               // 5000*100us = 0.5s 
const int duration = 100;             // 單次亮/熄時長 (us)
const int led_pin = 5;                // 內建 LED 腳位 
const int levels[3] = {10, 30, 100};  // 3 種亮度變化的亮燈時長

void twinkle(int led_pin, int duration, int duration_on, int total) {
  for(int count = 0;count < total; count++) {
    digitalWrite(led_pin, LOW);
    delayMicroseconds(duration_on);
    digitalWrite(led_pin, HIGH);
    delayMicroseconds(duration - duration_on);
  }
}
 
void setup() {
  pinMode(led_pin, OUTPUT);
}

void loop() {
  for(int i = 0; i < 3;i++) {
    twinkle(led_pin, duration, levels[i], total);
  }
}
```

1. 第 4 行就定義了內含 3 種亮燈時長的陣列。

2. 第 20 行利用迴圈讓變數 `i` 從 0 變化到 2(注意判斷條件是小於 3), 然後利用變數 `i` 當索引到陣列中取值作為亮燈時長呼叫 `twinkle`。這個迴圈就跟前面我們自己寫 3 次呼叫 `twinkle` 的效用是一樣的。
    :::info
    用迴圈讀取陣列內容時要特別留意判斷條件, 否則可能會出現指定的索引超過陣列內資料數量的情況, 導致執行時讀取到錯誤的值而出錯。
    :::

你可能會想說剛剛 3 行程式換現在的 3 行程式有差嗎？有, 如果是要變換 50 種亮度, 就會是 50 行程式縮減成 3 行了。

## 使用 sizeof 計算陣列內的資料數量

採用陣列的版本有個小問題, 在迴圈中必須以陣列的資料數量作為判斷條件, 這表示只要我們更改了陣列內含的資料數量, 就要記得更改迴圈中的判斷條件, 不然就無法搭配, 甚至可能出錯。

要解決這個問題, 可以使用 [`sizeof`](https://www.arduino.cc/reference/en/language/variables/utilities/sizeof/) 運算, 它可以告訴我們指定的**資料或是型別**實際**佔用的空間**大小, 單位是**位元組 (byte)**, 使用時要為想要判別的型別或是資料加上一層小括號, 例如：

```cpp
sizeof(int) //----> 4
```

:::info
不同的開發板上微處理器不同, 所得到的佔用空間大小也會不同, 在 Arduino Uno 上 `int` 的大小就是 2 個位元組, 而不是如上 D32 得到的 4。
:::

由於我們的陣列內含整數資料, 所以只要用**陣列佔用的空間大小除以整數佔用的空間大小**, 就會是陣列內的資料數量了, 我們可以再將剛剛的程式改寫如下：

```cpp=
const int total = 5000;   // 5000*100us = 0.5s 
const int duration = 100; // 單次亮/熄時長 (us)
const int led_pin = 5;    // 內建 LED 腳位 
const int levels[3] = {10, 30, 100};

void twinkle(int led_pin, int duration, int duration_on, int total) {
  for(int count = 0;count < total; count++) {
    digitalWrite(led_pin, LOW);
    delayMicroseconds(duration_on);
    digitalWrite(led_pin, HIGH);
    delayMicroseconds(duration - duration_on);
  }
}
 
void setup() {
  pinMode(led_pin, OUTPUT);
}

void loop() {
  for(int i = 0; i < sizeof(levels)/sizeof(int); i++) {
    twinkle(led_pin, duration, levels[i], total);
  }
}
```

第 20 行就用 `sizeof(levels)/sizeof(int)` 取得陣列內資料數量作為判斷條件, 替代直接寫死的 3, 這樣就可以隨意增減陣列內容彈性變化亮度層次了。
