BÀI 28 – GPIO STM32F103: BSRR – CÁCH SET/CLEAR GPIO BẰNG 1 LỆNH
Ở Bài 27, chúng ta đã học GPIOx->ODR để điều khiển trạng thái chân GPIO.
Hôm nay chúng ta học một thanh ghi cực kỳ quan trọng trong STM32:
GPIOx->BSRR– Bit Set/Reset Register
Đây chính là cách STM32 điều khiển GPIO nhanh, an toàn và nguyên tử (atomic) hơn so với việc đọc–sửa–ghi ODR.
1. Nhắc lại ODR
Ví dụ:
GPIOA->ODR |= (1 << 5);
→ PA5 = HIGH.
Và:
GPIOA->ODR &= ~(1 << 5);
→ PA5 = LOW.
Vấn đề là các câu lệnh trên thực hiện theo kiểu:
Đọc ODR
↓
Thay đổi bit
↓
Ghi lại ODR
Trong một số tình huống, quá trình read-modify-write này không tối ưu.
STM32F103 cung cấp BSRR để giải quyết vấn đề đó.
2. BSRR là gì?
Với GPIO:
GPIOA->BSRR
GPIOB->BSRR
GPIOC->BSRR
...
Thanh ghi này rộng 32 bit.
Có thể hình dung:
31 16 15 0
+----------------------------+----------------------------+
| RESET bits | SET bits |
+----------------------------+----------------------------+
GPIO pin 15...0 GPIO pin 15...0
Cụ thể:
Bit 0 → SET PA0
Bit 1 → SET PA1
...
Bit 15 → SET PA15
Bit 16 → RESET PA0
Bit 17 → RESET PA1
...
Bit 31 → RESET PA15
Công thức:
SET pin n → bit n
RESET pin n → bit n + 16
3. SET một chân GPIO
Ví dụ muốn:
PA5 = HIGH
Ta viết:
GPIOA->BSRR = (1 << 5);
Tức là:
1 << 5
cho kết quả:
0000 0000 0000 0000
0000 0000 0010 0000
Bit 5 của vùng SET = 1.
STM32 hiểu:
Hãy SET PA5.
Kết quả:
PA5 = HIGH
4. RESET một chân GPIO
Muốn:
PA5 = LOW
ta sử dụng phần RESET của BSRR.
PA5 tương ứng:
5 + 16 = 21
Vì vậy:
GPIOA->BSRR = (1 << 21);
Hoặc viết dễ hiểu hơn:
GPIOA->BSRR = (1 << (5 + 16));
Kết quả:
PA5 = LOW
5. Cách nhớ cực kỳ quan trọng
Bạn chỉ cần nhớ:
BSRR bit 0...15 → SET
BSRR bit 16...31 → RESET
Ví dụ:
| Chân | SET | RESET |
|---|---|---|
| PA0 | 1 << 0 |
1 << 16 |
| PA1 | 1 << 1 |
1 << 17 |
| PA2 | 1 << 2 |
1 << 18 |
| PA5 | 1 << 5 |
1 << 21 |
| PA8 | 1 << 8 |
1 << 24 |
| PA13 | 1 << 13 |
1 << 29 |
| PA15 | 1 << 15 |
1 << 31 |
6. Ví dụ với LED PC13
Blue Pill của bạn có LED tích hợp thường nằm ở:
PC13
Giả sử PC13 đã được cấu hình Output.
Bật PC13
GPIOC->BSRR = (1 << 13);
Tắt PC13
GPIOC->BSRR = (1 << (13 + 16));
hay:
GPIOC->BSRR = (1 << 29);
7. Nhưng LED Blue Pill thường là Active LOW
Đây là chỗ rất dễ nhầm.
Trên nhiều board Blue Pill:
PC13 = LOW → LED sáng
PC13 = HIGH → LED tắt
Vì LED được mắc theo kiểu active-low.
Do đó:
GPIOC->BSRR = (1 << (13 + 16));
→ PC13 LOW → LED SÁNG
Trong khi:
GPIOC->BSRR = (1 << 13);
→ PC13 HIGH → LED TẮT
Đây là lý do khi học STM32 bạn phải phân biệt:
GPIO HIGH/LOW
với:
Thiết bị ON/OFF
Hai khái niệm này không phải lúc nào cũng giống nhau.
8. So sánh ODR và BSRR
Ví dụ muốn bật PA5.
Cách 1 – ODR
GPIOA->ODR |= (1 << 5);
Cách 2 – BSRR
GPIOA->BSRR = (1 << 5);
Cả hai đều làm:
PA5 = HIGH
Nhưng cách hoạt động khác nhau.
9. ODR là Read–Modify–Write
Ví dụ:
GPIOA->ODR |= (1 << 5);
CPU phải thực hiện về bản chất:
Đọc ODR
↓
OR với bit 5
↓
Ghi ODR
Trong khi:
GPIOA->BSRR = (1 << 5);
chỉ cần ghi lệnh SET vào BSRR.
STM32 phần cứng thực hiện việc thay đổi GPIO.
10. Điều đặc biệt của BSRR: Atomic
Đây là khái niệm bạn cần bắt đầu làm quen từ bài này:
Atomic – nguyên tử
Hiểu đơn giản:
Một thao tác GPIO được thực hiện
mà không cần quá trình đọc–sửa–ghi ODR.
Điều này đặc biệt hữu ích khi chương trình có:
Interrupt
Timer
DMA
RTOS
nhiều phần mềm cùng tác động GPIO
Ở các bài nâng cao sau này, khái niệm atomic operation sẽ rất quan trọng.
11. Set nhiều chân cùng lúc
Ví dụ muốn:
PA0 = HIGH
PA1 = HIGH
PA2 = HIGH
Có thể viết:
GPIOA->BSRR =
(1 << 0) |
(1 << 1) |
(1 << 2);
Hoặc:
GPIOA->BSRR = 0x00000007;
Vì:
bit 0 = 1
bit 1 = 1
bit 2 = 1
→ PA0, PA1, PA2 đều HIGH.
12. Reset nhiều chân cùng lúc
Muốn:
PA0 = LOW
PA1 = LOW
PA2 = LOW
ta dùng:
GPIOA->BSRR =
(1 << 16) |
(1 << 17) |
(1 << 18);
Hoặc:
GPIOA->BSRR = 0x00070000;
13. Set và Reset các chân khác nhau trong cùng một lần ghi
Đây là một ưu điểm rất hay.
Ví dụ:
PA0 → HIGH
PA1 → LOW
Có thể:
GPIOA->BSRR =
(1 << 0) |
(1 << (1 + 16));
Tức là:
bit 0 = 1 → SET PA0
bit 17 = 1 → RESET PA1
Sau một lần ghi:
PA0 = HIGH
PA1 = LOW
14. Một ví dụ thực tế
Giả sử:
PA0 → LED đỏ
PA1 → LED xanh
Muốn:
LED đỏ ON
LED xanh OFF
nếu cả hai LED active-high:
GPIOA->BSRR =
(1 << 0) |
(1 << (1 + 16));
Sau đó muốn đảo:
LED đỏ OFF
LED xanh ON
thì:
GPIOA->BSRR =
(1 << (0 + 16)) |
(1 << 1);
15. BSRR không phải là một GPIO mới
Đây là điểm cần hiểu rõ.
Bạn có:
GPIOA
Bên trong GPIOA có nhiều thanh ghi:
GPIOA
├── CRL
├── CRH
├── IDR
├── ODR
├── BSRR
├── BRR
└── ...
Trong đó:
CRL/CRH → cấu hình chân
IDR → đọc chân
ODR → trạng thái output
BSRR → SET/RESET output
16. Nhìn GPIO như một hệ thống
Đến thời điểm này, bạn có thể hình dung:
GPIOA
│
┌──────────┼──────────┐
↓ ↓ ↓
CRL/CRH IDR ODR
│ │ │
│ │ └── trạng thái output
│ │
│ └───────────── đọc input
│
└──────────────────────── cấu hình GPIO
Và:
BSRR
│
┌───────┴───────┐
↓ ↓
SET RESET
│ │
└───────┬───────┘
↓
GPIO
17. Một câu lệnh cực kỳ quan trọng
Từ bây giờ, khi muốn bật GPIO:
GPIOA->BSRR = (1 << pin);
Khi muốn tắt:
GPIOA->BSRR = (1 << (pin + 16));
Ví dụ:
#define LED_PIN 13
Bật:
GPIOC->BSRR = (1 << LED_PIN);
Tắt:
GPIOC->BSRR = (1 << (LED_PIN + 16));
Đây là cách viết rất phù hợp khi bạn bắt đầu xây dựng driver GPIO riêng thay vì phụ thuộc vào Arduino API.
18. Một lỗi rất dễ mắc
Đừng viết:
GPIOA->BSRR |= (1 << 5);
trong cách sử dụng thông thường.
Hãy viết:
GPIOA->BSRR = (1 << 5);
Vì BSRR được thiết kế chủ yếu để ghi lệnh SET/RESET, không phải để bạn đọc nó rồi sửa giống ODR.
19. Bài tập Bài 28
Không cần HAL, không cần Arduino.
Giả sử:
#define LED_PIN 13
Hãy tự viết chương trình để:
Bài tập 1
PC13:
HIGH
→ chờ
→ LOW
→ chờ
Bài tập 2
Điều khiển:
PA0
PA1
PA2
theo thứ tự:
PA0 HIGH
PA1 HIGH
PA2 HIGH
PA0 LOW
PA1 LOW
PA2 LOW
Bài tập 3 – quan trọng nhất
Tạo hàm:
void GPIO_Set(GPIO_TypeDef *GPIOx, uint8_t pin);
và:
void GPIO_Reset(GPIO_TypeDef *GPIOx, uint8_t pin);
Ý tưởng:
void GPIO_Set(GPIO_TypeDef *GPIOx, uint8_t pin)
{
GPIOx->BSRR = (1 << pin);
}
và:
void GPIO_Reset(GPIO_TypeDef *GPIOx, uint8_t pin)
{
GPIOx->BSRR = (1 << (pin + 16));
}
Sau đó có thể gọi:
GPIO_Set(GPIOC, 13);
hoặc:
GPIO_Reset(GPIOC, 13);
20. Điều bạn cần nhớ sau Bài 28
Nếu chỉ nhớ 5 dòng, hãy nhớ:
ODR → trạng thái Output
IDR → đọc Input
BSRR → SET/RESET GPIO
BSRR bit 0..15 → SET
BSRR bit 16..31 → RESET
Và công thức:
// SET pin
GPIOx->BSRR = (1 << pin);
// RESET pin
GPIOx->BSRR = (1 << (pin + 16));
Lộ trình tiếp theo
Bài 29 rất phù hợp để học tiếp:
BRR– thanh ghi Reset GPIO và tại sao STM32F103 có cảBSRRlẫnBRR?
Sau đó chúng ta sẽ tiến tới một phần rất quan trọng: tự viết GPIO driver bằng thanh ghi, từ đó bạn sẽ thực sự hiểu bên trong các hàm kiểu digitalWrite() của Arduino hoạt động như thế nào.