Chào mừng các bạn đến với buổi ôn tập chuyên sâu thứ hai! Bài viết này được thiết kế để giải quyết toàn bộ các kiến thức về nhân Cortex-M0, cơ chế vận hành ngắt, ngoại vi PIT Timer, giao thức UART và các giải thuật hệ thống cốt lõi.
🎯 Mục tiêu ôn tập trọng tâm
- Quy trình thực thi Ngắt & Vector Table.
- NVIC và quản lý SysTick nâng cao.
- Hệ thống thanh ghi và Context Switching.
- Lập trình PIT Timer & UART Auto-baudrate.
- Dòng chảy UART-to-Queue và giải thuật Queue.
- Thao tác Flash & Kỹ thuật Bootloader Jump.
I. NHÂN CORTEX-M0 VÀ CƠ CHẾ NGẮT (INTERRUPT)
1. Quy trình thực hiện Ngắt chi tiết
Khi có một tín hiệu ngắt hợp lệ, CPU Cortex-M0 thực hiện quy trình 4 bước phần cứng:
- Stacking (Lưu trữ ngữ cảnh): CPU tự động đẩy 8 thanh ghi quan trọng (xPSR, PC, LR, R12, R3, R2, R1, R0) vào vùng nhớ Stack.
- Vector Fetch (Tìm địa chỉ): CPU truy cập bảng Vector để đọc địa chỉ của hàm xử lý ngắt (ISR).
- Execution (Thực thi): PC được nạp địa chỉ ISR và bắt đầu chạy code xử lý sự kiện.
- Unstacking (Khôi phục): Khi kết thúc ISR (lệnh
BX LR với giá trị EXC_RETURN), CPU tự động nạp lại 8 thanh ghi từ Stack để quay về chương trình cũ.
2. Tổ chức Bảng Vector Ngắt
Bảng Vector là một mảng các địa chỉ 32-bit chứa các con trỏ hàm (Exception/Interrupt Handlers):
- Vị trí 0 (Địa chỉ 0x00): Giá trị khởi tạo của Main Stack Pointer (MSP).
- Vị trí 1 (Địa chỉ 0x04): Địa chỉ của hàm Reset_Handler.
- Các vị trí tiếp theo: NMI, HardFault, SVC, PendSV, SysTick và các ngắt ngoại vi (UART, Timer…).
3. Vị trí và Di chuyển Bảng Vector
- Vị trí mặc định: Nằm ở đầu bộ nhớ Flash (thường là
0x0000 0000).
- Mục đích di chuyển (Relocation): Để hỗ trợ Bootloader. Khi hệ thống có nhiều Firmware, CPU cần trỏ bảng Vector tới vị trí bắt đầu của Firmware đang chạy (thông qua thanh ghi VTOR) để xử lý ngắt chính xác.
4. Chức năng của NVIC (Nested Vectored Interrupt Controller)
- Quản lý mức ưu tiên (Priority) cho các ngắt.
- Hỗ trợ ngắt lồng nhau (Nesting): Ngắt ưu tiên cao có thể ngắt ngang ngắt thấp.
- Cho phép/Vô hiệu hóa (Enable/Disable) và xóa cờ ngắt.
- Xử lý các ngắt đang chờ (Pending).
5. Các bước cấu hình SysTick
- Tính toán giá trị nạp lại:
Reload = (F_cpu * Delay_s) - 1.
- Ghi giá trị vào thanh ghi
SYST_RVR.
- Xóa giá trị hiện tại trong thanh ghi
SYST_CVR.
- Cấu hình thanh ghi
SYST_CSR: Chọn nguồn clock, bật ngắt, và bật Timer.
6. Hệ thống thanh ghi Cortex-M0
- General Purpose (R0 – R12): Dùng để tính toán dữ liệu tạm thời.
- Special Purpose:
- SP (R13): MSP (Main Stack Pointer) cho ngắt/hệ thống và PSP (Process Stack Pointer) cho Task.
- LR (R14): Link Register lưu địa chỉ trả về.
- PC (R15): Program Counter trỏ tới lệnh tiếp theo.
7. Hoạt động thanh ghi: Hàm gọi hàm vs Ngắt
| Hành động |
Hàm mẹ gọi hàm con |
Chương trình chính sang Ngắt |
| Lưu trữ địa chỉ trả về |
LR chứa địa chỉ thực của lệnh kế tiếp. |
LR được nạp giá trị đặc biệt EXC_RETURN (0xFFFFFFF1..). |
| Lưu ngữ cảnh |
Phần mềm tự thực hiện nếu cần. |
Phần cứng tự động thực hiện Stacking. |
8. So sánh các dòng Cortex của ARM
- Cortex-M0/M0+: Tối ưu cho giá thành và năng lượng (IoT, pin).
- Cortex-M3/M4: Hiệu năng cân bằng, M4 có thêm bộ DSP và FPU (xử lý số thực).
- Cortex-M7: Hiệu năng cực cao, hỗ trợ Cache, Pipeline sâu hơn cho ứng dụng đồ họa/tính toán nặng.
II. TIMER WITH PIT (PERIODIC INTERRUPT TIMER)
1. Các bước cấu hình PIT
- Cấp xung clock cho module PIT (trong thanh ghi SIM_SCGC6 hoặc tương đương).
- Bật module PIT bằng cách xóa bit MDIS trong thanh ghi
PIT_MCR.
- Thiết lập giá trị nạp lại
LDVAL = (T_mongmuon / T_clock_bus) - 1.
- Bật ngắt kênh (TIE) và bật Timer (TEN) trong thanh ghi
PIT_TCTRLn.
- Cho phép ngắt kênh PIT trên NVIC.
2. Thực hành: Toggle 2 LED độc lập (1s và 1.3s)
void PIT_IRQHandler(void) {
if (PIT_GetStatusFlags(PIT, kPIT_Chnl_0)) {
PIT_ClearStatusFlags(PIT, kPIT_Chnl_0);
LED_RED_Toggle(); // Cấu hình LDVAL cho 1s
}
if (PIT_GetStatusFlags(PIT, kPIT_Chnl_1)) {
PIT_ClearStatusFlags(PIT, kPIT_Chnl_1);
LED_BLUE_Toggle(); // Cấu hình LDVAL cho 1.3s
}
}
III. UART VÀ THUẬT TOÁN AUTO-BAUDRATE
1. Các bước cấu hình UART
- Tính toán giá trị chia (Baudrate Divisor) dựa trên Clock và Baudrate mong muốn.
- Cấu hình khung truyền: Data bits (8/9), Parity (None/Even/Odd), Stop bits (1/2).
- Bật bộ phát (TE) và bộ nhận (RE).
- Bật ngắt RX (RIE) để nhận dữ liệu bất đồng bộ.
2. Thuật toán tính Baudrate tự động (Auto-baudrate)
CPU sẽ sử dụng một Timer tốc độ cao để đo thời gian giữa các sườn tín hiệu của một ký tự đồng bộ được gửi từ Master (thường là ký tự 0x55 – 01010101).
Công thức: Baudrate = 1 / (Thời gian đo được của 1 bit).
IV. FLASH VÀ BOOTLOADER
1. Luồng dữ liệu UART sang Queue
- Ngắt UART RX xảy ra -> Nhảy vào ISR.
- Đọc byte từ thanh ghi
UART_DATA.
- Gọi hàm
Queue_Push(&uartQueue, byte).
- Trong
while(1), chương trình chính gọi Queue_Pop() để lấy dữ liệu ra xử lý mà không làm mất dữ liệu khi CPU bận.
2. Giải thuật Queue (Ring Buffer)
- Cấu trúc: Gồm mảng
data[], chỉ số head, tail và size.
- Push: Lưu vào vị trí
tail, sau đó tail = (tail + 1) % MAX_SIZE.
- Pop: Lấy từ vị trí
head, sau đó head = (head + 1) % MAX_SIZE.
3. Thao tác Flash (Program/Erase)
Quy trình bắt buộc: Unlock (Ghi mã bảo mật) -> Erase Sector (Xóa vùng nhớ về 0xFF) -> Program (Ghi dữ liệu mới) -> Lock (Khóa lại để bảo mật).
4. Chuyển từ Bootloader sang User App
- Vô hiệu hóa toàn bộ ngắt và ngoại vi (De-initialization).
- Thiết lập lại con trỏ MSP lấy từ địa chỉ đầu tiên của User App (0x0).
- Lấy địa chỉ Reset_Handler (địa chỉ 0x4 của App) nạp vào thanh ghi PC để thực thi lệnh nhảy.
Gợi ý bài tiếp theo: Tổng Kết Lộ Trình Đào Tạo Lập Trình Nhúng tại AIoT.