BÀI 22 – GPIO STM32F103: ĐỌC INPUT, PULL-UP/PULL-DOWN VÀ NÚT NHẤN
Ở Bài 21, chúng ta đã đi vào GPIO và hiểu rằng một chân GPIO có thể được cấu hình để xuất tín hiệu (Output) hoặc đọc tín hiệu (Input).
Bài 22 sẽ chuyển sang phần cực kỳ quan trọng:
STM32 làm thế nào để biết một chân đang ở mức HIGH hay LOW?
Đây chính là nền tảng để đọc nút nhấn, công tắc, cảm biến, tín hiệu từ module bên ngoài…
1. GPIO Input là gì?
Ví dụ:
Nút nhấn
│
└────── PA0
│
STM32F103
Khi PA0 được cấu hình Input, STM32 không điều khiển điện áp trên PA0.
Thay vào đó, STM32 chỉ quan sát:
PA0 = 0V → LOW
PA0 ≈ 3.3V → HIGH
Ta có thể hình dung:
STM32
┌─────────────┐
│ │
3.3V ─────┤ PA0 │
│ INPUT │
│ │
└─────────────┘
↑
đọc mức điện áp
2. Input khác Output như thế nào?
Đây là điểm bạn cần phân biệt thật chắc.
Output
STM32 đẩy điện áp ra ngoài:
STM32 PA0 ───────► LED
Ví dụ:
PA0 = 1;
→ PA0 xuất khoảng 3.3V.
Input
STM32 đọc điện áp từ bên ngoài:
Nút nhấn ───────► PA0 STM32
Ví dụ:
if (PA0 == 1)
{
// có tín hiệu HIGH
}
3. Vấn đề cực kỳ quan trọng: Floating Input
Giả sử:
không nối gì
│
│
▼
PA0
PA0 được cấu hình Input nhưng không nối với nguồn nào.
Khi đó PA0 có thể đọc:
0
1
0
1
1
0
...
một cách không ổn định.
Đây gọi là:
Floating Input – ngõ vào bị thả nổi.
4. Tại sao Floating lại nguy hiểm?
Một chân Input có trở kháng rất cao.
Nó có thể bị ảnh hưởng bởi:
- nhiễu điện từ
- điện dung ký sinh
- dây dẫn dài
- tín hiệu từ các chân khác
- môi trường xung quanh
Vì vậy:
PA0 không nối gì
↓
điện áp không xác định
↓
GPIO đọc lúc 0 lúc 1
Đây là lý do khi làm mạch thực tế, chúng ta thường sử dụng:
Pull-up hoặc Pull-down resistor.
5. Pull-up là gì?
Pull-up nghĩa là kéo chân Input lên VCC thông qua một điện trở.
3.3V
│
[R]
│
├──── PA0
│
Button
│
GND
Khi nút không nhấn:
3.3V
│
[R]
│
PA0
→ PA0 = HIGH.
Khi nút nhấn:
3.3V
│
[R]
│
PA0
│
Button
│
GND
→ PA0 = LOW.
Vậy:
| Trạng thái | PA0 |
|---|---|
| Không nhấn | 1 |
| Nhấn | 0 |
Đây là kiểu mạch rất phổ biến.
6. Pull-down là gì?
Ngược lại:
3.3V
│
Button
│
├──── PA0
│
[R]
│
GND
Khi không nhấn:
PA0 → LOW
Khi nhấn:
PA0 → HIGH
Bảng trạng thái:
| Trạng thái | PA0 |
|---|---|
| Không nhấn | 0 |
| Nhấn | 1 |
7. STM32 có điện trở Pull-up/Pull-down bên trong
Đây là một điểm rất hay của STM32.
Bạn không nhất thiết lúc nào cũng phải mắc điện trở ngoài.
GPIO STM32F103 có thể cấu hình:
Input Floating
Input Pull-up
Input Pull-down
Ví dụ:
PA0
│
├── Pull-up nội
│
└── Input circuit
STM32 sẽ tạo một đường kéo lên bên trong chip.
8. Hãy nhìn GPIO theo kiểu “đường tín hiệu”
Từ những bài trước, bạn đã học:
CPU
│
▼
Peripheral
│
▼
GPIO Register
│
▼
PA0
Khi đọc GPIO, CPU thực chất đang đọc bit tương ứng trong thanh ghi Input Data Register.
Thanh ghi quan trọng là:
IDR – Input Data Register
9. GPIOx_IDR
Ví dụ GPIOA:
GPIOA_IDR
Đây là thanh ghi chứa trạng thái logic hiện tại của các chân GPIOA.
Ví dụ:
GPIOA_IDR
bit 15 ─ PA15
bit 14 ─ PA14
...
bit 1 ─ PA1
bit 0 ─ PA0
Nếu:
PA0 = HIGH
thì:
GPIOA_IDR bit 0 = 1
Nếu:
PA0 = LOW
thì:
GPIOA_IDR bit 0 = 0
10. Đây chính là kiến thức từ Bài 16
Bạn đã học toán tử:
&
|
^
~
<<
>>
Bây giờ chúng ta sử dụng chúng trong thực tế.
Muốn kiểm tra PA0:
if (GPIOA->IDR & (1 << 0))
{
// PA0 = HIGH
}
Giải thích:
1 << 0
cho:
00000001
Sau đó:
GPIOA->IDR & 00000001
chỉ kiểm tra:
bit 0
11. Kiểm tra PA5
Nếu muốn đọc PA5:
if (GPIOA->IDR & (1 << 5))
{
// PA5 HIGH
}
Vì:
1 << 5
= 0010 0000
Nó tạo ra mask cho bit 5.
12. Cách viết dễ hiểu hơn
Bạn có thể định nghĩa:
#define PA0_PIN 0
sau đó:
if (GPIOA->IDR & (1 << PA0_PIN))
{
// HIGH
}
Hoặc:
#define BUTTON_PIN 0
if (GPIOA->IDR & (1 << BUTTON_PIN))
{
// Button input HIGH
}
13. Ví dụ thực tế: nút nhấn + LED PC13
Ta có:
Button
│
└──── PA0
STM32
│
└──── PC13 ─── LED
Giả sử PA0 sử dụng Pull-up:
Không nhấn → PA0 = 1
Nhấn → PA0 = 0
Chương trình:
if ((GPIOA->IDR & (1 << 0)) == 0)
{
// Button đang nhấn
GPIOC->BSRR = (1 << 13);
}
else
{
// Button không nhấn
GPIOC->BSRR = (1 << (13 + 16));
}
Bạn có thể thấy một điều rất thú vị:
Input dùng IDR để đọc.
Output dùng BSRR để điều khiển.
14. IDR và ODR
Hai thanh ghi này rất quan trọng:
IDR = Input Data Register
ODR = Output Data Register
Có thể nhớ:
Input → IDR → đọc
Output → ODR → dữ liệu xuất
Ví dụ:
GPIOA->IDR
→ đọc trạng thái chân.
Trong khi:
GPIOA->ODR
→ chứa dữ liệu output.
15. Nhưng đừng vội nhầm IDR với ODR
Ví dụ PA0 đang là Output:
PA0 ─── LED
Bạn ghi:
GPIOA->ODR |= (1 << 0);
→ yêu cầu PA0 xuất HIGH.
Nhưng nếu PA0 là Input:
GPIOA->IDR & (1 << 0)
→ đọc mức điện áp thực tế tại chân PA0.
16. Một điều cực kỳ quan trọng: Debounce
Nút nhấn cơ học không chuyển trạng thái sạch như:
HIGH ───────────── LOW
Thực tế thường giống:
HIGH ────┐
│ ┌─┐
└─┘ └─┐
└──── LOW
Hiện tượng này gọi là:
Button bouncing – nảy phím.
Một lần nhấn có thể khiến STM32 tưởng rằng người dùng nhấn nhiều lần.
Ví dụ:
Nhấn 1 lần
↓
STM32 đọc:
1 0 1 0 1 0 0 0
Nếu chương trình không xử lý debounce, nó có thể nhận nhiều lần nhấn.
17. Debounce đơn giản bằng delay
Ví dụ:
if ((GPIOA->IDR & (1 << 0)) == 0)
{
delay_ms(20);
if ((GPIOA->IDR & (1 << 0)) == 0)
{
// Xác nhận nút thực sự được nhấn
}
}
Ý tưởng:
Phát hiện LOW
↓
đợi 20 ms
↓
đọc lại
↓
vẫn LOW?
↓
đúng là nhấn
Sau này chúng ta sẽ học debounce bằng timer, tốt hơn rất nhiều so với delay.
18. STM32F103 và mức logic
Blue Pill của bạn sử dụng STM32F103.
GPIO hoạt động với mức logic dựa trên nguồn I/O của MCU, thông thường:
VDD ≈ 3.3V
Do đó khi làm việc với Blue Pill, hãy hình dung:
LOW ≈ 0V
HIGH ≈ 3.3V
Không nên mặc định rằng GPIO của STM32 là 5V tolerant ở mọi chân và mọi chế độ.
Một số chân STM32F103 có khả năng chịu 5V trong điều kiện nhất định, nhưng phải xem đúng datasheet của chân đó.
19. Bài thực hành cho Blue Pill của bạn
Bạn có thể dùng chính Blue Pill đang có.
Phần cứng
STM32 Blue Pill
PA0 ───── Button ───── GND
Cấu hình PA0:
Input Pull-up
LED onboard:
PC13
Logic
PA0
│
┌───┴───┐
│ Button│
└───┬───┘
│
GND
Không nhấn:
PA0 = HIGH
PC13 = trạng thái bình thường
Nhấn:
PA0 = LOW
PC13 = đổi trạng thái
20. Bài tập quan trọng nhất
Đừng chỉ học thuộc:
GPIOA->IDR
Hãy hiểu chuỗi tư duy:
Nút nhấn
↓
điện áp tại PA0
↓
GPIO input circuit
↓
GPIOA_IDR
↓
bit 0
↓
CPU đọc
↓
if()
↓
quyết định
↓
PC13
Đây chính là cách một chương trình C trên STM32 tương tác với phần cứng thật.
21. Kiến thức cần nhớ sau Bài 22
Bạn nên thuộc ý nghĩa, không nhất thiết học vẹt mã lệnh:
GPIO Input
GPIO Output
Floating
Pull-up
Pull-down
IDR
ODR
BSRR
HIGH
LOW
1 << bit
GPIOx->IDR & mask
Button debounce
Đặc biệt nhớ:
GPIO
┌────────────────────┐
│ │
│ INPUT │
│ ↓ │
Pin ───►│ IDR │
│ │
│ OUTPUT │
│ ↑ │
│ ODR / BSRR │
└────────────────────┘
Và đây là mốc rất quan trọng:
Bài 17: Memory Map
↓
Bài 18–21: GPIO / Register / Output
↓
Bài 22: GPIO Input + IDR + Pull-up/Pull-down
↓
Bài tiếp theo: chúng ta có thể đi sâu vào GPIO Register của STM32F103: CRL, CRH, IDR, ODR, BSRR, BRR và tự cấu hình PA0/PC13 không dùng HAL, không dùng Arduino, chỉ bằng thanh ghi.
Đó sẽ là lúc bạn bắt đầu thực sự thấy STM32 hoạt động ở cấp độ phần cứng.