Trong quá trình lập trình STM32F103 thanh ghi GPIO Input, việc hiểu rõ và làm việc trực tiếp với các thanh ghi ngoại vi là yếu tố then chốt để làm chủ vi điều khiển. Bài viết này sẽ đi sâu vào cách cấu hình chân GPIO làm input, đọc trạng thái nút nhấn thông qua thanh ghi GPIO_IDR, và điều khiển LED qua GPIO_ODR/BSRR trên STM32F103RCT6 mà không cần đến thư viện HAL.
1. Giới thiệu ngoại vi & Tại sao cần cấu hình GPIO Input?
GPIO (General Purpose Input/Output) là một trong những ngoại vi cơ bản nhưng vô cùng quan trọng, cho phép vi điều khiển tương tác với thế giới bên ngoài. Đối với chế độ Input, GPIO cho phép MCU đọc các tín hiệu số từ các thiết bị ngoại vi như nút nhấn, cảm biến, hoặc các module giao tiếp khác. Việc cấu hình chính xác các chân GPIO ở chế độ input là nền tảng để thu thập dữ liệu và đưa ra các quyết định trong chương trình.
Mục tiêu bài lập trình STM32F103 thanh ghi cấu hình GPIO input
- Cấu hình PC1 làm GPIO input pull-up.
- Điều khiển LED PA8 (LED0).
Chúng ta sẽ tập trung vào cấu hình chân PC1 làm đầu vào và PA8 làm đầu ra để minh họa. Để tìm hiểu thêm về kiến trúc tổng quan và các khái niệm cơ bản về lập trình thanh ghi, bạn có thể tham khảo hướng dẫn lập trình STM32F103 thanh ghi từ A-Z.

2. Define Struct thanh ghi kiểu Bit-field
Để truy cập các bit cụ thể trong các thanh ghi GPIO một cách dễ dàng và trực quan, chúng ta sử dụng cấu trúc bit-field trong C. Dưới đây là các định nghĩa struct cho các thanh ghi chính liên quan đến cấu hình GPIO Input STM32F103.
// 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;
// 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;
// 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;
// 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;
// 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;
// Trích xuất từ tài liệu RM0008, Section 7.3.7, Trang 112
// Thanh ghi Bật Clock Ngoại vi APB2 (RCC_APB2ENR)
typedef union {
struct {
__IOM uint32_t AFIOEN : 1; // Bit 0: Alternate function I/O clock enable
uint32_t reserved1 : 1; // Bit 1: Reserved
__IOM uint32_t IOPAEN : 1; // Bit 2: I/O port A clock enable
__IOM uint32_t IOPBEN : 1; // Bit 3: I/O port B clock enable
__IOM uint32_t IOPCEN : 1; // Bit 4: I/O port C clock enable
__IOM uint32_t IOPDEN : 1; // Bit 5: I/O port D clock enable
__IOM uint32_t IOPEEN : 1; // Bit 6: I/O port E clock enable
__IOM uint32_t IOPFEN : 1; // Bit 7: I/O port F clock enable
__IOM uint32_t IOPGEN : 1; // Bit 8: I/O port G clock enable
__IOM uint32_t ADC1EN : 1; // Bit 9: ADC1 interface clock enable
__IOM uint32_t ADC2EN : 1; // Bit 10: ADC2 interface clock enable
__IOM uint32_t TIM1EN : 1; // Bit 11: TIM1 timer clock enable
__IOM uint32_t SPI1EN : 1; // Bit 12: SPI1 clock enable
__IOM uint32_t TIM8EN : 1; // Bit 13: TIM8 timer clock enable
__IOM uint32_t USART1EN : 1; // Bit 14: USART1 clock enable
__IOM uint32_t ADC3EN : 1; // Bit 15: ADC3 interface clock enable
uint32_t reserved2 : 2; // Bit 16-18: Reserved
__IOM uint32_t TIM9EN : 1; // Bit 19: TIM9 timer clock enable
__IOM uint32_t TIM10EN : 1; // Bit 20: TIM10 timer clock enable
__IOM uint32_t TIM11EN : 1; // Bit 21: TIM11 timer clock enable
uint32_t reserved3 : 10; // Bit 22-31: Reserved
};
__IOM uint32_t reg; // Access entire register
} RCC_APB2ENR_Type;
// Định nghĩa cấu trúc con trỏ tới các thanh ghi GPIO
typedef struct {
__IOM GPIO_CRL_Type CRL; // Cấu hình chân GPIO 0-7
__IOM GPIO_CRH_Type CRH; // Cấu hình chân GPIO 8-15
__IM GPIO_IDR_Type IDR; // Đọc dữ liệu Input
__IOM GPIO_ODR_Type ODR; // Ghi dữ liệu Output / Cấu hình Pull-up/Pull-down
__OM GPIO_BSRR_Type BSRR; // Set/Reset bit một cách Atomic
__OM uint32_t BRR; // Reset bit (cho khả năng tương thích)
__IOM uint32_t LCKR; // Khóa cấu hình (Không sử dụng trong bài này)
} GPIO_TypeDef;
// Định nghĩa cấu trúc con trỏ tới thanh ghi RCC
typedef struct {
// ... các thanh ghi RCC khác ...
uint32_t reserved_base[6]; // Đệm cho các thanh ghi trước APB2ENR
__IOM RCC_APB2ENR_Type APB2ENR; // Thanh ghi kích hoạt clock ngoại vi APB2
// ... các thanh ghi RCC khác ...
} RCC_TypeDef;
// Định nghĩa địa chỉ cơ sở của các ngoại vi
#define PERIPH_BASE (0x40000000UL)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000UL)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000UL) // Địa chỉ GPIO Port C
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800UL) // Địa chỉ GPIO Port A
#define RCC_BASE (PERIPH_BASE + 0x21000UL) // Địa chỉ RCC
// Định nghĩa con trỏ tới các ngoại vi
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define RCC ((RCC_TypeDef *) RCC_BASE)
#define __IOM volatile
#define __IM volatile const
#define __OM volatile
3. Giải thích chức năng từng thanh ghi liên quan
Để cấu hình GPIO Input STM32F103 một cách chính xác, chúng ta cần hiểu rõ chức năng của từng thanh ghi điều khiển cổng GPIO và thanh ghi kích hoạt clock ngoại vi.
Các thanh ghi cấu hình GPIO Input trong lập trình STM32F103 thanh ghi
- GPIOx_CRL / GPIOx_CRH (Port configuration register low/high): (Trích xuất từ tài liệu RM0008, Section 9.2.1-9.2.2, Trang 196-197)
- Đây là các thanh ghi cấu hình chính cho các chân GPIO. Mỗi chân GPIO được điều khiển bởi 4 bit (2 bit MODE và 2 bit CNF).
- MODEy[1:0] (Mode bits): Xác định chế độ hoạt động của chân (Input, Output 10MHz, 2MHz, 50MHz). Đối với input, chúng ta sẽ đặt
00. - CNFy[1:0] (Configuration bits): Xác định loại input (Analog, Floating, Pull-up/Pull-down) hoặc loại output (Push-pull, Open-drain, Alternate function). Đối với input pull-up/pull-down, chúng ta sẽ đặt
10.
- RCC_APB2ENR (APB2 peripheral clock enable register): (Trích xuất từ tài liệu RM0008, Section 7.3.7, Trang 113)
- Thanh ghi này dùng để cấp xung clock cho các ngoại vi trên bus APB2, bao gồm các cổng GPIO (A, B, C, …). Bạn phải bật clock cho cổng GPIO tương ứng trước khi cấu hình hoặc sử dụng các chân của cổng đó. Ví dụ:
IOPAEN(Bit 2) cho GPIOA,IOPCEN(Bit 4) cho GPIOC.
- Thanh ghi này dùng để cấp xung clock cho các ngoại vi trên bus APB2, bao gồm các cổng GPIO (A, B, C, …). Bạn phải bật clock cho cổng GPIO tương ứng trước khi cấu hình hoặc sử dụng các chân của cổng đó. Ví dụ:
Thanh ghi GPIO_IDR trong STM32F103

GPIOx_IDR là một thanh ghi rất quan trọng trong bộ GPIO của vi điều khiển STM32F1. (Trích xuất từ tài liệu RM0008, Section 9.2.3, Trang 197)
- Chức năng: Dùng để đọc trạng thái logic tại các chân GPIO (ngõ vào). Thanh ghi này chỉ cho phép đọc (read-only) và phản ánh mức điện áp hiện tại trên các chân được cấu hình làm đầu vào.
- Mỗi bit trong GPIOx_IDR tương ứng với một chân pin của cổng đó (ví dụ: IDR0 cho PA0, IDR1 cho PA1, v.v.).
- Khi đọc GPIOx_IDR, ta biết được mức logic hiện tại của chân: 0 (chân ở mức thấp – GND) hoặc 1 (chân ở mức cao – 3.3V).
Ví dụ thanh ghi IDR
GPIOA_IDR = 0b0000 0000 0001 0100
Nghĩa là:
- PA2 = 1 (mức cao)
- PA4 = 1 (mức cao)
- Các chân còn lại = 0
Thanh ghi GPIO_ODR – Thanh ghi dữ liệu ngõ ra

GPIOx_ODR dùng để: (Trích xuất từ tài liệu RM0008, Section 9.2.4, Trang 198)
- Ghi giá trị logic ra các chân output.
- Hoặc đọc lại trạng thái đang được xuất ra (nếu cấu hình chân là output).
- Khi một chân GPIO được cấu hình là output (push-pull hoặc open-drain), thanh ghi GPIOx_ODR quyết định mức logic xuất ra.
- Khi chân được cấu hình là input (với pull-up/pull-down), bit tương ứng trong ODR không điều khiển đầu ra, nhưng có tác dụng kích hoạt điện trở pull-up / pull-down nội. Cụ thể, đặt ODRx = 1 sẽ kích hoạt pull-up, đặt ODRx = 0 sẽ kích hoạt pull-down.
Ví dụ thanh ghi ODR
GPIOA_ODR = 0b0000 0001 0000 0010
Nghĩa là:
- PA1 = mức 1 (cao)
- PA8 = mức 1 (cao)
- Các chân khác = mức 0 (thấp)
4. Quy trình cấu hình GPIO Input STM32F103
Quy trình cấu hình GPIO làm đầu vào (Input) được thực hiện theo các bước sau, dựa trên kiến trúc và mô tả trong tài liệu RM0008 (Section 9.1 và 9.2).
graph TD;
A["Bắt đầu"] --> B["Kích hoạt Clock cho Port GPIO (RCC_APB2ENR)"];
B --> C["Cấu hình chân Input (GPIOx_CRL/CRH)"];
C --> D{"Chọn chế độ Input?
(MODEy = 00)"};
D -- Có --> E{"Chọn loại Input?
(CNFy = 01: Floating
CNFy = 10: Pull-up/Pull-down)"};
E --> F["Nếu là Pull-up/Pull-down, cấu hình GPIOx_ODR
(ODRy = 1 cho Pull-up, ODRy = 0 cho Pull-down)"];
F --> G["Đọc trạng thái Input (GPIOx_IDR)"];
G --> H["Kết thúc"];
Các bước cấu hình GPIO Input cho STM32F103
- Bật Clock cho cổng GPIO: Kích hoạt clock cho cổng GPIO tương ứng (GPIOA, GPIOC trong trường hợp này) bằng cách set bit trong thanh ghi
RCC_APB2ENR. - Cấu hình chân GPIO ở chế độ Input: Sử dụng thanh ghi
GPIOx_CRL(cho các chân từ Px0 đến Px7) hoặcGPIOx_CRH(cho các chân từ Px8 đến Px15) để đặt các bitMODEyvề00(Input mode). - Cấu hình loại Input (CNFy):
01: Input Floating (mặc định sau reset).10: Input Pull-up / Pull-down.
- Kích hoạt Pull-up/Pull-down (nếu cần): Nếu chọn loại Input Pull-up / Pull-down, sử dụng thanh ghi
GPIOx_ODR:- Set bit
ODRytương ứng thành1để kích hoạt pull-up. - Clear bit
ODRytương ứng thành0để kích hoạt pull-down.
- Set bit
5. Code thực tế chạy trên Keil C
Dưới đây là đoạn mã C minh họa việc cấu hình GPIO Input STM32F103 để đọc nút nhấn PC1 và điều khiển LED PA8.
Code cấu hình GPIO input PC1
void GPIO_Button_Config(void) {
// Bật clock cho GPIOC (RM0008, Section 7.3.7, Trang 113)
RCC->APB2ENR.IOPCEN = 1;
// Cấu hình PC1 làm Input với Pull-up
// MODE1 = 00 (Input mode)
GPIOC->CRL.MODE1 = 0;
// CNF1 = 10 (Input with pull-up/pull-down)
GPIOC->CRL.CNF1 = 2;
// ODR1 = 1 (Kích hoạt Pull-up cho PC1)
GPIOC->ODR.ODR1 = 1;
}
Hàm đảo trạng thái LED PA8
void LED_Toggle(void) {
// Đảo trạng thái PA8
if (GPIOA->ODR.ODR8) {
GPIOA->BSRR.BR8 = 1; // Đặt chân PA8 mức thấp (Clear ODR8)
} else {
GPIOA->BSRR.BS8 = 1; // Đặt chân PA8 mức cao (Set ODR8)
}
}
Chương trình chính (main.c)
Trong hàm main, chúng ta sẽ gọi các hàm cấu hình và thực hiện vòng lặp chính để đọc trạng thái nút nhấn và điều khiển LED.
// Giả định các hàm SystemClock_Config() và GPIO_LED_Config() đã được định nghĩa
// và hoạt động đúng (ví dụ: cấu hình PA8 làm Output Push-pull).
int main(void) {
SystemClock_Config(); // Cấu hình clock hệ thống (ví dụ: 72MHz)
GPIO_LED_Config(); // Cấu hình PA8 làm output cho LED
GPIO_Button_Config(); // Cấu hình PC1 làm input nút nhấn
while (1) {
// Kiểm tra nếu PC1 ở mức thấp (nút nhấn được nhấn)
// IDR1 == 0 khi nút nhấn được nhấn (do pull-up)
if (GPIOC->IDR.IDR1 == 0) {
LED_Toggle();
// Thêm một độ trễ nhỏ để chống dội nút nhấn
// (Delay_Approx là hàm delay giả định)
Delay_Approx(30); // Debounce delay 0.03s
}
}
}
Bạn có thể tham khảo mã nguồn đầy đủ và chi tiết tại kho GitHub: Download code chương trình cấu hình GPIO Input STM32f103RCT6.
Chống dội nút nhấn (Debounce) bằng Delay
Delay_Approx(30);Hàm tạo trễ khoảng 30ms (0.03 giây).
Mục đích:
- Chống dội nút nhấn (debounce)
- Tránh LED đổi trạng thái nhiều lần do nhiễu khi nhấn nút
Trong thời gian delay 30ms, dù nút còn dội hay không, chương trình không phản ứng.
Vấn đề của cách chống dội đơn giản
Chương trình chống dội trên có thể hoạt động không chính xác:
- Nút nhấn cơ học không thay đổi tín hiệu ngay lập tức
- Khi nhấn hoặc nhả, tín hiệu dao động trong vài ms đến vài chục ms
Nhấn nút thật:
_______ ________
__| |_______|
Tín hiệu thực tế:
__|_||_|_|_|||_|__|____|_|_|__|
Hạn chế:
- Trong 30ms delay, nếu nút vẫn bị dội → LED có thể toggle thêm
- Nhấn nhanh hơn 30ms → bị bỏ qua
- Không kiểm tra cạnh → giữ nút vẫn bị toggle liên tục
Bạn hãy thử tìm cách cải thiện code, để tránh trường hợp giả nhấn khi thả nút nha ?
Chương trình chống dội cải thiện (Dò cạnh + debounce)
// Check if PC1 is low (button pressed)
uint32_t ButtonState = !((GPIOC->IDR.reg >> 1) & 1U);
if ((ButtonState) & (lastKeyState == 0)) // Detect edge
{
Delay_Approx(30); // Debounce delay 0.03s
if (!((GPIOC->IDR.reg >> 1) & 1U)) // Check the button still pressed after delay
{
LED_Toggle();
}
}
lastKeyState = ButtonState; // Save last key state
Cải thiện:
- Dùng biến lastKeyState để phát hiện cạnh
- Chỉ toggle khi từ 1 → 0
- Chỉ toggle 1 lần cho mỗi lần nhấn
- Không toggle khi giữ nút
- Lọc nhiễu tốt cả khi nhấn và nhả
6. Cách kiểm tra cấu hình GPIO Input (Debugging)
Để kiểm tra xem cấu hình GPIO Input STM32F103 của bạn đã đúng hay chưa, bạn có thể sử dụng các công cụ debug như Keil uVision để xem trực tiếp trạng thái của các thanh ghi.
Giải thích đọc GPIO input bằng IDR
Trong chương trình chính, dòng code đọc nút nhấn là if (GPIOC->IDR.IDR1 == 0).
- GPIOC->IDR.IDR1: Đây là cách truy cập trực tiếp bit 1 của thanh ghi Input Data Register (IDR) của Port C, nhờ vào cấu trúc bit-field đã định nghĩa.
- Khi nút nhấn được nhấn, chân PC1 sẽ được kéo xuống mức thấp (0V). Do PC1 được cấu hình là input pull-up, bình thường nó sẽ ở mức cao (3.3V). Vì vậy, khi đọc
GPIOC->IDR.IDR1 == 0nghĩa là nút nhấn đang được nhấn.
Trong Keil C, bạn có thể mở cửa sổ Peripheral Viewer (Debug -> Peripherals -> GPIO) hoặc Memory View để kiểm tra giá trị của các thanh ghi GPIOC_CRL, GPIOC_ODR, và GPIOC_IDR trong thời gian chạy. Điều này giúp bạn xác minh rằng các bit cấu hình đã được thiết lập đúng và giá trị đọc từ IDR thay đổi như mong đợi khi nút nhấn được tác động.
7. Kết luận
Qua bài viết này, bạn đã nắm được cách lập trình STM32F103 thanh ghi để cấu hình GPIO Input STM32F103RCT6, đọc trạng thái nút nhấn bằng GPIO_IDR và điều khiển LED thông qua GPIO_ODR/BSRR. Việc làm việc trực tiếp với các thanh ghi không chỉ giúp bạn hiểu sâu sắc hơn về kiến trúc của STM32 mà còn tối ưu hóa hiệu suất và kích thước mã nguồn. Đây là nền tảng vững chắc để bạn tiếp tục phát triển các ứng dụng nhúng phức tạp hơn với STM32 bare-metal.
Bài viết tiếp theo tôi sẽ giới thiệu các phương pháp chống dội nút nhấn bằng phần mềm khác. Bạn có thể tìm hiểu thêm về các kỹ thuật chống dội nút nhấn tại Các phương pháp chống dội nút nhấn STM32F103.
Tham khảo: RM0008 – Section 9.2 (trang 196–198) và Section 7.3.7 (trang 113). Mã nguồn tham khảo chi tiết có sẵn tại đây.
