Nội Dung Bài Viết
Trong quá trình lập trình STM32F103 thanh ghi, việc tạo delay chính xác và đo thời gian ổn định là yêu cầu nền tảng cho hầu hết các ứng dụng nhúng. Thay vì sử dụng delay vòng lặp không chính xác, SysTick timer – bộ định thời tích hợp trong lõi ARM Cortex-M3 – cung cấp cơ chế tạo ngắt định kỳ với độ chính xác cao, phù hợp cho các hệ thống real-time.
Trong bài viết này, chúng ta sẽ cấu hình SysTick timer STM32F103RCT6 ở mức thanh ghi, tạo ngắt mỗi 1ms, xây dựng hàm delay chính xác và ứng dụng thực tế để điều khiển LED bật/tắt mỗi 15 giây. Đồng thời, bài viết cũng giải thích chi tiết vai trò của từ khóa volatile, nguyên lý hoạt động của SysTick và kiểm chứng độ chính xác của timer trong thời gian dài, giúp bạn nắm vững nền tảng quan trọng khi lập trình STM32 bare-metal.
Mục tiêu bài viết Lập trình STM32F103 thanh ghi: Cấu hình Systick Timer STM32F103RCT6
Trong bài viết này, chúng ta sẽ thực hiện lập trình STM32F103 thanh ghi để:
- Cấu hình SysTick timer tạo ngắt chính xác 1ms
- Sử dụng SysTick để tạo delay chính xác
- Điều khiển LED tại chân PA8 bật/tắt mỗi 15 giây
- Kiểm tra độ chính xác của SysTick timer STM32F103 trong thời gian dài (2 phút)
Bài viết sử dụng STM32F103RCT6, lập trình bare-metal, không dùng HAL, phù hợp cho người học lập trình thanh ghi STM32F103.
Define Struct thanh ghi SysTick control and status register (STK_CTRL) cho lập trình STM32F103 thanh ghi: Cấu hình Systick Timer STM32F103RCT6
Dựa trên PM0056, Section 4.5 (trang 151-152): https://www.st.com/resource/en/programming_manual/pm0056-stm32f10xxx20xxx21xxxl1xxxx-cortexm3-programming-manual-stmicroelectronics.pdf
Thanh ghi SysTick control (SysTick_CTRL)

1 2 3 4 5 6 7 8 9 10 11 12 | // Thanh ghi Điều khiển SysTick (SysTick Control Register) typedef union { struct { // Structure for bit fields __IOM uint32_t ENABLE : 1; // Bit 0: Enable timer __IOM uint32_t TICKINT : 1; // Bit 1: Enable interrupt __IOM uint32_t CLKSOURCE : 1; // Bit 2: Clock source (0=AHB/8, 1=AHB) uint32_t reserved1 : 13; // Bit 3-15: Reserved __IOM uint32_t COUNTFLAG : 1; // Bit 16: Count flag uint32_t reserved2 : 15; // Bit 17-31: Reserved }; uint32_t reg; // Access entire register } SysTick_CTRL_Type; |
SysTick register map
1 2 3 4 5 6 7 | // Bản đồ thanh ghi SysTick (4.5.6 SysTick register map – Trang 154) typedef struct { SysTick_CTRL_Type CTRL; // 0x00: Điều khiển và trạng thái uint32_t LOAD; // 0x04: Giá trị nạp lại uint32_t VAL; // 0x08: Giá trị hiện tại uint32_t CALIB; // 0x0C: Giá trị hiệu chuẩn } SysTick_TypeDef; |

#define SYSTICK ((SysTick_TypeDef *)0xE000E010UL)
Giải thích về các thanh ghi trong SysTick control
- CTRL: Điều khiển timer và ngắt (Section 4.5.1, trang 151). ENABLE khởi động timer, TICKINT bật ngắt khi về 0, CLKSOURCE chọn clock xử lý hoặc clock chia.
- LOAD: Giá trị nạp lại cho tick (Section 4.5.2, trang 152).
- VAL: Chứa giá trị hiện tại của bộ đếm SysTick, đếm ngược từ giá trị được nạp trong thanh ghi LOAD (Reload Value Register) về 0. (Section 4.5.3, trang 153).
- CALIB: Cung cấp thông tin hiệu chuẩn cho SysTick, giúp xác định tần số clock hoặc các thông số khác để đảm bảo độ chính xác thời gian. Tuy nhiên, trên STM32F103RCT6, giá trị này thường không được sử dụng hoặc mặc định là 0. (Section 4.5.4, trang 153).
Cấu hình SysTick Timer STM32F103
Thông số cấu hình SysTick Timer 1ms
- SYSCLK = 72 MHz
- Chu kỳ mong muốn: 1 ms
- LOAD = 71999
(72 MHz / 1000 = 72.000 → LOAD = 72000 − 1)
Cấu hình SysTick theo tài liệu PM0056
Theo PM0056 – STM32F10xxx Reference Manual, Section 4.5.1 (trang 151):
CLKSOURCE = 1→ SysTick sử dụng AHB clockTICKINT = 1→ Cho phép ngắt SysTickENABLE = 1→ Bật SysTick timer
Tại sao cần dùng từ khóa volatile?
Trong C, từ khóa volatile được dùng để thông báo cho trình biên dịch rằng giá trị của biến có thể thay đổi bất ngờ, ngoài luồng thực thi thông thường.
Ngăn tối ưu hóa không mong muốn
Trình biên dịch có thể:
- Lưu biến vào register
- Bỏ qua việc đọc lại bộ nhớ nếu nghĩ rằng biến không đổi
Khi khai báo volatile, trình biên dịch (compiler) sẽ không giả định giá trị của biến là cố định và sẽ luôn đọc/ghi trực tiếp vào bộ nhớ. Điều này đảm bảo rằng mọi thay đổi đối với biến được phản ánh chính xác.
Các trường hợp cần dùng volatile
- Biến bị thay đổi bởi phần cứng: Ví dụ, một thanh ghi phần cứng (như
GPIOC->IDR) có thể thay đổi do sự kiện bên ngoài (như nhấn nút). - Biến dùng trong ngắt (interrupt): Một biến như msTicks, được tăng trong hàm xử lý ngắt (SysTick_Handler), có thể thay đổi bất kỳ lúc nào, ngay cả khi mã chính (main) không trực tiếp sửa đổi nó.
- Biến dùng trong đa luồng (multithreading): Trong các hệ thống đa luồng, một biến có thể được thay đổi bởi luồng khác.
Vì sao msTicks cần volatile?
msTicksđược tăng trong ngắt SysTick_Handler (xử lý ngắt SysTick, được gọi mỗi 1ms)main()vàDelay_ms()đọc giá trị msTicks- Nếu không có
volatile, compiler có thể không đọc lại msTicks từ RAM. - Ví dụ, nó có thể lưu giá trị msTicks vào một thanh ghi và không đọc lại từ bộ nhớ, dẫn đến việc bỏ qua các cập nhật từ ngắt.
- Điều này làm delay sai thời gian
➡️ Khai báo volatile đảm bảo msTicks luôn phản ánh đúng giá trị thực tế
Code cấu hình SysTick timer 1ms STM32F103
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | volatile uint32_t msTicks = 0; // SysTick interrupt handler void SysTick_Handler(void) { msTicks++; // Increment millisecond counter } void SysTick_Config_1ms(void) { // Configure SysTick for 1ms interrupts (PM0056, Section 4.5, page 151) SYSTICK->LOAD = 71999; // Reload value for 1ms at 72MHz SYSTICK->VAL = 0; // Clear current value SYSTICK->CTRL.CLKSOURCE = 1; // Use AHB clock SYSTICK->CTRL.TICKINT = 1; // Enable interrupt SYSTICK->CTRL.ENABLE = 1; // Enable SysTick } void Delay_ms(uint32_t ms) { // Delay for specified milliseconds uint32_t start = msTicks; while (msTicks - start < ms); } |
➡️ SysTick interrupt mỗi 1ms → msTicks tăng chính xác → delay ổn định
Giá trị 71999 được sử dụng trong SYSTICK->LOAD để cấu hình SysTick timer tạo ra ngắt mỗi 1ms khi hệ thống chạy ở tần số 72MHz.
Link Github: Download chương trình cấu hình Systick timer 1ms STM32F103
Chương trình main điều khiển LED PA8 bật/tắt mỗi 15 giây
1 2 3 4 5 6 7 8 9 10 11 12 | int main(void) { SystemClock_Config(); GPIO_LED_Config(); SysTick_Config_1ms(); while (1) { GPIOA->BSRR.BR8 = 1; // LED on Delay_ms(15000); // 15s delay GPIOA->BSRR.BS8 = 1; // LED off Delay_ms(15000); } } |
Chương trình sử dụng systick timer 1ms để delay thời gian bât/tắt LED (PA8) mỗi 15 giây.
Link Github: Download chương trình cấu hình Systick timer 1ms STM32F103
Nguyên lý hoạt động của SysTick Timer
- SysTick là một bộ đếm giảm (down-counter) 24-bit trong lõi ARM Cortex-M3.
- SysTick đếm ngược từ giá trị được đặt trong thanh ghi LOAD (Reload Value Register) về 0, sau đó tạo ngắt (nếu được kích hoạt) và nạp lại giá trị từ LOAD để tiếp tục đếm.
- Tần số ngắt phụ thuộc vào:
- Tần số clock của SysTick (được chọn bởi CLKSOURCE).
- Giá trị trong thanh ghi LOAD.
Công thức tính chu kỳ ngắt SysTick
Tinterrupt = (LOAD+1) / Clock Frequency
Trong đó:
Tinterrupt: thời gian giữa 2 ngắt (giây)LOAD: Giá trị trong SYSTICK->LOAD.Clock Frequency: tần số clock SysTick (Hz)
Tần số clock của SysTick
- Khi
CLKSOURCE= 1, SysTick sử dụng tần số AHB clock (HCLK), không chia tần (PM0056, Section 4.5.1, page 151). - Trong cấu hình ở trên, tần số clock của SysTick là 72MHz (72,000,000 Hz):
- Hệ thống sử dụng HSE 8MHz với PLL x9, dẫn đến SYSCLK = 72MHz.
- AHB prescaler (HPRE = 0) không chia tần, do đó HCLK = SYSCLK = 72MHz.
Tính toán LOAD cho SysTick 1ms
- Clock SysTick = 72 MHz
- Chu kỳ mong muốn = 1 ms = 0.001 s
- Để tạo ngắt mỗi 1ms (0.001 giây), ta cần tính số chu kỳ clock cần thiết:
Number of clock cycles = TInterrupt x Clock Frequency
Thay số:
Number of clock cycles = 0.001s x 72000000 Hz = 72000
- Theo công thức SysTick, số chu kỳ clock bằng LOAD + 1 (vì đếm từ LOAD về 0, tổng cộng LOAD + 1 chu kỳ):
LOAD + 1 = 72000 ==> LOAD = 72000 - 1 = 71999
- Do đó, đặt SYSTICK->LOAD = 71999 sẽ tạo ra ngắt mỗi 1ms khi tần số clock là 72MHz.
Tham chiếu: PM0056, Section 4.5 (trang 150 – 154).
➡️ Đặt SYSTICK->LOAD = 71999 là chính xác
Chương trình kiểm tra độ chính xác SysTick Timer (2 phút)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | void SimpleDelayDebounce(GPIO_TypeDef* port, uint32_t pin, uint32_t* buttonState, uint32_t active) { static uint32_t lastKeyState = 0; uint32_t ButtonState = (((GPIOC->IDR.reg >> pin) & 1U) == active) ? 1: 0; *buttonState = 0; if ((ButtonState) & (lastKeyState == 0)) // Detect edge { Delay_Approx(30); // Debounce delay 0.03s if (((port->IDR.reg >> pin) & 1U) == active) // Check the button still pressed after delay { *buttonState = ButtonState; } } lastKeyState = ButtonState; } uint32_t checkSysTickTimer1s = 0; int main(void) { SystemClock_Config(); // Initialize system clock GPIO_LED_Config(); GPIO_Button_Config(); SysTick_Config_1ms(); uint32_t timeStart = msTicks; uint32_t timeStart_toggle = msTicks; while (1) { uint32_t ButtonState = 0; SimpleDelayDebounce(GPIOC,1,&ButtonState, 0); if (ButtonState) { LED_Toggle(); checkSysTickTimer1s = 0; timeStart = msTicks; } // Check systick timer 1second if (msTicks - timeStart >= 1000) { checkSysTickTimer1s++; timeStart = msTicks; } } } |
Link Github: Download chương trình cấu hình Systick timer 1ms STM32F103
Kết quả kiểm tra thực tế
Chương trình reset checkSysTickTimer1s bằng 0 và cập nhật giá trị timeStart mỗi khi nút nhấn (PC1) được nhấn.
Kiểm tra bằng cách so sánh thời gian hệ thống windows hoặc thời gian bấm giờ tại thời điểm nút nhấn được nhấn. Cách này chỉ kiểm tra tương đối thời gian systick.
Thời gian hệ thống tại lúc nhấn nút nhấn.

Thời gian hệ thống và thời gian systick timer đếm tại thời điểm 60 giây.

Thời gian systick timer đếm mỗi giây sau 2 phút là 120 và thời gian hệ thống windows là 4:42:00 – 4:40:00, đúng 2 phút. Như vậy thời gian systick đếm gần chính xác.
➡️ Cấu hình SysTick timer STM32F103 đếm thời gian gần như chính xác tuyệt đối
Kết luận
- SysTick timer là giải pháp chuẩn để tạo delay chính xác trong lập trình STM32F103 thanh ghi
- Việc sử dụng
volatilelà bắt buộc khi làm việc với ngắt - SysTick phù hợp cho:
- Delay không blocking
- Scheduler đơn giản
- Đo thời gian chính xác
👉 Đây là nền tảng quan trọng cho các bài tiếp theo như:
- FSM
- Debounce nút nhấn
- Non-blocking delay
- RTOS cơ bản
