Bài 21: Lập trình STM32 Bit Band điều khiển GPIO

lap trinh stm32 bit band dieu khien GPIO

STM32 Bit Band được sử dụng khi muốn điều khiển ngoại vi một cách nhanh chóng, nhưng có lẽ do mất thời gian định nghĩa ra các địa chỉ nên không có nhiều người viết hướng dẫn.

Trong bài này chúng ta sẽ học cách điều khiển GPIO bằng 4 cách khác nhau, và cách sử dụng bit band trong STM32 nhé.

Bài 21 trong Serie Học STM32 từ A tới Z

Các cách điều khiển GPIO

Thông thường có 4 cách điều khiển GPIO, đó là:

  • Sử dụng hàm điều khiển GPIO của thư viện như: HAL_GPIO_WritePin, HAL_GPIO_ReadPin…
  • Sử dụng thanh ghi ODR: khi thay đổi giá trị của thanh ghi ODR, giá trị đó sẽ được đưa ra các chân của PORT.
  • Sử dụng bit field: tạo ra struct có các phần tử 1 bit, đọc ghi giá trị qua struct đó.
  • Sử dụng bit band: sử dụng một vùng địa chỉ dành riêng cho việc truy cập theo từng bit.

Điều khiển bằng hàm trong thư viện

Đây là cách làm đơn giản nhất, nhưng cũng là cách mất nhiều thời gian xử lý nhất.

Ví dụ trong HAL chúng ta sử dụng các hàm:

  • HAL_GPIO_WritePin
  • HAL_GPIO_ReadPin
  • HAL_GPIO_TogglePin

Điều khiển bằng thanh ghi

Lập trình bằng thanh ghi giúp tốc độ chương trình tăng lên, do không phải chạy qua các lệnh kiểm tra trung gian (thư viện phải viết để tương thích với nhiều dòng vi điều khiển khác nhau).

Thao tác với thanh ghi chúng ta có 3 tác vụ chính là đọc/ghi/xóa.

Ví dụ: mình sẽ ghi và xóa bit 13 của thanh ghi ODR thuộc GPIOC. Lệnh này tương ứng với việc bạn bật và tắt LED trên chân PC13.

  • Để ghi bit: GPIOC->ODR |= 1<<13; Giải thích: chúng ta dịch số 1 sang trái 13 đơn vị, tương ứng với bit 13 trên ODR của GPIOC, sau đó dùng phép toán OR để đặt bit đó lên 1.
  • Để xóa bit: GPIOC->ODR &= ~(1<<13); Tương tự dịch bit như trên, nhưng đảo bit rồi dùng phép toán AND, lúc này bit 13 sẽ bị xóa về 0.
  • Để đọc bit: PC13_IN = (GPIOC->IDR) & (1<<13); Phép AND với mặt nạ (1<<13) sẽ đưa tất cả các bit khác về 0, chỉ giữ lại bit 13. Kết quả bằng 0 nếu chân đang ở mức 0, và khác 0 (bằng 0x2000) nếu chân ở mức 1. Muốn kết quả đúng là 0 hoặc 1 thì dùng (GPIOC->IDR >> 13) & 1.

Lưu ý: Lệnh GPIOC->ODR |= 1<<13; thực chất gồm 3 bước đọc – sửa – ghi. Nếu giữa chừng có ngắt nhảy vào và cũng sửa chân khác của GPIOC, giá trị ngắt vừa ghi sẽ bị đè mất. Để bật/tắt chân an toàn, hãy dùng thanh ghi BSRR (GPIOC->BSRR = 1<<13; để set, GPIOC->BSRR = 1<<(13+16); để reset) hoặc dùng bit band như phần dưới, cả 2 cách đều chỉ là 1 lệnh ghi duy nhất.

Điều khiển bằng bit field

Đầu tiên, bạn phải tạo một cấu trúc trường bit (bit field) theo yêu cầu của bạn.

/* define structure of Port Pin*/
typedef struct {
volatile unsigned int Bit0:1;
volatile unsigned int Bit1:1;
volatile unsigned int Bit2:1;
volatile unsigned int Bit3:1;
.
.
volatile unsigned int Bit31:1;
}SPortPin;

Tạo một con trỏ tới kiểu bit field mô tả ở trên và gán cho nó địa chỉ của thanh ghi mà bạn muốn truy cập.

volatile  SPortPin *psGpioPort  = (volatile  SPortPin *)(GPIO_BASE + GPIO_OFFSET);

Với GPIO_BASE là địa chỉ bắt đầu của GPIO, GPIO_OFFSET là độ lệch của thanh ghi so với địa chỉ đó (VD: ODR là 0x0C).

Bây giờ cấu trúc bit field của bạn đã được ánh xạ với thanh ghi phần cứng mà bạn muốn truy cập. Với GPIO thì đó là thanh ghi ODR (để ghi) và IDR (để đọc).

Để đọc giá trị từ thanh ghi chúng ta sử dụng lệnh:

Value  =  psGpioPort-> Bit1;

Để ghi giá trị vào thanh ghi ta sử dụng:

psGpioPort-> Bit1  =  1;
// hoặc
psGpioPort-> Bit1  =  0;

Điều khiển bằng bit band

Bit Band là gì?

Bit-banding là cơ chế ánh xạ mỗi bit của một vùng nhớ (Bit Band Region) thành một word (số 32 bit) riêng trong một vùng địa chỉ khác gọi là vùng bí danh (Alias Region). Vùng bí danh không phải là bộ nhớ thật để lưu dữ liệu, mà chỉ là một “cửa sổ” địa chỉ dẫn thẳng tới từng bit.

Nghĩa là khi bạn ghi dữ liệu vào một word trong Bit Band Alias Region, phần cứng sẽ tự thực hiện thao tác đọc/sửa/ghi để thay đổi đúng bit tương ứng trong Bit Band Region. Tất cả các thao tác này do phần cứng thực hiện trong 1 lệnh duy nhất, nên rất nhanh và không bị ngắt chen ngang.

Bạn cứ hình dung một tòa chung cư (thanh ghi 32 bit) có 32 căn hộ (bit). Bình thường muốn bật đèn 1 căn, bạn phải đọc sổ cả tòa, sửa 1 dòng rồi ghi lại cả sổ. Bit band giống như mỗi căn hộ có 1 công tắc riêng với địa chỉ riêng, bấm đúng công tắc là xong.

Bit band thường được sử dụng để truy cập nhanh các thanh ghi, đặc biệt trong các ứng dụng cần điều khiển GPIO thật nhanh.

Cách này sẽ giúp việc lập trình GPIO trên STM32 đơn giản hơn rất nhiều, không cần nhiều câu lệnh lằng nhằng.

Bit band explained
Cách dữ liệu map với nhau trong SRAM bit band

Bit Band Region trên STM32

ARM định nghĩa 2 vùng nhớ bit-band cho Cortex-M3, mỗi vùng là 1MB và được ánh xạ tới một vùng bí danh 32MB. Mỗi word 32 bit trong vùng bí danh tương ứng với một bit trong vùng bit-band.

Memory bit band region
bit band map

Vùng bit-band đầu tiên là từ 0x2000 0000 đến 0x200F FFFF của SRAM, và vùng bí danh tương ứng của nó là 0x2200 0000 tới 0x23FF FFFF.

Vùng bit-band còn lại là từ 0x4000 0000 đến 0x400F FFFF của các ngoại vi, và vùng bí danh tương ứng là từ 0x4200 0000 đến 0x43FF FFFF.

Chi tiết các bạn đọc lại bài Bản đồ bộ nhớ trên STM32

Cách truy cập vào vùng bit band trên STM32

Để tính địa chỉ trong vùng bit-band bí danh, ta dùng công thức sau:

bit_band_address = alias_region_base + (region_base_offset x 32) + (bit_number x 4)

Ví dụ: chúng ta sẽ điều khiển LED trên chân PC13

  • alias_region_base = 0x4200 0000 là địa chỉ bắt đầu vùng bí danh của ngoại vi
  • region_base_offset là độ lệch của thanh ghi muốn truy cập so với đầu vùng bit-band (0x4000 0000). Thanh ghi ODR của Port C có địa chỉ 0x4001 100C, nên offset = 0x4001 100C – 0x4000 0000 = 0x0001 100C
  • bit_number = 13 chính là vị trí bit trên thanh ghi đó

Vậy theo công thức chúng ta tính được địa chỉ bit band của PC13 là: 0x4200 0000 + 0x1100C x 32 + 13 x 4 = 0x422201B4

Nếu cứ tính bằng tay thì cũng rất mất thời gian tra cứu phải không, ai mà nhớ hết được. Vậy nên để dễ dàng trong tính toán, mình sẽ sử dụng các định nghĩa sẵn có trong thư viện HAL và vài dòng code để tính nhanh hơn.

Lập trình Bit Band với STM32 HAL

Trong bài này mình sẽ sử dụng PA0 làm nút nhấn và PC13 điều khiển LED, giống bài 3 STM32 GPIO, và xem code đã được thay đổi như thế nào nhé!

Phần cứng: 1 nút nhấn nối giữa PA0 và GND (dùng điện trở kéo lên bên trong), LED dùng luôn LED có sẵn trên chân PC13 của KIT Blue Pill.

Sơ đồ đấu nối STM32F103C8T6 Blue Pill với nút nhấn: PA0 nối nút nhấn xuống GND, LED PC13 có sẵn trên board

Tạo một project trên CubeMX. SYS – Debug – Serial Wire, nhớ làm bước này để chúng ta debug nhé.

stm32 bit band cube 1

GPIO cho PC13 là Output, PA0 là Input Pull Up

stm32 bit band cube 2

Gen code và mở trên Keil C.

Trong Keil C mình sẽ khai báo các biến chứa địa chỉ bit band của PC13 và PA0.

stm32 bit band code 1

Sử dụng công thức bên trên để tính toán, với:

  • PERIPH_BB_BASE: địa chỉ bắt đầu vùng bí danh bit band của ngoại vi (0x4200 0000)
  • (uint32_t)(&GPIOC->ODR): địa chỉ của thanh ghi ODR thuộc port C (khi tính phải trừ đi PERIPH_BASE để ra offset)
  • pin_num: chân hay bit cần can thiệp

stm32 bit band code 2

Nhấn F7 để build và Ctrl+F5 để vào Debug. Trong Debug add các biến vào Watch 1.

stm32 bit band code 5

Nhấn Run để lấy giá trị các biến, sau đó define PC13_OUT và PA0_IN bằng các giá trị đọc được.

stm32 bit band code 3

Lưu ý: Khi define địa chỉ bit band thành biến để đọc ghi, phải ép kiểu thành con trỏ volatile, ví dụ #define PC13_OUT (*(volatile uint32_t *)0x422201B4). Thiếu volatile, trình biên dịch có thể tối ưu bỏ bớt các lần đọc PA0_IN trong vòng while, khiến nút nhấn “không ăn” khi bật tối ưu code.

Tiếp theo trong while chúng ta đọc giá trị của PA0 rồi ghi vào PC13 (bạn có thể xóa các dòng tính toán bên trên đi được rồi nhé).

stm32 bit band code 4

Chúng ta sẽ được kết quả tương tự bài GPIO nhưng code được thu gọn hơn rất nhiều, đúng không nào.

Để tiếp tục với các chân GPIO khác, các bạn cũng làm tương tự nhé.

Kết

Chúng ta đã học qua các cách điều khiển GPIO trong STM32 HAL. Các cách này có thể ứng dụng với nhiều dòng vi điều khiển khác nhau chứ không riêng gì STM32, vì bản chất của vi điều khiển là giống nhau mà (riêng bit band chỉ có trên lõi Cortex-M3/M4, Cortex-M0 và M7 không có).
Để sử dụng bit band, quan trọng là bạn cần nắm rõ bản đồ bộ nhớ (memory map) của dòng đó. Nếu không, hãy sử dụng các định nghĩa đã khai báo sẵn trong thư viện như HAL, SPL, LL…, bạn chỉ cần tìm tới nơi define chúng là sẽ rõ mình đang truy cập tới địa chỉ nào nhé.

Nếu cảm thấy bài viết này có ích hãy chia sẻ với bạn bè nhé. Đừng quên gia nhập hội những anh em Nghiện lập trình nhé!!

5/5 - (2 bình chọn)

2 những suy nghĩ trên “Bài 21: Lập trình STM32 Bit Band điều khiển GPIO”

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *