Atlas Robotics
Kiến thức nền tảngCơ bản

Điện trở Pull-up/Pull-down: giải quyết vấn đề chân GPIO "lơ lửng"

Atlasbot · 20/8/2026 · 9 phút đọc

Điện trở Pull-up/Pull-down: giải quyết vấn đề chân GPIO "lơ lửng"
Pull-up kéo chân về HIGH mặc định, pull-down kéo về LOW mặc định

Đấu một nút nhấn vào chân GPIO mà không có gì thêm, code đọc digitalRead() có thể trả về giá trị thay đổi lung tung dù không ai chạm vào nút — đây gần như luôn là hậu quả của chân floating (lơ lửng), và điện trở pull-up/pull-down là giải pháp tiêu chuẩn.

Vấn đề chân floating

Khi một chân GPIO cấu hình INPUT nhưng không nối chắc chắn vào HIGH hay LOW (VD: một đầu nút nhấn nối vào chân, đầu còn lại nối GND, nhưng khi không nhấn thì chân "lơ lửng" không nối gì) — chân này rất nhạy với nhiễu điện từ môi trường (dây dẫn xung quanh hoạt động như ăng-ten thu nhiễu nhỏ), khiến digitalRead() đọc ra giá trị 0 hoặc 1 ngẫu nhiên dù trạng thái vật lý không đổi.

Pull-up resistor — mặc định HIGH

Điện trở pull-up nối chân GPIO với nguồn dương (VCC) qua một điện trở giá trị lớn (thường 4.7kΩ-10kΩ). Khi nút nhấn chưa bấm, chân được "kéo lên" HIGH ổn định qua điện trở này. Khi bấm nút (nút nối chân xuống GND), chân bị kéo xuống LOW — dòng qua điện trở lúc này rất nhỏ nên không gây đoản mạch.

VCC ──[ Điện trở pull-up ]── Chân GPIO ── Nút nhấn ── GND

Chưa bấm: chân = HIGH (kéo lên qua điện trở)
Đang bấm: chân = LOW (nối thẳng xuống GND, thắng điện trở)

Pull-down resistor — mặc định LOW

Ngược lại, điện trở pull-down nối chân GPIO xuống GND. Khi nút chưa bấm, chân ở mức LOW ổn định. Khi bấm nút (nút nối chân lên VCC), chân bị kéo lên HIGH.

Chân GPIO ──[ Điện trở pull-down ]── GND
Chân GPIO ── Nút nhấn ── VCC

Chưa bấm: chân = LOW (kéo xuống qua điện trở)
Đang bấm: chân = HIGH (nối thẳng lên VCC, thắng điện trở)

Tóm lại: Pull-up = mặc định HIGH, bấm thành LOW (logic "active-low"). Pull-down = mặc định LOW, bấm thành HIGH (logic "active-high"). Chọn loại nào phụ thuộc cách đấu nút nhấn với nguồn/GND.

Pull-up nội (internal) — không cần điện trở rời

Hầu hết vi điều khiển hiện đại (Arduino, STM32, ESP32) đã tích hợp sẵn điện trở pull-up bên trong chip, bật được bằng phần mềm — không cần hàn thêm điện trở rời cho trường hợp dùng đơn giản như đọc nút nhấn:

// Arduino — bật pull-up nội bằng phần mềm
pinMode(2, INPUT_PULLUP);

void loop() {
  if (digitalRead(2) == LOW) {   // LOW nghĩa là đang bấm (logic active-low của pull-up)
    // xử lý khi nút được nhấn
  }
}

Phần lớn vi điều khiển không có pull-down nội phổ biến như pull-up (một số dòng có, nhưng ít hơn) — nếu cần pull-down, thường vẫn phải hàn điện trở rời, hoặc đổi cách đấu mạch sang dùng pull-up nội với logic active-low thay vì active-high.

Kiến thức kỹ thuật

Công thức

Áp dụng định luật Ohm (U = I×R, xem bài Điện áp/Dòng điện/Công suất) để tính dòng qua điện trở pull-up khi nút đang được bấm — đây là dòng "lãng phí" chạy thẳng xuống GND, cần đủ nhỏ để không tốn năng lượng nhưng đủ lớn để chống nhiễu:

I = VCC / R

VD: VCC = 5V, R pull-up = 10kΩ
I = 5V / 10.000Ω = 0.5mA

Dòng 0.5mA rất nhỏ so với giới hạn chân GPIO (20-40mA) — đây là lý do điện trở pull-up/pull-down luôn chọn giá trị lớn (kΩ), không phải giá trị nhỏ như điện trở hạn dòng LED (thường chỉ hàng trăm Ω).

Thông số

Giá trị điện trở pull-up/pull-down phổ biến và dòng tiêu thụ tương ứng ở nguồn 5V:

Giá trị điện trởDòng khi activeĐánh giá
1kΩ5mAQuá nhỏ — lãng phí dòng, ít dùng
4.7kΩ~1.06mAPhổ biến, chống nhiễu tốt
10kΩ0.5mAPhổ biến nhất, cân bằng tốt
1MΩ0.005mAQuá lớn — dễ bị nhiễu lấn át

Register / Bit / Byte

Trên STM32, việc bật pull-up/pull-down nội (thay vì để chân floating hay push-pull thường) được cấu hình qua thanh ghi GPIOx_PUPDR (Pull-Up/Pull-Down Register) — cũng dùng 2 bit cho mỗi chân giống GPIOx_MODER: 00 = không pull, 01 = pull-up, 10 = pull-down, 11 = dự trữ (không dùng). Hàm pinMode(pin, INPUT_PULLUP) bên Arduino thực chất chỉ set đúng 2 bit này ở giá trị 01 cho chân tương ứng.

Ứng dụng thực tế

Nút dừng khẩn cấp (E-stop) trên robot AMR gần như luôn thiết kế theo kiểu active-low với pull-up — mạch mặc định đọc HIGH (an toàn) khi dây không đứt, và chỉ chuyển LOW khi nút thực sự được bấm hoặc dây bị đứt/hở mạch. Thiết kế này an toàn hơn hẳn kiểu active-high: nếu dây tín hiệu bị đứt do rung động trong lúc robot di chuyển, chân sẽ tự trôi về trạng thái "coi như đã bấm nút dừng" thay vì im lặng báo "vẫn an toàn" — cùng nguyên lý pull-up/pull-down cũng áp dụng cho các công tắc hành trình (limit switch) ở nhiều điểm trên cơ cấu cơ khí của robot.

Ví dụ thực tế: mạch nút nhấn hoàn chỉnh

const int BUTTON_PIN = 2;
const int LED_PIN = 13;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // dùng pull-up nội, không cần điện trở rời
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  bool pressed = (digitalRead(BUTTON_PIN) == LOW);  // LOW = đang nhấn
  digitalWrite(LED_PIN, pressed ? HIGH : LOW);
}

Cách đấu: một chân nút nhấn nối vào chân 2, chân còn lại nối thẳng xuống GND — không cần điện trở rời vì đã dùng INPUT_PULLUP. Đây là cách đấu nút nhấn phổ biến nhất trong các dự án Arduino/ESP32 vì tiết kiệm 1 linh kiện.

Lỗi thường gặp

  • Nhầm logic active-low khi dùng pull-up — dùng INPUT_PULLUP nhưng code lại kiểm tra == HIGH để phát hiện nhấn nút — logic ngược, code sẽ phản ứng khi không nhấn nút thay vì khi nhấn.
  • Bỏ qua điện trở, để chân floating — kết quả đọc lung tung, dễ nhầm là lỗi phần cứng hoặc lỗi code trong khi nguyên nhân chỉ là thiếu pull-up/pull-down.
  • Chọn giá trị điện trở không phù hợp — điện trở quá nhỏ (VD: 220Ω) khi bấm nút sẽ tạo dòng ngắn mạch lớn không cần thiết qua điện trở; điện trở quá lớn (VD: 1MΩ) có thể không đủ mạnh để chống nhiễu ổn định — khoảng 4.7kΩ-10kΩ là phổ biến và an toàn cho hầu hết trường hợp.
  • Dùng pull-down nội trên chip không hỗ trợ — gọi hàm/thiết lập pull-down nội trên vi điều khiển chỉ có pull-up nội (không có pull-down nội) sẽ không có tác dụng như mong đợi hoặc báo lỗi biên dịch — kiểm tra datasheet trước khi giả định chip có cả hai.

Kết luận

Pull-up/pull-down giải quyết đúng vấn đề "chân floating" — kéo chân GPIO về một mức xác định khi chưa có tín hiệu chủ động, tránh đọc nhiễu ngẫu nhiên. Ưu tiên dùng pull-up nội (INPUT_PULLUP) khi có thể vì tiết kiệm linh kiện — chỉ cần điện trở rời khi cần pull-down hoặc chip không hỗ trợ pull-up nội. Bài tiếp theo trong chuyên mục nói về Interrupt — cách xử lý sự kiện như nhấn nút hiệu quả hơn so với liên tục đọc digitalRead() trong vòng lặp.

Điện trở Pull-up/Pull-down: giải quyết vấn đề chân GPIO "lơ lửng" — Atlasbot