Nội Dung Bài Viết
Trong thế giới lập trình STM32F103 thanh ghi, NVIC (Nested Vectored Interrupt Controller) là một thành phần cốt lõi giúp vi điều khiển STM32F103 xử lý các ngắt một cách thông minh và hiệu quả. Nếu bạn đang tìm hiểu về lập trình STM32F103 thanh ghi, việc nắm vững NVIC trong STM32F103 sẽ giúp bạn xây dựng các ứng dụng thời gian thực ổn định, như hệ thống điều khiển tự động hoặc giao tiếp ngoại vi. Bài viết này sẽ giải thích chi tiết NVIC là gì, cấu trúc của nó, cách sử dụng các thanh ghi quan trọng, bảng ánh xạ IRQ, và các ví dụ code thực tế cấu hình NVIC trong STM32F103 để bạn áp dụng trong lập trình STM32F103 thanh ghi.
NVIC (Nested Vectored Interrupt Controller) là gì?
NVIC (Nested Vectored Interrupt Controller) là bộ điều khiển ngắt được tích hợp sẵn trong nhân ARM Cortex-M3 của vi điều khiển STM32F103. Đây là thành phần cốt lõi của hệ thống ngắt, đóng vai trò trung tâm trong việc tiếp nhận, quản lý và phân phối các yêu cầu ngắt từ ngoại vi đến CPU.
NVIC chịu trách nhiệm:
- Quản lý các ngắt ngoại vi: Timer, UART, ADC, GPIO, DMA…
- Xử lý vectored interrupt – nhảy trực tiếp đến hàm ISR tương ứng, không cần dò tìm
- Hỗ trợ ngắt lồng nhau (nested interrupt)
→ Ngắt có mức ưu tiên cao hơn có thể chen ngang ngắt đang xử lý - Cho phép thiết lập mức ưu tiên (priority) cho từng nguồn ngắt
👉 Nói cách khác, NVIC chính là “bộ não của hệ thống ngắt”, giúp CPU quyết định khi nào cần tạm dừng chương trình chính để xử lý một sự kiện quan trọng.
Trong lập trình STM32F103 thanh ghi, NVIC giúp tối ưu hóa hiệu suất bằng cách hỗ trợ lên đến 68 nguồn ngắt (IRQ), với mức ưu tiên từ 0-15 (0 là cao nhất). Theo reference manual RM0008 của STMicroelectronics, NVIC được tích hợp để hỗ trợ nested-vectored interrupts, cho phép xử lý nhanh chóng mà không cần phần mềm can thiệp thủ công.
Lợi ích của NVIC trong lập trình STM32F103 thanh ghi bao gồm:
- Phản hồi nhanh với các sự kiện ngoại vi, giảm thời gian trễ.
- Hỗ trợ đa nhiệm bằng cách cho phép ngắt lồng ghép, hữu ích trong các ứng dụng như robot hoặc cảm biến IoT.
- Dễ dàng cấu hình qua các thanh ghi, giúp lập trình viên kiểm soát chính xác hệ thống ngắt.
Define struct thanh ghi NVIC cho lập trình STM32F103 thanh ghi: Cấu hình NVIC trong STM32F103
Dựa trên PM0056, Section 15.4 (trang 118-129):
Define struct thanh ghi NVIC_ISERx
![Hướng dẫn lập trình STM32F103 thanh ghi: Cấu hình NVIC trong STM32F103 - Thanh ghi ISER[n] – Interrupt Set-Enable Register](https://svtdhnlu.com/wp-content/uploads/2026/01/Thanh-ghi-cho-phep-ngat-ISER.webp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Base address: 0xE000E100 (cho ISER, ICER, ISPR, ICPR, IABR, IPR) // NVIC_STIR riêng tại 0xE000EF00 // Interrupt Set-Enable Register (NVIC_ISERx) - Offset: 0x000 + (x*4), x=0..2 // Mô tả: Viết 1 set enable interrupt (bật ngắt). Đọc trả trạng thái hiện tại. Viết 0 không ảnh hưởng. // Access: rw (read-set, write-1-to-enable) typedef union { struct { __IO uint32_t SETENA : 32; // Bits 31:0 - SETENA[x]: Set enable bit for IRQ x (0-31 cho ISER0, 32-63 cho ISER1, 64-80 cho ISER2) // 0: Interrupt disabled // 1: Interrupt enabled }; __IO uint32_t reg; // Access entire register } NVIC_ISER_Type; |
Define struct thanh ghi NVIC_ICERx

1 2 3 4 5 6 7 8 9 10 11 | // Interrupt Clear-Enable Register (NVIC_ICERx) - Offset: 0x080 + (x*4), x=0..2 // Mô tả: Viết 1 clear enable interrupt (tắt ngắt). Đọc trả trạng thái hiện tại. Viết 0 không ảnh hưởng. // Access: rw (read-clear, write-1-to-disable) typedef union { struct { __IO uint32_t CLRENA : 32; // Bits 31:0 - CLRENA[x]: Clear enable bit for IRQ x // Read: Current enable state // Write 1: Disable interrupt; Write 0: No effect }; __IO uint32_t reg; // Access entire register } NVIC_ICER_Type; |
Define struct thanh ghi NVIC_ISPRx

1 2 3 4 5 6 7 8 9 10 11 12 | // Interrupt Set-Pending Register (NVIC_ISPRx) - Offset: 0x100 + (x*4), x=0..2 // Mô tả: Viết 1 set pending interrupt (làm ngắt pending). Đọc trả trạng thái pending. Viết 0 không ảnh hưởng. // Lưu ý: Với level-sensitive, có thể không set pending nếu đã active. // Access: rw (read-set, write-1-to-pend) typedef union { struct { __IO uint32_t SETPEND : 32; // Bits 31:0 - SETPEND[x]: Set pending bit for IRQ x // 0: Interrupt not pending // 1: Interrupt pending (software trigger) }; __IO uint32_t reg; // Access entire register } NVIC_ISPR_Type; |
Define struct thanh ghi NVIC_ICPRx

1 2 3 4 5 6 7 8 9 10 11 | // Interrupt Clear-Pending Register (NVIC_ICPRx) - Offset: 0x180 + (x*4), x=0..2 // Mô tả: Viết 1 clear pending interrupt. Đọc trả trạng thái pending. Viết 0 không ảnh hưởng. // Access: rw (read-clear, write-1-to-clear) typedef union { struct { __IO uint32_t CLRPEND : 32; // Bits 31:0 - CLRPEND[x]: Clear pending bit for IRQ x // Read: Current pending state // Write 1: Clear pending; Write 0: No effect }; __IO uint32_t reg; // Access entire register } NVIC_ICPR_Type; |
Define struct thanh ghi NVIC_IABRx

1 2 3 4 5 6 7 8 9 10 11 | // Interrupt Active Bit Register (NVIC_IABRx) - Offset: 0x200 + (x*4), x=0..2 // Mô tả: Read-only, chỉ active interrupts (đang được xử lý). Viết không ảnh hưởng. // Access: r (read-only) typedef union { struct { __I uint32_t ACTIVE : 32; // Bits 31:0 - ACTIVE[x]: Active bit for IRQ x // 0: Interrupt not active // 1: Interrupt active (processor servicing) }; __I uint32_t reg; // Access entire register } NVIC_IABR_Type; |
Define struct thanh ghi NVIC_IPRx

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Interrupt Priority Register (NVIC_IPRx) - Offset: 0x300 + (n*4), n=0..20 (mỗi thanh ghi chứa 4 IRQ) // Mô tả: Cấu hình ưu tiên cho ngắt (giá trị thấp hơn = ưu tiên cao hơn). Byte-accessible. // Mỗi IRQ dùng 8 bits, nhưng chỉ [7:4] được dùng (4-bit cho 16 mức), [3:0] bỏ qua. // Ví dụ: IPR0 bits[7:0] cho IRQ0, [15:8] cho IRQ1, v.v. // Access: rw (byte-accessible) typedef union { struct { __IO uint32_t PRI_0 : 8; // Bits 7:0 - Priority for IRQ 4n (e.g., IRQ0 for IPR0) // 0x00-0xFF: Priority level (lower = higher priority; only [7:4] used) __IO uint32_t PRI_1 : 8; // Bits 15:8 - Priority for IRQ 4n+1 (e.g., IRQ1) __IO uint32_t PRI_2 : 8; // Bits 23:16 - Priority for IRQ 4n+2 (e.g., IRQ2) __IO uint32_t PRI_3 : 8; // Bits 31:24 - Priority for IRQ 4n+3 (e.g., IRQ3) }; __IO uint32_t reg; // Access entire register (word) } NVIC_IPR_Type; |
Define struct thanh ghi NVIC_STIRx

1 2 3 4 5 6 7 8 9 10 11 12 | // Software Trigger Interrupt Register (NVIC_STIR) - Offset: 0xE00 (từ base 0xE000EF00) // Mô tả: Viết ID ngắt (0-239) vào bits[8:0] để trigger pending (software-generated interrupt). // Đọc trả undefined. Chỉ privileged software. // Access: w (write-only) typedef union { struct { uint32_t reserved : 23; // Bits 31:9: Reserved (must be 0) __O uint32_t INTID : 9; // Bits 8:0: Interrupt ID to trigger (0-239 cho IRQ0-239) // Write: Trigger IRQ(INTID) as pending }; __O uint32_t reg; // Access entire register } NVIC_STIR_Type; |
Sơ đồ thanh ghi NVIC trong STM32F103 (NVIC register map)


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // NVIC Main Register Block (base 0xE000E100) typedef struct { NVIC_ISER_Type ISER[3]; // 0x000-0x008: Interrupt Set-Enable (x=0-2) uint32_t RESERVED0[29]; // 0x00C-0x07C: Reserved NVIC_ICER_Type ICER[3]; // 0x080-0x088: Interrupt Clear-Enable (x=0-2) uint32_t RESERVED1[29]; // 0x08C-0x0FC: Reserved NVIC_ISPR_Type ISPR[3]; // 0x100-0x108: Interrupt Set-Pending (x=0-2) uint32_t RESERVED2[29]; // 0x10C-0x17C: Reserved NVIC_ICPR_Type ICPR[3]; // 0x180-0x188: Interrupt Clear-Pending (x=0-2) uint32_t RESERVED3[29]; // 0x18C-0x1FC: Reserved NVIC_IABR_Type IABR[3]; // 0x200-0x208: Interrupt Active Bit (x=0-2) uint32_t RESERVED4[61]; // 0x20C-0x2FC: Reserved NVIC_IPR_Type IPR[21]; // 0x300-0x350: Interrupt Priority (0-20, covers 84 IRQs) uint32_t RESERVED5[683]; // 0x354-0xDFC: Reserved NVIC_STIR_Type STIR; // 0xE00: Software trigger interrupt register } NVIC_Type; #define NVIC ((NVIC_Type *) 0xE000E100UL) |
Cấu trúc của NVIC trong STM32F103
Trong STM32F103, NVIC được ánh xạ vào vùng System Control Space (SCS) của Cortex-M3.
- Base address NVIC:
0xE000E100 - Tài liệu tham chiếu: PM0056 – Cortex-M3 Programming Manual
Các thanh ghi NVIC quan trọng
| Thanh ghi | Chức năng | Số lượng |
|---|---|---|
| ISER[0..2] | Bật ngắt (Set Enable) | 3 × 32 = 96 bit → đủ cho 81 IRQ |
| ICER[0..2] | Tắt ngắt (Clear Enable) | 3 thanh ghi x 32 bit |
| ISPR[0..2] | Đặt pending (ngắt đang chờ) | 3 thanh ghi x 32 bit |
| ICPR[0..2] | Xóa pending | 3 thanh ghi x 32 bit |
| IABR[0..2] | Trạng thái active (đang xử lý) | 3 thanh ghi x 32 bit |
| IPR[0..20] | Thiết lập mức ưu tiên (4 IRQ/thanh ghi) | 21 thanh ghi x 8 bit x4 |
STM32F103 hỗ trợ tối đa 68 IRQ ngoại vi, được chia thành các nhóm 32-bit.
Bảng ánh xạ IRQ (Interrupt Vector Table) – RẤT QUAN TRỌNG
📘 RM0008 – Trang 198–206 – Mục 10.1.2



Bảng Interrupt Vector Table cho biết:
- Mỗi ngoại vi (TIM, USART, EXTI, ADC…) ứng với IRQ number nào
- Tên ISR handler tương ứng
- Thứ tự vector trong bộ nhớ
👉 Có thể hình dung bảng IRQ giống như danh bạ điện thoại:
- Bạn muốn gọi TIM2 → phải biết “số điện thoại” của nó là IRQ 28
Bảng IRQ dùng để làm gì?
| Việc cần làm | Dùng bảng IRQ để… |
|---|---|
| 1. Bật ngắt | Tìm IRQ Number → dùng trong NVIC->ISER[] |
| 2. Tắt ngắt | Dùng cùng IRQ Number với NVIC->ICER[] |
| 3. Đặt ưu tiên | Tính IPR[index] và PRI_x từ IRQ Number |
| 4. Viết ISR | Đặt tên hàm xử lý đúng: void TIM2_IRQHandler() |
Bảng này rất quan trọng trong lập trình STM32F103 thanh ghi vì nó giúp ánh xạ đúng ngoại vi với NVIC, tránh lỗi như ngắt không kích hoạt.
Minh họa cấu hình NVIC với TIM2
| Bước | Hành động | Dựa vào bảng IRQ |
|---|---|---|
| 1 | Tìm TIM2 trong bảng → Position = 44 | Position: 44 |
| 2 | Tính IRQ Number → 44 – 16 = 28 | IRQ number: 28 |
| 3 | Bật ngắt | NVIC_Interrupt_Enable(28) |
| 4 | Đặt ưu tiên | NVIC_Set_Interrupt_Priority(28, 5) |
| 5 | Viết ISR | void TIM2_IRQHandler() |
Sơ đồ hoạt động của NVIC
Khi một ngắt xảy ra, NVIC sẽ kiểm tra:
Ngắt đã được enable chưa?
Priority có cao hơn ngắt đang chạy không?
Nếu có, NVIC sẽ tạm dừng chương trình chính, nhảy vào hàm phục vụ ngắt (ISR) tương ứng. Sau khi ISR chạy xong, CPU quay lại vị trí trước khi bị ngắt.
Trong lập trình STM32F103 thanh ghi, sơ đồ này giúp hiểu cách NVIC hỗ trợ nested interrupts, nơi ngắt ưu tiên cao có thể chen ngang, tối ưu cho các hệ thống thời gian thực.
Thanh ghi ISER – Interrupt Set Enable Register
Trong lập trình STM32F103 thanh ghi, ISER là bước đầu tiên để kích hoạt ngắt, thường kết hợp với EXTI cho GPIO.
![Hướng dẫn lập trình STM32F103 thanh ghi: Cấu hình NVIC - Thanh ghi ISER[n] – Interrupt Set-Enable Register](https://svtdhnlu.com/wp-content/uploads/2026/01/Thanh-ghi-cho-phep-ngat-ISER.webp)
Mô tả thanh ghi ISER
| Trường | Mô tả |
|---|---|
| Địa chỉ | 0xE000E100 + x * 4 (x = 0,1,2) |
| Kích thước | 32-bit |
| Phạm vi IRQ | ISER[0]: IRQ 0–31 ISER[1]: IRQ 32–63 ISER[2]: IRQ 64–80 |
| Cơ chế | Write-1-to-enable, Read = current state |
| Viết 1 | Bật ngắt tương ứng |
| Viết 0 | Không ảnh hưởng |
| Đọc | Trả về trạng thái bật/tắt |
Code enable IRQ
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 | /** * @brief Bật ngắt (Interrupt Enable) cho IRQ chỉ số irqIndex * @param irqIndex: Số thứ tự IRQ (0-80), ví dụ: TIM2_IRQn = 28 * @retval None * * @note Dựa trên PM0056, Section 4.3.2 - NVIC_ISERx * - ISER[0]: IRQ 0-31 * - ISER[1]: IRQ 32-63 * - ISER[2]: IRQ 64-80 * - Viết 1 vào bit → bật ngắt, viết 0 → không ảnh hưởng */ void NVIC_Interrupt_Enable(uint8_t irqIndex) { if (irqIndex > 80) { return; // STM32F103 chỉ hỗ trợ tối đa IRQ 80 (PM0056, Table 44) } if(irqIndex <= 31) { NVIC->ISER[0].reg |= (1 << irqIndex); // Bật IRQ 0-31 trong ISER[0] } else if (irqIndex <= 63) { NVIC->ISER[1].reg |= (1 << (irqIndex-32)); // Bật IRQ 32-63 → bit 0-31 trong ISER[1] } else if (irqIndex <= 80) { NVIC->ISER[2].reg |= (1 << (irqIndex-64)); // Bật IRQ 64-80 → bit 0-16 trong ISER[2] } } |
Link Github: Download code cầu hình NVIC trong lập trình STM32F103RCT6
Thanh ghi ICER – Interrupt Clear Enable Register
ICER hữu ích để tắt ngắt tạm thời trong lập trình STM32F103 thanh ghi, tránh xung đột.

Mô tả thanh ghi ICER
| Trường | Mô tả |
|---|---|
| Địa chỉ | 0xE000E180 + x * 4 |
| Cơ chế | Write-1-to-disable, Read = current state |
| Viết 1 | Tắt ngắt |
| Viết 0 | Không ảnh hưởng |
| Đọc | Trạng thái hiện tại |
Link Github: Download code cầu hình NVIC trong lập trình STM32F103RCT6
Thanh ghi IPR – Interrupt Priority Register
Trong lập trình STM32F103 thanh ghi, IPR cho phép tùy chỉnh ưu tiên.

Mô tả thanh ghi IPR
| Trường | Mô tả |
|---|---|
| Số lượng | 21 thanh ghi (IPR0 – IPR20) |
| Mỗi thanh ghi | Chứa 4 IRQ |
| Mỗi IRQ | Dùng 8 bit, nhưng chỉ bit [7:4] có hiệu lực |
| Bit [3:0] | Bị bỏ qua (implementation defined) |
👉 Priority càng nhỏ → độ ưu tiên càng cao
Cấu trúc 1 thanh ghi IPR
| Bit 31–24 | Bit 23–16 | Bit 15–8 | Bit 7–0 |
|---|---|---|---|
| IRQ (4n+3) | IRQ (4n+2) | IRQ (4n+1) | IRQ (4n) |
Code set priority
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 48 49 50 51 52 53 | /** * @brief Thiết lập mức ưu tiên (Priority Level) cho IRQ * @param irqIndex: Số thứ tự IRQ (0-80) * @param level: Mức ưu tiên (0-15), 0 = cao nhất * @retval None * * @note Dựa trên PM0056, Section 4.3.8 - NVIC_IPR[x] * - Mỗi thanh ghi IPR chứa 4 IRQ (8 bit mỗi IRQ) * - Chỉ bit [7:4] có hiệu lực → level << 4 * - idxIPR = irqIndex / 4 → chỉ số thanh ghi * - idxPRI = irqIndex % 4 → vị trí trong thanh ghi (0-3) */ void NVIC_Set_Interrupt_Priority(int8_t irqIndex, uint8_t level) { if (irqIndex > 80) { return; // IRQ không hợp lệ } if (level > 15) { level = 15; // Giới hạn mức ưu tiên (4 bit → 0-15) } level = (level & 0x0F) << 4; // Dịch trái 4 bit → ghi vào bit [7:4] if (irqIndex < 0) { // System handler priority registers (SHPRx) config } else { uint8_t idxIPR = irqIndex/4; // Index of the IPR register (0-20) uint8_t idxPRI = irqIndex%4; // Sub-field index: 0=PRI_0, 1=PRI_1, 2=PRI_2, 3=PRI_3 if (idxPRI == 0) { NVIC->IPR[idxIPR].PRI_0 = level; // IRQ 4n → byte [7:0] } else if (idxPRI == 1) { NVIC->IPR[idxIPR].PRI_1 = level; // IRQ 4n+1 → byte [15:8] } else if (idxPRI == 2) { NVIC->IPR[idxIPR].PRI_2 = level; // IRQ 4n+2 → byte [23:16] } else { NVIC->IPR[idxIPR].PRI_3 = level; // IRQ 4n+3 → byte [31:24] } } }; |
Link Github: Download code cầu hình NVIC trong lập trình STM32F103RCT6
Kiểm tra cấu hình NVIC trong STM32F103 bằng Keil uVision
Ví dụ cấu hình TIM2 IRQ priority = 5
Bật bảng trạng thái thanh ghi NVIC: Từ giao diện uVision, Chọn Menu > Peripherals > System Viewer > NVIC.

TIMER2 IRQ nằm ở vị trí thứ 28 trong bảng vector ngắt:

Set mức độ ưu tiên ngắt TIMER2 là 5 và ở vị trí 28 nên thanh ghi PR7 và IPR_0 được set giá trị 0x50. Vì mỗi thanh ghi PR có 4 thanh ghi IPR, tương ứng IRQn, IRQn+1, IRQn+3, IRQn+4.

Lỗi Thường Gặp Khi Lập Trình STM32F103 Thanh Ghi Với NVIC Và Cách Khắc Phục
- Lỗi 1: Ngắt không kích hoạt – Nguyên nhân: Chưa bật ISER hoặc sai IRQ number. Khắc phục: Kiểm tra bảng vector trong RM0008 và sử dụng NVIC_Interrupt_Enable đúng.
- Lỗi 2: Ưu tiên không hoạt động – Nguyên nhân: Sai bit shift trong IPR. Khắc phục: Luôn shift level << 4 như trong code ví dụ.
- Lỗi 3: Ngắt lồng ghép gây stack overflow – Nguyên nhân: Ưu tiên quá gần nhau. Khắc phục: Đặt khoảng cách ưu tiên lớn và kiểm tra stack size.
Kết Luận: Tại Sao NVIC Quan Trọng Trong Lập Trình STM32F103 Thanh Ghi?
NVIC trong STM32F103 là công cụ mạnh mẽ giúp lập trình viên xây dựng hệ thống ngắt đáng tin cậy. Bằng cách nắm vững các thanh ghi như ISER, ICER, IPR và bảng IRQ, bạn có thể tối ưu hóa dự án của mình. Nếu bạn mới bắt đầu lập trình STM32F103 thanh ghi, hãy thử ví dụ với TIMER2 để thực hành. Tham khảo thêm tài liệu chính thức từ STMicroelectronics để đi sâu hơn.
- NVIC là trái tim của hệ thống ngắt STM32F103
- Hiểu rõ IRQ mapping + ISER + IPR là nền tảng để:
- Lập trình timer
- Xử lý ngắt GPIO
- Viết driver UART, ADC, DMA
- Lập trình NVIC bằng thanh ghi giúp:
- Hiểu sâu kiến trúc Cortex-M3
- Tối ưu hiệu năng
- Debug dễ dàng hơn HAL
FAQ Về NVIC Trong STM32F103
- NVIC hỗ trợ bao nhiêu mức ưu tiên? 16 mức (0-15).
- Làm thế nào để bật ngắt toàn cục? Sử dụng __enable_irq().
- Tài liệu nào cần đọc? RM0008 (STM32 reference manual) và PM0056 (Cortex-M3 programming manual).
