---
# System prepended metadata

title: 04 if 流程控制與取餘數運算--設計漸亮效果
tags: [Arduino, CPP]

---

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

# 04 if 流程控制與取餘數運算--設計漸亮效果

前面的教學讓我們可以用快速切換的方式呈現不同的亮度, 如果可以把亮燈的時間從 0 變化到 100us, 就可以呈現漸亮的效果。為了要做到這一點, 我們可以新增一個變數 `count`, 紀錄切換亮/熄的次數, 每切換 100 次, 即每 100\*100us = 10ms, 也就是 0.01 秒, 就將點亮的時間增加 1us, 這樣就可以每隔一小段時間讓 LED 變更亮一點。

為了能夠判斷是否已經切換亮/熄 100 次, 還需要新的功能, 叫做 [`if`](https://www.arduino.cc/reference/en/language/structure/control-structure/if/) 敘述, `if` 的語法如下：

```cpp
if(判斷條件) {
  條件成立時要執行的動作
}
判斷條件成不成立都會從這裡繼續
```

`if` 的後面必須跟著一對小括號, 然後再跟著一個區塊。小括號內是要**判斷的條件**, 如果條件成立, 就會執行區塊內的敘述, 執行完後再從區塊後的敘述開始執行。如果條件不成立, 就會直接跳到區塊後的敘述。以我們的例子來說, 就可以利用這樣的寫法判斷是否已經切換 100 次：

```cpp
if(count == 100) {
  duration_on = duration_on + 1;
  count = 0;    
}
```

這裡的 [**`==`**](https://www.arduino.cc/reference/en/language/structure/comparison-operators/equalto/) 是比較兩邊的值是否相等？也就是比較變數 `count` 的值是不是 100？若是相等, 判斷就成立, 才會執行區塊內增加亮燈時長的敘述。

C++ 會用 [`boolean`](https://www.arduino.cc/reference/en/language/variables/data-types/boolean/) 這種型態的資料來表示比較結果, 它只有兩種可能值, [`true`](https://www.arduino.cc/reference/en/language/variables/constants/constants/) 代表**成立**、**真**的意思, 而 [`false`](https://www.arduino.cc/reference/en/language/variables/constants/constants/) 代表**不成立**、**假**的意思。
    
:::info
常用的比較運算如下：

|運算|說明|運算|說明|運算|說明|
|----|----|----|----|----|----|
|[<](https://www.arduino.cc/reference/en/language/structure/comparison-operators/lessthan/)|小於|[<=](https://www.arduino.cc/reference/en/language/structure/comparison-operators/lessthanorequalto/)|小於或等於|[==](https://www.arduino.cc/reference/en/language/structure/comparison-operators/equalto/)|相等|
|[>](https://www.arduino.cc/reference/en/language/structure/comparison-operators/greaterthan/)|大於|[>=](https://www.arduino.cc/reference/en/language/structure/comparison-operators/greaterthan/)|大於或等於|[!=](https://www.arduino.cc/reference/en/language/structure/comparison-operators/notequalto/)|不相等|
:::

以下就是依此修改的程式：

```cpp=
int duration_on = 0; // 亮燈時間
int count = 0;       // 計算切換次數

void setup() {
  pinMode(5, OUTPUT);
}

void loop() {
  if(count == 100) {
    duration_on = duration_on + 1;
    count = 0;    
  }
  digitalWrite(5, LOW);
  delayMicroseconds(duration_on);
  digitalWrite(5, HIGH);
  delayMicroseconds(100 - duration_on);
  count = count + 1;
}
```

1. 第 2 行是新增的變數 `count`, 代表切換亮/熄的次數, 初始的值為 0。

2. 第 17 行是在 `loop` 的最後, 也就是切換亮/熄之後, 把 `count` 的值加 1, 表示又多切換了 1 次。注意到這裡等號的左右邊都出現了變數 `count`, 執行順序是**先計算等號右邊**, 用 `count` 的值加 1, 然後**將計算結果放回** `count`, 所以 `count` 的值就會增加 1。

    ```mermaid
    flowchart LR
    count[/count/]
    exp[[0 + 1]]
    one([1])
    count-- 取值 -->exp--運算-->one--存回-->count
    ```

4. 第 9 行就是套用剛剛介紹的 `if` 判斷是否已經切換了 100 次？

6. 第 10 行就是在 `count` 達到 100, 也就是亮/熄切換 100 次時將點亮時間加長 1us 的程式。

8. 第 11 行是將 `count` 變回 0 **重新計數**, 這一行很重要, 如果忘了這一行, 之後 `if` 的判斷就會因為 `count` 已經超過 100 而不會成立了。

現在你可以重新上傳程式看看, 你應該會看到內建 LED 慢慢亮起來, 然後就熄掉了。如果來不及看到, 可以按一下板子上的 RESET 鈕重新執行。

## 不同資料型別的問題

咦！剛剛的程式為什麼最後 LED 會熄掉呢？這是因為當 `duration_on` 超過 100 後, 用 `100 - duration_on` 計算的熄燈時間就會變成負數, 但是 `delayMicroseconds` 希望你給它的參數是 [`unsigned int`](https://www.arduino.cc/reference/en/language/variables/data-types/unsignedint/) 型別的資料, 這種資料是沒有負數的。遇到這種狀況時, C++ 會自動幫你轉換資料, `int` 型別的 -1 會被轉換成 `unsigned int` 的 4294967295, 以此作為熄燈的持續時間, 4294967295us 約是 1.2 小時, 所以會熄掉很久, 像是永遠不會亮了。

## 多層的 if

如果要避免前述問題, 我們可以再加一個 `if` 判斷這種情況, 讓 LED 重新回到不亮後再慢慢亮起：

```cpp=
int duration_on = 0; // 亮燈時間
int count = 0;       // 計算測換次數

void setup() {
  pinMode(5, OUTPUT);
}

void loop() {
  if(count == 100) {
    duration_on = duration_on + 1;
    if(duration_on > 100)
      duration_on = 0;  
    count = 0;    
  }
  digitalWrite(5, LOW);
  delayMicroseconds(duration_on);
  digitalWrite(5, HIGH);
  delayMicroseconds(100 - duration_on);
  count = count + 1;
}
```

在第 11 行我們額外加了一個 `if` 判斷 `duration_on` 是否已經超過了100？如果是的話就把 `duration_on` 設回一開始的 0。這裡有兩點需要特別留意：

1. 如果 `if` 的區塊內只有**單一敘述**, 那麼可以省略區塊的左右大括號, 就像是第 12 行那樣。
2. `if` 內可以包含 `if`, 進行**多層**的條件判斷, 為了容易區分層次, 每一層區塊內的程式都會再往右邊縮, 像是 9~19 行是函式的區塊, 內縮了一層；10~13 行是第 1 個 `if` 的區塊, 又縮了一層；而第 12 行是第 2 個 `if` 的區塊, 又再縮了一層, 這樣可以清楚辨識每一個區塊的層次, 提升程式的易讀性。
    
    :::info
    在 Arduio IDE 中, 當你在左大括號後按下 <kbd>enter</kbd> 時, 它會自動幫你往右縮排, 是很貼心的設計。
    :::
    
重新上傳後, 就可以看到 LED 漸亮, 到最亮後又會熄掉漸亮不斷重複。

## 使用取餘數運算減少程式行數

在剛剛的範例中, 我們希望 `duration_on`  的值會在 0~100 之間循環遞增, 所以使用了這樣的結構：

```CPP=10
    duration_on = duration_on + 1;
    if(duration_on > 100)
      duration_on = 0;  
```

上述寫法佔了 3 行程式, 不過在 C++ 中提供有 [`%` 取餘數](https://www.arduino.cc/reference/en/language/structure/arithmetic-operators/remainder/)的運算, 會依照整數除法運算, 但是以餘數作為計算的結果。利用取餘數的運算, 就可以將這 3 行程式簡化成 1 行, 像是這樣：

```cpp
    duration_on = (duration_on + 1) % 101;
```

一開始看可能覺得這行程式很奇怪, 每次遞增數值後再除以 101 取餘數, 我們可以一步步計算看看。當 `duration_on` 的值從 0 遞增到 100 的過程中, 因為 `duration_on` 的值都小於 101, 因此除以 101 的餘數就是自己不會變, 但是當 `duration_on` 的值變為 101 時, 除以 101 取餘數就會因為可以整除而得到 0, 如下所示：

| duration_on | duration_on+1 | (durarion_on+1) % 101 |
|:-----------:|:-------------:|:---------------------:|
|      0      |       1       |           1           |
|      1      |       2       |           2           |
|      2      |       3       |           3           |
|     ...     |      ...      |          ...          |
|     99      |      100      |          100          |
|     100     |      101      |           0           |

如此一來, `duration_on` 的值就會在 0~101 之間循環變化了。依此修改的完整程式如下：

```cpp=
int duration_on = 0; // 亮燈時間
int count = 0;       // 計算測換次數

void setup() {
  pinMode(5, OUTPUT);
}

void loop() {
  if(count == 100) {
    duration_on = (duration_on + 1) % 101;
    count = 0;    
  }
  digitalWrite(5, LOW);
  delayMicroseconds(duration_on);
  digitalWrite(5, HIGH);
  delayMicroseconds(100 - duration_on);
  count = count + 1;
}
```

重新上傳就會發現程式雖然變短了, 但是功能不變。

要特別提醒的是, 在 C++ 程式中, 也和我們習慣的四則運算一樣, 會有優先順序, 如果你漏掉了小括號, 把剛剛的那一行程式寫成這樣：

```cpp
    duration_on = duration_on + 1 % 101;
```

就會因為取餘數的優先順序大於加法, 所以會變成：

```cpp
    duration_on = duration_on + (1 % 101);
```

由於 `1 % 101` 是 1, 所以 `duration_on` 就會不斷遞增, 程式就會出錯, 跟之前一樣只會出現一次漸亮的效果後就不會再亮了。為了避免出錯, 也省去我們記憶不同運算之間的優先順序, 建議您養成加上小括號的習慣, 強制安排個別運算之間的順序。

利用取餘數運算讓變數的值在某一區間內循環變化的技巧很常見, 既簡潔又方便, 一開始看到可能覺得不習慣, 多看幾次就會得心應手了。
