Trong quá trình cấu hình GPIO Output STM32F103, việc nắm vững cách làm việc trực tiếp với thanh ghi là yếu tố then chốt để làm chủ vi điều khiển ở mức bare-metal. Bài viết này sẽ hướng dẫn anh em kỹ sư cách cấu hình chân GPIO làm output, cụ thể là chân PA8 trên STM32F103RCT6, và điều khiển nó nhấp nháy một cách đơn giản nhất.
1. Giới thiệu ngoại vi & Tại sao cần cấu hình?
GPIO (General Purpose Input/Output) là một trong những ngoại vi cơ bản và quan trọng nhất của bất kỳ vi điều khiển nào. Nó cho phép MCU tương tác với thế giới bên ngoài, từ việc điều khiển LED, relay, cho đến đọc trạng thái nút nhấn hay giao tiếp với các module ngoại vi khác. Trên STM32F103, mỗi chân GPIO có thể được cấu hình linh hoạt để hoạt động ở nhiều chế độ khác nhau, bao gồm input (floating, pull-up/pull-down, analog) và output (push-pull, open-drain) với các tốc độ khác nhau. Việc cấu hình đúng GPIO là nền tảng để các ứng dụng nhúng của chúng ta hoạt động như mong muốn.
Cấu hình GPIO Output
Trong bài viết này, chúng ta tập trung vào việc cấu hình chân PA8 (LED0) làm output và thực hiện chương trình bật/tắt LED bằng thanh ghi. Để làm được điều này, chúng ta cần:
- Bật clock cho cổng GPIOA.
- Cấu hình chân PA8 với chế độ Output Push-pull, tốc độ 2 MHz.
- Điều khiển trạng thái bật/tắt của LED thông qua thanh ghi dữ liệu output.

2. Define Struct thanh ghi kiểu Bit-field
Để truy cập các bit cụ thể trong thanh ghi một cách rõ ràng và an toàn, chúng ta sẽ định nghĩa các cấu trúc (struct) kiểu bit-field. Việc này giúp code của chúng ta dễ đọc, dễ bảo trì và hạn chế lỗi khi thao tác bit.
Define struct GPIO
Đây là cấu trúc tổng quát cho một cổng GPIO, bao gồm tất cả các thanh ghi liên quan:
// Trích xuất từ tài liệu RM0008, Section 9.5, Trang 194
// Bản đồ thanh ghi GPIO (GPIO Register Map)
typedef struct {
GPIO_CRL_Type CRL; // 0x00: Cấu hình thấp (Port configuration register low)
GPIO_CRH_Type CRH; // 0x04: Cấu hình cao (Port configuration register high)
__IM uint32_t IDR; // 0x08: Dữ liệu input (Port input data register)
GPIO_ODR_Type ODR; // 0x0C: Dữ liệu output (Port output data register)
GPIO_BSRR_Type BSRR; // 0x10: Set/Reset bit (Port bit set/reset register)
__OM uint32_t BRR; // 0x14: Reset bit (Port bit reset register)
__IOM uint32_t LCKR; // 0x18: Khóa cấu hình (Port configuration lock register)
} GPIO_TypeDef;
// Định nghĩa địa chỉ cơ sở cho các cổng GPIO
#define GPIOA ((GPIO_TypeDef *)0x40010800UL)
#define GPIOB ((GPIO_TypeDef *)0x40010C00UL)
#define GPIOC ((GPIO_TypeDef *)0x40011000UL)
Thanh ghi port configuration register low (GPIOx_CRL) (x=A..G)
Thanh ghi này dùng để cấu hình các chân GPIO từ 0 đến 7. Mỗi chân được điều khiển bởi 2 bit MODE và 2 bit CNF.

// Trích xuất từ tài liệu RM0008, Section 9.2.1, Trang 171
// Thanh ghi Cấu hình thấp GPIO (GPIO Configuration Low Register)
typedef union {
struct { // Structure for bit fields
__IOM uint32_t MODE0 : 2; // Bit 0-1: Pin 0 mode
__IOM uint32_t CNF0 : 2; // Bit 2-3: Pin 0 configuration
__IOM uint32_t MODE1 : 2; // Bit 4-5: Pin 1 mode
__IOM uint32_t CNF1 : 2; // Bit 6-7: Pin 1 configuration
__IOM uint32_t MODE2 : 2; // Bit 8-9: Pin 2 mode
__IOM uint32_t CNF2 : 2; // Bit 10-11: Pin 2 configuration
__IOM uint32_t MODE3 : 2; // Bit 12-13: Pin 3 mode
__IOM uint32_t CNF3 : 2; // Bit 14-15: Pin 3 configuration
__IOM uint32_t MODE4 : 2; // Bit 16-17: Pin 4 mode
__IOM uint32_t CNF4 : 2; // Bit 18-19: Pin 4 configuration
__IOM uint32_t MODE5 : 2; // Bit 20-21: Pin 5 mode
__IOM uint32_t CNF5 : 2; // Bit 22-23: Pin 5 configuration
__IOM uint32_t MODE6 : 2; // Bit 24-25: Pin 6 mode
__IOM uint32_t CNF6 : 2; // Bit 26-27: Pin 6 configuration
__IOM uint32_t MODE7 : 2; // Bit 28-29: Pin 7 mode
__IOM uint32_t CNF7 : 2; // Bit 30-31: Pin 7 configuration
};
__IOM uint32_t reg; // Access entire register
} GPIO_CRL_Type;
Thanh ghi port configuration register high (GPIOx_CRH) (x=A..G)
Thanh ghi này dùng để cấu hình các chân GPIO từ 8 đến 15. Tương tự CRL, mỗi chân được điều khiển bởi 2 bit MODE và 2 bit CNF.

// Trích xuất từ tài liệu RM0008, Section 9.2.2, Trang 172
// Thanh ghi Cấu hình cao GPIO (GPIO Configuration High Register)
typedef union {
struct {
__IOM uint32_t MODE8 : 2; // Bit 0-1: Pin 8 mode
__IOM uint32_t CNF8 : 2; // Bit 2-3: Pin 8 configuration
__IOM uint32_t MODE9 : 2; // Bit 4-5: Pin 9 mode
__IOM uint32_t CNF9 : 2; // Bit 6-7: Pin 9 configuration
__IOM uint32_t MODE10 : 2; // Bit 8-9: Pin 10 mode
__IOM uint32_t CNF10 : 2; // Bit 10-11: Pin 10 configuration
__IOM uint32_t MODE11 : 2; // Bit 12-13: Pin 11 mode
__IOM uint32_t CNF11 : 2; // Bit 14-15: Pin 11 configuration
__IOM uint32_t MODE12 : 2; // Bit 16-17: Pin 12 mode
__IOM uint32_t CNF12 : 2; // Bit 18-19: Pin 12 configuration
__IOM uint32_t MODE13 : 2; // Bit 20-21: Pin 13 mode
__IOM uint32_t CNF13 : 2; // Bit 22-23: Pin 13 configuration
__IOM uint32_t MODE14 : 2; // Bit 24-25: Pin 14 mode
__IOM uint32_t CNF14 : 2; // Bit 26-27: Pin 14 configuration
__IOM uint32_t MODE15 : 2; // Bit 28-29: Pin 15 mode
__IOM uint32_t CNF15 : 2; // Bit 30-31: Pin 15 configuration
};
__IOM uint32_t reg; // Access entire register
} GPIO_CRH_Type;
Thanh ghi dữ liệu ngõ ra (GPIOx_ODR)

// Trích xuất từ tài liệu RM0008, Section 9.2.4, Trang 173
// Thanh ghi Dữ liệu Output GPIO (GPIO Output Data Register)
typedef union {
struct {
__IOM uint32_t ODR0 : 1; // Bit 0: Output data for pin 0
__IOM uint32_t ODR1 : 1; // Bit 1: Output data for pin 1
__IOM uint32_t ODR2 : 1; // Bit 2: Output data for pin 2
__IOM uint32_t ODR3 : 1; // Bit 3: Output data for pin 3
__IOM uint32_t ODR4 : 1; // Bit 4: Output data for pin 4
__IOM uint32_t ODR5 : 1; // Bit 5: Output data for pin 5
__IOM uint32_t ODR6 : 1; // Bit 6: Output data for pin 6
__IOM uint32_t ODR7 : 1; // Bit 7: Output data for pin 7
__IOM uint32_t ODR8 : 1; // Bit 8: Output data for pin 8
__IOM uint32_t ODR9 : 1; // Bit 9: Output data for pin 9
__IOM uint32_t ODR10 : 1; // Bit 10: Output data for pin 10
__IOM uint32_t ODR11 : 1; // Bit 11: Output data for pin 11
__IOM uint32_t ODR12 : 1; // Bit 12: Output data for pin 12
__IOM uint32_t ODR13 : 1; // Bit 13: Output data for pin 13
__IOM uint32_t ODR14 : 1; // Bit 14: Output data for pin 14
__IOM uint32_t ODR15 : 1; // Bit 15: Output data for pin 15
uint32_t reserved1 : 16; // Bit 16-31: Reserved
};
__IOM uint32_t reg; // Access entire register
} GPIO_ODR_Type;
Thanh ghi Set/Reset bit (GPIOx_BSRR)
// Trích xuất từ tài liệu RM0008, Section 9.2.5, Trang 173
// Thanh ghi Set/Reset Bit GPIO (GPIO Bit Set/Reset Register)
typedef union {
struct {
__OM uint32_t BS0 : 1; // Bit 0: Set pin 0
__OM uint32_t BS1 : 1; // Bit 1: Set pin 1
__OM uint32_t BS2 : 1; // Bit 2: Set pin 2
__OM uint32_t BS3 : 1; // Bit 3: Set pin 3
__OM uint32_t BS4 : 1; // Bit 4: Set pin 4
__OM uint32_t BS5 : 1; // Bit 5: Set pin 5
__OM uint32_t BS6 : 1; // Bit 6: Set pin 6
__OM uint32_t BS7 : 1; // Bit 7: Set pin 7
__OM uint32_t BS8 : 1; // Bit 8: Set pin 8
__OM uint32_t BS9 : 1; // Bit 9: Set pin 9
__OM uint32_t BS10 : 1; // Bit 10: Set pin 10
__OM uint32_t BS11 : 1; // Bit 11: Set pin 11
__OM uint32_t BS12 : 1; // Bit 12: Set pin 12
__OM uint32_t BS13 : 1; // Bit 13: Set pin 13
__OM uint32_t BS14 : 1; // Bit 14: Set pin 14
__OM uint32_t BS15 : 1; // Bit 15: Set pin 15
__OM uint32_t BR0 : 1; // Bit 16: Reset pin 0
__OM uint32_t BR1 : 1; // Bit 17: Reset pin 1
__OM uint32_t BR2 : 1; // Bit 18: Reset pin 2
__OM uint32_t BR3 : 1; // Bit 19: Reset pin 3
__OM uint32_t BR4 : 1; // Bit 20: Reset pin 4
__OM uint32_t BR5 : 1; // Bit 21: Reset pin 5
__OM uint32_t BR6 : 1; // Bit 22: Reset pin 6
__OM uint32_t BR7 : 1; // Bit 23: Reset pin 7
__OM uint32_t BR8 : 1; // Bit 24: Reset pin 8
__OM uint32_t BR9 : 1; // Bit 25: Reset pin 9
__OM uint32_t BR10 : 1; // Bit 26: Reset pin 10
__OM uint32_t BR11 : 1; // Bit 27: Reset pin 11
__OM uint32_t BR12 : 1; // Bit 28: Reset pin 12
__OM uint32_t BR13 : 1; // Bit 29: Reset pin 13
__OM uint32_t BR14 : 1; // Bit 30: Reset pin 14
__OM uint32_t BR15 : 1; // Bit 31: Reset pin 15
};
__OM uint32_t reg; // Access entire register
} GPIO_BSRR_Type;
Thanh ghi Reset bit (GPIOx_BRR)
// Trích xuất từ tài liệu RM0008, Section 9.2.6, Trang 174
// Thanh ghi Reset Bit GPIO (GPIO Bit Reset Register)
typedef union {
struct {
__OM uint32_t BR0 : 1; // Bit 0: Reset pin 0
__OM uint32_t BR1 : 1; // Bit 1: Reset pin 1
__OM uint32_t BR2 : 1; // Bit 2: Reset pin 2
__OM uint32_t BR3 : 1; // Bit 3: Reset pin 3
__OM uint32_t BR4 : 1; // Bit 4: Reset pin 4
__OM uint32_t BR5 : 1; // Bit 5: Reset pin 5
__OM uint32_t BR6 : 1; // Bit 6: Reset pin 6
__OM uint32_t BR7 : 1; // Bit 7: Reset pin 7
__OM uint32_t BR8 : 1; // Bit 8: Reset pin 8
__OM uint32_t BR9 : 1; // Bit 9: Reset pin 9
__OM uint32_t BR10 : 1; // Bit 10: Reset pin 10
__OM uint32_t BR11 : 1; // Bit 11: Reset pin 11
__OM uint32_t BR12 : 1; // Bit 12: Reset pin 12
__OM uint32_t BR13 : 1; // Bit 13: Reset pin 13
__OM uint32_t BR14 : 1; // Bit 14: Reset pin 14
__OM uint32_t BR15 : 1; // Bit 15: Reset pin 15
uint32_t reserved1 : 16; // Bit 16-31: Reserved
};
__OM uint32_t reg; // Access entire register
} GPIO_BRR_Type;
Thanh ghi dữ liệu ngõ vào (GPIOx_IDR)

// Trích xuất từ tài liệu RM0008, Section 9.2.3, Trang 172
// Thanh ghi Dữ liệu Input GPIO (GPIO Input Data Register)
typedef union {
struct {
__IM uint32_t IDR0 : 1; // Bit 0: Input data for pin 0
__IM uint32_t IDR1 : 1; // Bit 1: Input data for pin 1
__IM uint32_t IDR2 : 1; // Bit 2: Input data for pin 2
__IM uint32_t IDR3 : 1; // Bit 3: Input data for pin 3
__IM uint32_t IDR4 : 1; // Bit 4: Input data for pin 4
__IM uint32_t IDR5 : 1; // Bit 5: Input data for pin 5
__IM uint32_t IDR6 : 1; // Bit 6: Input data for pin 6
__IM uint32_t IDR7 : 1; // Bit 7: Input data for pin 7
__IM uint32_t IDR8 : 1; // Bit 8: Input data for pin 8
__IM uint32_t IDR9 : 1; // Bit 9: Input data for pin 9
__IM uint32_t IDR10 : 1; // Bit 10: Input data for pin 10
__IM uint32_t IDR11 : 1; // Bit 11: Input data for pin 11
__IM uint32_t IDR12 : 1; // Bit 12: Input data for pin 12
__IM uint32_t IDR13 : 1; // Bit 13: Input data for pin 13
__IM uint32_t IDR14 : 1; // Bit 14: Input data for pin 14
__IM uint32_t IDR15 : 1; // Bit 15: Input data for pin 15
uint32_t reserved1 : 16; // Bit 16-31: Reserved
};
__IM uint32_t reg; // Access entire register
} GPIO_IDR_Type;
Thanh ghi Khóa cấu hình (GPIOx_LCKR)
// Trích xuất từ tài liệu RM0008, Section 9.2.7, Trang 174
// Thanh ghi Khóa cấu hình GPIO (GPIO Configuration Lock Register)
typedef union {
struct {
__IOM uint32_t LCK0 : 1; // Bit 0: Lock bit 0
__IOM uint32_t LCK1 : 1; // Bit 1: Lock bit 1
__IOM uint32_t LCK2 : 1; // Bit 2: Lock bit 2
__IOM uint32_t LCK3 : 1; // Bit 3: Lock bit 3
__IOM uint32_t LCK4 : 1; // Bit 4: Lock bit 4
__IOM uint32_t LCK5 : 1; // Bit 5: Lock bit 5
__IOM uint32_t LCK6 : 1; // Bit 6: Lock bit 6
__IOM uint32_t LCK7 : 1; // Bit 7: Lock bit 7
__IOM uint32_t LCK8 : 1; // Bit 8: Lock bit 8
__IOM uint32_t LCK9 : 1; // Bit 9: Lock bit 9
__IOM uint32_t LCK10 : 1; // Bit 10: Lock bit 10
__IOM uint32_t LCK11 : 1; // Bit 11: Lock bit 11
__IOM uint32_t LCK12 : 1; // Bit 12: Lock bit 12
__IOM uint32_t LCK13 : 1; // Bit 13: Lock bit 13
__IOM uint32_t LCK14 : 1; // Bit 14: Lock bit 14
__IOM uint32_t LCK15 : 1; // Bit 15: Lock bit 15
__IOM uint32_t LCKK : 1; // Bit 16: Lock key
uint32_t reserved1 : 15; // Bit 17-31: Reserved
};
__IOM uint32_t reg; // Access entire register
} GPIO_LCKR_Type;3. Giải thích chức năng từng thanh ghi liên quan
Dựa trên tài liệu RM0008, Section 9.2 (trang 171-177) và Section 7.3.7 (trang 112-113), chúng ta sẽ đi sâu vào chức năng của từng thanh ghi:
Ý nghĩa thanh ghi GPIO
- GPIOx_CRL / GPIOx_CRH (Port Configuration Register Low/High):
- Dùng để cấu hình chế độ hoạt động (MODE) và cấu hình đầu vào/ra (CNF) cho mỗi chân GPIO.
- MODE[1:0]: Định nghĩa tốc độ và chế độ output (00: Input, 01: Output 10 MHz, 10: Output 2 MHz, 11: Output 50 MHz).
- CNF[1:0]: Định nghĩa cấu hình input/output (00: General purpose push-pull output, 01: General purpose open-drain output, 10: Alternate function push-pull output, 11: Alternate function open-drain output cho output mode; 00: Analog mode, 01: Floating input, 10: Input with pull-up/pull-down cho input mode).
- GPIOx_ODR (Port Output Data Register):
- Dùng để đặt trạng thái logic (0 hoặc 1) cho các chân GPIO đã cấu hình là output.
- Các bit có thể đọc/ghi, tuy nhiên, việc ghi trực tiếp có thể gây ra lỗi read-modify-write trong môi trường đa luồng. Để đảm bảo an toàn, nên sử dụng BSRR.
- Khi chân được cấu hình là input, bit tương ứng trong ODR không điều khiển đầu ra, nhưng có tác dụng khi cấu hình input pull-up / pull-down.

Chú thích: Sơ đồ bit của thanh ghi GPIOx_ODR. - GPIOx_IDR (Port Input Data Register):
- Dùng để đọc trạng thái logic hiện tại (0 hoặc 1) của các chân GPIO đã cấu hình là input.
- Thanh ghi này chỉ cho phép đọc (read-only).

Chú thích: Sơ đồ bit của thanh ghi GPIOx_IDR. - GPIOx_BSRR (Port Bit Set/Reset Register):
- Cung cấp cơ chế nguyên tử (atomic) để set (BSy) hoặc reset (BRy) riêng lẻ từng bit trong thanh ghi ODR. Điều này giúp tránh các race condition khi nhiều tác vụ cùng truy cập ODR.
- Ghi ‘1’ vào BSy (bit 0-15) sẽ SET bit tương ứng trong ODR.
- Ghi ‘1’ vào BRy (bit 16-31) sẽ RESET bit tương ứng trong ODR.
- Ghi ‘0’ không có tác dụng.
- GPIOx_BRR (Port Bit Reset Register):
- Là một thanh ghi chỉ ghi (write-only) chuyên dùng để RESET riêng lẻ từng bit trong ODR.
- Ghi ‘1’ vào BRy (bit 0-15) sẽ RESET bit tương ứng trong ODR.
- Ghi ‘0’ không có tác dụng. (Lưu ý: Chức năng tương tự như các bit BR trong BSRR, thường được sử dụng khi chỉ cần Reset).
- GPIOx_LCKR (Port Configuration Lock Register):
- Cho phép khóa cấu hình của các chân GPIO, ngăn không cho thay đổi các thiết lập trong CRL và CRH cho đến khi reset vi điều khiển.
- Sử dụng một chuỗi ghi cụ thể vào bit LCKK (Bit 16) để kích hoạt/vô hiệu hóa chức năng khóa.
4. Quy trình cấu hình
Để cấu hình một chân GPIO làm Output, chúng ta cần thực hiện các bước sau (dựa trên RM0008, Section 9.1 và 9.2):
graph TD;
A["Bắt đầu"] --> B["1. Bật Clock cho GPIO Port (ví dụ: GPIOA)"];
B --> C["2. Cấu hình Chế độ (MODE) cho chân GPIO (ví dụ: PA8)"];
C --> D["3. Cấu hình Loại Output (CNF) cho chân GPIO (ví dụ: PA8)"];
D --> E["4. Điều khiển trạng thái Output (ODR, BSRR, BRR)"];
E --> F["Kết thúc"];
- Bật Clock cho GPIO Port: Mọi ngoại vi trên STM32 đều cần được cấp clock để hoạt động. Đối với GPIO, chúng ta sử dụng thanh ghi
RCC_APB2ENRđể bật clock cho cổng tương ứng (ví dụ: GPIOA). - Cấu hình Chế độ (MODE) cho chân GPIO: Sử dụng các bit
MODEy[1:0]trong thanh ghiGPIOx_CRL(cho chân 0-7) hoặcGPIOx_CRH(cho chân 8-15) để chọn chế độ Output với tốc độ mong muốn. - Cấu hình Loại Output (CNF) cho chân GPIO: Sử dụng các bit
CNFy[1:0]trongGPIOx_CRLhoặcGPIOx_CRHđể chọn giữa Push-pull hoặc Open-drain. - Điều khiển trạng thái Output: Sử dụng thanh ghi
GPIOx_ODRđể đặt giá trị (HIGH/LOW) cho chân, hoặc dùngGPIOx_BSRR/GPIOx_BRRđể set/reset bit một cách nguyên tử.
5. Code thực tế chạy trên Keil C
Bây giờ chúng ta sẽ áp dụng các kiến thức trên vào code thực tế để cấu hình chân PA8 làm output và điều khiển LED nhấp nháy.
Cấu hình chung cho hệ thống
// Cấu hình Clock hệ thống (có thể tham khảo bài viết cấu hình clock) - VÍ DỤ:
// Trích xuất từ tài liệu RM0008, Section 7.3.7, Trang 112
// Giả định có RCC_TypeDef và các Struct khác đã được define
// Ví dụ đơn giản bật HSE và PLL để chạy ở 72MHz
// Cần include "stm32f10x.h" hoặc định nghĩa tương đương.
void SystemClock_Config(void) {
// Bật HSE (High Speed External) oscillator
// Tham khảo RM0008, Section 7.3.1, Trang 99
RCC->CR.HSEON = 1;
// Đợi cho HSE ổn định và sẵn sàng
while (RCC->CR.HSERDY == 0);
// Cấu hình FLASH_ACR trước khi chuyển SYSCLK sang tần số cao
// Bật Prefetch Buffer để tăng hiệu suất
FLASH->ACR.PRFTBE = 1;
// Thiết lập 2 chu kỳ chờ (wait states) cho Flash khi SYSCLK = 72 MHz
FLASH->ACR.LATENCY = 2;
// Cấu hình PLL: Nguồn HSE, hệ số nhân x9
// Tham khảo RM0008, Section 7.3.2, Trang 101
RCC->CFGR.PLLSRC = 1; // Chọn HSE làm nguồn clock cho PLL
RCC->CFGR.PLLMUL = 7; // Hệ số nhân PLL x9 (0111b tương ứng x9)
RCC->CR.PLLON = 1; // Bật PLL
// Đợi cho PLL ổn định và sẵn sàng
while (RCC->CR.PLLRDY == 0);
// Đặt các bộ chia tần số (prescaler): HCLK=/1, PCLK1=/2, PCLK2=/1
// Tham khảo RM0008, Section 7.3.2, Trang 101-102
RCC->CFGR.HPRE = 0; // HCLK = SYSCLK (Không chia)
RCC->CFGR.PPRE1 = 4; // PCLK1 = HCLK/2 (Chia 2 cho APB1, max 36MHz)
RCC->CFGR.PPRE2 = 0; // PCLK2 = HCLK (Không chia cho APB2, max 72MHz)
// Chọn PLL làm System Clock (SYSCLK)
// Tham khảo RM0008, Section 7.3.2, Trang 101
RCC->CFGR.SW = 2; // Chọn PLL làm SYSCLK (10b)
// Đợi cho SYSCLK chuyển sang PLL
while (RCC->CFGR.SWS != 2);
}
Hàm delay (không chính xác)
void Delay_Approx(uint32_t ms) {
// Approximate delay using loop, ~1us per iteration at 72MHz
volatile uint32_t i;
for (i = 0; i < ms * 1000; i++);
}Code cấu hình GPIO Output cho PA8
void GPIO_LED_Config(void)
{
// Bật clock cho GPIOA (RM0008, Section 7.3.7, trang 112)
// Bit 2: IOPAEN
RCC->APB2ENR.IOPAEN = 1;
// Cấu hình PA8 làm Output Push-pull, tốc độ 2 MHz
// PA8 nằm trong thanh ghi GPIOA_CRH (chân 8-15)
GPIOA->CRH.MODE8 = 2; // MODE8 = 0b10 (Output mode, max speed 2 MHz)
GPIOA->CRH.CNF8 = 0; // CNF8 = 0b00 (General purpose output push-pull)
GPIOA->ODR.ODR8 = 1; // Set the initialize value as High (turn off LED)
// Let try to config LED1 (PD02) as Output push pull, 2MHz
// Enter your code here
}Chương trình chính (main.c)
int main(void)
{
SystemClock_Config(); // Initialize system clock
// Cấu hình chân GPIO PA8
GPIO_LED_Config();
while (1) {
// Toggle PA8
LED_On();
// LED_Off_LED1();
Delay_Approx(1000); // ~1s delay
LED_Off();
// LED_On_LED1();
Delay_Approx(1000);
}
}
Github link: Download code cấu hình GPIO Output Bật Tắt LED0 (PA08)
6. Cách kiểm tra cấu hình (Debugging)
Để kiểm tra xem cấu hình GPIO đã đúng hay chưa, chúng ta có thể sử dụng các công cụ debug như Keil uVision hoặc STM32CubeIDE. Các bước kiểm tra cơ bản:
- Kết nối Debugger: Sử dụng ST-Link hoặc J-Link để kết nối kit STM32 với máy tính.
- Khởi động Debug Session: Trong Keil, chọn Debug -> Start/Stop Debug Session.
- Kiểm tra thanh ghi: Mở cửa sổ Peripheral Viewer hoặc Memory Viewer và kiểm tra các thanh ghi sau:
- RCC_APB2ENR: Đảm bảo bit
IOPAEN(Bit 2) đã được SET lên 1. - GPIOA_CRH: Kiểm tra các bit
MODE8vàCNF8để xác nhận chúng đã được cấu hình thành0b10và0b00tương ứng. - GPIOA_ODR: Trong quá trình chạy, bạn có thể thấy bit
ODR8thay đổi giữa 0 và 1, tương ứng với việc LED bật/tắt.
- RCC_APB2ENR: Đảm bảo bit
- Đặt Breakpoint: Đặt breakpoint tại các dòng code thay đổi trạng thái LED và quan sát sự thay đổi của thanh ghi
GPIOA_ODRhoặc trạng thái thực tế của LED trên board.

7. Kết luận
Qua bài viết này, bạn đã nắm được cách lập trình STM32 thanh ghi để cấu hình GPIO Output STM32F103, từ việc định nghĩa các struct thanh ghi kiểu bit-field theo chuẩn RM0008, giải thích chức năng từng bit, cho đến việc viết code thực tế để điều khiển chân PA8 làm output và nhấp nháy LED. Việc cấu hình đúng MODE và CNF giúp chân GPIO hoạt động chính xác theo yêu cầu, đặc biệt trong các ứng dụng điều khiển LED.
Bên cạnh đó, ví dụ bật/tắt LED với hàm delay vòng lặp cho thấy hạn chế của phương pháp delay không chính xác, do phụ thuộc vào tần số hệ thống và tối ưu của compiler. Đây là điểm cần lưu ý khi lập trình STM32 bằng thanh ghi, nhất là trong các ứng dụng yêu cầu thời gian chính xác.
Tóm lại, việc hiểu rõ cách cấu hình output STM32F103 ở mức thanh ghi là nền tảng quan trọng, giúp bạn chủ động hơn trong việc tối ưu hiệu năng, kiểm soát phần cứng và phát triển các ứng dụng nhúng trên STM32 một cách hiệu quả và ổn định.
