第 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 位:
| 位元編號 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
|---|---|---|---|---|---|---|---|---|
| 這一位的值 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
二進位 → 十進位:哪一位是 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。
| A | B | A AND B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
OR(或):前門或後門。 有人從前門進來,或從後門進來,門鈴都會響。只要「有一個是 1」輸出就是 1。
| A | B | A OR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
NOT(非):唱反調。 只有一個輸入,輸出永遠相反。
| A | NOT A |
|---|---|
| 0 | 1 |
| 1 | 0 |
XOR(互斥或):樓梯燈的雙切開關。 樓上樓下各一個開關,任何一個撥一下,燈的狀態就反過來。兩個輸入「不一樣」時輸出 1,「一樣」時輸出 0。
| A | B | A XOR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
在 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),有三種:
- 軟體等一下:偵測到按下後,先
delay(30)等彈跳結束,再確認一次是不是還按著。最簡單,本章用這招。 - 硬體濾波:第 10 章的 RC 低通濾波器,把那幾毫秒的快速跳動磨掉。
- 記住上一次狀態:只在「上次是放開、這次是按下」的那一刻算一次,而且距離上次變化要超過 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 次
}
步驟
- 用階段一的接線與程式,上傳後打開序列埠監控視窗(9600)。
- 看數字。用手指去摸那條懸空的杜邦線、靠近它、拿走,看數字會不會亂跳。
- 把程式改成階段二(
INPUT_PULLUP),接上按鈕,重新上傳。 - 不按時看數字,按住時看數字。
- 進階:把
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); // 慢一點,才看得清楚
}
步驟
- 接線、上傳,打開序列埠監控視窗。預設是 AND 模式。
- 兩個按鈕都不按、只按 A、只按 B、兩個都按,四種組合各試一次,記錄 LED 亮不亮,填進真值表。
- 在監控視窗輸入
o按 Enter,重做四種組合。 - 輸入
x,再做一次。 - 輸入
n,這時只看按鈕 A:不按時 LED 亮、按下反而暗。 - 把你填的表跟核心概念裡的真值表對一下。
你應該看到
- AND:只有兩個都按時 LED 才亮。
- OR:有按任何一個就亮。
- XOR:只按一個時亮,兩個都按反而暗。
- NOT:不按 A 時亮,按 A 時暗。
觀察與記錄
| A(按=1) | B(按=1) | AND 的 LED | OR 的 LED | XOR 的 LED | NOT A 的 LED |
|---|---|---|---|---|---|
| 0 | 0 | ||||
| 0 | 1 | ||||
| 1 | 0 | ||||
| 1 | 1 |
實驗 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;
}
}
步驟
- 上傳版本一,打開監控視窗。用正常力道按 10 下,看最後數字是多少。
- 再按 10 下,但這次故意按慢一點、輕一點,看數字。
- 上傳版本二,重做步驟 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。
想一想
- 二進位
01010101是十進位多少?
參考答案
64 + 16 + 4 + 1 = 85。
- 把十進位 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。
- 一個保全系統:「窗戶被打開 OR 門被打開」就要響,但「主人在家」時不要響。請用 AND、OR、NOT 寫出條件,再寫成 Arduino 的 if。
參考答案
(窗開 OR 門開) AND NOT 主人在家。程式:if ((windowOpen || doorOpen) && !ownerHome) { 響警報; }。括號很重要,先算 OR 再算 AND。
- 為什麼用
INPUT_PULLUP時,按鈕要接到 GND 而不是 5V?如果有人把按鈕接到 5V,會發生什麼事?
參考答案
上拉電阻已經把腳位拉到 5V,按鈕接 GND 才能把它拉成 LOW,產生變化。如果按鈕接 5V,沒按是 5V、按下還是 5V,程式永遠讀到 HIGH,按鈕等於沒用。(接 5V 不會燒壞,因為上拉電阻很大,但就是沒反應。)
- 實驗 11-3 用 30 ms 去彈跳。如果有人一秒內能按 40 下(每下只隔 25 ms),計數器會發生什麼事?要怎麼改?
參考答案
25 ms 比 30 ms 短,第二下會被當成第一下的彈跳忽略掉,會漏數。可以把 30 改成 10–15 ms(前提是用示波器量過你的按鈕彈跳都在 10 ms 以內),或改用硬體 RC 濾波。但正常人一秒按不到 40 下,30 ms 對大多數應用都夠。
- 開放挑戰:用兩顆 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。 |
| 位元組 | Byte | 8 個位元,可以表示 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_PULLUP | Arduino 腳位內建的上拉電阻,用 pinMode 打開。 |
| 彈跳 | Bounce | 按鈕金屬片碰撞時產生的多次快速開關。 |
| 去彈跳 | Debounce | 用程式或電路把彈跳濾掉,讓一次按下只算一次。 |
| 單次觸發 | Single Trigger | 示波器只抓一次事件就停住的模式,用來看不重複的訊號。 |