1. Giới thiệu
Trong bất kỳ hệ điều hành thời gian thực (RTOS) nào, ngắt hệ thống định kỳ (System Tick - SysTick) đóng vai trò vô cùng quan trọng, vì nó là trái tim tạo ra nhịp thời gian (time base) cho các thành phần như:
- Bộ định thời (Timers)
- Delay hoặc timeout
- Scheduler (lập lịch cho task)
- Timeout trong hàng đợi, semaphore, mutex...
FreeRTOS – một trong những RTOS phổ biến nhất cho vi điều khiển – sử dụng ngắt SysTick để kích hoạt scheduler và duy trì hoạt động đồng bộ thời gian. Nhưng điều gì xảy ra khi có ngắt khác xen vào, hoặc khi SysTick trùng lúc một ngắt cao hơn đang chạy? Bài viết này sẽ phân tích chi tiết về:
- Cơ chế hoạt động của SysTick trong FreeRTOS
- Tương tác giữa SysTick và các ngắt khác
- Điều gì xảy ra khi các ngắt đồng thời xảy ra
- Tác động đến kernel và các task
2. Ngắt SysTick là gì?
2.1. Tổng quan về SysTick
SysTick là một bộ định thời 24-bit tích hợp trong hầu hết các vi điều khiển ARM Cortex-M. Nó thường được sử dụng để tạo ra ngắt định kỳ, với chu kỳ thường là 1ms.
Khi được cấu hình, SysTick sẽ:
- Đếm lùi từ giá trị reload xuống 0
- Khi chạm 0, sinh ra ngắt SysTick_Handler
- Tự động reload lại và tiếp tục đếm
2.2. Trong FreeRTOS, SysTick được dùng để:
- Tăng biến đếm thời gian hệ thống:
xTickCount
- Kích hoạt lập lịch (nếu
configUSE_PREEMPTION == 1
) - Kiểm tra timeout của delay, semaphore, queue
3. Cấu trúc xử lý SysTick trong FreeRTOS
3.1. Cấu hình trong FreeRTOSConfig.h
#define configTICK_RATE_HZ 1000 // SysTick 1ms
#define configUSE_TICK_HOOK 0
#define configUSE_PREEMPTION 1
3.2. ISR (Interrupt Service Routine)
void SysTick_Handler(void)
{ // Tăng tick count hệ thống if (xTaskIncrementTick() != pdFALSE) { // Nếu có task ưu tiên cao hơn, gọi context switch portYIELD_FROM_ISR(); }
}
xTaskIncrementTick()
: tăng biến tick count, kiểm tra timeout của các task delay.portYIELD_FROM_ISR()
: yêu cầu chuyển context nếu cần thiết.
4. Điều gì xảy ra khi ngắt khác xảy ra cùng lúc SysTick?
Câu trả lời liên quan đến mức độ ưu tiên ngắt và cơ chế ngắt lồng nhau (nested interrupts).
4.1. Cơ chế xử lý ngắt của Cortex-M
Vi xử lý Cortex-M hỗ trợ:
- Nested Interrupts: cho phép ngắt cao hơn gián đoạn ngắt thấp hơn.
- Mỗi ngắt có một mức ưu tiên riêng (0 là cao nhất)
- NVIC (Nested Vectored Interrupt Controller) điều khiển việc này
4.2. Ưu tiên ngắt SysTick
- Mặc định, FreeRTOS cấu hình ngắt SysTick có mức ưu tiên thấp để không làm gián đoạn các ngắt quan trọng như UART, ADC, SPI...
- Mức ưu tiên này thường nằm dưới
configMAX_SYSCALL_INTERRUPT_PRIORITY
.
Ví dụ:
#define configKERNEL_INTERRUPT_PRIORITY 255 // thấp nhất
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 // ngắt có thể gọi hàm FreeRTOS
5. Tình huống cụ thể và cách CPU xử lý
Tình huống 1: Ngắt khác đang chạy khi SysTick xảy ra
Giả sử ngắt UART (mức ưu tiên cao hơn SysTick) đang được xử lý, và đúng lúc này thì SysTick đến hạn.
Điều gì xảy ra?
- CPU không dừng ngắt UART, mà tiếp tục xử lý nó.
- Ngắt SysTick sẽ chờ đến khi CPU thoát khỏi ngắt UART (do mức ưu tiên thấp hơn).
- Sau khi ngắt UART kết thúc, CPU mới gọi đến
SysTick_Handler
.
Kết luận: SysTick không bị bỏ qua, chỉ bị trì hoãn. Tick vẫn được đếm.
Tình huống 2: SysTick đang chạy thì có ngắt cao hơn xảy ra
Ví dụ: đang xử lý trong SysTick_Handler
, thì ngắt EXTI (ngoại vi) có mức ưu tiên cao hơn được kích hoạt.
CPU làm gì?
- Cortex-M cho phép ngắt lồng nhau, do đó CPU tạm dừng SysTick, nhảy sang xử lý EXTI.
- Sau khi EXTI xong, CPU quay lại tiếp tục phần còn lại của
SysTick_Handler
.
Kết luận: SysTick vẫn được xử lý đầy đủ, chỉ là tạm ngưng trong thời gian xử lý ngắt EXTI.
Tình huống 3: Ngắt ngoài có ưu tiên thấp hơn SysTick
- Nếu EXTI hoặc ADC có mức ưu tiên thấp hơn SysTick, thì khi
SysTick_Handler
đang chạy, ngắt đó phải chờ SysTick xử lý xong mới được phục vụ.
6. Vậy tick count có bị mất không?
Câu trả lời là không. Cơ chế SysTick được thiết kế rất ổn định:
- Tick không mất: mỗi lần hết chu kỳ 1ms, bộ SysTick tạo một ngắt. Ngắt đó sẽ được xử lý sớm nhất có thể.
- Trong điều kiện ngắt dày đặc, ngắt SysTick có thể bị trễ, nhưng không mất.
xTickCount
vẫn tăng đều, nhưng không hoàn toàn chính xác nếu có độ trễ lớn.
Bạn có thể theo dõi xTaskGetTickCount()
hoặc vApplicationTickHook()
để đo độ trễ nếu cần.
7. Có nên tăng mức ưu tiên của SysTick?
Thông thường không nên tăng mức ưu tiên của SysTick vượt quá configMAX_SYSCALL_INTERRUPT_PRIORITY
. Lý do:
- Vi phạm nguyên tắc FreeRTOS: ISR không nên gọi API nếu mức ưu tiên quá cao.
- Gây ảnh hưởng đến các ISR quan trọng khác.
8. Tác động đến Scheduler
Khi xTaskIncrementTick()
phát hiện một task đã đến hạn:
- Nếu
configUSE_PREEMPTION == 1
, nó sẽ gọiportYIELD_FROM_ISR()
. - Lập lịch sẽ chuyển sang task mới sau khi
SysTick_Handler
kết thúc.
Nếu ngắt khác xảy ra giữa quá trình này: lập lịch bị trì hoãn một vài chu kỳ.
9. Tóm tắt hoạt động khi xảy ra ngắt lồng nhau
Tình huống | Ưu tiên ngắt | Hành vi CPU | Tick đếm có bị mất? |
---|---|---|---|
Ngắt khác đang chạy, SysTick đến | Cao hơn SysTick | SysTick chờ | Không |
SysTick đang chạy, ngắt khác đến | Cao hơn SysTick | CPU xử lý ngắt kia trước | Không |
Ngắt ngoài thấp hơn đến khi SysTick đang chạy | Thấp hơn | Chờ SysTick xong | Không |
Nhiều ngắt cao liên tiếp | Tùy cấu hình | SysTick có thể bị trì hoãn đáng kể | Có thể sai lệch thời gian thực một ít |
10. Mô phỏng luồng hoạt động (Sơ đồ)
11. Một số mẹo và cảnh báo
- Nếu bạn gọi API FreeRTOS trong ISR, đảm bảo ngắt đó có mức ưu tiên bằng hoặc thấp hơn
configMAX_SYSCALL_INTERRUPT_PRIORITY
- Tránh xử lý quá lâu trong ISR, bao gồm cả
SysTick_Handler
- Dùng
vApplicationTickHook()
để gắn thêm hành vi custom mà không sửa FreeRTOS code
12. Kết luận
Ngắt SysTick trong FreeRTOS là nền tảng để duy trì hoạt động của hệ điều hành. Cơ chế ưu tiên ngắt và xử lý ngắt lồng nhau của Cortex-M đảm bảo rằng:
- SysTick không bị mất (chỉ có thể bị trễ)
- Scheduler hoạt động chính xác trong môi trường ngắt dày đặc
- FreeRTOS vẫn ổn định khi có ngắt lồng nhau
Việc hiểu rõ hoạt động của SysTick và cách nó tương tác với các ngắt khác sẽ giúp bạn thiết kế hệ thống nhúng ổn định hơn, tránh lỗi khó tìm và tối ưu hóa thời gian đáp ứng của task.