🌱 STM32 - 19. Ví Dụ Sử Dụng Systick Timer Trong STM32

🌱 STM32 - 19. Ví Dụ Sử Dụng Systick Timer Trong STM32

    Ở post trước, mình đã giới thiệu với các bạn về Systick Timer - Overview & Registers, post này sẽ lấy một ví dụ điển hình để các bạn có thể sử dụng Systick.

STM32F401 NUCLEO Board

Hình 1: Board STM32F401 NUCLEO với các chân GPIO

    👉 Tham khảo tài liệu Core Cortex M4 Device Generic User Guide, chúng ta có hẳn một phần Hints & Tips, mà không cần tham khảo ông nào trên mạng cả 😅

Systick Tips

Hình 1: Hints & Tips từ Cortex-M4 User Guide

    👉 Để cấu hình cho Systick Timer, chúng ta sẽ làm theo 3 bước trên:

  • Đặt giá trị đếm ban đầu - Reload Value bằng thanh ghi SYST_RVR.
  • Xóa giá trị đếm hiện tại trên thanh ghi SYST_CVR.
  • Cấu hình hoạt động cho Systick Timer bằng thanh ghi Control SYST_CSR:
    • Chọn nguồn cấp xung Clock cho Systick bằng Bit[2] - CLKSOURCE.
    • Cho phép ngắt Systick (nếu sử dụng ngắt) bằng Bit[1] - TICKINT.
    • Cho phép bộ đếm Systick hoạt động bằng Bit[0] - ENABLE.

👉 Example - myHAL_Delay()

    Ví dụ này mình sẽ sử dụng ngắt Systick để tạo 2 hàm delay (delay ms và delay us). Ý tưởng giống như hàm HAL_Delay nhưng đơn giản hơn.

    Vậy đầu tiên mình cần tính toán về Clock, hãy tính làm sao cho mỗi lần Systick Timer tràn, thì chúng ta đã đếm được 1us. Mình sẽ triển khai tính toán trên Vi điều khiển STM32F401 của mình như sau:

💚 Đầu tiên cấu hình Clock

    Sử dụng Internal Clock HSI (Xem bài RCC và cấu hình Clock tại đây):

    Mình chọn tần số clock là 16MHz và bus AHB cấp cho Systick cũng mang tần số này.

Systick

Hình 2: Sơ đồ clock cho Systick

💚 Define các thanh ghi Systick

    Các bạn có thể tham khảo tài liệu Core để xem địa chỉ các thanh ghi của Systick, ở post trước về Systick Registers mình cũng đã nói qua.

Register

Hình 3: Địa chỉ các thanh ghi Systick

💚 Cấu hình cho Systick Timer

    Mình sẽ chọn Clock Source cho Systick là External Clock, tức là AHB/8 = 16MHz/8 = 2MHz. Như vậy, mỗi một chu kỳ xung clock sẽ là 1/2MHz = 0.5us. Vậy để đếm được 1us, chúng ta chỉ cần 2 chu kỳ clock là đủ.

    ⇒ Cần nạp thanh ghi Reload giá trị là 2 - 1 = 1 (vì đếm từ N-1 xuống 0). Sau đó chúng ta cho phép ngắt Systick bằng thanh ghi SYST_CSR.

Systick

Hình 4: Cấu hình SYST_CSR

💚 Cấu hình cho hàm Delay

    Cho phép bộ đếm chạy và bắt đầu đếm số lần tràn của Systick Timer bằng chương trình phục vụ ngắt SysTick_Handler(). Dưới đây là đoạn code hoàn chỉnh:

#include <stdint.h>
#define RCC_BASE        0x40023800
#define SYSTICK_BASE    0xE000E010

// RCC
#define RCC_CR          (*(volatile uint32_t *)(RCC_BASE + 0x00))
#define RCC_CFGR        (*(volatile uint32_t *)(RCC_BASE + 0x08))

// Systick
#define SYST_CSR        (*(volatile uint32_t *)(SYSTICK_BASE + 0x00))
#define SYST_RVR        (*(volatile uint32_t *)(SYSTICK_BASE + 0x04))
#define SYST_CVR        (*(volatile uint32_t *)(SYSTICK_BASE + 0x08))

// RCC
#define RCC_CR_HSION    (1 << 0)
#define RCC_CR_HSIRDY   (1 << 1)
#define RCC_CFGR_SW_HSI (0 << 0)
#define RCC_CFGR_HPRE_DIV1 (0 << 4)

// SYST_CSR
#define SYST_CSR_COUNTFLAG  (1 << 16)
#define SYST_CSR_CLKSOURCE  (1 << 2)
#define SYST_CSR_TICKINT    (1 << 1)
#define SYST_CSR_ENABLE     (1 << 0)

// Global Variable to count the Systick Overflow
volatile uint32_t usTicks = 0;

// Clock Configuration
void SystemClock_Config(void) {
    RCC_CR |= RCC_CR_HSION;          // Enable HSI
    while (!(RCC_CR & RCC_CR_HSIRDY)); // Wait for HSI ready
    RCC_CFGR &= ~(RCC_CFGR_SW_HSI);  // SYSCLK = HSI = 16 MHz
    RCC_CFGR &= ~(RCC_CFGR_HPRE_DIV1); // AHB = 16 MHz
}

// Systick Initialization
void SysTick_Init(void) {
    SYST_RVR = 1;           // Reload = 2 - 1 (1us with clock 2MHz)
    SYST_CVR = 0;           //
    SYST_CSR = 0;           // Clear old configuration
    SYST_CSR |= SYST_CSR_TICKINT;    // Enable Systick Interrupt
    SYST_CSR &= ~SYST_CSR_CLKSOURCE; // Clock = AHB/8 = 2MHz
    SYST_CSR |= SYST_CSR_ENABLE;     // Enable Systick
}

// Systick Interrupt Handler
void SysTick_Handler(void) {
    if (usTicks > 0) {
        usTicks--;
    }
    if (usTicks == 0) {
        SYST_CSR &= ~SYST_CSR_ENABLE; // Disable Systick after Done
    }
}

// Hàm delay microsecond
void Delay_us(uint32_t us) {
    usTicks = us * 2; // Each us need 2 tick
    SYST_CVR = 0;     // Reset counter
    SYST_CSR |= SYST_CSR_ENABLE; // Enable Systick
    while (usTicks);  // Wait for Counting
}

// Delay millisecond
void Delay_ms(uint32_t ms) {
    Delay_us(ms * 1000); // 1ms = 1000us
}

int main(void) {
    SystemClock_Config();
    SysTick_Init();

    while (1) {
        Delay_ms(1000); // Delay 1s
        // Add Your Application Code (Example: Blinked LED)
    }
}
    

💬 Ở đây mỗi tick chúng ta sẽ nhảy vào SysTick_Handler(). Mỗi microsecond sẽ tương ứng là 2 tick. Khi nhảy vào ISR, chúng ta sẽ giảm tick này đi, cho đến khi usTicks này bằng 0 thì dừng và disable Systick.

ISR

Hình 6: Hàm SysTick_Handler

💬 Có hàm Delay_us rồi thì hàm Delay_ms cũng rất đơn giản, 1ms = 1000us.

Delayms

Hình 7: Hàm Delay_ms

    ➤ Các bạn có thể DOWNLOAD Code Tại đây!

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

6 Nhận xét

  1. dạ anh cho em hỏi ở hàm Systick_Handler() chỗ while(usTisks); mình truyền usTicks bằng 1000 thì làm sao để chương trình thoát được vòng while đó ạ

    Trả lờiXóa
  2. à em thấy cái hàm ở dưới r ạ, nãy em k để ý. cảm ơn anh ạ

    Trả lờiXóa
  3. mình gọi hàm Systick_Handler() ở trong vòng while(usTisks); đúng không ạ

    Trả lờiXóa
    Trả lời
    1. Không nhé, Systick_Handler() là hàm phục vụ ngắt và sẽ được thực thi khi ngắt Systick xảy ra

      Xóa
  4. dạ anh cho em hỏi làm sao để chương trình gọi hàm Systick_Handler() khi có ngắt xảy ra vậy ạ

    Trả lờiXóa
    Trả lời
    1. Em tìm hiểu về flow của ngắt nhé, khi ngắt xảy ra CPU sẽ tìm tới vector table và lấy được địa chỉ của hàm Systick_Handler

      Xóa
Mới hơn Cũ hơn
//