Lập trình STM32F103 Thanh Ghi: Cấu hình Counter Mode STM32F103RCT6

Trong quá trình cấu hình Timer Counter Mode STM32F103, việc hiểu và lập trình trực tiếp các thanh ghi là yếu tố then chốt để khai thác tối đa tiềm năng của vi điều khiển. Bài viết này sẽ cung cấp một hướng dẫn chi tiết, từ cơ bản đến nâng cao, về cách thiết lập chế độ đếm sự kiện từ tín hiệu ngoài trên STM32F103RCT6, giúp bạn dễ dàng áp dụng vào các dự án nhúng phức tạp.

1. Giới thiệu ngoại vi & Tại sao cần cấu hình Timer Counter Mode STM32F103?

Giới thiệu về Counter Mode trên STM32F103

Counter Mode (external signal on TI2) là chế độ mà timer (như TIM2) không sử dụng clock nội bộ mà đếm dựa trên tín hiệu ngoài. Ví dụ điển hình là cấu hình Counter Mode STM32F103 với SMS = 7 (external clock mode 1), TS = 6 (TI2FP2). Trong chế độ này, timer hoạt động như một bộ đếm (counter) thay vì tạo tín hiệu định kỳ như Timer Mode. Thay vì sử dụng clock nội bộ (đồng hồ hệ thống), bạn có thể cấu hình timer để đếm các sự kiện từ một tín hiệu ngoài (external signal) thông qua các chân đầu vào như TI1 hoặc TI2.
Ví dụ: Mỗi khi có 1 xung logic (ví dụ: rising edge) ở chân PA1 (TIM2_CH2), thanh ghi đếm CNT của TIM2 sẽ tăng lên 1. Điều này làm cho cấu hình Counter Mode STM32F103RCT6 trở nên lý tưởng cho các ứng dụng thực tế, giúp tối ưu hóa hiệu suất hệ thống mà không cần phần mềm can thiệp liên tục.

Ứng dụng của Counter Mode

Cấu hình Counter Mode STM32F103 được áp dụng rộng rãi trong các lĩnh vực sau:

  • Đếm số xung encoder (bánh xe quay) để đo lường vị trí hoặc tốc độ quay.
  • Đếm số sản phẩm (sensor quang, cảm biến hồng ngoại) trong dây chuyền sản xuất tự động.
  • Đếm tần số hoặc xung từ cảm biến tốc độ, hữu ích trong hệ thống giám sát xe hơi hoặc máy móc công nghiệp.
  • Theo dõi sự kiện từ các thiết bị ngoại vi như nút bấm hoặc cảm biến chuyển động, giúp giảm tải cho CPU.

Bằng cách sử dụng cấu hình Counter Mode STM32F103RCT6, bạn có thể xử lý dữ liệu thời gian thực mà không làm gián đoạn các nhiệm vụ khác của microcontroller.

2. Hướng dẫn đọc Reference Manual (Self-study Guidance)

Để thực sự làm chủ ngoại vi này, bạn hãy mở tài liệu RM0008 của STMicroelectronics, sử dụng tính năng tìm kiếm (Ctrl + F) với các từ khóa như TIM2 general-purpose timers, RCC APB1ENR, GPIO Port A configuration register low. Hãy tập trung tìm kiếm Block Diagram của ngoại vi Timer để hiểu luồng tín hiệu và cách tín hiệu ngoài được đưa vào bộ đếm. Điều này sẽ củng cố kiến thức về cách các thanh ghi tương tác với nhau ở cấp độ phần cứng.

3. Define Struct thanh ghi kiểu Bit-field

Định nghĩa Struct cho RCC – Clock Control Register

// Trích xuất từ tài liệu RM0008, Section 7.3.8, Trang 115
// Thanh ghi Bật Clock Ngoại vi APB1 (RCC_APB1ENR)
typedef union {
    struct {
        __IOM uint32_t TIM2EN : 1; // Bit 0: TIM2 timer clock enable
        __IOM uint32_t TIM3EN : 1; // Bit 1: TIM3 timer clock enable
        __IOM uint32_t TIM4EN : 1; // Bit 2: TIM4 timer clock enable
        __IOM uint32_t TIM5EN : 1; // Bit 3: TIM5 timer clock enable
        __IOM uint32_t TIM6EN : 1; // Bit 4: TIM6 timer clock enable
        __IOM uint32_t TIM7EN : 1; // Bit 5: TIM7 timer clock enable
        __IOM uint32_t TIM12EN : 1; // Bit 6: TIM12 timer clock enable
        __IOM uint32_t TIM13EN : 1; // Bit 7: TIM13 timer clock enable
        __IOM uint32_t TIM14EN : 1; // Bit 8: TIM14 timer clock enable
        uint32_t reserved1 : 2;    // Bit 9-10: Reserved
        __IOM uint32_t WWDGEN : 1; // Bit 11: Window watchdog clock enable
        uint32_t reserved2 : 2;    // Bit 12-13: Reserved
        __IOM uint32_t SPI2EN : 1; // Bit 14: SPI2 clock enable
        __IOM uint32_t SPI3EN : 1; // Bit 15: SPI3 clock enable
        uint32_t reserved3 : 1;    // Bit 16: Reserved
        __IOM uint32_t USART2EN : 1; // Bit 17: USART2 clock enable
        __IOM uint32_t USART3EN : 1; // Bit 18: USART3 clock enable
        __IOM uint32_t UART4EN : 1; // Bit 19: UART4 clock enable
        __IOM uint32_t UART5EN : 1; // Bit 20: UART5 clock enable
        __IOM uint32_t I2C1EN : 1; // Bit 21: I2C1 clock enable
        __IOM uint32_t I2C2EN : 1; // Bit 22: I2C2 clock enable
        __IOM uint32_t USBEN : 1;  // Bit 23: USB clock enable
        uint32_t reserved4 : 1;    // Bit 24: Reserved
        __IOM uint32_t CANEN : 1;  // Bit 25: CAN clock enable
        uint32_t reserved5 : 1;    // Bit 26: Reserved
        __IOM uint32_t BKPEN : 1;  // Bit 27: Backup interface clock enable
        __IOM uint32_t PWREN : 1;  // Bit 28: Power interface clock enable
        __IOM uint32_t DACEN : 1;  // Bit 29: DAC interface clock enable
        uint32_t reserved6 : 2;    // Bit 30-31: Reserved
    };
    __IOM uint32_t reg; // Access entire register
} RCC_APB1ENR_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;

// RCC Base Address and Peripheral Definition
#define RCC_BASE              (0x40021000UL)
#define RCC                   ((RCC_TypeDef *) RCC_BASE)

// Trích xuất từ tài liệu RM0008, Section 7.3.11, Trang 122
// Bản đồ thanh ghi RCC (Reset and Clock Control)
typedef struct {
    volatile RCC_CR_Type CR;         // 0x00: Clock control register
    volatile RCC_CFGR_Type CFGR;     // 0x04: Clock configuration register
    volatile RCC_CIR_Type CIR;       // 0x08: Clock interrupt register
    volatile RCC_APB2RSTR_Type APB2RSTR; // 0x0C: APB2 peripheral reset register
    volatile RCC_APB1RSTR_Type APB1RSTR; // 0x10: APB1 peripheral reset register
    volatile RCC_AHBENR_Type AHBENR;  // 0x14: AHB peripheral clock enable register
    volatile RCC_APB2ENR_Type APB2ENR; // 0x18: APB2 peripheral clock enable register
    volatile RCC_APB1ENR_Type APB1ENR; // 0x1C: APB1 peripheral clock enable register
    volatile RCC_BDCR_Type BDCR;      // 0x20: Backup domain control register
    volatile RCC_CSR_Type CSR;       // 0x24: Control/status register
} RCC_TypeDef;

Định nghĩa Struct cho GPIOA – General Purpose Input/Output Port A

// Trích xuất từ tài liệu RM0008, Section 9.2.1, Trang 171
// Thanh ghi Cấu hình Cổng I/O Thấp (Port configuration register low - GPIOx_CRL) (x=A..G)
typedef union {
    struct { // Structure for bit fields
        // Bit 0-7: Pin 0 configuration
        __IOM uint32_t MODE0 : 2; // Bit 0-1: Pin 0 mode
        __IOM uint32_t CNF0  : 2; // Bit 2-3: Pin 0 configuration
        // Bit 8-15: Pin 1 configuration
        __IOM uint32_t MODE1 : 2; // Bit 4-5: Pin 1 mode
        __IOM uint32_t CNF1  : 2; // Bit 6-7: Pin 1 configuration
        // Bit 16-23: Pin 2 configuration
        __IOM uint32_t MODE2 : 2; // Bit 8-9: Pin 2 mode
        __IOM uint32_t CNF2  : 2; // Bit 10-11: Pin 2 configuration
        // Bit 24-31: Pin 3 configuration
        __IOM uint32_t MODE3 : 2; // Bit 12-13: Pin 3 mode
        __IOM uint32_t CNF3  : 2; // Bit 14-15: Pin 3 configuration
        // Bit 32-39: Pin 4 configuration
        __IOM uint32_t MODE4 : 2; // Bit 16-17: Pin 4 mode
        __IOM uint32_t CNF4  : 2; // Bit 18-19: Pin 4 configuration
        // Bit 40-47: Pin 5 configuration
        __IOM uint32_t MODE5 : 2; // Bit 20-21: Pin 5 mode
        __IOM uint32_t CNF5  : 2; // Bit 22-23: Pin 5 configuration
        // Bit 48-55: Pin 6 configuration
        __IOM uint32_t MODE6 : 2; // Bit 24-25: Pin 6 mode
        __IOM uint32_t CNF6  : 2; // Bit 26-27: Pin 6 configuration
        // Bit 56-63: Pin 7 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;

// GPIOA Base Address and Peripheral Definition
#define GPIOA_BASE            (0x40010800UL)
#define GPIOA                 ((GPIO_TypeDef *) GPIOA_BASE)

// 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 {
    __IOM GPIO_CRL_Type CRL;        /*!< GPIO port configuration register low,      Address offset: 0x00 */
    __IOM uint32_t CRH;        /*!< GPIO port configuration register high,     Address offset: 0x04 */
    __IM  uint32_t IDR;        /*!< GPIO port input data register,             Address offset: 0x08 */
    __IOM uint32_t ODR;        /*!< GPIO port output data register,            Address offset: 0x0C */
    __IOM uint32_t BSRR;       /*!< GPIO port bit set/reset register,          Address offset: 0x10 */
    __IOM uint32_t BRR;        /*!< GPIO port bit reset register,              Address offset: 0x14 */
    __IOM uint32_t LCKR;       /*!< GPIO port configuration lock register,     Address offset: 0x18 */
} GPIO_TypeDef;

Định nghĩa Struct cho TIM2 – General Purpose Timer 2

// Trích xuất từ tài liệu RM0008, Section 15.4.1, Trang 404
// Thanh ghi Điều khiển 1 (Control register 1 - TIMx_CR1)
typedef union {
    struct {
        __IOM uint32_t CEN : 1; // Bit 0: Counter enable
        __IOM uint32_t UDIS : 1; // Bit 1: Update disable
        __IOM uint32_t URS : 1; // Bit 2: Update request source
        __IOM uint32_t OPM : 1; // Bit 3: One-pulse mode
        __IOM uint32_t DIR : 1; // Bit 4: Counter direction
        __IOM uint32_t CMS : 2; // Bit 5-6: Center-aligned mode selection
        __IOM uint32_t ARPE : 1; // Bit 7: Auto-reload preload enable
        __IOM uint32_t CKD : 2; // Bit 8-9: Clock division
        uint32_t reserved : 22; // Bit 10-31: Reserved
    };
    __IOM uint32_t reg; // Access entire register
} TIM_CR1_Type;

// Trích xuất từ tài liệu RM0008, Section 15.4.3, Trang 407
// Thanh ghi Điều khiển Chế độ Slave (Slave mode control register - TIMx_SMCR)
typedef union {
    struct {
        __IOM uint32_t SMS : 3; // Bit 0-2: Slave mode selection
        uint32_t reserved0 : 1; // Bit 3: Reserved
        __IOM uint32_t TS : 3; // Bit 4-6: Trigger selection
        __IOM uint32_t MSM : 1; // Bit 7: Master/slave mode
        __IOM uint32_t ETF : 4; // Bit 8-11: External trigger filter
        __IOM uint32_t ETPS : 2; // Bit 12-13: External trigger prescaler
        __IOM uint32_t ECE : 1; // Bit 14: External clock enable
        __IOM uint32_t ETP : 1; // Bit 15: External trigger polarity
        uint32_t reserved1 : 16; // Bit 16-31: Reserved
    };
    __IOM uint32_t reg; // Access entire register
} TIM_SMCR_Type;

// Trích xuất từ tài liệu RM0008, Section 15.4.7 (Input capture mode), Trang 413
// Thanh ghi Chế độ Capture/Compare 1 (Capture/compare mode register 1 - TIMx_CCMR1)
typedef union {
    struct {
        __IOM uint32_t CC1S    : 2; // Bit 1:0: Capture/Compare 1 selection
        __IOM uint32_t IC1PSC  : 2; // Bit 3:2: Input capture 1 prescaler
        __IOM uint32_t IC1F    : 4; // Bit 7:4: Input capture 1 filter
        __IOM uint32_t CC2S    : 2; // Bit 9:8: Capture/Compare 2 selection
        __IOM uint32_t IC2PSC  : 2; // Bit 11:10: Input capture 2 prescaler
        __IOM uint32_t IC2F    : 4; // Bit 15:12: Input capture 2 filter
        uint32_t reserved1   : 16; // Bit 16-31: Reserved
    };
    __IOM uint32_t reg; // Access entire register
} TIM_CCMR1_Type;

// Trích xuất từ tài liệu RM0008, Section 15.4.9, Trang 417
// Thanh ghi Kích hoạt Capture/Compare (Capture/compare enable register - TIMx_CCER)
typedef union {
    struct {
        __IOM uint32_t CC1E    : 1; // Bit 0: Capture/Compare 1 output enable
        __IOM uint32_t CC1P    : 1; // Bit 1: Capture/Compare 1 output Polarity
        uint32_t reserved0   : 2; // Bit 2-3: Reserved
        __IOM uint32_t CC2E    : 1; // Bit 4: Capture/Compare 2 output enable
        __IOM uint32_t CC2P    : 1; // Bit 5: Capture/Compare 2 output Polarity
        uint32_t reserved1   : 2; // Bit 6-7: Reserved
        __IOM uint32_t CC3E    : 1; // Bit 8: Capture/Compare 1 output enable
        __IOM uint32_t CC3P    : 1; // Bit 9: Capture/Compare 1 output Polarity
        uint32_t reserved2   : 2; // Bit 10-11: Reserved
        __IOM uint32_t CC4E    : 1; // Bit 12: Capture/Compare 1 output enable
        __IOM uint32_t CC4P    : 1; // Bit 13: Capture/Compare 1 output Polarity
        uint32_t reserved3   : 18; // Bit 14-31: Reserved
    };
    __IOM uint32_t reg; // Access entire register
} TIM_CCER_Type;

// TIM2 Base Address and Peripheral Definition
#define TIM2_BASE             (0x40000000UL)
#define TIM2                  ((TIM_TypeDef *) TIM2_BASE)

// Bản đồ thanh ghi TIM (15.4.19 TIMx register map – Trang 423)
typedef struct {
    __IOM TIM_CR1_Type CR1; // 0x00: Control 1
    __IOM uint32_t CR2; // 0x04: Control 2
    __IOM TIM_SMCR_Type SMCR; // 0x08: Slave mode control
    __IOM uint32_t DIER; // 0x0C: DMA/interrupt enable
    __IOM uint32_t SR; // 0x10: Status
    __IOM uint32_t EGR; // 0x14: Event generation
    __IOM TIM_CCMR1_Type CCMR1; // 0x18: Capture/compare mode 1
    __IOM uint32_t CCMR2; // 0x1C: Capture/compare mode 2
    __IOM TIM_CCER_Type CCER; // 0x20: Capture/compare enable
    __IOM uint32_t CNT; // 0x24: Counter value
    __IOM uint32_t PSC; // 0x28: Prescaler
    __IOM uint32_t ARR; // 0x2C: Auto-reload
    __IOM uint32_t RCR; // 0x30: Repetition counter
    __IOM uint32_t CCR1; // 0x34: Capture/compare 1
    __IOM uint32_t CCR2; // 0x38: Capture/compare 2
    __IOM uint32_t CCR3; // 0x3C: Capture/compare 3
    __IOM uint32_t CCR4; // 0x40: Capture/compare 4
    __IOM uint32_t BDTR; // 0x44: Break and dead-time
    __IOM uint32_t DCR; // 0x48: DMA control
    __IOM uint32_t DMAR; // 0x4C: DMA address
} TIM_TypeDef;

4. Giải thích chuyên sâu (What – Why – How) chức năng từng thanh ghi liên quan

Thanh ghi RCC_APB1ENR (Bit TIM2EN)

  • What: Bit TIM2EN (Timer 2 clock enable) tại vị trí Bit 0 của thanh ghi RCC_APB1ENR dùng để kích hoạt xung nhịp (clock) cho bộ đếm Timer 2. Khi bit này được set lên 1, module TIM2 sẽ nhận được clock từ bus APB1 và sẵn sàng để hoạt động.
  • Why (Circuit-level): Trong kiến trúc vi điều khiển, mỗi ngoại vi đều cần có một nguồn xung nhịp để hoạt động. Việc điều khiển bật/tắt clock thông qua thanh ghi RCC_APB1ENR không chỉ giúp tiết kiệm điện năng bằng cách vô hiệu hóa clock cho các ngoại vi không sử dụng, mà còn là một cơ chế an toàn để đảm bảo rằng ngoại vi không hoạt động ngẫu nhiên trước khi được cấu hình đầy đủ. Nếu quên bật clock, ngoại vi sẽ không phản hồi hoặc hoạt động không chính xác, dẫn đến các lỗi khó debug.
  • How (Ví dụ):
    RCC->APB1ENR.TIM2EN = 1; // Kích hoạt clock cho TIM2

Thanh ghi RCC_APB2ENR (Bit IOPAEN)

  • What: Bit IOPAEN (IO port A clock enable) tại vị trí Bit 2 của thanh ghi RCC_APB2ENR dùng để kích hoạt xung nhịp cho cổng GPIOA. Điều này là cần thiết khi bạn muốn sử dụng bất kỳ chân nào thuộc Port A (ví dụ PA1) làm đầu vào hoặc đầu ra.
  • Why (Circuit-level): Tương tự như TIM2, mỗi cổng GPIO cũng cần clock để các flip-flop và logic gate bên trong hoạt động đúng cách, cho phép cấu hình và điều khiển trạng thái các chân. Bật clock cho GPIOA là bước đầu tiên để chân PA1 có thể nhận diện và xử lý tín hiệu từ bên ngoài.
  • How (Ví dụ):
    RCC->APB2ENR.IOPAEN = 1; // Kích hoạt clock cho cổng GPIOA

Thanh ghi GPIOA_CRL (MODE1 và CNF1)

  • What: Cụm bit MODE1[1:0] (Bit 5:4) và CNF1[1:0] (Bit 7:6) trong thanh ghi GPIOA_CRL được sử dụng để cấu hình chân PA1. Trong trường hợp này, chúng ta sẽ cấu hình PA1 là đầu vào Floating Input để nhận tín hiệu ngoài cho Timer.
  • Why (Circuit-level): Mỗi chân GPIO trên STM32 có thể được cấu hình ở nhiều chế độ khác nhau (Input, Output, Alternate Function). Để chân PA1 hoạt động như một đầu vào cho Timer, nó cần được đặt ở chế độ Input và cụ thể hơn là Floating Input. Chế độ Floating Input đảm bảo rằng chân không bị kéo lên (pull-up) hay kéo xuống (pull-down) nội bộ, cho phép tín hiệu ngoài điều khiển hoàn toàn trạng thái logic của chân, giảm thiểu nhiễu và đảm bảo đọc xung chính xác.
  • How (Ví dụ):
    GPIOA->CRL.MODE1 = 0; // Đặt PA1 là Input mode (00)
    GPIOA->CRL.CNF1  = 1; // Đặt PA1 là Floating Input (01)

Thanh ghi TIMx_CR1 (Bit CEN)

  • What: Bit CEN (Counter enable) tại vị trí Bit 0 của thanh ghi TIMx_CR1 điều khiển việc bắt đầu hoặc dừng bộ đếm của Timer. Khi bit này được set lên 1, bộ đếm (TIMx_CNT) sẽ bắt đầu đếm. Khi là 0, bộ đếm sẽ dừng.
  • Why (Circuit-level): Bit CEN đóng vai trò như một công tắc chính cho bộ đếm. Việc có khả năng bật/tắt bộ đếm cho phép chúng ta kiểm soát chính xác thời điểm Timer bắt đầu hoạt động, tránh việc đếm sai hoặc không mong muốn trong quá trình cấu hình. Sau khi cấu hình xong tất cả các thanh ghi liên quan, chúng ta mới kích hoạt CEN để Timer bắt đầu đếm.
  • How (Ví dụ):
    TIM2->CR1.CEN = 0; // Dừng bộ đếm trước khi cấu hình
    // ... cấu hình khác ...
    TIM2->CR1.CEN = 1; // Kích hoạt bộ đếm sau khi cấu hình xong

Thanh ghi TIMx_CCMR1 (Input Capture Mode – CC2S và IC2F)

  • What: Trong chế độ Input Capture, thanh ghi TIMx_CCMR1 (Capture/compare mode register 1) được dùng để cấu hình kênh Input Capture. Cụ thể, CC2S[1:0] (Bit 9:8) dùng để chọn đầu vào cho kênh 2 (ví dụ: TI2) và IC2F[3:0] (Bit 15:12) dùng để cấu hình bộ lọc nhiễu cho tín hiệu đầu vào.
  • Why (Circuit-level):
    • CC2S: Để Timer đếm các sự kiện từ một chân GPIO cụ thể (như PA1), chúng ta cần ‘kết nối’ chân này với kênh Input Capture tương ứng của Timer. CC2S = 01 (TI2FP2 mapped to TI2) sẽ chỉ định rằng kênh 2 của Timer sẽ đọc tín hiệu từ chân TI2 (Input 2).
    • IC2F: Tín hiệu từ môi trường bên ngoài thường có nhiễu (noise) hoặc hiện tượng rung xung (chatter), đặc biệt là từ các cảm biến cơ khí như encoder. Bộ lọc IC2F là một mạch digital, hoạt động bằng cách yêu cầu tín hiệu phải giữ ổn định ở một trạng thái logic nhất định trong một số chu kỳ clock của timer trước khi được công nhận là một cạnh hợp lệ. Điều này giúp loại bỏ các xung nhiễu ngắn, đảm bảo bộ đếm chỉ ghi nhận các sự kiện thực tế, tăng độ tin cậy của hệ thống.
  • How (Ví dụ):
    TIM2->CCMR1.CC2S = 1;   // Chọn đầu vào TI2 cho kênh 2 (01)
    TIM2->CCMR1.IC2F = 8;   // Áp dụng bộ lọc 0x8 cho TI2 (N=8)

Thanh ghi TIMx_CCER (Bit CC2P)

  • What: Bit CC2P (Capture/Compare 2 output Polarity) tại vị trí Bit 5 của thanh ghi TIMx_CCER được sử dụng để cấu hình cực tính của tín hiệu đầu vào cho kênh 2. Khi sử dụng Counter Mode với tín hiệu ngoài, bit này xác định Timer sẽ đếm theo cạnh lên (rising edge) hay cạnh xuống (falling edge).
  • Why (Circuit-level): Các cảm biến và nguồn tín hiệu ngoài có thể tạo ra xung ở nhiều cực tính khác nhau. Việc chọn cực tính đúng (cạnh lên hoặc cạnh xuống) là rất quan trọng để Timer đếm chính xác các sự kiện mong muốn. Nếu tín hiệu là cạnh lên và bạn cấu hình đếm cạnh xuống, Timer sẽ không hoạt động đúng hoặc bỏ qua các sự kiện. Việc này là một phần của mạch phát hiện cạnh (edge detector) trong module Timer.
  • How (Ví dụ):
    TIM2->CCER.CC2P = 0; // Chọn cực tính không đảo (cạnh lên) cho TI2FP2

Thanh ghi TIMx_SMCR (SMS và TS)

  • What: Thanh ghi TIMx_SMCR (Slave mode control register) chứa các bit SMS[2:0] (Bit 2:0) để chọn chế độ slave và TS[2:0] (Bit 6:4) để chọn nguồn trigger. Trong Counter Mode với tín hiệu ngoài, chúng ta sẽ cấu hình SMS = 7 (External Clock Mode 1) và TS = 6 (TI2FP2) để Timer đếm theo tín hiệu từ chân TI2.
  • Why (Circuit-level):
    • SMS (Slave Mode Selection): Chế độ Slave cho phép Timer được điều khiển bởi một nguồn bên ngoài (external trigger). Khi SMS = 7, Timer sẽ hoạt động như một bộ đếm xung clock bên ngoài, mỗi xung trên chân trigger sẽ làm bộ đếm CNT tăng lên 1. Điều này khác biệt hoàn toàn với chế độ Timer thông thường sử dụng clock nội bộ.
    • TS (Trigger Selection): TS chọn chính xác nguồn tín hiệu nào sẽ hoạt động như trigger. Khi TS = 6 (TI2FP2), nó chỉ định rằng tín hiệu từ chân TI2 (đã được lọc và chọn cực tính) sẽ là nguồn trigger cho chế độ đếm ngoài. Sự kết hợp này tạo thành một ‘ống dẫn’ tín hiệu từ chân GPIO qua bộ lọc, bộ phát hiện cạnh và cuối cùng kích hoạt bộ đếm.
  • How (Ví dụ):
    TIM2->SMCR.SMS = 7; // Chọn External Clock Mode 1
    TIM2->SMCR.TS  = 6; // Chọn Trigger Source là TI2FP2

Thanh ghi TIMx_CNT (Counter)

  • What: Thanh ghi TIMx_CNT (Counter register) là một thanh ghi 16-bit, chứa giá trị đếm hiện tại của Timer. Giá trị này sẽ tăng lên 1 mỗi khi một sự kiện trigger được phát hiện theo cấu hình của Counter Mode.
  • Why (Circuit-level): Đây chính là trái tim của bộ đếm. Giá trị trong CNT phản ánh số lượng sự kiện đã xảy ra. Đối với các ứng dụng như đếm xung encoder, việc đọc giá trị từ CNT giúp xác định vị trí hoặc quãng đường đã đi được. Vì TIM2 là timer 16-bit, nó có thể đếm từ 0 đến 65535. Khi đạt đến giá trị tối đa, nó sẽ tràn (overflow) và quay về 0.
  • How (Ví dụ):
    uint32_t current_count = TIM2->CNT; // Đọc giá trị đếm hiện tại

5. Quy trình cấu hình Counter Mode STM32F103 & Hardware Traps

Lưu đồ thuật toán cấu hình

graph TD;
 A["Bắt đầu"] --> B["Bật Clock cho TIM2 (RCC_APB1ENR.TIM2EN = 1)"];
 B --> C["Bật Clock cho GPIOA (RCC_APB2ENR.IOPAEN = 1)"];
 C --> D["Cấu hình chân PA1 làm Input Floating (GPIOA_CRL)"];
 D --> E["Dừng Timer (TIM2_CR1.CEN = 0)"];
 E --> F["Chọn TI2 làm đầu vào cho kênh 2 (TIM2_CCMR1.CC2S = 01)"];
 F --> G["Cấu hình bộ lọc cho TI2 (TIM2_CCMR1.IC2F = X)"];
 G --> H["Cấu hình cực tính TI2FP2 (TIM2_CCER.CC2P = 0/1)"];
 H --> I["Chọn External Clock Mode 1 (TIM2_SMCR.SMS = 7)"];
 I --> J["Chọn Trigger Source TI2FP2 (TIM2_SMCR.TS = 6)"];
 J --> K["Kích hoạt Timer (TIM2_CR1.CEN = 1)"];
 K --> L["Đọc giá trị đếm từ TIM2_CNT"];
 L --> M["Kết thúc"];
TI2 external clock connection example
Chú thích: TI2 external clock connection example

Bật clock cho TIM2

Thanh ghi APB1ENR bật clock cho các ngoại vi trên bus APB1, bao gồm TIM2. Đặt TIM2EN = 1 kích hoạt clock cho TIM2, với tần số clock là 72MHz (Nếu SYSCLK = 72MHz và APB1 prescaler = 1). Bước này là nền tảng cho mọi cấu hình Counter Mode STM32F103RCT6, vì không có clock, timer sẽ không hoạt động.

RCC->APB1ENR.TIM2EN = 1;

Cấu hình chân PA1 làm Input Floating cho TIM2_CH2

  • Chân PA1 có thể được cấu hình làm đầu vào cho kênh 2 của TIM2 (TIM2_CH2).
  • RCC->APB2ENR.IOPAEN = 1: Bật clock cho cổng GPIOA.
  • GPIOA->CRL.MODE1 = 0: Đặt chế độ input (mode 00) cho bit 1 (PA1).
  • GPIOA->CRL.CNF1 = 1 (cụ thể là CNF1[1:0] = 01 trong cấu hình 4-bit): Chọn chế độ floating input (không kéo lên/kéo xuống), phù hợp để nhận tín hiệu ngoài.
  • Mục đích: PA1 được sử dụng làm nguồn tín hiệu đầu vào cho TI2, nơi tín hiệu ngoài sẽ được đọc.
RCC->APB2ENR.IOPAEN = 1; 
GPIOA->CRL.MODE1 = 0; // Input mode 
GPIOA->CRL.CNF1 = 1; // Floating input

Trong cấu hình Counter Mode STM32F103, việc chọn chế độ floating input giúp tránh nhiễu từ nguồn điện nội bộ, đảm bảo tín hiệu ngoài được đọc chính xác.

Chọn TI2 làm đầu vào cho kênh 2

  • Thanh ghi TIMx_CCMR1, set CC2S = 01.
TIM2->CCMR1 &= ~ (0x3<<8); // Configure channel 2 detect rising edge (CC2S = 01) 
TIM2->CCMR1 |= (1<<8);

Bước này định nghĩa kênh đầu vào, là yếu tố then chốt trong cấu hình Counter Mode STM32F103RCT6 để liên kết phần cứng với timer.

Cấu hình bộ lọc cho TI2

  • Thanh ghi TIMx_CCMR1, set giá trị cho IC2F[3:0].
  • Giữ IC2F = 0000 nếu không cần lọc nhiễu cho TI2.
TIM2->CCMR1 &= ~(0xF << 12); // Clear bit 15:12 
TIM2->CCMR1 |= (0x8 << 12); // filter TI2, IC2F = 0x8

Để nâng cao độ tin cậy, bạn có thể điều chỉnh IC2F lên giá trị cao hơn (như 0x8) nếu môi trường có nhiễu điện từ, giúp cấu hình Counter Mode STM32F103 hoạt động ổn định hơn trong ứng dụng thực tế.

Cấu hình cực tính TI2FP2

  • Thanh ghi TIMx_CCER, set CC2P = 0 nếu cấu hình đếm cạnh lên.
  • Set CC2P = 1 nếu cấu hình đếm cạnh xuống.
TIM2->CCER &= ~(1 << 5); // Select rising edge polarity CC2p = 0

Lựa chọn này tùy thuộc vào loại tín hiệu ngoài, ví dụ rising edge phổ biến cho encoder.

Chọn External Clock Mode 1

Thanh ghi TIMx_SMCR (Slave Mode Control Register) điều khiển chế độ hoạt động của timer khi sử dụng tín hiệu ngoài.

  • Set SMS = 111 (tức 7).
    • Giá trị SMS = 7 chọn External Clock Mode 1, trong đó timer đếm các sự kiện từ một tín hiệu ngoài được chọn làm trigger.
    • Trong chế độ này, mỗi lần tín hiệu ngoài (sau khi lọc và điều chỉnh) chuyển từ mức thấp sang mức cao (rising edge) hoặc mức thấp (falling edge), bộ đếm (CNT) tăng 1.
TIM2->SMCR.SMS = 7; // External clock mode 1

Chọn Trigger Source TI2FP2

  • Thanh ghi TIMx_SMCR, set TS = 110 (tức 6):
    • TS (Trigger Selection) chọn nguồn trigger. Giá trị 110 tương ứng với TI2FP2 (Filtered Timer Input 2), nghĩa là tín hiệu từ chân TI2 (PA1) được sử dụng sau khi qua bộ lọc (filter) để loại nhiễu.
    • TI2FP2 là tín hiệu đã được xử lý từ TI2, phụ thuộc vào cấu hình bộ lọc (ICxF) trong thanh ghi CCMR1.
TIM2->SMCR.TS = 6; // Trigger source TI2FP2

Kích hoạt Timer (Bật bộ đếm)

  • Thanh ghi CR1 (Control Register 1) điều khiển hoạt động của timer. Đặt CEN (Counter Enable) = 1 khởi động bộ đếm.
  • Sau khi bật, timer sẽ đếm các sự kiện rising edge hoặc falling edge từ tín hiệu ngoài trên PA1 (TI2FP2), và giá trị đếm được lưu trong TIM2->CNT.
TIM2->CR1.CEN = 1; // Enable counter

Đọc giá trị đếm từ TIM2_CNT

  • Thanh ghi CNT (Counter Register) chứa giá trị hiện tại của bộ đếm. Bạn có thể đọc TIM2->CNT bất kỳ lúc nào để lấy số lượng sự kiện (rising edge) đã được đếm từ tín hiệu ngoài.
countEncoderChannelB = TIM2->CNT;

Cảnh báo Bẫy phần cứng (Hardware Traps)

⚠️ BẪY PHẦN CỨNG (HARDWARE TRAP): Rất nhiều bạn quên cấp Clock ở thanh ghi RCC trước khi cấu hình bất kỳ ngoại vi nào (GPIO, Timer, USART…), dẫn đến MCU bị treo (HardFault) hoặc ngoại vi không hoạt động. Luôn nhớ rằng bước đầu tiên là phải bật clock cho cả Timer và GPIO trước khi tiến hành cấu hình các thanh ghi khác. Căn cứ từ tài liệu RM0008, Section 7.3.7 và 7.3.8 cho các thanh ghi kích hoạt clock. Ngoài ra, việc cấu hình sai chế độ chân GPIO (ví dụ: không phải floating input) cũng có thể làm tín hiệu đầu vào bị sai lệch hoặc không ổn định.

6. Code thực tế chạy trên Keil C

Dưới đây là chương trình mẫu sử dụng cấu hình Counter Mode STM32F103 để đếm tín hiệu channel B của Encoder với xung cạnh lên. Mã này được viết bằng C, sử dụng thư viện thanh ghi trực tiếp.

void TIM2_Counter_Config(void) {
    // Enable TIM2 clock
    RCC->APB1ENR.TIM2EN = 1;
    // Configure PA1 as input for TIM2_CH2
    RCC->APB2ENR.IOPAEN = 1;
    GPIOA->CRL.MODE1 = 0; // Input mode
    GPIOA->CRL.CNF1 = 1; // Floating input
    // Disable counter before configuration
    TIM2->CR1.CEN = 0;
   
    TIM2->CCMR1 &= ~ (0x3<<8); // Configure channel 2 detect rising edge (CC2S = 01) TIM2->CCMR1 |= (1<<8); TIM2->CCMR1 &= ~(0xF << 12); // Clear bit 15:12 TIM2->CCMR1 |= (0x8 << 12); // filter TI2, IC2F = 0x8 TIM2->CCER &= ~(1 << 5); // Select rising edge polarity CC2p = 0 TIM2->SMCR.SMS = 7; // External clock mode 1
    TIM2->SMCR.TS = 6; // Trigger source TI2FP2
    TIM2->CR1.CEN = 1; // Enable counter
}

uint32_t countEncoderChannelB = 0;

int main(void)
{
    SystemClock_Config(); // Initialize system clock
    TIM2_Counter_Config();
    while (1) {
        countEncoderChannelB = TIM2->CNT;
    }
}

Link Github: Download chương trình cấu hình Counter Mode STM32F103RCT6

7. Cách kiểm tra cấu hình Counter Mode (Debugging)

Sơ đồ kết nối board STM32F103RCT6 mini và Encoder rotary cho cấu hình Counter Mode STM32F103

Với chế độ counter mode, chỉ cần kết nối một kênh encoder như một đầu vào đếm xung.

Sơ đồ kết nối board STM32F103RCT6 mini và Encoder rotary cho cấu hình Counter Mode STM32F103
Chú thích: Sơ đồ kết nối board STM32F103RCT6 mini và Encoder rotary cho cấu hình Counter Mode STM32F103
Board STM32F103RCT6 miniEncoder Rotary
Chân 3v3Chân VDD (+)
Chân GNDChân GND
Chân PA1Chân VDD

Hướng dẫn kiểm tra kết quả đếm xung encoder với counter mode trên Keil C

Thiết lập cửa sổ Watch luôn cập nhật giá trị variable trong Keil C: Vào menu View >> Tick chọn Periodic Window Update

Selected Periodic window update cho Keil C
Chú thích: Selected Periodic window update cho Keil C

Mỗi lần quay encoder, xung sẽ được đếm và giá trị sẽ được cập nhật và biểu thị qua biến countEncoderChannelB trong cửa sổ Watch 1 trên Keil C

Add counter variable vào cửa sổ Watch Keil C
Chú thích: Add counter variable vào cửa sổ Watch Keil C

8. Kết luận

Lợi ích và lưu ý khi sử dụng Counter Mode trên STM32F103

Sử dụng cấu hình Counter Mode STM32F103 mang lại nhiều lợi ích như giảm tải CPU, độ chính xác cao trong đếm sự kiện thời gian thực, và dễ dàng tích hợp với các cảm biến ngoại vi. Tuy nhiên, hãy lưu ý:

  • Kiểm tra tần số tín hiệu ngoài không vượt quá giới hạn của timer (thường lên đến 72MHz).
  • Nếu gặp nhiễu, tăng giá trị lọc IC2F để ổn định.
  • Trong dự án lớn, kết hợp với interrupt để xử lý overflow của CNT (khi đạt 0xFFFF).

Nếu bạn gặp vấn đề trong cấu hình Counter Mode STM32F103RCT6, tham khảo tài liệu Reference Manual RM0008 của STMicroelectronics để có thêm chi tiết.

Câu hỏi thường gặp (FAQ) về Counter Mode STM32F103

❓ Counter Mode khác gì Encoder Mode trên STM32F103?

Counter Mode sử dụng timer để đếm xung từ một kênh duy nhất (TI1 hoặc TI2), phù hợp cho các ứng dụng như đếm sản phẩm, đo tần số hoặc đếm xung encoder đơn kênh.Trong khi đó, Encoder Mode sử dụng hai kênh TI1 và TI2, cho phép xác định cả số xung lẫn chiều quay, thường dùng cho encoder quadrature (X2, X4).
👉 Nếu chỉ cần đếm số xung → dùng Counter Mode
👉 Nếu cần xác định hướng quay → dùng Encoder Mode

❓ TIM2 trong Counter Mode STM32F103 đếm tối đa bao nhiêu xung?

TIM2 là timer 16-bit, vì vậy trong Counter Mode STM32F103, thanh ghi CNT có thể đếm từ:
0 → 0xFFFF (65.535 xung)
Khi CNT tràn, timer sẽ quay về 0. Trong các ứng dụng cần đếm lâu dài, nên kết hợp interrupt tràn (update interrupt) để mở rộng số xung đếm.

❓ Khi nào nên sử dụng bộ lọc IC2F trong Counter Mode?

Bộ lọc IC2F nên được sử dụng khi:
– Tín hiệu ngoài có nhiễu hoặc rung xung
– Encoder cơ hoặc cảm biến quang đặt trong môi trường công nghiệp
– Dây tín hiệu dài hoặc không được che chắn tốt
Nếu tín hiệu sạch (logic TTL rõ ràng), có thể đặt IC2F = 0 để tăng tốc độ đáp ứng.
Ngược lại, khi môi trường nhiễu, nên tăng IC2F (ví dụ IC2F = 0x8) để đảm bảo Counter Mode STM32F103RCT6 hoạt động ổn định.

Tóm lại, cấu hình Counter Mode STM32F103 là một công cụ mạnh mẽ giúp tận dụng timer để đếm sự kiện thời gian thực từ tín hiệu ngoài, mang lại hiệu suất cao và giảm tải cho CPU trong các dự án nhúng. Từ việc đếm xung encoder đến giám sát cảm biến tốc độ, Counter Mode trên STM32F103RCT6 không chỉ đơn giản hóa quy trình lập trình mà còn mở ra nhiều ứng dụng thực tế trong lĩnh vực tự động hóa và IoT. Với hướng dẫn chi tiết thông qua lập trình thanh ghi trực tiếp, mã nguồn mẫu và các lưu ý quan trọng, bạn hoàn toàn có thể triển khai ngay hôm nay. Nếu bạn đang phát triển dự án trên STM32F103, hãy thử nghiệm Counter Mode để tối ưu hóa hệ thống của mình.
Bài viết này cung cấp hướng dẫn toàn diện về cấu hình Counter Mode STM32F103 trong chuỗi bài hướng dẫn lập trình STM32F103 thanh ghi, giúp bạn áp dụng nhanh chóng vào dự án. Nếu cần thêm ví dụ hoặc mã nguồn mở rộng, hãy để lại bình luận!
Tham khảo mã nguồn tại đây.

Viết một bình luận

This site uses Akismet to reduce spam. Learn how your comment data is processed.