###### 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, 這樣就可以隨意增減陣列內容彈性變化亮度層次了。