Thông thường, để truy cập vào 1 bit trên 1 thanh ghi, chúng ta sử dụng các thao tác Setbit, clear bit thông qua toán tử AND hoặc OR
Ví dụ, để set bit 2 trên PORTA ta thao tác vào thanh ghi ODR ( được ánh xạ tới ngõ ra)
1 |
GPIOA->ODR |= 1<<2; |
Hoặc để xóa bit
1 |
GPIOA->ODR &= ~(1<<2); |
Trong ngôn ngữ C, có 1 cách khác nữa để thao tác lên bit đó là sử dụng bit fields tuy nhiên bit fields cần khai báo trong struct và tốc độ truy cập của nó cũng rất chậm
Ở bài này, mình sẽ giới thiệu với các bạn 1 phương pháp khác để can thiệp vào 1 bit trên thanh ghi của vi điều khiển STM32 thông qua bit banding
Trong STM32 lõi M3, có hẳn 1MB bộ nhớ được gọi là bit-band region. Trong vùng này mỗi bit đều được truy cập riêng lẻ. Để truy cập vào mỗi bit trong vùng này, chúng ta cần thao tác thông qua 1 vùng được gọi là aliased region.
Để ánh xa tới 1 bit trong bit-band region chúng ta cần tới 4 byte trong vùng aliased region, như vậy vùng aliased region sẽ có kích thước là 32MB
Vùng bit-band dành riêng cho ngoại vi có địa chỉ bit-band region là 0x40000000 và địa chỉ bắt đầu của aliased region là 0x42000000
Với bit band, chúng ta có thể thao tác lên 1 bit duy nhất trong thanh ghi ngoại vi do vậy khi làm việc với gpio và các biến cờ ( true/flase) sẽ dễ dàng hơn rất nhiều
Cách tính địa chỉ aliased region cho từng bít cho vùng ngoại vi
bit_address = 0x42000000 + byte_offset * 32 + bit_offset * 4;
Trong đó:
- bit_offset là vị trí của bit trong thanh ghi mà ta muốn truy cập
- byte_offset là địa chỉ của thanh ghi ta muốn truy cập
- bit_address là địa chỉ của bit đó
Tạo thư viện GPIO bit band
Trong STM32 thư viện HAL, để thao tác lên 1 chân, ta cần khai báo cả PORT lẫn PIN. Điều này làm mình thấy khá khó chịu, bây giờ mình sẽ sử dụng bitband để tạo thư viện GPIO có cách sử dụng giống như trên các dòng 8051,AVR
Ví dụ, muốn thao tác lên chân A6
Ta tính địa chỉ của bit A6 = 0x42000000 + (unsigned int)(&GPIOA->ODR) * 32 + 6 * 4;
Kết quả là 0x42210198
Chú ý: Câu lệnh (unsigned int)(&GPIOA->ODR) dùng để lấy ra địa chỉ của thanh ghi GPIOA->ODR
Bây giờ chúng ta truy cập vào địa chỉ 0x42210198 thông qua ép kiểu con trỏ. Ví dụ để set bit A6 lên 1
1 |
*(_IO uint32_t *)0x42210198=1; |
Rất đơn giản phải không , bây giờ mình sẽ tính sẵn tất cả địa chỉ của các chân GPIO rồi định nghĩa thêm tên chân để tạo thành thư viện GPIO
Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#define PIN_A0 (*(volatile uint32_t *)0x42210180) #define PIN_A1 (*(volatile uint32_t *)0x42210184) #define PIN_A2 (*(volatile uint32_t *)0x42210188) #define PIN_A3 (*(volatile uint32_t *)0x4221018C) #define PIN_A4 (*(volatile uint32_t *)0x42210190) #define PIN_A5 (*(volatile uint32_t *)0x42210194) #define PIN_A6 (*(volatile uint32_t *)0x42210198) #define PIN_A7 (*(volatile uint32_t *)0x4221019C) #define PIN_A8 (*(volatile uint32_t *)0x422101A0) #define PIN_A9 (*(volatile uint32_t *)0x422101A4) #define PIN_A10 (*(volatile uint32_t *)0x422101A8) #define PIN_A11 (*(volatile uint32_t *)0x422101AC) #define PIN_A12 (*(volatile uint32_t *)0x422101B0) #define PIN_A13 (*(volatile uint32_t *)0x422101B4) #define PIN_A14 (*(volatile uint32_t *)0x422101B8) #define PIN_A15 (*(volatile uint32_t *)0x422101BC) |
Và giờ chỉ việc gọi PIN_A0 = 1; để set chân A0 lên mức 1
Với các cổng khác tương tự nhé !
anh ơi làm sao biết đc địa chỉ thanh ghi GPIOA->ODR
dùng & để lấy ra địa chỉ