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

Trong quá trình lập trình vi điều khiển STM32 thanh ghi, cấu hình Timer Mode là một trong những chức năng quan trọng nhất, được sử dụng để tạo delay chính xác, đo thời gian, tạo ngắt định kỳ, PWM, encoder và nhiều ứng dụng thời gian thực khác. Bài viết này sẽ hướng dẫn bạn lập trình STM32F103RCT6 ở mức thanh ghi (bare-metal) để cấu hình Timer Mode STM32F103, cụ thể là TIM2 tạo ngắt mỗi 1ms, từ đó xây dựng biến millis tương tự Arduino và sử dụng nó để đếm thời gian chính xác theo giây, điều khiển LED nhấp nháy 1Hz.

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

Tổng quan về Timer Mode trên STM32F103

STM32F103RCT6 sở hữu nhiều timer đa năng, trong đó TIM2 là một timer 16-bit (LƯU Ý QUAN TRỌNG: Theo RM0008, Section 15.3.1, Trang 368, các General-purpose timers TIM2-TIM5 chỉ là 16-bit), hỗ trợ các chế độ:

  • Đếm lên (up-counter)
  • Đếm xuống (down-counter)
  • Center-aligned mode

Timer Mode STM32F103 thường được sử dụng để tạo sự kiện định kỳ, giúp vi điều khiển xử lý tác vụ theo thời gian mà không cần polling liên tục, từ đó tiết kiệm tài nguyên CPU. Trong bài viết này, chúng ta tập trung vào chế độ cơ bản nhất: tạo ngắt định kỳ 1ms với tần số hệ thống 72MHz, rất phù hợp cho:

  • Xây dựng hàm millis
  • Delay chính xác
  • Đồng bộ thời gian
  • Scheduler đơn giản

Giới thiệu Timer 2 (TIM2)

Timer 2 (TIM2) là một timer 16-bit trên STM32F103RCT6, được sử dụng để tạo ngắt định kỳ.

Cấu hình cơ bản (mục tiêu):

  • Bật clock cho TIM2
  • Đặt Prescaler: PSC = 71 → 72MHz / (71+1) = 1MHz
  • Đặt Auto Reload Register: ARR = 999 → 1MHz / (999+1) = 1kHz = 1ms
  • Kích hoạt ngắt khi đếm xong một chu kỳ
  • Kích hoạt ngắt TIM2 trong NVIC

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 RM0008PM0056. Sử dụng tính năng tìm kiếm (Ctrl + F) với các từ khóa sau:

  • Đối với RM0008 (Reference Manual):
    • General-purpose timers (TIM2 to TIM5): Để đọc tổng quan về các tính năng và cấu trúc khối.
    • TIMx_CR1, TIMx_DIER, TIMx_SR, TIMx_PSC, TIMx_ARR, TIMx_CNT: Để tìm các thanh ghi điều khiển, trạng thái, và các thanh ghi giá trị quan trọng.
    • RCC_APB1ENR: Để tìm thanh ghi bật/tắt xung nhịp cho các ngoại vi trên bus APB1.
    • Tìm kiếm “TIM2 Block Diagram” để hiểu luồng tín hiệu và các khối chức năng của Timer.
  • Đối với PM0056 (Programming Manual):
    • NVIC: Để hiểu cách hoạt động của bộ điều khiển ngắt vectored lồng nhau.
    • Tìm kiếm “Interrupt Vector Table” hoặc “Table of interrupt sources” trong phụ lục của RM0008 (hoặc datasheet của chip) để xác định số ngắt cụ thể (IRQn) của TIM2, giúp cấu hình NVIC.

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

Dựa trên RM0008, Section 15.4 (trang 404-408):

Define struct thanh ghi TIMx Control Register 1

Thanh ghi TIM Control Register 1 cho hướng dẫn lâp trình STM32F103 thanh ghi cấu hình Timer Mode STM32F103RCT6
Chú thích: TIM Control Register 1.
// Trích xuất từ tài liệu RM0008, Section 15.4.1, Trang 423
// Thanh ghi điều khiển Timer 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;
} TIM_CR1_Type;

Define struct thanh ghi TIMx Slave Mode Control Register

Thanh ghi TIM Slave Mode Control Register trong lập trình STM32F103 thanh ghi cấu hình TIMx STM32F103RCT6
Chú thích: TIM Slave Mode Control Register.
// Thanh ghi Điều khiển Chế độ Slave của TIM (TIM Slave Mode Control Register)
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;
} TIM_SMCR_Type;

Define struct thanh ghi TIMx Status Register

Thanh ghi TIMx Status Register trong lập trình STM32F103 thanh ghi cấu hình các mode TIMx STM32F103RCT6
Chú thích: TIMx Status Register.
// Thanh ghi Trạng thái của TIM (TIM Status Register)
typedef union {
    struct {
        __IOM uint32_t UIF : 1; // Bit 0: Update interrupt flag
        __IOM uint32_t CC1IF : 1; // Bit 1: Capture/compare 1 interrupt flag
        __IOM uint32_t CC2IF : 1; // Bit 2: Capture/compare 2 interrupt flag
        __IOM uint32_t CC3IF : 1; // Bit 3: Capture/compare 3 interrupt flag
        __IOM uint32_t CC4IF : 1; // Bit 4: Capture/compare 4 interrupt flag
        uint32_t reserved0 : 1; // Bit 5: Reserved
        __IOM uint32_t TIF : 1; // Bit 6: Trigger interrupt flag
        uint32_t reserved1 : 2; // Bit 7-8: Reserved
        __IOM uint32_t CC1OF : 1; // Bit 9: Capture/compare 1 overcapture flag
        __IOM uint32_t CC2OF : 1; // Bit 10: Capture/compare 2 overcapture flag
        __IOM uint32_t CC3OF : 1; // Bit 11: Capture/compare 3 overcapture flag
        __IOM uint32_t CC4OF : 1; // Bit 12: Capture/compare 4 overcapture flag
        uint32_t reserved2 : 19; // Bit 13-31: Reserved
    };
    __IOM uint32_t reg;
} TIM_SR_Type;

Bản đồ thanh ghi TIMx (TIMx register map)

Bản đồ thanh ghi TIMx (TIMx register map) STM32F103 - Hướng dẫn lập trình STM32F103 thanh ghi
Chú thích: Bản đồ thanh ghi TIMx (TIMx register map).
// Bản đồ thanh ghi TIM (15.4.19 TIMx register map – Trang 423)
typedef struct {
    TIM_CR1_Type CR1; // 0x00: Control 1
    __IO uint32_t CR2; // 0x04: Control 2
    TIM_SMCR_Type SMCR; // 0x08: Slave mode control
    __IO uint32_t DIER; // 0x0C: DMA/interrupt enable
    TIM_SR_Type SR; // 0x10: Status
    __O uint32_t EGR; // 0x14: Event generation
    __IO uint32_t CCMR1; // 0x18: Capture/compare mode 1
    __IO uint32_t CCMR2; // 0x1C: Capture/compare mode 2
    __IO uint32_t CCER; // 0x20: Capture/compare enable
    __IO uint32_t CNT; // 0x24: Counter value
    __IO uint32_t PSC; // 0x28: Prescaler
    __IO uint32_t ARR; // 0x2C: Auto-reload
    __IO uint32_t RCR; // 0x30: Repetition counter
    __IO uint32_t CCR1; // 0x34: Capture/compare 1
    __IO uint32_t CCR2; // 0x38: Capture/compare 2
    __IO uint32_t CCR3; // 0x3C: Capture/compare 3
    __IO uint32_t CCR4; // 0x40: Capture/compare 4
    __IO uint32_t BDTR; // 0x44: Break and dead-time
    __IO uint32_t DCR; // 0x48: DMA control
    __IO uint32_t DMAR; // 0x4C: DMA address
} TIM_TypeDef;
#define TIM2 ((TIM_TypeDef *)0x40000000UL)

4. Giải thích chuyên sâu (What – Why – How) các thanh ghi quan trọng cấu hình counter mode

Với mục tiêu cấu hình Timer 2 để tạo ngắt 1ms, chúng ta cần tập trung vào các thanh ghi sau:

RCC_APB1ENR (APB1 peripheral clock enable register):

  • What: Thanh ghi này chịu trách nhiệm cấp xung nhịp (clock) cho các ngoại vi hoạt động trên bus APB1. Mỗi bit tương ứng với một ngoại vi, khi set bit lên ‘1’, clock sẽ được cấp cho ngoại vi đó.
  • Why (Circuit-level): Trong kiến trúc vi điều khiển, để tiết kiệm năng lượng và quản lý tài nguyên hiệu quả, các khối ngoại vi sẽ không hoạt động (sleep) cho đến khi được cấp clock. Việc bật clock là bước đầu tiên và bắt buộc, tương tự như việc cấp nguồn cho một mạch điện tử. Nếu quên bước này, mọi thao tác cấu hình thanh ghi của Timer sẽ không có tác dụng, giống như việc cố gắng điều khiển một thiết bị không được bật nguồn.
  • How (Ví dụ): Để bật clock cho TIM2:
    RCC->APB1ENR.TIM2EN = 1; // Set bit TIM2EN (Bit 0) lên 1
    // Hoặc cách khác (không khuyến khích nếu đã có struct bit-field)
    // RCC->APB1ENR.reg |= (1UL << 0);
    

TIMx_PSC (Prescaler register):

Lập trình STM32F103 thanh ghi: Cấu hình Timer Mode STM32F103 - Thanh ghi PSC (Prescaler) STM32F103
Chú thích: Thanh ghi PSC (Prescaler).
  • What: Thanh ghi bộ chia tần số đầu vào cho bộ đếm Timer. Giá trị nạp vào PSC sẽ làm chậm tần số clock của Timer đi (PSC + 1) lần.
  • Why (Circuit-level): Tần số clock hệ thống (ví dụ 72MHz) thường quá cao để đếm các khoảng thời gian dài. Bộ chia tần số (Prescaler) là một counter tích hợp, cho phép giảm tần số xung đầu vào xuống một mức phù hợp hơn, giúp Timer có thể đếm được các chu kỳ thời gian dài mà vẫn giữ độ phân giải hợp lý. Việc này giúp mở rộng phạm vi đếm của Timer mà không cần tăng kích thước của thanh ghi đếm.
  • How (Ví dụ): Để có tần số 1MHz từ 72MHz:
    TIM2->PSC= 71; // Clock Timer = 72MHz / (71 + 1) = 1MHz
    

TIMx_ARR (Auto-Reload Register):

Lập trình STM32F103 thanh ghi: Cấu hình Timer Mode STM32F103 - Thanh ghi TIMx Auto-Reload Register (ARR) STM32F103
Chú thích: Thanh ghi TIMx Auto-Reload Register (ARR).
  • What: Thanh ghi giá trị nạp lại tự động. Khi bộ đếm (CNT) đạt đến giá trị trong ARR, nó sẽ tự động được reset về 0 (trong chế độ đếm lên) hoặc nạp lại giá trị ARR (trong chế độ đếm xuống) và tạo ra một sự kiện Update Event (UEV).
  • Why (Circuit-level): ARR hoạt động như một ngưỡng đếm. Nó cho phép chúng ta định nghĩa chính xác chu kỳ hoạt động của Timer. Mỗi khi bộ đếm đạt tới ARR, một sự kiện ngắt được tạo ra, báo hiệu rằng một khoảng thời gian nhất định đã trôi qua. Điều này là nền tảng để tạo ra các ngắt định kỳ (periodic interrupts) hoặc các tín hiệu PWM với chu kỳ chính xác.
  • How (Ví dụ): Để tạo ngắt mỗi 1ms với tần số Timer 1MHz:
    TIM2->ARR= 999; // 1MHz / (999 + 1) = 1kHz = 1ms
    

TIMx_CR1 (Control Register 1):

Thanh ghi TIM Control Register 1 cho hướng dẫn lâp trình STM32F103 thanh ghi cấu hình Timer Mode STM32F103RCT6
Chú thích: TIM Control Register 1.
  • What: Chứa các bit điều khiển chung cho Timer, bao gồm bật/tắt bộ đếm (CEN), chế độ cập nhật (URS), và kích hoạt Preload cho ARR (ARPE).
  • Why (Circuit-level):
    • CEN (Counter Enable): Đây là ‘công tắc’ chính để khởi động hoặc dừng Timer. Không bật CEN, Timer sẽ không đếm.
    • URS (Update Request Source): Quan trọng để kiểm soát sự kiện Update Event (UEV). Khi URS = 1, chỉ tràn/tràn dưới của bộ đếm mới tạo ra UEV, tránh việc ghi thanh ghi tạo ra UEV không mong muốn, đảm bảo ngắt Update (UI) chỉ xảy ra theo chu kỳ đếm.
    • ARPE (Auto-Reload Preload Enable): Khi bật, giá trị ghi vào ARR sẽ được ‘preload’ vào một thanh ghi đệm và chỉ thực sự được nạp vào thanh ghi ARR hoạt động khi có sự kiện UEV. Điều này đảm bảo sự thay đổi chu kỳ Timer được đồng bộ, tránh các lỗi đếm không nhất quán khi thay đổi ARR trong khi Timer đang chạy.
  • How (Ví dụ): Để cấu hình các bit này:
    TIM2->CR1.URS = 1;  // Chỉ có overflow/underflow mới tạo update interrupt
    TIM2->CR1.ARPE = 1; // Enable auto-reload preload
    TIM2->CR1.CEN = 1;  // Bật bộ đếm Timer
    

TIMx_DIER (DMA/Interrupt Enable Register):

    • What: Thanh ghi cho phép các yêu cầu DMA hoặc ngắt từ Timer. Chúng ta quan tâm đến bit UIE (Update Interrupt Enable).
    • Why (Circuit-level): Bit UIE cho phép Timer gửi yêu cầu ngắt tới NVIC khi một sự kiện Update Event (UEV) xảy ra. Ngắt là cơ chế hiệu quả để CPU xử lý các tác vụ định kỳ mà không cần phải liên tục kiểm tra (polling) trạng thái của Timer, giúp CPU rảnh rỗi thực hiện các công việc khác.
    • How (Ví dụ): Để bật ngắt Update:
      TIM2->DIER |= (1 << 0)// Bật ngắt Update 

TIMx_SR (Status Register):

Thanh ghi TIMx Status Register trong lập trình STM32F103 thanh ghi cấu hình các mode TIMx STM32F103RCT6
Chú thích: TIMx Status Register.
  • What: Thanh ghi chứa các cờ trạng thái của Timer, bao gồm cờ UIF (Update Interrupt Flag).
  • Why (Circuit-level): Cờ UIF được set lên ‘1’ bởi phần cứng khi một sự kiện Update Event (UEV) xảy ra. Trong trình xử lý ngắt, chúng ta phải xóa cờ này bằng cách ghi ‘0’ vào nó. Nếu không xóa, ngắt sẽ liên tục được kích hoạt, gây ra hiện tượng CPU bị kẹt trong vòng lặp ngắt (interrupt storm). Đây là một cơ chế báo hiệu từ phần cứng để phần mềm biết khi nào một sự kiện đã hoàn thành.
  • How (Ví dụ): Để xóa cờ ngắt Update trong ISR:
    if (TIM2->SR.UIF == 1)
    {
        TIM2->SR.UIF = 0; // Xóa cờ ngắt Update
    }
    

NVIC (Nested Vectored Interrupt Controller):

  • What: NVIC là một thành phần trong nhân ARM Cortex-M3, quản lý tất cả các ngắt của hệ thống. Nó chịu trách nhiệm kích hoạt, vô hiệu hóa, thiết lập độ ưu tiên và quản lý trạng thái của các ngắt.
  • Why (Circuit-level): Dù Timer đã tạo yêu cầu ngắt, nhưng để CPU thực sự phản hồi và nhảy đến trình xử lý ngắt (ISR), NVIC cần được cấu hình để cho phép ngắt đó. Nó giống như việc bạn phải bật chuông báo cháy (Timer tạo yêu cầu) VÀ bật hệ thống cảnh báo trung tâm (NVIC) thì lính cứu hỏa mới biết để đến. NVIC cũng quản lý độ ưu tiên, cho phép các ngắt quan trọng hơn được xử lý trước.
  • How (Ví dụ): Để bật ngắt TIM2 và đặt độ ưu tiên (giả định TIM2_IRQn là số ngắt của TIM2):
    // Bật ngắt TIM2 trong NVIC
    NVIC->ISER[TIM2_IRQn / 32] = (1UL << (TIM2_IRQn % 32));
    // Đặt độ ưu tiên cho ngắt TIM2 (ví dụ: ưu tiên thấp nhất)
    NVIC->IP[TIM2_IRQn] = 0x0F; // Giá trị 0-15, 0 là cao nhất, 15 là thấp nhất
    

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

Dưới đây là quy trình cấu hình TIM2 để tạo ngắt 1ms và những lỗi sai phổ biến cần tránh:

graph TD;
    A["Bắt đầu"] --> B["1. Bật Clock cho TIM2 (RCC_APB1ENR)"];
    B --> C["2. Cấu hình Prescaler (TIMx_PSC)"];
    C --> D["3. Cấu hình Auto-Reload Register (TIMx_ARR)"];
    D --> E["4. Cấu hình Control Register 1 (TIMx_CR1)"];
    E --> F["5. Bật ngắt Update (TIMx_DIER.UIE)"];
    F --> G["6. Bật ngắt TIM2 trong NVIC"];
    G --> H["7. Bắt đầu Timer (TIMx_CR1.CEN)"];
    H --> I["Hoàn thành cấu hình"];

Clock Timer và bus APB1

Timer mode STM32F103 hoạt động dựa trên clock từ bus APB1.

Với cấu hình:

  • SYSCLK = 72MHz
  • APB1 prescaler = 1

➡️ Clock cấp cho TIM2 là 72MHz

Lưu ý: Nếu APB1 prescaler ≠ 1, clock timer sẽ được nhân đôi theo quy tắc trong RM0008.

Bật Clock cho TIM2 (RCC_APB1ENR)

Thanh ghi RCC->APB1ENR điều khiển clock cho các ngoại vi trên bus APB1, trong đó TIM2 thuộc nhóm này.

Đặt bit TIM2EN = 1 để bật clock cho TIM2.
RCC->APB1ENR.TIM2EN = 1;
⚠️ Nếu không bật clock, timer sẽ không hoạt động, counter không tăng – đây là lỗi rất thường gặp khi lập trình STM32 thanh ghi.

Cấu hình Prescaler (TIMx_PSC)

Lập trình STM32F103 thanh ghi: Cấu hình Timer Mode STM32F103 - Thanh ghi PSC (Prescaler) STM32F103
Thanh ghi PSC (Prescaler)

Vai trò Prescaler

Prescaler chia tần số clock đầu vào của timer để tạo tần số đếm thấp hơn.

Tính toán Prescaler

  • Clock TIM2 = 72MHz
  • Chọn PSC = 71

Công thức:

T_tick = (PSC + 1) / f_clock
= 72 / 72MHz
= 1µs

➡️ Sau prescaler, timer đếm với chu kỳ 1µs (1MHz).

TIM2->PSC = 71;

Lưu ý: Giá trị PSC chỉ được cập nhật khi có update event, vì vậy nên set PSC trước khi bật timer.

Cấu hình Auto-Reload Register (TIMx_ARR)

Lập trình STM32F103 thanh ghi: Cấu hình Timer Mode STM32F103 - Thanh ghi TIMx Auto-Reload Register (ARR) STM32F103
Thanh ghi TIMx Auto-Reload Register (ARR)

Vai trò ARR

ARR xác định giá trị tối đa mà timer đếm tới trước khi:

  • Tràn
  • Reload về 0
  • Tạo update event (và ngắt nếu được bật)

Tính toán ARR

  • Chu kỳ đếm = 1µs
  • 1ms = 1000µs

Vì timer đếm từ 0 → ARR:

ARR + 1 = 1000
ARR = 999
TIM2->ARR = 999;

➡️ Timer đếm từ 0 đến 999 → đúng 1ms cho mỗi chu kỳ.

Bật ngắt Update (TIMx_DIER.UIE)

Thanh ghi DIER (DMA/Interrupt Enable Register) điều khiển các nguồn ngắt của timer.

  • Bit 0: UIE – Update Interrupt Enable
TIM2->DIER |= (1 << 0);

Update event xảy ra khi timer tràn từ ARR về 0, tức là mỗi 1ms trong cấu hình này.

Cấu hình Control Register 1 (TIMx_CR1)

Thanh ghi CR1 điều khiển hoạt động của timer.

  • Bit CEN = 1 → bật bộ đếm
TIM2->CR1.CEN = 1;

Sau khi bật:

  • Timer bắt đầu đếm từ 0
  • Mỗi 1µs tăng 1
  • Khi đạt 999 → tràn → tạo ngắt

Cơ chế hoạt động của Timer Mode STM32F103

  • TIM2 đếm từ 0 → 999
  • Mỗi lần đếm = 1µs
  • Sau 1000 lần (1ms):
    • Cờ UIF trong TIM2->SR được set
    • Nếu UIE = 1 → CPU nhảy vào TIM2_IRQHandler()

⚠️ Trong hàm ngắt bắt buộc phải xóa cờ UIF, nếu không ngắt sẽ lặp vô hạn.

TIM2->SR &= ~(1 << 0); // Clear UIF

⚠️ BẪY PHẦN CỨNG (HARDWARE TRAP):

  • Quên cấp Clock: Rất nhiều kỹ sư mới mắc lỗi quên bật bit TIM2EN trong thanh ghi RCC_APB1ENR trước khi cấu hình các thanh ghi khác của TIM2. Hậu quả là Timer không hoạt động, và bạn có thể tốn rất nhiều thời gian debug mà không hiểu tại sao. Luôn nhớ: Bật nguồn trước khi vận hành!
  • Quên xóa cờ ngắt (UIF): Trong hàm xử lý ngắt TIM2_IRQHandler, nếu không xóa bit UIF trong TIMx_SR, ngắt sẽ liên tục kích hoạt ngay sau khi thoát khỏi ISR, khiến CPU bị kẹt trong vòng lặp ngắt vô hạn (interrupt storm), không thể thực thi các tác vụ khác.
  • Sử dụng sai độ rộng Timer: Như đã điều chỉnh ở trên, TIM2 trên STM32F103 là 16-bit. Nếu bạn tính toán PSC/ARR dựa trên giả định 32-bit, thời gian sẽ bị sai lệch nghiêm trọng hoặc Timer sẽ không hoạt động đúng. Luôn kiểm tra kỹ Reference Manual cho dòng chip cụ thể của bạn.
  • Đặt ưu tiên ngắt không hợp lý: Trong các hệ thống phức tạp, việc không đặt đúng độ ưu tiên ngắt trong NVIC có thể dẫn đến các vấn đề về độ trễ hoặc xung đột giữa các ngắt quan trọng.
  • Không bật Global Interrupt: Đảm bảo ngắt toàn cục được bật (sử dụng __enable_irq() hoặc asm("CPSIE I") nếu bạn không dùng thư viện CMSIS) để CPU có thể phản hồi các yêu cầu ngắt.

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

Dưới đây là mã nguồn hoàn chỉnh để cấu hình Timer 2 tạo ngắt 1ms, xây dựng biến millis, và nhấp nháy LED.

Hàm ngắt TIM2

void TIM2_IRQHandler(void) {
    // Check UIF flag
    if (TIM2-> SR.UIF)
    {
    	TIM2->SR.UIF = 0;  // Remove flag
    	millis++;
    }
}

Code cấu hình Timer 2 (1ms Interrupt)

void TIM2_Timer_Config(void) {
    // Enable TIM2 clock (RM0008, Section 7.3.8, page 115)
    RCC->APB1ENR.TIM2EN = 1;

    // Configure TIM2 for 1ms interrupts
    TIM2->PSC = 71;         // Prescaler: 72MHz / 72 = 1MHz
    TIM2->ARR = 999;        // Auto-reload: 1MHz / 1000 = 1kHz
    TIM2->DIER |= (1 << 0); // Enable update interrupt
    TIM2->CR1.CEN = 1;      // Enable counter
}

Code main tạo ngắt 1ms và nhấp nháy LED

uint32_t countevery1second = 0;
volatile uint32_t millis = 0;
int main(void) 
{
    SystemClock_Config();  // Initialize system clock
    GPIO_LED_Config();
    GPIO_Button_Config();
    SysTick_Config_1ms();
    TIM2_Timer_Config();

    NVIC_Interrupt_Enable(TIM2_IRQ);
    NVIC_Set_Interrupt_Priority(TIM2_IRQ, 5);

    uint32_t startTimer = millis;

    while (1) {
        
        if (millis - startTimer >= 1000)
        {
            startTimer = millis;
            countevery1second++;
            LED_Toggle();
        }
    }
}

Lưu ý về biến volatile:

  • Trong ngữ cảnh lập trình nhúng, đặc biệt là với các biến được chia sẻ giữa hàm chính (main loop) và các hàm xử lý ngắt (ISR), việc sử dụng từ khóa volatilebắt buộc.
  • What: Từ khóa volatile báo cho trình biên dịch rằng giá trị của biến có thể thay đổi bất cứ lúc nào mà không phải do chương trình hiện tại tác động trực tiếp.
  • Why: Trình biên dịch thường thực hiện các tối ưu hóa bằng cách lưu trữ giá trị của biến vào thanh ghi CPU để truy xuất nhanh hơn. Nếu một biến như millis không phải là volatile, trình biên dịch có thể giả định rằng giá trị của nó trong main sẽ không thay đổi trừ khi main tự ghi vào nó. Khi millis được cập nhật trong ISR, main sẽ vẫn đọc giá trị cũ từ thanh ghi của CPU, dẫn đến lỗi logic nghiêm trọng. Từ khóa volatile buộc trình biên dịch luôn đọc giá trị mới nhất của biến từ bộ nhớ, đảm bảo tính nhất quán giữa main loop và ISR.
  • How: Đơn giản là thêm volatile vào khai báo biến dùng chung:
    volatile uint32_t millis = 0;
    

    Ý nghĩa chương trình

    • Timer mode STM32F103 tạo tick 1ms chính xác
    • millis là bộ đếm thời gian toàn cục
    • countevery1second:
      • Đếm số giây đã trôi qua
      • Dùng để kiểm chứng độ chính xác timer
    • LED nhấp nháy 1Hz
    • NVIC_Interrupt_Enable(TIM2_IRQ): cho phép CPU nhận ngắt từ TIM2
    • NVIC_Set_Interrupt_Priority(28, 5): Đặt mức ưu tiên 5 cho ngắt từ TIM2

Link Github: Download chương trình cấu hình STM32F103 timer mode 1ms TIM2

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

Để kiểm tra xem Timer đã được cấu hình và hoạt động chính xác hay chưa, bạn có thể sử dụng các phương pháp sau:

  • Sử dụng Debugger (Keil uVision, STM32CubeIDE):
    • Đặt breakpoint trong hàm TIM2_IRQHandler() để kiểm tra xem ngắt có được kích hoạt đúng chu kỳ hay không.
    • Kiểm tra giá trị của các thanh ghi Timer (TIM2->CNT, TIM2->PSC, TIM2->ARR, TIM2->SR.reg, TIM2->DIER.reg) trong cửa sổ Peripheral View (hoặc Memory View) của debugger. Đảm bảo các giá trị được cấu hình đúng.
    • Theo dõi biến milliscountevery1second trong cửa sổ Watch để đảm bảo chúng tăng lên chính xác theo thời gian thực.
  • Sử dụng Logic Analyzer/Oscilloscope:
    • Nếu bạn muốn kiểm tra độ chính xác cao hơn hoặc trực quan hóa tín hiệu, có thể cấu hình một chân GPIO nhấp nháy trong ISR và đo chu kỳ của tín hiệu đó bằng Logic Analyzer hoặc Oscilloscope. Điều này giúp xác nhận tần số ngắt thực tế.

Kết quả test hoạt động timer 1ms

Lập trình nhúng STM32F013 thanh ghi: Hướng dẫn cấu hình TIM2 với Timer mode 1ms - Kiểm tra hoạt động Timer 1ms
Chú thích: Thời điểm bắt đầu check timer 1ms.
  • Thời điểm bắt đầu: 12:03:00
Lập trình nhúng STM32F013 thanh ghi: Hướng dẫn cấu hình TIM2 với Timer mode 1ms - Kiểm tra hoạt động Timer 1ms sau 2 phút
Chú thích: Kiểm tra tại timer 1ms sau 2 phút.
  • Thời điểm sau 2 phút: 12:05:00
  • Giá trị countevery1second = 120

➡️ Timer 2 hoạt động chính xác trong 120 giây, sai số gần như bằng 0.

8. Kết luận

Cấu hình Timer Mode STM32F103 ở mức thanh ghi là một kỹ năng nền tảng và cực kỳ quan trọng cho mọi kỹ sư lập trình nhúng. Bằng việc làm chủ các thanh ghi như PSC, ARR, CR1DIER, bạn có thể tạo ra các cơ chế định thời chính xác, từ các hàm delay đơn giản đến việc xây dựng hệ điều hành thời gian thực (RTOS). Bài viết cũng đã chỉ ra lỗi phổ biến về độ rộng Timer 2 và cách khắc phục theo tài liệu chính thức.

Qua bài viết này, hy vọng bạn đã nắm vững kiến thức về ngắt Timer STM32 và có thể tự tin triển khai các ứng dụng yêu cầu độ chính xác thời gian cao. Hãy luôn nhớ tầm quan trọng của việc đọc hiểu Reference Manual và cẩn trọng với các bẫy phần cứng.

Tham khảo mã nguồn đầy đủ của dự á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.