Skip to content

5 Bật đèn LED bằng thanh ghi

Tài liệu

Datasheet Reference manual cho STM32F407 RM0090

Các bước cấu hình GPIO

Để điều khiển LED qua thanh ghi (không dùng thư viện), cần thực hiện 3 bước:

  1. Bật clock cho GPIOB
  2. Cấu hình chân PB2 làm đầu ra
  3. Xuất mức điện áp để bật/tắt LED

Bật clock cho GPIOB

Mặc định, tất cả các ngoại vi (peripheral) của STM32 đều không được cấp xung clock để tiết kiệm năng lượng. Vì vậy, trước khi sử dụng bất kỳ ngoại vi nào, ta cần bật clock tương ứng.

Trong ví dụ này, LED được nối với chân PB2, nên cần bật clock cho GPIOB trước. Theo tài liệu (trang 37), GPIOB thuộc bus AHB1, vì vậy ta phải cấu hình thanh ghi RCC_AHB1ENR để bật clock.

Tại trang 114 của datasheet, có hình minh họa thanh ghi AHB1ENR dưới đây:

img

Từ hình minh họa trong tài liệu, ta thấy địa chỉ lệch (offset) của thanh ghi bật clock cho bus AHB1 là 0x30.

Theo trang 72 của sổ tay kỹ thuật, địa chỉ cơ bản (base address) của khối RCC (Reset and Clock Control) là: 0x40023800

Vậy, địa chỉ thực tế của thanh ghi RCC_AHB1ENR (AHB1 clock enable register) sẽ là: 0x40023800 + 0x30 = 0x40023830

Sau khi biết được địa chỉ thanh ghi, ta cần biết bit nào dùng để bật clock cho GPIOB. Theo trang 182 của datasheet, bit số 1 trong thanh ghi này điều khiển GPIOB.

Đã xác định được địa chỉ cần thao tác, còn cách cấu hình giá trị này như thế nào? Chúng ta tiếp tục xem phần mô tả của thanh ghi này trong sổ tay hướng dẫn, tại trang 182, nơi có mô tả thanh ghi như hình mẫu.

img

Trong thanh ghi RCC_AHB1ENR, bit số 1 dùng để bật clock cho GPIOB

Để bật clock cho GPIOB, ta ghi 1 vào bit 1. Để không ảnh hưởng các bit khác, ta dùng phép OR (|=):

c
RCC->AHB1ENR |= (1 << 1);  // Bật clock cho GPIOB

Lệnh này tương đương với:

c
RCC->AHB1ENR |= 0x00000002;

Bit số 0: GPIOA Bit số 1: GPIOB Bit số 2: GPIOC ... (tính từ bit 0 trở đi)

Vì các địa chỉ thanh ghi và bit đã được định nghĩa sẵn trong file tiêu đề (stm32f4xx.h), bạn chỉ cần dùng cú pháp RCC->AHB1ENR như trên, không cần tự tính địa chỉ thủ công.

Cấu hình chế độ GPIO

Cấu hình GPIO gồm hai bước chính: Cấu hình chân ở chế độ đầu ra (Output Mode) và Xuất mức logic cao (hoặc thấp) ra chân GPIO


Cấu hình chân ở chế độ đầu ra (Output Mode)

Cấu hình port mode

Để bật LED, cần cấu hình chân GPIO ở chế độ đầu ra. Ta sử dụng thanh ghi GPIOx_MODER như minh họa:

img

Ở hình trên có thể thấy GPIOx_MODER có địa chỉ lệch là 0x00, chúng ta muốn bật chân GPIOB nên cần cộng thêm địa chỉ cơ sở của GPIOB là 0x4002 0400, tức là địa chỉ của GPIOx_MODER0x4002 0400 + 0x00. Tiếp theo chúng ta xem phần giới thiệu về thanh ghi này, như hình dưới đã chỉ rõ.

img

Mỗi chân chiếm 2 bit. Với PB2, ta thao tác trên bit 4 và 5 của thanh ghi.

Chúng ta cần cấu hình chân PB2 thành chế độ đầu ra, điều này tương ứng với việc thiết lập giá trị 01 cho hai bit điều khiển chân này trong thanh ghi GPIOB_MODER. Vì mỗi chân chiếm 2 bit, PB2 sẽ tương ứng với bit 5 và bit 4.

Biểu diễn nhị phân là: 0000 0000 0000 0000 0000 0000 0001 00000x00000010 (bit 4 = 1, bit 5 = 0).

Để cấu hình đúng mà không làm thay đổi các bit khác, ta cần xóa bit 5 và bit 4 trước, sau đó mới ghi giá trị 01:

c
GPIOB->MODER &= ~((uint32_t)(0x03 << (2 * 2))); // Xóa bit 4 và 5 (2 * 2 vì PB2)
GPIOB->MODER |=  (0x01 << (2 * 2));             // Thiết lập bit 4 = 1 (chế độ output)
Cấu hình floating /pull-up/ pull-down

Ở chế độ đầu ra, thông thường ta để GPIO ở trạng thái floating — tức không kéo lên (pull-up) cũng không kéo xuống (pull-down). Điều này giúp tránh dòng rò không mong muốn.

img

Có thể thấy offset của thanh ghi kéo lên/kéo xuống GPIOx_PUPDR0x0C. Do địa chỉ cơ sở của GPIOB0x4002 0400, nên địa chỉ thực tế của thanh ghi GPIOB_PUPDR là:

c
GPIOB_PUPDR = 0x40020400 + 0x0C = 0x4002040C

Chi tiết cấu trúc của thanh ghi này được thể hiện trong hình bên dưới:

img

Để cấu hình GPIOB thành floating, ta cấu hình thanh ghi GPIOx_PUPDR, mỗi chân cũng chiếm 2 bit. Với PB2 (bit 5 và 4), ta cần đặt về 00:No pull-up, pull-down:

c
GPIOB->PUPDR &= ~((uint32_t)(0x03 << (2 * 2)));  // Thiết lập chế độ floating cho PB2

Lưu ý: Tất cả các địa chỉ thanh ghi như GPIOB->MODERGPIOB->PUPDR đã được định nghĩa sẵn trong file tiêu đề của STM32, nên không cần tính thủ công offset địa chỉ.

Cấu hình loại chân xuất (Output Type)

Khi cấu hình GPIO ở chế độ đầu ra, ta cần xác định kiểu đầu ra thông qua thanh ghi GPIOx_OTYPER. Mỗi chân chỉ chiếm 1 bit, cho phép chọn giữa hai loại:

  • 0: Push-Pull (Đẩy kéo) – xuất được cả mức cao và thấp.
  • 1: Open-Drain (Mở cực) – chỉ kéo xuống mức thấp; nếu cần mức cao, phải có điện trở kéo lên bên ngoài.

Trong hầu hết các ứng dụng thông thường, ta chọn Push-Pull để đơn giản mạch và đảm bảo tín hiệu rõ ràng.

Ví dụ: Cấu hình chân PB2 là Push-Pull

c
GPIOB->OTYPER &= ~(0x01 << 2);  // Bit 2 = 0 → Push-Pull

Nếu cần cấu hình là Open-Drain:

c
GPIOB->OTYPER |=  (0x01 << 2);  // Bit 2 = 1 → Open-Drain

Như vậy, việc cấu hình loại chân đảm bảo GPIO hoạt động đúng theo yêu cầu của mạch ngoài.

Cấu hình tốc độ đầu ra

Tốc độ ảnh hưởng đến khả năng chuyển mạch của GPIO. Cần cấu hình thanh ghi GPIOx_OSPEEDR, mỗi chân chiếm 2 bit. Các mức tốc độ:

  • 00: Thấp tốc (Low speed) – tiết kiệm điện, dùng cho tín hiệu chậm.
  • 01: Trung tốc (Medium speed) – cân bằng giữa tốc độ và tiêu thụ.
  • 10: Cao tốc (High speed) – phù hợp truyền dữ liệu nhanh.
  • 11: Rất cao tốc (Very high speed) – dành cho ứng dụng tần số cao như SPI tốc độ cao.

img

Từ sơ đồ bên dưới, mỗi chân GPIO được điều khiển bởi 2 bit. Dựa trên bảng tra cứu chính thức, ta có thể thấy mối quan hệ giữa mức tốc độ và tần số hoạt động:

img

Ví dụ, nếu muốn cấu hình PB2 chạy ở 100 MHz, thì cần đặt mức 11 (Very High Speed), và đoạn mã tương ứng là:

Trong file tiêu đề của STM32 đã định nghĩa phần lớn các địa chỉ, vì vậy chúng ta chỉ cần sử dụng các định nghĩa này.

c
GPIOB->OSPEEDR |= (0x03 << (2 * 2));  // Cấu hình tốc độ 100MHz cho PB2

Như vậy, việc cấu hình tốc độ chuyển mạch cho chân GPIO đã hoàn tất. Bạn có thể áp dụng tương tự cho các chân khác bằng cách thay đổi chỉ số bit phù hợp.


Cấu hình GPIO để xuất mức cao

Sau khi đã cấu hình chân GPIO ở chế độ đầu ra, bước tiếp theo là xuất mức logic ra chân – tức là đưa chân lên mức cao (1) hoặc mức thấp (0).

Ví dụ: để bật LED gắn vào chân PB2, ta cần đưa PB2 lên mức cao.

Làm thế nào để PB2 phát ra mức cao? Dựa vào sổ tay người dùng (Reference Manual) của dòng STM32, có nhiều cách ghi dữ liệu ra chân GPIO:

Dùng thanh ghi ODR (Output Data Register)

Về bộ điều khiển xuất ra port như trong hình mô tả.

img

Có thể thấy địa chỉ lệch của thanh ghi điều khiển đầu ra cổng là 0x14, do đó địa chỉ của thanh ghi GPIOx_ODR tương ứng là 0x4002 0400 + 0x14. Chúng ta tiếp tục xem phần giới thiệu về thanh ghi này, như hình minh họa.

img

Từ sơ đồ có thể biết được GPIOB_ODR thanh ghi 16 bit thấp có hiệu lực, mỗi chân tương ứng một bit, ghi 0 vào bit tương ứng để xuất mức thấp, ghi 1 để xuất mức cao.

Trong tệp tiêu đề của STM32, đã định nghĩa hầu hết các địa chỉ, vì vậy chúng ta chỉ cần gọi những định nghĩa này là xong.

GPIOB->ODR &= ~(0x01 << 2);  // Đặt PB2 về mức thấp
GPIOB->ODR |=  (0x01 << 2);  // Đặt PB2 lên mức cao

Lưu ý: Dùng phép &= ~ để xóa bit (xuất 0), và |= để đặt bit (xuất 1). Chân số 2 → dịch 1 sang trái 2 bit.

Thanh ghi thao tác bit của cổng (Bit Set/Reset Register – BSRR)

Bên cạnh ODR, STM32 còn cung cấp một thanh ghi rất hữu ích để thao tác từng bit độc lập, gọi là GPIOx_BSRR.

Khi ghi vào BSRR, bạn có thể set hoặc reset bit tương ứng mà không ảnh hưởng đến các bit khác – rất an toàn trong môi trường ngắt hoặc đa nhiệm.

img

Từ sơ đồ có thể thấy địa chỉ bù của thanh ghi điều khiển xuất trong cổng là 0x18, do đó địa chỉ của thanh ghi GPIOB_BSRR tương ứng là 0x4002 0400 + 0x18. Chúng ta tiếp tục xem giới thiệu về thanh ghi này, như hình đã chỉ rõ.

img

Có thể hiểu rằng thanh ghi GPIOx_BSRR là 32 bit, chia làm hai phần:

  • 16 bit thấp (bit 0–15): Mỗi bit tương ứng với một chân GPIO (Px0–Px15).
    • Ghi 1 vào bit này → chân tương ứng được đặt mức cao (1)
    • Ghi 0không ảnh hưởng gì, giữ nguyên trạng thái.
  • 16 bit cao (bit 16–31): Cũng tương ứng với các chân GPIO (Px0–Px15).
    • Ghi 1 vào bit này → chân tương ứng được đặt mức thấp (0)
    • Ghi 0giữ nguyên trạng thái.

Tóm lại:

  • Ghi vào bit 0–15 → để SET chân (mức cao)
  • Ghi vào bit 16–31 → để RESET chân (mức thấp)

Trong tệp tiêu đề của STM32 đã định nghĩa hầu hết các địa chỉ, vì vậy chúng ta chỉ cần gọi các định nghĩa địa chỉ đó là được.

c
GPIOB->BSRR |= (0x01 << (2 + 16));  // Ghi vào bit 18 → reset PB2
GPIOB->BSRR |= (0x01 << 2);        // Ghi vào bit 2 → set PB2

Điều gì xảy ra nếu bạn set và reset cùng lúc?

Ví dụ đoạn lệnh sau:

c
GPIOB->BSRR = (1 << 2) | (1 << (2 + 16));  // Set và reset PB2 cùng lúc
  • Bit 2 (Set PB2) = 1
  • Bit 18 (Reset PB2) = 1 Kết quả thực tế: phụ thuộc vào thời điểm mạch xử lý bit nào trước, có thể là set hoặc reset, hoặc trạng thái không rõ ràng (glitch, xung ngắn).
Thanh ghi đảo trạng thái các bit của cổng GPIO

Thanh ghi đảo bit (toggle) có chức năng tương tự như tên gọi – đảo ngược mức điện áp tại các chân tương ứng. Đây là thao tác rất tiện lợi và hữu ích khi cần thay đổi trạng thái nhanh chóng, như chớp LED, tạo xung vuông,…. Phần này sẽ không trình bày quá nhiều, các bạn có thể tự nghiên cứu thêm.

Hiện tượng thử nghiệm

Khi sử dụng đoạn mã đã trình bày ở phần trước, bạn cần lưu ý rằng các macro như RCC hay tên thanh ghi thường đã được định nghĩa sẵn trong các file tiêu đề của STM32 (CMSIS hoặc ST HAL). Do đó:

  • Không cần tự viết lại địa chỉ thủ công, trừ khi bạn muốn thao tác cực kỳ thấp mức.
  • Có thể tùy chỉnh tên hàm hoặc biến để dễ quản lý hơn, ví dụ: thêm tiền tố như LED_, BSP_ (Board Support Package), v.v.

Trong tài liệu đính kèm theo board phát triển STM32F407VET6 (bản Liêu Sơn Phi – Trời Bản Bộ), tại thư mục:

Phần 03 Tài liệu phần mềm / Mã ví dụ / 001 Đăng ký quạt

có chứa đoạn mã hoàn chỉnh để cấu hình GPIO và bật LED.

Sau khi biên dịch và nạp chương trình vào board, bạn sẽ thấy:

Đèn LED trên board sẽ sáng lên, chứng tỏ chân GPIO đã được cấu hình đúng và đang xuất mức cao.

Nếu LED không sáng → kiểm tra lại clock GPIO, mode, output type, và mức logic xuất ra.

Bài tập thực hành

Mục tiêu: Hiểu và áp dụng cấu hình GPIO để điều khiển LED, thay đổi trạng thái theo thời gian.

Yêu cầu:

  1. Sử dụng lại đoạn mã đã học để:
    • Cấu hình chân PB2 làm output (Push-Pull, tốc độ cao).
    • Bật sáng LED gắn với PB2.
  2. Viết thêm một đoạn chương trình đơn giản để:
    • Nhấp nháy LED PB2 với chu kỳ 500ms (bật–tắt luân phiên).
    • Dùng lệnh toggle với thanh ghi ODR hoặc viết thủ công bằng BSRR.

Gợi ý:

c
int main(void)
{
    HAL_Init();  // Bắt buộc để cấu hình SysTick cho HAL_Delay

    // Cấu hình GPIO
    
    
    
    
    
    
    

    while (1)
    {
                                                // Đảo trạng thái
        HAL_Delay(500);                         // Trễ 500ms
    }
}