Skip to content

Công nghệ 9 giây

“Website hướng dẫn, thử nghiệm và giải thích công nghệ theo cách dễ hiểu cho mọi người.”

Menu
  • Home
  • Công nghệ
  • STM32
  • ESP32
  • Altium Desinger
  • Kiến thức điện máy
Menu

BÀI 23 – GPIO STM32F103: ĐỌC NÚT NHẤN, PULL-UP, PULL-DOWN VÀ DEBOUNCE

Posted on August 20, 2026August 23, 2026 by congnghe9s

BÀI 23 – GPIO STM32F103: ĐỌC NÚT NHẤN, PULL-UP, PULL-DOWN VÀ DEBOUNCE

Ở Bài 22, chúng ta đã đi từ việc ghi dữ liệu vào GPIO Output đến việc điều khiển chân GPIO bằng thanh ghi.

Bài 23 sẽ làm điều ngược lại:

STM32 đọc trạng thái điện áp bên ngoài thông qua GPIO Input.

Đây là bài cực kỳ quan trọng, vì sau bài này bạn có thể bắt đầu làm:

  • nút nhấn;
  • công tắc;
  • cảm biến;
  • đọc tín hiệu từ module;
  • đọc trạng thái chân GPIO;
  • điều khiển LED bằng nút nhấn;
  • sau này tiến tới UART, SPI, I²C, encoder…

1. GPIO Input là gì?

Ví dụ ta có:

        STM32F103
       +---------+
       |         |
       |      PA0|-------- Nút nhấn
       |         |
       +---------+

Khi cấu hình PA0 là Input, STM32 không chủ động xuất HIGH hoặc LOW.

Thay vào đó, STM32 quan sát điện áp trên PA0.

Có thể hiểu:

GPIO INPUT
    │
    ▼
điện áp tại chân
    │
    ├── gần 0V  → 0
    │
    └── gần VDD → 1

Ví dụ STM32 chạy 3.3 V:

PA0 = 0V    → GPIO đọc 0
PA0 = 3.3V  → GPIO đọc 1

2. Nhưng có một vấn đề rất quan trọng

Hãy tưởng tượng PA0 không nối với bất kỳ thứ gì:

PA0 ───────────── không nối

Bạn có thể nghĩ:

“Không nối thì chắc chắn là LOW.”

Không đúng.

Chân Input lúc này có thể bị floating.

Điện áp có thể dao động:

0
1
0
1
1
0
0
1
...

và kết quả đọc có thể không ổn định.

Đây là lý do chúng ta cần:

Pull-up hoặc Pull-down.


3. Pull-up là gì?

Pull-up nghĩa là dùng một điện trở kéo chân GPIO về VCC.

        3.3V
         │
        [R]
         │
         ├──── PA0
         │
       BUTTON
         │
        GND

Khi không nhấn nút:

3.3V
 │
 R
 │
 PA0

PA0 được kéo lên HIGH.

Vì vậy:

Không nhấn → PA0 = 1

Khi nhấn nút:

3.3V
 │
 R
 │
 PA0
 │
BUTTON
 │
GND

PA0 bị nối xuống GND:

Nhấn → PA0 = 0

Do đó:

Nút PA0
Không nhấn 1
Nhấn 0

Đây gọi là:

Active LOW


4. Pull-down thì ngược lại

Mạch:

             3.3V
               │
             BUTTON
               │
               ├──── PA0
               │
              [R]
               │
              GND

Không nhấn:

PA0 = 0

Nhấn:

PA0 = 1

Bảng:

Nút PA0
Không nhấn 0
Nhấn 1

Đây là:

Active HIGH


5. STM32F103 có Pull-up/Pull-down bên trong

Đây là một điểm rất hay.

Bạn không nhất thiết lúc nào cũng phải dùng điện trở bên ngoài.

STM32F103 có thể cấu hình:

GPIO Input Floating
GPIO Input Pull-up
GPIO Input Pull-down

Về ý tưởng:

          VDD
           │
       Pull-up
           │
           ├──── GPIO
           │
       Pull-down
           │
          GND

Bạn có thể bật một trong hai.


6. Thanh ghi nào quyết định GPIO Input?

Đây là lúc kiến thức Bài 16 → Bài 17 → Bài 18 → Bài 22 bắt đầu kết nối với nhau.

Với STM32F103, mỗi GPIO port có:

CRL
CRH
IDR
ODR
BSRR
BRR
LCKR

Trong đó hôm nay chúng ta đặc biệt quan tâm:

IDR

7. IDR – Input Data Register

IDR có tên:

Input Data Register

Nó chứa trạng thái logic mà STM32 đang đọc trên các chân GPIO.

Ví dụ:

GPIOA->IDR

có 16 bit:

bit15 bit14 ... bit2 bit1 bit0

Tương ứng:

PA15 PA14 ... PA2 PA1 PA0

Ví dụ:

GPIOA->IDR
             │
             └── bit 0 = trạng thái PA0

8. Đọc PA0

Ta có thể kiểm tra:

if (GPIOA->IDR & (1 << 0))
{
    // PA0 = HIGH
}

Tại sao?

Ta có:

GPIOA->IDR

giả sử:

xxxxxxxxxxxxxxx1

AND với:

0000000000000001

kết quả:

0000000000000001

→ PA0 = HIGH.


9. Nếu PA0 = LOW?

Ví dụ:

GPIOA->IDR

xxxxxxxxxxxxxxx0

AND:

0000000000000001

sẽ cho:

0000000000000000

Do đó:

if (GPIOA->IDR & (1 << 0))
{
    // HIGH
}
else
{
    // LOW
}

10. Cách viết dễ hiểu hơn

Ta có thể tạo macro:

#define PA0_READ()   ((GPIOA->IDR >> 0) & 1)

Sau đó:

if (PA0_READ())
{
    // HIGH
}

hoặc:

if (PA0_READ() == 0)
{
    // LOW
}

Đây chính là kỹ thuật:

đọc một bit trong một thanh ghi nhiều bit.


11. Ví dụ thực tế: nút nhấn + LED

Giả sử:

PA0 = BUTTON
PC13 = LED

Mạch:

              3.3V
               │
          Pull-up
               │
               ├──── PA0
               │
             BUTTON
               │
              GND


STM32

PC13 ───── LED

Logic:

Không nhấn:
PA0 = 1
LED OFF

Nhấn:
PA0 = 0
LED ON

Code ý tưởng:

while (1)
{
    if ((GPIOA->IDR & (1 << 0)) == 0)
    {
        GPIOC->BSRR = (1 << 13);
    }
    else
    {
        GPIOC->BRR = (1 << 13);
    }
}

Lưu ý: LED trên Blue Pill thường mắc theo kiểu active LOW, nên khi thực hành với LED PC13 bạn có thể phải đảo logic.


12. Một điều cực kỳ quan trọng: Input không có nghĩa là IDR luôn đúng 0/1 theo ý bạn

Có một chuỗi rất quan trọng:

Điện áp bên ngoài
       ↓
mạch input của GPIO
       ↓
ngưỡng logic
       ↓
IDR
       ↓
CPU đọc

Ví dụ:

0V       → LOW
3.3V     → HIGH

Nhưng vùng điện áp giữa hai mức logic không nên được xem đơn giản là:

0V → 0
1V → 1
2V → 1
3V → 1

Mà datasheet quy định các ngưỡng điện áp cụ thể.

Điều này rất quan trọng khi bạn kết nối:

  • cảm biến;
  • module 5 V;
  • Arduino;
  • ESP32;
  • STM32 khác;
  • transistor;
  • comparator.

13. Debounce – vấn đề rất nổi tiếng của nút nhấn

Bạn nhấn nút một lần.

Bạn nghĩ STM32 sẽ thấy:

1
↓
0

Nhưng thực tế tiếp điểm cơ khí có thể rung:

1
0
1
0
1
0
0
0

trong vài ms.

STM32 chạy rất nhanh.

Ví dụ CPU chạy:

72 MHz

thì trong 1 ms CPU đã thực hiện được rất nhiều chu kỳ.

Vì vậy STM32 có thể nhìn thấy:

nhấn 1 lần

thành:

nhấn
nhả
nhấn
nhả
nhấn

14. Đây gọi là Contact Bounce

Hình dung:

Thực tế:

HIGH ─────┐
          │
          └─┐ ┌─┐ ┌──────
            │ │ │
LOW         └─┘ └─┘

Thay vì:

HIGH ──────────┐
               │
               └────────── LOW

Đây là lý do một nút nhấn đôi khi khiến chương trình:

“Một lần nhấn nhưng tăng biến 5 lần.”


15. Cách đơn giản nhất để debounce

Một phương pháp rất cơ bản:

if (button_pressed)
{
    delay_ms(20);

    if (button_pressed)
    {
        // xác nhận đã nhấn
    }
}

Tư duy:

Phát hiện nhấn
      ↓
chờ 20 ms
      ↓
đọc lại
      ↓
vẫn nhấn?
      ↓
YES → xác nhận

Đây là debounce bằng thời gian.

Sau này chúng ta sẽ học cách làm debounce không dùng delay, tốt hơn cho hệ thống thực tế.


16. Tại sao không nên lạm dụng delay?

Ví dụ:

while (1)
{
    if (button_pressed)
    {
        delay_ms(20);
    }

    read_sensor();
    update_display();
    control_motor();
}

Trong thời gian:

delay_ms(20);

CPU đang bị chặn.

Nếu hệ thống có:

  • LCD;
  • motor;
  • UART;
  • timer;
  • cảm biến;
  • nhiều nút;

thì delay sẽ trở thành vấn đề.

Sau này ta sẽ dùng:

SysTick
Timer
Interrupt
State Machine

để xử lý tốt hơn.


17. Một kiến thức rất quan trọng: GPIO Input và Output khác nhau ở đâu?

Hãy so sánh.

Output

STM32:

CPU
 ↓
ODR/BSRR
 ↓
GPIO
 ↓
điện áp OUT

Tức là:

STM32 điều khiển điện áp chân.


Input

điện áp bên ngoài
 ↓
GPIO
 ↓
IDR
 ↓
CPU

Tức là:

STM32 quan sát điện áp chân.

Đây là hai hướng dữ liệu ngược nhau.


18. Nhìn GPIO bằng tư duy thanh ghi

Đây là điều tôi muốn bạn đặc biệt ghi nhớ.

Khi gặp GPIO:

OUTPUT

hãy nghĩ:

ODR
BSRR
BRR

Khi gặp:

INPUT

hãy nghĩ:

IDR

Khi cần:

CONFIGURATION

hãy nghĩ:

CRL
CRH

Có thể tóm tắt:

           GPIO
            │
     ┌──────┼──────┐
     │      │      │
   Config  Input  Output
     │      │      │
   CRL/H   IDR   ODR/BSRR/BRR

19. Bài tập thực hành trên Blue Pill

Với Blue Pill của bạn, hãy thử bài sau.

Phần cứng

STM32 Blue Pill
│
├── PC13 → LED onboard
│
└── PA0  → nút nhấn

Bạn có thể sử dụng pull-up nội bộ để giảm linh kiện.

Mục tiêu:

Không nhấn → LED OFF

Nhấn PA0 → LED ON

Sau đó đảo lại:

Không nhấn → LED ON

Nhấn PA0 → LED OFF

20. Bài tập nâng cấp

Sau khi làm được nút nhấn, hãy tạo biến:

int counter = 0;

Mỗi lần nhấn nút:

counter++

Sau đó gửi:

Counter = 1
Counter = 2
Counter = 3
...

qua UART.

Nhưng có một yêu cầu:

Nhấn một lần chỉ được tăng đúng một lần.

Đây là lúc bạn sẽ bắt đầu hiểu thật sự về:

Button
↓
Debounce
↓
Edge detection
↓
State machine

21. Một khái niệm mới: Level và Edge

Đây là kiến thức rất quan trọng trước khi sang các bài nâng cao.

Level

Kiểm tra trạng thái hiện tại:

if (button == 0)
{
    // đang nhấn
}

Nó trả lời:

“Hiện tại nút có đang nhấn không?”


Edge

Phát hiện sự thay đổi:

1 → 0

hoặc:

0 → 1

Nó trả lời:

“Có vừa xảy ra một lần nhấn không?”

Hai khái niệm này hoàn toàn khác nhau.


22. Tư duy cực kỳ quan trọng

Hãy tưởng tượng chương trình chạy:

Loop 1: button = 1
Loop 2: button = 1
Loop 3: button = 0
Loop 4: button = 0
Loop 5: button = 0
Loop 6: button = 1

Nếu bạn viết:

if (button == 0)
{
    counter++;
}

thì:

Loop 3 → counter++
Loop 4 → counter++
Loop 5 → counter++

Một lần nhấn có thể tăng 3 lần.

Nhưng nếu ta phát hiện cạnh:

1 → 0

thì chỉ:

Loop 3 → counter++

Đây chính là nền tảng của button event.


23. Bạn cần ghi nhớ 6 thứ sau Bài 23

① GPIO Input

GPIO chuyển sang chế độ:

quan sát tín hiệu bên ngoài

② IDR

GPIOA->IDR

dùng để đọc trạng thái chân.

③ Pull-up

Không nhấn → HIGH
Nhấn → LOW

④ Pull-down

Không nhấn → LOW
Nhấn → HIGH

⑤ Debounce

Nút cơ khí có thể rung:

1 → 0 → 1 → 0 → 1

nên cần xử lý chống dội.

⑥ Level ≠ Edge

Level = đang ở trạng thái nào?
Edge  = vừa thay đổi trạng thái?

24. Sơ đồ tư duy của toàn bộ Bài 23

                  GPIO INPUT
                      │
          ┌───────────┴───────────┐
          │                       │
       Điện áp                  IDR
          │                       │
    ┌─────┴─────┐                 │
    │           │                 │
  HIGH         LOW                │
    │           │                 │
    └─────┬─────┘                 │
          │                       │
       Button                  CPU đọc
          │
     ┌────┴────┐
     │         │
 Pull-up    Pull-down
     │         │
 Active LOW Active HIGH
     │
 Debounce
     │
     ↓
 Edge Detection
     │
     ↓
 Button Event

🎯 Bài tiếp theo

BÀI 24 chúng ta sẽ đi sâu vào một vấn đề rất quan trọng:

GPIO STM32F103: CRL/CRH thực sự cấu hình Input và Output như thế nào?

Lúc đó chúng ta sẽ không dùng HAL, mà tự tay thao tác các bit trong:

GPIOA->CRL
GPIOA->CRH

và bạn sẽ thấy chính xác vì sao một chân STM32 có thể trở thành:

Input Floating
Input Pull-up
Input Pull-down
Output Push-Pull
Output Open-Drain
Alternate Function

Đây sẽ là bài nối trực tiếp từ Bài 23 → hiểu cấu trúc GPIO của STM32F103 ở mức thanh ghi.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Bạn có thể hướng dẫn tôi quy trình sửa chữa một mainboard laptop cũng như mainboard của máy tính để bàn không?
  • Có những mức nguồn DC nào trên một bo mạch mainboard laptop?Kỹ thuật đốt dòng để kiểm tra nguồn laptop được thực hiện như thế nào?
  • bài tiếp theo hợp lý là: BÀI 63 – SENSOR FUSION THỰC CHIẾN: ENCODER + IMU MPU6050 + EKF + ODOMETRY X,Y,θ TRÊN STM32F103
  • BÀI 62 – EKF (EXTENDED KALMAN FILTER) CHO ROBOT 2 BÁNH: FUSION ENCODER + IMU + ƯỚC LƯỢNG X, Y, θ, V, ω
  • BÀI 61 – KALMAN FILTER CHO ENCODER + IMU: ƯỚC LƯỢNG TỐC ĐỘ, GÓC θ VÀ SENSOR FUSION

Recent Comments

No comments to show.

Archives

  • September 2026
  • August 2026
  • July 2026
  • June 2026
  • May 2026
  • February 2026

Categories

  • Altium Desinger
  • Công nghệ
  • ESP32
  • Khác
  • Kiến thức điện máy
  • STM32
Live 3D Globes Visitor
©2026 Công nghệ 9 giây | Design: Newspaperly WordPress Theme