BÀI 27 – GPIO STM32: THANH GHI OUTPUT DATA (ODR) VÀ SET/CLEAR BIT
Ở Bài 26, ta đã đi đến việc điều khiển GPIO bằng cách tác động trực tiếp vào thanh ghi. Bài 27 sẽ đi sâu vào một thanh ghi cực kỳ quan trọng:
GPIOx_ODR – Output Data Register
Đây chính là nơi STM32 lưu trạng thái logic mà bạn muốn xuất ra chân GPIO.
1. Nhắc lại: GPIO thực chất hoạt động như thế nào?
Ví dụ ta dùng LED trên chân:
PC13
Khi muốn LED thay đổi trạng thái, CPU không “ra lệnh cho LED” trực tiếp.
CPU thực hiện:
CPU
↓
Thanh ghi GPIOC
↓
Mạch GPIO
↓
PC13
↓
LED
Vì vậy, lập trình GPIO thực chất là:
Đọc/ghi các bit trong những thanh ghi GPIO.
2. GPIOC_ODR là gì?
ODR viết tắt của:
Output Data Register
Có thể hiểu đơn giản:
ODR chứa giá trị logic đầu ra của các chân GPIO.
Ví dụ:
GPIOC_ODR
là thanh ghi dữ liệu đầu ra của GPIO port C.
STM32F103 có 16 chân trong mỗi GPIO port:
PC0
PC1
PC2
...
PC13
PC14
PC15
Vì vậy ODR có các bit:
Bit: 15 14 13 12 ... 2 1 0
| | | | | | |
PC15 PC14 PC13 PC2 PC1 PC0
Ví dụ:
GPIOC->ODR
có thể chứa:
0000 0010 0000 0000
Bit 9 = 1.
Điều đó nghĩa là:
PC9 = HIGH
3. PC13 nằm ở bit nào?
Đây là điều bạn phải nhớ.
PC13
có số chân là:
13
nên tương ứng với:
bit 13
Muốn đưa PC13 lên HIGH:
GPIOC->ODR |= (1 << 13);
Muốn đưa PC13 xuống LOW:
GPIOC->ODR &= ~(1 << 13);
Đây chính là kỹ thuật set bit và clear bit.
4. Phân tích câu lệnh SET
Ta có:
GPIOC->ODR |= (1 << 13);
Hãy tách ra.
Bước 1
1
Trong nhị phân:
0000 0000 0000 0001
Bước 2
Dịch trái 13 bit:
1 << 13
kết quả:
0010 0000 0000 0000
hay:
0x2000
Bước 3
Thực hiện OR:
GPIOC->ODR |= 0x2000;
Ví dụ ODR hiện tại:
0000 0000 0100 0010
mask:
0010 0000 0000 0000
OR:
-----------------
0010 0000 0100 0010
Bit 13 chắc chắn trở thành:
1
Các bit khác không bị thay đổi.
Đây là ưu điểm cực kỳ quan trọng của:
|=
5. CLEAR BIT
Muốn PC13 = LOW:
GPIOC->ODR &= ~(1 << 13);
Ta có:
1 << 13
=
0010 0000 0000 0000
Đảo bit:
~(1 << 13)
=
1101 1111 1111 1111
Sau đó AND:
ODR
AND
mask
Bit 13 chắc chắn trở thành:
0
Trong khi các bit khác giữ nguyên.
6. SET và CLEAR phải thuộc lòng
Bạn nên nhớ hai mẫu này:
SET bit
REG |= (1 << n);
CLEAR bit
REG &= ~(1 << n);
Ví dụ:
GPIOC->ODR |= (1 << 13); // PC13 = 1
GPIOC->ODR &= ~(1 << 13); // PC13 = 0
Đây là hai mẫu câu lệnh bạn sẽ gặp rất nhiều trong lập trình STM32.
7. Tại sao không viết thế này?
Bạn có thể nghĩ:
GPIOC->ODR = (1 << 13);
Nó cũng đưa PC13 lên 1.
Nhưng có một vấn đề rất lớn.
Giả sử trước đó:
ODR:
PC15 PC14 PC13 PC12 PC11 ... PC1 PC0
1 0 0 1 1 0 1
Nếu viết:
GPIOC->ODR = (1 << 13);
thì toàn bộ ODR sẽ trở thành:
0010 0000 0000 0000
Các bit khác bị xóa.
Trong khi:
GPIOC->ODR |= (1 << 13);
chỉ tác động vào bit 13.
Đây là sự khác biệt rất quan trọng.
8. Một ví dụ thực tế
Giả sử:
PC13 → LED
PC14 → LED2
PC15 → LED3
ODR ban đầu:
0000 0000 0000 0000
Cho PC13 HIGH:
GPIOC->ODR |= (1 << 13);
ODR:
0010 0000 0000 0000
Tiếp tục PC14 HIGH:
GPIOC->ODR |= (1 << 14);
ODR:
0110 0000 0000 0000
Bây giờ nếu clear PC13:
GPIOC->ODR &= ~(1 << 13);
ODR trở thành:
0100 0000 0000 0000
PC14 vẫn HIGH.
Đó chính là lý do phải sử dụng mask + bitwise operator.
9. Nhưng STM32 còn có thanh ghi tốt hơn ODR
Đây là phần rất quan trọng của Bài 27.
STM32F103 còn có:
GPIOx_BSRR – Bit Set/Reset Register
Ví dụ:
GPIOC->BSRR = (1 << 13);
→ SET PC13.
Còn RESET PC13:
GPIOC->BSRR = (1 << (13 + 16));
hay:
GPIOC->BSRR = (1 << 29);
Vì:
BSRR bit 0 → SET PC0
BSRR bit 1 → SET PC1
...
BSRR bit 15 → SET PC15
BSRR bit 16 → RESET PC0
BSRR bit 17 → RESET PC1
...
BSRR bit 31 → RESET PC15
Sơ đồ:
GPIOx_BSRR
31 16 15 0
+----------------------------+----------------------------+
| RESET bits | SET bits |
+----------------------------+----------------------------+
PC15 ... PC0 PC15 ... PC0
10. ODR và BSRR khác nhau thế nào?
| Thanh ghi | Chức năng |
|---|---|
ODR |
Dữ liệu output |
BSRR |
Set/Reset từng bit |
IDR |
Đọc input |
CRL |
Cấu hình PC0–PC7 |
CRH |
Cấu hình PC8–PC15 |
Bạn có thể hình dung:
GPIOC
│
┌────────┼────────┐
│ │ │
IDR ODR BSRR
│ │ │
INPUT OUTPUT SET/RESET
11. Tại sao BSRR rất hay?
Ví dụ muốn bật PC13:
GPIOC->BSRR = (1 << 13);
Muốn tắt:
GPIOC->BSRR = (1 << 29);
Không cần:
|=
hoặc:
&=~
BSRR được thiết kế riêng cho việc set/reset GPIO.
Đặc biệt, thao tác BSRR có tính atomic ở cấp độ phần cứng, rất hữu ích khi chương trình có interrupt hoặc nhiều luồng xử lý phần cứng liên quan đến GPIO.
12. Một điểm rất quan trọng về Blue Pill PC13
Với Blue Pill thông thường, LED onboard trên:
PC13
thường là active LOW.
Có nghĩa là:
PC13 = 0 → LED sáng
PC13 = 1 → LED tắt
Do đó:
GPIOC->ODR |= (1 << 13);
không nhất thiết làm LED sáng.
Nó làm:
PC13 = HIGH
và LED onboard thường sẽ tắt.
Muốn LED sáng:
GPIOC->ODR &= ~(1 << 13);
hoặc dùng BSRR:
GPIOC->BSRR = (1 << (13 + 16));
13. Chương trình thực hành Bài 27
Sau khi PC13 đã được cấu hình output, chúng ta có thể thử:
#include "stm32f1xx.h"
int main(void)
{
// Bật clock GPIOC
RCC->APB2ENR |= (1 << 4);
// PC13 output push-pull, 2 MHz
GPIOC->CRH &= ~(0xF << 20);
GPIOC->CRH |= (0x2 << 20);
while (1)
{
// LED ON
GPIOC->BSRR = (1 << 29);
for (volatile int i = 0; i < 1000000; i++);
// LED OFF
GPIOC->BSRR = (1 << 13);
for (volatile int i = 0; i < 1000000; i++);
}
}
Ở đây có một điều rất đáng chú ý:
GPIOC->BSRR = (1 << 29);
là:
Reset PC13
→ PC13 LOW
→ LED onboard sáng.
Còn:
GPIOC->BSRR = (1 << 13);
là:
Set PC13
→ PC13 HIGH
→ LED tắt.
14. Bài tập cho bạn
Không cần HAL, hãy tự viết chương trình:
Bài tập 1
Cho:
PC13
nhấp nháy bằng:
GPIOC->ODR
Không được dùng HAL_GPIO_WritePin().
Bài tập 2
Viết lại chương trình trên nhưng dùng:
GPIOC->BSRR
Bài tập 3 – rất quan trọng
Nếu:
GPIOC->ODR = 0xA000;
hãy xác định:
PC15 = ?
PC14 = ?
PC13 = ?
PC12 = ?
Gợi ý:
0xA000
=
1010 0000 0000 0000
15. Kiến thức cần thuộc sau Bài 27
Bạn không cần học thuộc lòng hàng trăm thanh ghi, nhưng 4 mẫu sau phải thật chắc:
// SET bit
REG |= (1 << n);
// CLEAR bit
REG &= ~(1 << n);
// TEST bit
if (REG & (1 << n))
{
}
// TOGGLE bit
REG ^= (1 << n);
Đặc biệt:
|
&
^
~
<<
chính là nền tảng để bạn điều khiển register của STM32.
Lộ trình tiếp theo
Bài 28 chúng ta nên đi vào một thứ cực kỳ quan trọng:
GPIOx_BSRR – tại sao STM32 thiết kế riêng thanh ghi SET/RESET, thế nào là thao tác atomic, và tại sao
BSRRthường an toàn hơn việc sửaODRbằng|=/&=~.
Sau Bài 28, chúng ta có thể bắt đầu ghép GPIO Input + GPIO Output để làm một bài rất thực tế: nhấn nút → điều khiển LED, hoàn toàn bằng thanh ghi STM32F103, chưa cần HAL.