Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 11 章 數位邏輯

本章目標

  • 學完你會說出數位的 0 與 1 對應到什麼電壓,以及 Arduino 判斷 HIGH/LOW 的門檻在哪裡。
  • 學完你會把 0–255 的十進位數換成 8 位元二進位,也會換回來,並且知道「位元」和「位元組」是什麼。
  • 學完你會寫出 AND、OR、NOT、XOR 的真值表,並用它們組合出「門開 AND 天黑 → 亮燈」這種判斷。
  • 學完你會解釋為什麼按鈕一定要搭配上拉或下拉電阻,並會用 Arduino 內建的 INPUT_PULLUP。
  • 學完你會用示波器或邏輯分析儀親眼看到按鈕彈跳,並用程式把它去掉。

開場:按一下,怎麼算了三次?

社團的學長做了一個「按一下就加一」的計數器:按鈕接到 Arduino,每按一次螢幕上的數字就加 1。看起來很簡單,可是他按了 10 次,螢幕卻顯示 17,有時候又是 13。他把程式檢查了三遍,一個字都沒錯。

另一個同學在做玄關的自動燈:門打開「而且」天黑了才亮,白天開門不用亮。他寫了兩個 if,結果燈不是一直亮,就是完全不亮。

這兩個問題看起來不一樣,其實都跟同一件事有關:電腦的世界裡只有 0 和 1,而真實世界的按鈕、感測器、電線,都沒有那麼乾脆。 這一章我們要學「數位」到底是什麼,怎麼用 0 和 1 算數、怎麼用 0 和 1 做判斷,最後拿示波器去看按鈕按下去的那一瞬間,到底發生了什麼事,你會發現學長的計數器一點錯都沒有,錯的是按鈕。

核心概念

只有兩種狀態:0 與 1

前面幾章的電壓可以是 1.3V、2.75V、4.2V,任何數字都行,這叫類比(Analog),像一個可以無段調整的水龍頭。這一章開始我們講數位(Digital):只有「開」和「關」兩種狀態,像牆上的電燈開關,不是開就是關,沒有「半開」。

數位世界把這兩種狀態叫 0 與 1,在 Arduino 程式裡叫 LOW 與 HIGH。對 5V 的 Arduino UNO 來說:

  • 輸出時:HIGH 就是 5V,LOW 就是 0V。
  • 輸入時:Arduino 不會要求你剛好給 5V 或 0V,它有一條門檻(Threshold)。大致上,低於 1.5V 一定讀成 LOW,高於 3.0V 一定讀成 HIGH,中間 1.5V 到 3.0V 是「灰色地帶」,讀到什麼不保證。
 5V ┤ ═══════════ 一定是 HIGH
    │
3.0V┤ ───────────
    │   灰色地帶(不保證)
1.5V┤ ───────────
    │
 0V ┤ ═══════════ 一定是 LOW

ESP32 是 3.3V 系統,門檻也跟著縮:大約低於 0.8V 是 LOW、高於 2.5V 是 HIGH。

這個「灰色地帶」就是數位厲害的地方:假設一條線本來應該是 5V,因為雜訊變成 4.2V、有時候 4.7V,沒關係,反正都在 3.0V 以上,Arduino 讀到的一樣是 HIGH。數位訊號天生就比類比訊號抗雜訊,這也是為什麼電腦、手機全部都用數位。

二進位:只用 0 和 1 數數

只有 0 和 1,怎麼表示 178 這種數字?想一想我們平常怎麼數:十進位每一位是「個、十、百、千」,每往左一位乘 10。二進位(Binary)一樣,只是每往左一位乘 2,每一位只能是 0 或 1。每一位叫一個位元(bit),8 個位元湊在一起叫一個位元組(Byte)。

8 位元的每一位「值多少錢」如下,從右往左數,最右邊是第 0 位:

位元編號b7b6b5b4b3b2b1b0
這一位的值1286432168421

二進位 → 十進位:哪一位是 1,就把那一位的值加起來。

算一個:10110010 = 128 + 0 + 32 + 16 + 0 + 0 + 2 + 0 = 178。

全部都是 1 的 11111111 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255。所以 8 位元最大就是 255,最小是 00000000 = 0,一共 256 種。這就是為什麼 analogWrite() 的範圍是 0–255:它用一個位元組存。

十進位 → 二進位:從 128 開始往下問「夠不夠減?」,夠就寫 1 並減掉,不夠就寫 0。

算一個:100

  • 128?不夠 → 0
  • 64?夠,100 − 64 = 36 → 1
  • 32?夠,36 − 32 = 4 → 1
  • 16?不夠 → 0
  • 8?不夠 → 0
  • 4?夠,4 − 4 = 0 → 1
  • 2?不夠 → 0
  • 1?不夠 → 0

所以 100 = 01100100。驗算:64 + 32 + 4 = 100,對了。

第 13 章你會看到 analogRead() 的範圍是 0–1023,那是因為它用 10 個位元:最大值是 1024 − 1 = 1023。

邏輯閘:用 0 和 1 做判斷

有了 0 和 1,接下來要「做決定」。做決定的基本零件叫邏輯閘(Logic Gate),每種閘都有一張真值表(Truth Table),列出所有輸入組合的結果。我們用生活情境記住四種:

AND(且):兩把鑰匙的保險箱。 兩把鑰匙都要轉,門才會開。只有「全部是 1」輸出才是 1。

ABA AND B
000
010
100
111

OR(或):前門或後門。 有人從前門進來,或從後門進來,門鈴都會響。只要「有一個是 1」輸出就是 1。

ABA OR B
000
011
101
111

NOT(非):唱反調。 只有一個輸入,輸出永遠相反。

ANOT A
01
10

XOR(互斥或):樓梯燈的雙切開關。 樓上樓下各一個開關,任何一個撥一下,燈的狀態就反過來。兩個輸入「不一樣」時輸出 1,「一樣」時輸出 0。

ABA XOR B
000
011
101
110

在 Arduino 程式裡,這四種分別寫成:

邏輯程式寫法例子
AND&&a && b
OR||a || b
NOT!!a
XOR!=(兩個布林值「不相等」)a != b

把閘串起來就能做更複雜的判斷。開場的自動燈:

門開了(doorOpen)──┐
                    AND ──► 亮燈
天黑了(isDark)────┘

程式就是一行:if (doorOpen && isDark) { digitalWrite(ledPin, HIGH); }。那位同學寫兩個獨立的 if,等於把 AND 拆成了兩個各自獨立的判斷,當然不對。

再進階一點:「門開 AND 天黑 AND NOT 假日」就是 doorOpen && isDark && !isHoliday。

上拉與下拉電阻:別讓腳位「飄」

現在來看真實世界的按鈕。你可能會想:按鈕一腳接 5V、一腳接 D2,按下去 D2 就是 HIGH,不就好了?

問題是:沒按的時候 D2 接到哪裡? 什麼都沒接。這種腳位叫浮接(Floating),像一面沒綁好的旗子,隨風亂飄:旁邊的電線、你的手靠近,它就會在 HIGH 和 LOW 之間亂跳。實驗 11-1 你會親眼看到這個現象。

解法是用一顆電阻把腳位「輕輕拉住」,給它一個預設值:

下拉電阻(Pull-down Resistor):電阻接到 GND,沒按時被拉成 LOW;按鈕接到 5V,按下時變 HIGH。

 5V ──── 按鈕 ────┬──── Arduino D2
                  │
                [10kΩ]  下拉電阻
                  │
                 GND

上拉電阻(Pull-up Resistor):電阻接到 5V,沒按時被拉成 HIGH;按鈕接到 GND,按下時變 LOW。

 5V ────[10kΩ]────┬──── Arduino D2
       上拉電阻    │
                  按鈕
                  │
                 GND

兩種都可以,為什麼本書從這一章開始都用上拉?因為 Arduino 的每個腳位裡面已經內建一顆上拉電阻,程式寫 pinMode(2, INPUT_PULLUP) 就打開它,不用再外接電阻,接線少一半。內建上拉大約 20–50 kΩ。

要記住一件反直覺的事:用上拉時,按下去讀到的是 LOW,放開是 HIGH。 程式裡要寫 if (digitalRead(2) == LOW) 才是「被按下」。

算一下電阻值合不合理:用 10 kΩ 外接上拉,按下時電流 = 5V ÷ 10000 Ω = 0.5 mA,很小,安全又省電。內建上拉若是 30 kΩ,電流只有約 0.17 mA。電阻太小(例如 100 Ω)按下去會流 50 mA,白白浪費電還可能燒腳位;電阻太大(例如 10 MΩ)拉的力氣太弱,又會被雜訊干擾。10 kΩ 是最常見的好選擇。

按鈕彈跳:按一下,其實是按了好幾下

最後解開學長的計數器之謎。按鈕裡面是兩片金屬片,按下去時它們互相碰撞。金屬片有彈性,像跳到彈簧床上,會彈起來、落下、再彈起來,反覆幾次才停。這整個過程大約 1 到 10 毫秒,叫彈跳(Bounce)。

 電壓(上拉,按下時)
 5V ┤ ──────┐  ┌┐ ┌┐
    │       │  ││ ││
 0V ┼       └──┘└─┘└──────────────
          ▲
      手指按下     ◄── 約 1~10 ms ──►

對你的手指來說這是「按了一下」,但 Arduino 一秒可以讀腳位幾十萬次,它看到的是 HIGH→LOW→HIGH→LOW→HIGH→LOW,數了三次。學長的程式沒錯,是按鈕在騙它。

解法叫去彈跳(Debounce),有三種:

  1. 軟體等一下:偵測到按下後,先 delay(30) 等彈跳結束,再確認一次是不是還按著。最簡單,本章用這招。
  2. 硬體濾波:第 10 章的 RC 低通濾波器,把那幾毫秒的快速跳動磨掉。
  3. 記住上一次狀態:只在「上次是放開、這次是按下」的那一刻算一次,而且距離上次變化要超過 30 ms。這是最常見的正式寫法,實驗 11-3 會用到。

動手做

實驗 11-1:浮接的腳位會亂飄,上拉就乖了

材料

  • Arduino UNO 與 USB 線、麵包板、杜邦線
  • 按鈕開關 ×1
  • 10 kΩ 電阻 ×1(給外接上拉用)

接線(分兩階段)

階段一:故意浮接。

 Arduino D2 ──── 一條杜邦線,另一頭什麼都不接(懸空)

階段二:內建上拉加按鈕。

 Arduino D2 ──┬── 按鈕 ──── Arduino GND
              │
         (Arduino 裡面的上拉電阻,程式打開)

  • 階段一:D2 插一條杜邦線,另一頭懸空在麵包板外。
  • 階段二:按鈕一腳 → D2,另一腳 → GND。

程式

// 實驗 11-1:看浮接 vs 上拉
const int buttonPin = 2;     // 按鈕接 D2

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);         // 階段一:純輸入,沒有上拉(會飄)
  // pinMode(buttonPin, INPUT_PULLUP);   // 階段二:改用這行(把上面那行註解掉)
}

void loop() {
  int state = digitalRead(buttonPin);   // 讀腳位,得到 0 或 1
  Serial.println(state);                // 印到序列埠
  delay(50);                            // 每秒印 20 次
}

步驟

  1. 用階段一的接線與程式,上傳後打開序列埠監控視窗(9600)。
  2. 看數字。用手指去摸那條懸空的杜邦線、靠近它、拿走,看數字會不會亂跳。
  3. 把程式改成階段二(INPUT_PULLUP),接上按鈕,重新上傳。
  4. 不按時看數字,按住時看數字。
  5. 進階:把 INPUT_PULLUP 改回 INPUT,但在 D2 和 5V 之間外接一顆 10 kΩ 電阻,看是不是跟內建上拉一樣穩。

你應該看到

  • 階段一:數字可能一直是 0 或 1,但手一靠近就開始 0、1 亂跳,完全不受控制。
  • 階段二:沒按穩穩是 1,按住穩穩是 0,手靠近也不會變。

觀察與記錄

情況沒碰時讀到手靠近時讀到按住按鈕時讀到穩不穩?
浮接(INPUT,懸空)(沒有按鈕)
內建上拉(INPUT_PULLUP)
外接 10 kΩ 上拉(INPUT)

實驗 11-2:用兩個按鈕模擬邏輯閘

材料

  • Arduino UNO、麵包板、杜邦線
  • 按鈕開關 ×2
  • 紅色 LED ×1、220 Ω 電阻 ×1

接線

 Arduino D2 ──── 按鈕 A ──── GND     (輸入 A,內建上拉)
 Arduino D3 ──── 按鈕 B ──── GND     (輸入 B,內建上拉)

 Arduino D9 ──[ 220Ω ]──►|── GND    (輸出:LED)
                        LED

  • 按鈕 A:一腳 → D2,另一腳 → GND。
  • 按鈕 B:一腳 → D3,另一腳 → GND。
  • Arduino D9 → 220 Ω → LED 長腳,LED 短腳 → GND。

程式(在序列埠輸入 a、o、x、n 切換 AND / OR / XOR / NOT)

// 實驗 11-2:邏輯閘模擬器
const int buttonAPin = 2;    // 輸入 A
const int buttonBPin = 3;    // 輸入 B
const int ledPin = 9;        // 輸出 LED
char gateMode = 'a';         // 目前的閘:a=AND, o=OR, x=XOR, n=NOT(A)

void setup() {
  Serial.begin(9600);
  pinMode(buttonAPin, INPUT_PULLUP);   // 按下 = LOW
  pinMode(buttonBPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  Serial.println("輸入 a=AND, o=OR, x=XOR, n=NOT 切換模式");
}

void loop() {
  // 有人打字就切換模式
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'a' || c == 'o' || c == 'x' || c == 'n') {
      gateMode = c;
      Serial.print("切換成:");
      Serial.println(gateMode);
    }
  }

  // 讀按鈕:按下是 LOW,所以用 == LOW 轉成 true(1)
  bool inputA = (digitalRead(buttonAPin) == LOW);
  bool inputB = (digitalRead(buttonBPin) == LOW);
  bool output = false;

  // 依模式計算輸出
  if (gateMode == 'a')      output = inputA && inputB;   // AND
  else if (gateMode == 'o') output = inputA || inputB;   // OR
  else if (gateMode == 'x') output = (inputA != inputB); // XOR
  else if (gateMode == 'n') output = !inputA;            // NOT(只看 A)

  digitalWrite(ledPin, output ? HIGH : LOW);   // 1 就亮,0 就暗

  // 把這一刻的真值表印出來
  Serial.print("A="); Serial.print(inputA);
  Serial.print(" B="); Serial.print(inputB);
  Serial.print(" 模式="); Serial.print(gateMode);
  Serial.print(" 輸出="); Serial.println(output);
  delay(200);                                  // 慢一點,才看得清楚
}

步驟

  1. 接線、上傳,打開序列埠監控視窗。預設是 AND 模式。
  2. 兩個按鈕都不按、只按 A、只按 B、兩個都按,四種組合各試一次,記錄 LED 亮不亮,填進真值表。
  3. 在監控視窗輸入 o 按 Enter,重做四種組合。
  4. 輸入 x,再做一次。
  5. 輸入 n,這時只看按鈕 A:不按時 LED 亮、按下反而暗。
  6. 把你填的表跟核心概念裡的真值表對一下。

你應該看到

  • AND:只有兩個都按時 LED 才亮。
  • OR:有按任何一個就亮。
  • XOR:只按一個時亮,兩個都按反而暗。
  • NOT:不按 A 時亮,按 A 時暗。

觀察與記錄

A(按=1)B(按=1)AND 的 LEDOR 的 LEDXOR 的 LEDNOT A 的 LED
00
01
10
11

實驗 11-3:按鈕計數器,先看它亂數,再把彈跳去掉

材料

  • 和實驗 11-2 相同(只用按鈕 A 與 LED)。

接線

  • 同實驗 11-2,按鈕 A 接 D2 與 GND,LED 接 D9。

程式(先用「沒有去彈跳」的版本)

// 實驗 11-3(版本一):沒有去彈跳的計數器
const int buttonPin = 2;
const int ledPin = 9;
int pressCount = 0;             // 按了幾次
bool lastState = HIGH;          // 上一次讀到的狀態(上拉:沒按是 HIGH)

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  bool nowState = digitalRead(buttonPin);
  // 上次是放開(HIGH)、這次是按下(LOW)→ 算一次
  if (lastState == HIGH && nowState == LOW) {
    pressCount = pressCount + 1;
    Serial.print("次數:");
    Serial.println(pressCount);
    digitalWrite(ledPin, pressCount % 2);   // 奇數次亮、偶數次暗
  }
  lastState = nowState;                     // 記住這次的狀態
}

去彈跳的版本只多了「距離上次變化要超過 30 ms」這個條件:

// 實驗 11-3(版本二):加上去彈跳
const int buttonPin = 2;
const int ledPin = 9;
int pressCount = 0;
bool lastState = HIGH;
unsigned long lastChangeTime = 0;     // 上次狀態改變的時間(毫秒)
const unsigned long debounceDelay = 30;   // 彈跳最多幾毫秒就結束

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  bool nowState = digitalRead(buttonPin);
  // 狀態有變,而且距離上次變化超過 30 ms,才相信它
  if (nowState != lastState && (millis() - lastChangeTime) > debounceDelay) {
    lastChangeTime = millis();          // 記下這次變化的時間
    if (nowState == LOW) {              // 是「按下」這一邊的變化
      pressCount = pressCount + 1;
      Serial.print("次數:");
      Serial.println(pressCount);
      digitalWrite(ledPin, pressCount % 2);
    }
    lastState = nowState;
  }
}

步驟

  1. 上傳版本一,打開監控視窗。用正常力道按 10 下,看最後數字是多少。
  2. 再按 10 下,但這次故意按慢一點、輕一點,看數字。
  3. 上傳版本二,重做步驟 1 和 2。

你應該看到

  • 版本一:按 10 下常常數到 12、15 甚至 20,而且每次都不一樣。
  • 版本二:按 10 下就是 10,LED 也乖乖一亮一暗。

觀察與記錄

版本第一輪按 10 下,顯示第二輪按 10 下,顯示第三輪按 10 下,顯示LED 亮暗正常嗎?
版本一(沒去彈跳)
版本二(去彈跳 30 ms)

用儀器看看

示波器:抓住按下的那一瞬間(彈跳)

按鈕彈跳只發生在按下那幾毫秒,之後就沒了,所以不能用 Auto 模式一直掃,要用 Single(單次)觸發:示波器等著,一看到你要的事件就拍一張、停住。

  • 接法:CH1 探棒尖端 → D2(按鈕 A 那條線),接地夾 → GND。用實驗 11-3 的接線,程式隨便哪個版本都行,重點是 INPUT_PULLUP 要打開。
  • CH1:1 V/div 或 2 V/div,DC 耦合。
  • 時間:先用 1 ms/div,看到彈跳後可以縮到 200 µs/div 看細節。
  • 觸發:來源 CH1,下降邊緣(因為按下時是 HIGH→LOW),準位約 2.5V。
  • 模式:按 Single。畫面會顯示「等待觸發」。
  • 把觸發位置往左移一點(大約留 1 格在觸發點前面),這樣能看到按下前的 5V。

然後按一下按鈕。示波器應該在按下的瞬間抓到畫面並停住:

 5V ┤ ───────┐ ┌─┐  ┌┐
    │        │ │ │  ││
 0V ┼        └─┘ └──┘└──────────────────
             ▲
          觸發點      ◄─ 彈跳約 1~5 ms ─►

多按幾次(每次按 Single 重新等待),你會發現每次彈跳的次數和長度都不一樣,有時候只彈一次,有時候彈五、六次。放開按鈕時也會彈跳,把觸發邊緣改成「上升」就能看到。

也用三用電表量一下 D2 對 GND:沒按是約 5V(內建上拉拉上去的),按住是 0V。

邏輯分析儀:把真值表「錄」下來

邏輯分析儀只看 0 和 1,不看電壓大小,一次可以看 8 條線,剛好拿來看邏輯閘。

  • 接法:CH0 → D2(按鈕 A)、CH1 → D3(按鈕 B)、CH2 → D9(LED 輸出),GND → Arduino GND(一定要接)。用實驗 11-2 的程式。
  • PulseView 設定:取樣率 1 MHz,取樣數 5 M(約 5 秒)。
  • 按下「Run」後,在這 5 秒內慢慢做四種按鈕組合。

畫面上會看到三條線:

 CH0 (A) ─────┐        ┌────┐        ┌──
              └────────┘    └────────┘
 CH1 (B) ──────────┐             ┌───────
                   └─────────────┘
 CH2 (LED, AND) ────────┐   ┌───────────   ← 只有 A、B 都是 0(都按下)時才變 1
                        └───┘

(注意:因為用上拉,按下是 0,所以 AND 的 LED 亮時 A 和 B 都是低電位。)把滑鼠移到 CH0 剛變低的地方放大到 µs 等級,你會在邊緣附近看到一堆密密麻麻的細線,那就是彈跳,邏輯分析儀看得比示波器更清楚。

常見錯誤與除錯

  • 序列埠的數字永遠是 1,按下去也不變 → 按鈕插的方向錯了(四腳按鈕有兩對腳本來就連通),或按鈕另一腳沒接 GND → 把按鈕轉 90 度重插,或用三用電表的蜂鳴檔量按鈕哪兩腳按下才通。
  • 沒按時數字就亂跳 → 忘了寫 INPUT_PULLUP,腳位浮接 → 檢查 pinMode。
  • 邏輯閘的結果全部相反 → 忘記上拉時「按下是 LOW」,程式裡用 == HIGH 當按下 → 改成 == LOW,或像範例一樣先轉成 bool。
  • 輸入 o 之後模式沒切換 → 監控視窗鮑率不是 9600,或「行尾」設定送出了多餘字元 → 設 9600,行尾選「沒有行尾」或「NL」都可以,程式只認 a/o/x/n。
  • 去彈跳後按很快會漏掉 → 30 ms 太長 → 改成 15–20 ms,但不要低於彈跳本身的長度(用示波器量你的按鈕彈多久再決定)。
  • 示波器一直「等待觸發」抓不到 → 觸發邊緣選成上升、或準位設太高 → 下降邊緣、準位 2.5V;也確認探棒真的夾在 D2 那條線上。
  • 邏輯分析儀全部是直線 → GND 沒接,或取樣率太低 → GND 一定要接 Arduino GND;取樣率至少 1 MHz。

想一想

  1. 二進位 01010101 是十進位多少?
參考答案

64 + 16 + 4 + 1 = 85。

  1. 把十進位 200 換成 8 位元二進位。
參考答案

200 − 128 = 72(b7 = 1),72 − 64 = 8(b6 = 1),32 不夠(0),16 不夠(0),8 − 8 = 0(b3 = 1),其餘 0。答案 11001000。驗算:128 + 64 + 8 = 200。

  1. 一個保全系統:「窗戶被打開 OR 門被打開」就要響,但「主人在家」時不要響。請用 AND、OR、NOT 寫出條件,再寫成 Arduino 的 if。
參考答案

(窗開 OR 門開) AND NOT 主人在家。程式:if ((windowOpen || doorOpen) && !ownerHome) { 響警報; }。括號很重要,先算 OR 再算 AND。

  1. 為什麼用 INPUT_PULLUP 時,按鈕要接到 GND 而不是 5V?如果有人把按鈕接到 5V,會發生什麼事?
參考答案

上拉電阻已經把腳位拉到 5V,按鈕接 GND 才能把它拉成 LOW,產生變化。如果按鈕接 5V,沒按是 5V、按下還是 5V,程式永遠讀到 HIGH,按鈕等於沒用。(接 5V 不會燒壞,因為上拉電阻很大,但就是沒反應。)

  1. 實驗 11-3 用 30 ms 去彈跳。如果有人一秒內能按 40 下(每下只隔 25 ms),計數器會發生什麼事?要怎麼改?
參考答案

25 ms 比 30 ms 短,第二下會被當成第一下的彈跳忽略掉,會漏數。可以把 30 改成 10–15 ms(前提是用示波器量過你的按鈕彈跳都在 10 ms 以內),或改用硬體 RC 濾波。但正常人一秒按不到 40 下,30 ms 對大多數應用都夠。

  1. 開放挑戰:用兩顆 LED(接 D9、D10)和一個按鈕,做一個「2 位元計數器」:每按一次,兩顆 LED 以二進位顯示 0、1、2、3,然後回到 0。提示:count % 4、bitRead(count, 0)、bitRead(count, 1)。
參考答案

用實驗 11-3 版本二的去彈跳架構,每次按下 count = (count + 1) % 4,然後 digitalWrite(9, bitRead(count, 0))(b0 亮 LED1)、digitalWrite(10, bitRead(count, 1))(b1 亮 LED2)。按四次會看到:暗暗 → 亮暗 → 暗亮 → 亮亮 → 暗暗。想要 3 位元就加第三顆 LED 和 % 8。

本章小結

  • 數位只有 0(LOW)和 1(HIGH)。UNO 讀輸入時,低於約 1.5V 是 LOW、高於約 3.0V 是 HIGH,中間不保證;這個緩衝區讓數位訊號很抗雜訊。
  • 二進位每往左一位乘 2,8 位元(1 位元組)的值是 128、64、32、16、8、4、2、1,範圍 0–255。十進位轉二進位用「從 128 往下問夠不夠減」。
  • AND 要全部是 1,OR 只要有一個 1,NOT 反過來,XOR 是「不一樣就 1」。程式寫法 &&、||、!、!=。
  • 輸入腳位不能浮接。用上拉或下拉電阻給它預設值;Arduino 的 INPUT_PULLUP 內建上拉,按鈕接 GND,按下是 LOW。
  • 10 kΩ 是上拉/下拉的常用值,按下時只流 0.5 mA。
  • 按鈕按下的瞬間會彈跳 1–10 ms,Arduino 會數成好幾次。用示波器 Single 觸發、下降邊緣就能抓到。
  • 去彈跳:等 30 ms 再相信它、加 RC 濾波、或只在狀態改變且超過 30 ms 時才算一次。

名詞表

中文英文一句話解釋
類比Analog電壓可以是任何數值的訊號,像無段調整的水龍頭。
數位Digital只有 0 和 1 兩種狀態的訊號。
門檻Threshold判斷 HIGH 或 LOW 的電壓分界線。
二進位Binary只用 0 和 1 的計數方式,每往左一位乘 2。
位元bit二進位的一位,只能是 0 或 1。
位元組Byte8 個位元,可以表示 0–255。
邏輯閘Logic Gate對 0 和 1 做判斷的基本單元,如 AND、OR、NOT、XOR。
真值表Truth Table列出所有輸入組合與對應輸出的表格。
且AND全部輸入是 1,輸出才是 1。
或OR任何一個輸入是 1,輸出就是 1。
非NOT把輸入反過來。
互斥或XOR兩個輸入不一樣時輸出 1。
浮接Floating輸入腳位什麼都沒接,狀態隨雜訊亂飄。
上拉電阻Pull-up Resistor接到電源的電阻,讓腳位預設為 HIGH。
下拉電阻Pull-down Resistor接到 GND 的電阻,讓腳位預設為 LOW。
內建上拉INPUT_PULLUPArduino 腳位內建的上拉電阻,用 pinMode 打開。
彈跳Bounce按鈕金屬片碰撞時產生的多次快速開關。
去彈跳Debounce用程式或電路把彈跳濾掉,讓一次按下只算一次。
單次觸發Single Trigger示波器只抓一次事件就停住的模式,用來看不重複的訊號。