BÀI 19 – GPIO STM32F103: TỪ REGISTER ĐẾN CHÂN GPIO THỰC TẾ
Ở Bài 17, bạn đã học Memory Map của STM32F103.
Ở Bài 18, ta bắt đầu đi vào GPIO.
Bài 19 sẽ nối toàn bộ kiến thức lại:
Địa chỉ bộ nhớ → Peripheral → GPIO port → Register → Bit → chân vật lý → LED
Đây là bài rất quan trọng nếu mục tiêu của bạn là lập trình STM32 bằng C ở mức register, chứ không chỉ gọi HAL.
1. GPIO thực chất là gì?
GPIO = General Purpose Input/Output
Một chân GPIO có thể được cấu hình để:
- xuất mức HIGH/LOW
- đọc mức HIGH/LOW
- Analog
- Alternate Function
- một số chức năng đặc biệt khác
Ví dụ:
STM32F103
│
├── GPIOA
│ ├── PA0
│ ├── PA1
│ ├── PA2
│ └── ...
│
├── GPIOB
│ ├── PB0
│ ├── PB1
│ └── ...
│
└── GPIOC
├── PC13
└── ...
Trên Blue Pill của bạn, LED onboard thường nối với:
PC13
Vì vậy PC13 là một ví dụ cực kỳ tốt để học GPIO.
2. Điều quan trọng: GPIOC không phải là một biến
Khi viết:
GPIOC
ta thường đang nói đến địa chỉ cơ sở của peripheral GPIOC.
Trong STM32F103, vùng GPIO nằm trong vùng Peripheral.
Một địa chỉ cơ sở quan trọng:
GPIOA = 0x40010800
GPIOB = 0x40010C00
GPIOC = 0x40011000
GPIOD = 0x40011400
GPIOE = 0x40011800
Đây chính là kiến thức từ Memory Map của Bài 17 được sử dụng trực tiếp.
3. GPIOC có rất nhiều register
GPIOC không chỉ có một register.
Có các register quan trọng:
GPIOC
│
├── CRL
├── CRH
├── IDR
├── ODR
├── BSRR
├── BRR
└── LCKR
Đối với STM32F103:
| Register | Chức năng |
|---|---|
| CRL | cấu hình PA0–PA7 / PB0–PB7 / PC0–PC7 |
| CRH | cấu hình PA8–PA15 / PB8–PB15 / PC8–PC15 |
| IDR | đọc trạng thái chân |
| ODR | xuất trạng thái |
| BSRR | set/reset bit |
| BRR | reset bit |
| LCKR | khóa cấu hình |
Và đây chính là lúc kiến thức về pointer + struct + bit manipulation từ các bài trước bắt đầu phát huy tác dụng.
4. Tại sao PC13 dùng CRH chứ không phải CRL?
Đây là câu hỏi rất quan trọng.
GPIO có 16 chân:
PC0
PC1
PC2
...
PC7
PC8
PC9
...
PC15
STM32 chia chúng thành hai nhóm:
CRL → pin 0 đến pin 7
CRH → pin 8 đến pin 15
PC13 nằm trong:
PC8 → PC15
nên:
PC13 → GPIOC_CRH
5. Mỗi chân chiếm 4 bit
Đây là điểm bạn cần đặc biệt hiểu.
Trong CRL/CRH:
Mỗi GPIO pin = 4 bit
Ví dụ CRH:
31 0
┌────┬────┬────┬──── ... ────┐
│PC15│PC14│PC13│ ... │ PC8 │
└────┴────┴────┴──── ... ────┘
Mỗi chân:
MODE[1:0]
CNF[1:0]
Tổng cộng:
4 bit / pin
6. Vị trí PC13 trong CRH
PC8 bắt đầu ở bit:
0
PC9:
4
PC10:
8
PC11:
12
PC12:
16
PC13:
20
PC14:
24
PC15:
28
Vậy:
PC13 → CRH bit 20–23
Đây là một bài tập bit-shift cực kỳ thực tế.
7. MODE và CNF
Mỗi chân có:
CNF1 CNF0 MODE1 MODE0
Ví dụ:
PC13
│
├── MODE1
├── MODE0
├── CNF1
└── CNF0
MODE
MODE xác định tốc độ hoặc Input mode.
Các giá trị thường gặp:
00 = Input mode
01 = Output 10 MHz
10 = Output 2 MHz
11 = Output 50 MHz
Ví dụ muốn PC13 là output 2 MHz:
MODE = 10
8. CNF xác định kiểu GPIO
Với Output:
CNF = 00
→ General purpose push-pull.
Vậy:
MODE = 10
CNF = 00
ta có:
0010
Cho PC13.
9. Tư duy quan trọng nhất: không sửa cả register
Giả sử:
GPIOC->CRH = 0x00000000;
thì bạn đang xóa cấu hình của:
PC8
PC9
PC10
...
PC15
Điều này rất nguy hiểm nếu những chân khác đang được sử dụng.
Thay vào đó phải sử dụng:
mask
Đây là lý do Bài 16 về:
&
|
~
<<
rất quan trọng.
10. Xóa cấu hình PC13
PC13 nằm ở bit 20–23.
Mask:
0xF << 20
Tương đương:
1111
dịch trái 20 bit
Sau đó:
GPIOC->CRH &= ~(0xF << 20);
Ý nghĩa:
Xóa đúng 4 bit cấu hình PC13, nhưng giữ nguyên các bit khác.
Đây chính là tư duy lập trình register.
11. Đặt PC13 thành Output Push-Pull 2 MHz
Sau khi xóa:
GPIOC->CRH &= ~(0xF << 20);
Ta đặt:
MODE = 10
CNF = 00
Giá trị 4 bit:
0010
Vì vậy:
GPIOC->CRH |= (0x2 << 20);
Kết quả:
PC13 = Output Push-Pull 2 MHz
12. Nhưng vẫn còn một vấn đề
GPIOC chưa chắc đã được cấp clock.
STM32F1 có hệ thống clock cho peripheral.
GPIOC thuộc:
APB2
Clock của GPIOC được điều khiển bởi:
RCC
Cụ thể:
RCC_APB2ENR
Có bit:
IOPCEN
Nếu clock GPIOC chưa bật:
GPIOC
không hoạt động đúng.
13. Đây là chuỗi tư duy hoàn chỉnh
Khi muốn điều khiển PC13 bằng register, hãy nghĩ:
RCC
│
│ bật clock GPIOC
▼
GPIOC
│
├── CRH
│ └── cấu hình PC13
│
└── ODR / BSRR / BRR
└── điều khiển PC13
Đây là kiến trúc mà bạn cần bắt đầu hình thành trong đầu.
14. Bật clock GPIOC
Ví dụ:
RCC->APB2ENR |= (1 << 4);
Tại sao là bit 4?
Các bit clock GPIO thường được bố trí:
bit 2 → IOPAEN
bit 3 → IOPBEN
bit 4 → IOPCEN
bit 5 → IOPDEN
bit 6 → IOPEEN
Do đó:
1 << 4
chính là:
0001 0000
15. Sau đó cấu hình PC13
GPIOC->CRH &= ~(0xF << 20);
GPIOC->CRH |= (0x2 << 20);
Bây giờ PC13 là:
Output
Push-Pull
2 MHz
16. Xuất HIGH/LOW bằng ODR
ODR = Output Data Register.
Ví dụ:
GPIOC->ODR |= (1 << 13);
→ PC13 = HIGH.
Ngược lại:
GPIOC->ODR &= ~(1 << 13);
→ PC13 = LOW.
Bạn có thể nhìn thấy ngay mối liên hệ:
PC13
↓
bit 13
↓
1 << 13
17. Toggle PC13
Nếu muốn đảo trạng thái:
GPIOC->ODR ^= (1 << 13);
Nếu đang:
0
thì thành:
1
Nếu đang:
1
thì thành:
0
Đây chính là toán tử XOR mà bạn đã học.
18. Nhưng có cách tốt hơn ODR
STM32F1 có:
BSRR
Bit 0–15:
Set
Bit 16–31:
Reset
Ví dụ set PC13:
GPIOC->BSRR = (1 << 13);
Reset PC13:
GPIOC->BSRR = (1 << (13 + 16));
hay:
GPIOC->BSRR = (1 << 29);
19. Tại sao BSRR rất hay?
Giả sử:
GPIOC->ODR |= (1 << 13);
Đây là kiểu:
Read
Modify
Write
Trong một số tình huống nhiều phần mềm/ngắt cùng thao tác GPIO, điều này có thể gây vấn đề.
BSRR cho phép thao tác set/reset bit theo cơ chế phần cứng thuận tiện hơn.
Ví dụ:
GPIOC->BSRR = (1 << 13);
nghĩa là:
SET PC13
Còn:
GPIOC->BSRR = (1 << 29);
nghĩa là:
RESET PC13
20. Code hoàn chỉnh kiểu Register
Một ví dụ tối giản:
#include "stm32f1xx.h"
void GPIOC_PC13_Init(void)
{
// 1. Enable clock GPIOC
RCC->APB2ENR |= (1 << 4);
// 2. Clear configuration PC13
GPIOC->CRH &= ~(0xF << 20);
// 3. PC13 = Output Push-Pull, 2 MHz
GPIOC->CRH |= (0x2 << 20);
}
int main(void)
{
GPIOC_PC13_Init();
while (1)
{
GPIOC->BSRR = (1 << 13); // SET PC13
for (volatile int i = 0; i < 1000000; i++);
GPIOC->BSRR = (1 << 29); // RESET PC13
for (volatile int i = 0; i < 1000000; i++);
}
}
Lưu ý: LED onboard PC13 trên Blue Pill thường mắc theo kiểu active-low, nên có thể bạn sẽ thấy LED sáng khi PC13 ở LOW và tắt khi HIGH.
21. Điều tôi muốn bạn đặc biệt hiểu ở bài này
Đừng học thuộc:
GPIOC->CRH &= ~(0xF << 20);
Hãy hiểu nó.
Ta có:
PC13
↓
pin number = 13
↓
PC13 thuộc CRH
↓
mỗi pin 4 bit
↓
(13 - 8) × 4
↓
20
Vì vậy:
GPIOC->CRH
và:
20
không phải con số thần bí.
Nó được tính ra từ cấu trúc phần cứng.
22. Công thức cực kỳ quan trọng
Đối với STM32F1:
Pin 0–7
CRL
Vị trí:
pin × 4
Pin 8–15
CRH
Vị trí:
(pin - 8) × 4
Ví dụ:
PC5
CRL
bit = 5 × 4 = 20
PC13
CRH
bit = (13 - 8) × 4
= 20
Bạn có thể tự tính cho bất kỳ GPIO nào.
23. Bài tập thực hành
Không cần học tiếp ngay. Hãy thử tự làm 5 câu này.
Câu 1
PC5 nằm trong:
CRL hay CRH?
Câu 2
PC5 bắt đầu ở bit số mấy?
Câu 3
PC10 nằm trong:
CRL hay CRH?
Câu 4
PC10 bắt đầu ở bit số mấy?
Câu 5
Muốn set PC13 bằng BSRR thì viết:
GPIOC->BSRR = ?
24. Bức tranh lớn sau Bài 19
Đến đây kiến thức của bạn đã nối được như sau:
Bài 1
Kiểu dữ liệu
↓
Bài 5
Pointer
↓
Bài 7–13
Struct / typedef / enum
↓
Bài 16
Bit / Mask / Shift
↓
Bài 17
Memory Map
↓
Bài 18
GPIO
↓
Bài 19
GPIO Register
↓
┌───────────────┐
│ RCC │
│ GPIOC │
│ CRH │
│ ODR │
│ BSRR │
└───────────────┘
↓
PC13
↓
LED
Đây chính là bước chuyển từ “học C” sang “lập trình vi điều khiển bằng C”.
Ở Bài 20, chúng ta nên đi sâu vào GPIO Input: đọc nút nhấn bằng IDR, pull-up/pull-down, debounce, và quan trọng nhất là hiểu tại sao đọc một chân GPIO lại chỉ là đọc một bit trong một register.