Trong quá trình cấu hình NVIC trong STM32F103, việc hiểu rõ kiến trúc và cách tương tác với các thanh ghi của bộ điều khiển ngắt này là cực kỳ quan trọng. NVIC (Nested Vectored Interrupt Controller) không chỉ giúp vi điều khiển STM32F103 xử lý các yêu cầu ngắt một cách thông minh mà còn tối ưu hóa hiệu suất cho các ứng dụng thời gian thực.
1. Giới thiệu ngoại vi & Tại sao cần cấu hình NVIC?
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.
Kiến Trúc Tổng Quát Của STM32F103 Cần Nắm Vững
Trước khi đi sâu vào code, hãy nắm rõ cấu trúc của STM32F103 – dòng MCU phổ biến với lõi ARM Cortex-M3, flash 64-512KB và tốc độ clock lên đến 72MHz.
- Lõi xử lý ARM Cortex-M3: Chịu trách nhiệm thực thi lệnh, hỗ trợ interrupt qua NVIC (Nested Vectored Interrupt Controller) và SysTick timer cho delay chính xác.
- Hệ thống bus: Bao gồm AHB (Advanced High-performance Bus) cho tốc độ cao và APB1/APB2 (Advanced Peripheral Bus) cho ngoại vi chậm hơn. Điều này ảnh hưởng đến việc cấu hình clock qua RCC (Reset and Clock Control).
Các ngoại vi chính:
- GPIO: Điều khiển input/output, hỗ trợ Push-Pull, Open-Drain.
- Timer: General-purpose timer cho PWM, capture, delay.
- USART/UART: Giao tiếp serial cho truyền nhận dữ liệu.
- ADC: Chuyển đổi analog-to-digital cho cảm biến.
- FLASH: Lưu trữ chương trình và dữ liệu non-volatile.
- Các ngoại vi khác: SPI, I2C, CAN (tùy model).
Hiểu kiến trúc này giúp bạn tránh lỗi phổ biến như quên bật clock cho ngoại vi, dẫn đến chương trình không chạy.
2. Hướng dẫn đọc Reference Manual (Self-study Guidance)
Để thực sự làm chủ NVIC, bạn hãy mở tài liệu PM0056 – Cortex-M3 Programming Manual. Sử dụng tính năng tìm kiếm (Ctrl + F) với từ khóa NVIC hoặc Interrupt Controller. Hãy tập trung vào Section 4.3 Nested vectored interrupt controller (NVIC) và đặc biệt là Block Diagram của NVIC (nếu có) để hiểu luồng tín hiệu và cách các thành phần của NVIC tương tác với nhau. Việc này sẽ cung cấp một cái nhìn tổng quan sâu sắc về cách hệ thống ngắt hoạt động.
Tài Liệu Bắt Buộc Khi Học Hướng Dẫn Lập Trình STM32F103 Thanh Ghi
Để học hiệu quả, bạn cần các tài liệu chính hãng từ STMicroelectronics và ARM:
Reference Manual RM0008
Tài liệu cốt lõi, mô tả chi tiết từng thanh ghi (register map), bit chức năng và thứ tự cấu hình. Tải miễn phí tại website ST.
Link tải Reference Manual RM0008 STM32F103: STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced Arm®-based 32-bit MCUs – Reference manualDatasheet STM32F103xx
Thông tin về pinout, thông số điện (voltage, current), clock max và kích thước flash/RAM. Ví dụ, STM32F103C8T6 có 64KB flash, 20KB RAM.
Link tải Datasheet STM32F103xx: Datasheet – STM32F103xC, STM32F103xD, STM32F103xE – High-density performance line Arm®-based 32-bit MCU with 256 to 512KB Flash, USB, CAN, 11 timers, 3 ADCs, 13 communication interfacesARM Cortex-M3 Technical Reference Manual
Giải thích NVIC, SysTick, exception handling và vector table.
Link tải ARM Cortex-M3 Technical Reference Manual: STM32F10xxx/20xxx/21xxx/L1xxxx Cortex®-M3 programming manual
Kết hợp với hướng dẫn lập trình STM32F103 thanh ghi trên svtdhnlu.com, bạn sẽ có lộ trình học thực tế, kèm code mẫu.
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 và chức nă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ơ đồ thanh ghi NVIC trong STM32F103 (NVIC register map)


3. Define Struct thanh ghi kiểu Bit-field
Dựa trên PM0056, các thanh ghi NVIC được định nghĩa dưới dạng các mảng 32-bit. Để truy cập ở mức bit-field, chúng ta sẽ định nghĩa các union/struct tương ứng.
Define struct thanh ghi NVIC_ISERx
![Thanh ghi ISER[n] – Interrupt Set-Enable Register](https://svtdhnlu.com/wp-content/uploads/2026/01/Thanh-ghi-cho-phep-ngat-ISER.webp)
// Trích xuất từ tài liệu PM0056, Section 4.3.2, Trang 120
// Thanh ghi Kích hoạt Ngắt (Interrupt Set-Enable Register)
typedef union {
struct {
__IOM uint32_t SETENA : 32; // Bits 31:0: Bit kích hoạt ngắt
};
__IOM uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_ISER_Type;
Define struct thanh ghi NVIC_ICERx

// Trích xuất từ tài liệu PM0056, Section 4.3.3, Trang 121
// Thanh ghi Vô hiệu hóa Ngắt (Interrupt Clear-Enable Register)
typedef union {
struct {
__IOM uint32_t CLRENA : 32; // Bits 31:0: Bit vô hiệu hóa ngắt
};
__IOM uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_ICER_Type;
Define struct thanh ghi NVIC_ISPRx
// Trích xuất từ tài liệu PM0056, Section 4.3.4, Trang 122
// Thanh ghi Thiết lập Trạng thái Chờ Ngắt (Interrupt Set-Pending Register)
typedef union {
struct {
__IOM uint32_t SETPEND : 32; // Bits 31:0: Thiết lập trạng thái chờ ngắt
};
__IOM uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_ISPR_Type;
Define struct thanh ghi NVIC_ICPRx
// Trích xuất từ tài liệu PM0056, Section 4.3.5, Trang 123
// Thanh ghi Xóa Trạng thái Chờ Ngắt (Interrupt Clear-Pending Register)
typedef union {
struct {
__IOM uint32_t CLRPEND : 32; // Bits 31:0: Xóa trạng thái chờ ngắt
};
__IOM uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_ICPR_Type;
Define struct thanh ghi NVIC_IABRx
// Trích xuất từ tài liệu PM0056, Section 4.3.6, Trang 124
// Thanh ghi Trạng thái Hoạt động Ngắt (Interrupt Active Bit Register)
typedef union {
struct {
__IM uint32_t ACTIVE : 32; // Bits 31:0: Cờ trạng thái hoạt động ngắt
};
__IM uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_IABR_Type;
Define struct thanh ghi NVIC_IPRx
// Trích xuất từ tài liệu PM0056, Section 4.3.7, Trang 125
// Thanh ghi Ưu tiên Ngắt (Interrupt Priority Register)
typedef union {
struct {
__IOM uint32_t PRI_0 : 8; // Bits 7:0: Mức ưu tiên cho IRQ (4n)
__IOM uint32_t PRI_1 : 8; // Bits 15:8: Mức ưu tiên cho IRQ (4n+1)
__IOM uint32_t PRI_2 : 8; // Bits 23:16: Mức ưu tiên cho IRQ (4n+2)
__IOM uint32_t PRI_3 : 8; // Bits 31:24: Mức ưu tiên cho IRQ (4n+3)
};
__IOM uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_IPR_Type;
Define struct thanh ghi NVIC_STIRx

// Trích xuất từ tài liệu PM0056, Section 4.3.8, Trang 126 và 4.3.11, Trang 129
// Thanh ghi Kích hoạt Ngắt bằng phần mềm (Software Trigger Interrupt Register)
typedef union {
struct {
__O uint32_t INTID : 9; // Bits 8:0: ID ngắt cần kích hoạt (0-239)
uint32_t reserved : 23; // Bits 9-31: Reserved
};
__O uint32_t reg; // Truy cập toàn bộ thanh ghi
} NVIC_STIR_Type;
Tổng hợp cấu trúc thanh ghi NVIC
// Trích xuất từ tài liệu PM0056, Section 4.3.11, Trang 128-129
// Khối thanh ghi chính của NVIC (base 0xE000E100)
typedef struct {
NVIC_ISER_Type ISER[3]; // Offset 0x000-0x008: Interrupt Set-Enable (x=0-2)
uint32_t RESERVED0[29]; // Offset 0x00C-0x07C: Reserved
NVIC_ICER_Type ICER[3]; // Offset 0x080-0x088: Interrupt Clear-Enable (x=0-2)
uint32_t RESERVED1[29]; // Offset 0x08C-0x0FC: Reserved
NVIC_ISPR_Type ISPR[3]; // Offset 0x100-0x108: Interrupt Set-Pending (x=0-2)
uint32_t RESERVED2[29]; // Offset 0x10C-0x17C: Reserved
NVIC_ICPR_Type ICPR[3]; // Offset 0x180-0x188: Interrupt Clear-Pending (x=0-2)
uint32_t RESERVED3[29]; // Offset 0x18C-0x1FC: Reserved
NVIC_IABR_Type IABR[3]; // Offset 0x200-0x208: Interrupt Active Bit (x=0-2)
uint32_t RESERVED4[61]; // Offset 0x20C-0x2FC: Reserved
NVIC_IPR_Type IPR[21]; // Offset 0x300-0x350: Interrupt Priority (0-20, bao phủ 84 IRQ)
uint32_t RESERVED5[683]; // Offset 0x354-0xDFC: Reserved
} NVIC_Type;
// Định nghĩa con trỏ đến khối thanh ghi NVIC
#define NVIC ((volatile NVIC_Type *) 0xE000E100UL)
// Thanh ghi Software Trigger Interrupt Register (địa chỉ cơ sở riêng)
#define NVIC_STIR_REG ((volatile NVIC_STIR_Type *) 0xE000EF00UL)
4. Giải thích chuyên sâu (What – Why – How) các thanh ghi
Để cấu hình NVIC trong STM32F103 một cách hiệu quả, việc hiểu rõ từng thanh ghi là điều cốt yếu. Dưới đây là phân tích chi tiết về các thanh ghi quan trọng:
Thanh ghi ISER (Interrupt Set-Enable Register):
![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)
- What: ISER là nhóm các thanh ghi cho phép (enable) các nguồn ngắt ngoại vi. Khi một bit trong ISER được set lên ‘1’, ngắt tương ứng sẽ được kích hoạt.
- Why (Circuit-level): Nhà sản xuất thiết kế ISER để cung cấp khả năng điều khiển từng ngắt một cách linh hoạt. Về mặt phần cứng, việc set bit này sẽ mở một ‘cổng’ logic cho tín hiệu ngắt đi vào bộ xử lý ngắt. Khi ngắt không cần thiết, việc tắt nó đi giúp giảm tải cho CPU, tránh xử lý những sự kiện không mong muốn và tiết kiệm năng lượng. Nếu không có cơ chế này, mọi tín hiệu ngắt từ ngoại vi đều sẽ ‘đánh thức’ CPU, gây lãng phí chu kỳ xử lý.
- How (Ví dụ):
// Kích hoạt ngắt cho TIM2 (IRQn = 28) // TIM2 nằm trong ISER[0] (IRQ 0-31) NVIC->ISER[0].SETENA = (1U << 28); // Sử dụng bit-field trực tiếp
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
/**
* @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):

- What: ICER là nhóm các thanh ghi dùng để vô hiệu hóa (disable) các nguồn ngắt ngoại vi. Ghi ‘1’ vào một bit sẽ clear bit tương ứng trong ISER, tắt ngắt. Ghi ‘0’ không có tác dụng.
- Why (Circuit-level): Tương tự ISER, ICER cung cấp khả năng vô hiệu hóa ngắt một cách có chủ đích. Trong nhiều trường hợp, sau khi xử lý một ngắt hoặc trong một số chế độ hoạt động, chúng ta muốn tạm thời hoặc vĩnh viễn tắt một ngắt nào đó. Cơ chế ‘write-1-to-clear’ đảm bảo rằng việc clear ngắt là một thao tác an toàn, không làm ảnh hưởng đến các ngắt khác nếu có nhiều tác vụ cùng truy cập vào thanh ghi này.
- How (Ví dụ):
// Vô hiệu hóa ngắt cho TIM2 (IRQn = 28) NVIC->ICER[0].CLRENA = (1U << 28); // Sử dụng bit-field trực tiếp
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 |
Code disable IRQ
/**
* @brief Tắt ngắt (Interrupt Disable) cho IRQ chỉ số irqIndex
* @param irqIndex: Số thứ tự IRQ (0-80)
* @retval None
*
* @note Dựa trên PM0056, Section 4.3.3 - NVIC_ICERx
* - ICER[0..2] tương ứng ISER[0..2]
* - Viết 1 vào bit → tắt ngắt, viết 0 → không ảnh hưởng
*/
void NVIC_Interrupt_Disable(uint8_t irqIndex)
{
if (irqIndex > 80)
{
return; // Giới hạn IRQ hợp lệ
}
if(irqIndex <= 31)
{
NVIC->ICER[0].reg |= (1 << irqIndex); // Tắt IRQ 0-31 trong ICER[0]
}
else if (irqIndex <= 63)
{
NVIC->ICER[1].reg |= (1 << (irqIndex-32)); // Tắt IRQ 32-63
}
else if (irqIndex <= 80)
{
NVIC->ICER[2].reg |= (1 << (irqIndex-64)); // Tắt IRQ 64-80
}
}Link Github: Download code cầu hình NVIC trong lập trình STM32F103RCT6
Thanh ghi ISPR (Interrupt Set-Pending Register):
- What: ISPR là nhóm các thanh ghi dùng để thiết lập trạng thái ‘pending’ cho một ngắt bằng phần mềm. Nghĩa là, bạn có thể buộc một ngắt chuyển sang trạng thái chờ xử lý mà không cần tín hiệu ngắt thực tế từ phần cứng.
- Why (Circuit-level): Tính năng này rất hữu ích cho việc kiểm thử (debugging) các hàm xử lý ngắt hoặc để kích hoạt một ngắt theo yêu cầu của phần mềm, ví dụ như trong các hệ thống RTOS để truyền thông điệp giữa các task thông qua cơ chế ngắt. Nó mô phỏng một yêu cầu ngắt từ phần cứng.
- How (Ví dụ):
// Kích hoạt trạng thái pending cho TIM2 (IRQn = 28) NVIC->ISPR[0].SETPEND = (1U << 28);
Thanh ghi ICPR (Interrupt Clear-Pending Register):
- What: ICPR là nhóm các thanh ghi dùng để xóa trạng thái ‘pending’ của một ngắt. Khi một bit trong ICPR được set lên ‘1’, trạng thái pending của ngắt tương ứng sẽ bị xóa.
- Why (Circuit-level): Trong trường hợp một ngắt bị pending do phần mềm hoặc do lỗi phần cứng không mong muốn, ICPR cho phép chúng ta chủ động xóa trạng thái này để tránh ngắt bị xử lý lặp lại hoặc vô thời hạn. Nó giúp hệ thống lấy lại trạng thái ổn định.
- How (Ví dụ):
// Xóa trạng thái pending cho TIM2 (IRQn = 28) NVIC->ICPR[0].CLRPEND = (1U << 28);
Thanh ghi IABR (Interrupt Active Bit Register):
- What: IABR là nhóm các thanh ghi chỉ đọc, cho biết ngắt nào đang ở trạng thái ‘active’. Một ngắt ở trạng thái active khi CPU đang thực thi hàm ISR của nó, hoặc khi một ngắt ưu tiên cao hơn đã chen ngang một ngắt active khác.
- Why (Circuit-level): Thanh ghi này cực kỳ hữu ích cho việc gỡ lỗi (debugging) hệ thống ngắt phức tạp, đặc biệt là trong các tình huống ngắt lồng nhau (nested interrupts). Bằng cách đọc IABR, kỹ sư có thể biết chính xác ngắt nào đang được CPU phục vụ tại một thời điểm nhất định, giúp phát hiện các vấn đề như ngắt bị chặn không đúng cách hoặc thứ tự ưu tiên bị sai.
- How (Ví dụ):
// Kiểm tra xem TIM2 có đang active không if (NVIC->IABR[0].ACTIVE & (1U << 28)) { // TIM2 đang active }
Thanh ghi IPR (Interrupt Priority Register):

- What: IPR là nhóm các thanh ghi dùng để thiết lập mức ưu tiên cho từng nguồn ngắt ngoại vi. STM32F103 có 16 mức ưu tiên (0-15), trong đó 0 là mức ưu tiên cao nhất. Mỗi thanh ghi IPR chứa 4 trường priority 8-bit, nhưng chỉ 4 bit cao nhất [7:4] được sử dụng.
- Why (Circuit-level): Mức ưu tiên ngắt là tính năng cốt lõi của NVIC, cho phép hệ thống phản ứng linh hoạt với các sự kiện. Bằng cách gán mức ưu tiên khác nhau, ngắt quan trọng có thể chen ngang ngắt ít quan trọng hơn đang được xử lý, đảm bảo các tác vụ critical được ưu tiên. Điều này là nền tảng cho các ứng dụng thời gian thực cứng (hard real-time systems). Về mặt phần cứng, mạch so sánh ưu tiên trong NVIC sẽ liên tục kiểm tra mức ưu tiên của các ngắt pending và đang active để quyết định ngắt nào sẽ được cấp quyền xử lý tiếp theo.
- How (Ví dụ):
// Đặt mức ưu tiên cho TIM2 (IRQn = 28) là 5 // IRQ 28 nằm trong IPR[28/4] = IPR[7] // Vị trí trong thanh ghi IPR[7] là (28%4) = 0 (bits 7:0) NVIC->IPR[7].PRI_0 = (5U << 4); // Dịch trái 4 bit để ghi vào bit [7:4]
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
/**
* @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
Thanh ghi STIR (Software Trigger Interrupt Register):
- What: STIR là một thanh ghi cho phép phần mềm kích hoạt một ngắt. Bằng cách ghi ID của ngắt vào thanh ghi này, ngắt tương ứng sẽ được đặt vào trạng thái pending.
- Why (Circuit-level): Thanh ghi này có vai trò quan trọng trong việc debug và kiểm thử, cho phép giả lập các tình huống ngắt mà không cần kích hoạt phần cứng. Nó cũng hữu ích trong việc xây dựng các cơ chế truyền thông nội bộ hoặc đồng bộ hóa trong các hệ thống đa tác vụ, nơi một task có thể ‘ra hiệu’ cho một task khác thông qua một ngắt phần mềm.
- How (Ví dụ):
// Kích hoạt ngắt TIM2 (IRQn = 28) bằng phần mềm // Cần sử dụng con trỏ riêng cho STIR NVIC_STIR_REG->INTID = 28;
5. Quy trình cấu hình NVIC & Hardware Traps
Minh họa cấu hình NVIC với TIM2:
graph TD;
A["Bắt đầu"] --> B{"Xác định IRQ Number cho ngoại vi"};
B --> C["Tính toán Index và Bit vị trí trong ISER/ICER"];
C --> D["Kích hoạt ngắt bằng NVIC_Interrupt_Enable() (Ghi vào ISER)"];
D --> E["Tính toán Index và Bit vị trí trong IPR"];
E --> F["Đặt mức ưu tiên ngắt bằng NVIC_Set_Interrupt_Priority() (Ghi vào IPR)"];
F --> G["Viết hàm phục vụ ngắt (ISR) với tên chính xác (vd: TIM2_IRQHandler)"];
G --> H["Kích hoạt ngắt ngoại vi (vd: TIM_ITConfig cho Timer)"];
H --> I["Bật ngắt toàn cục (vd: __enable_irq())"];
I --> J["Hoàn thành cấu hình ngắt"];
⚠️ BẪY PHẦN CỨNG (HARDWARE TRAP): Rất nhiều kỹ sư mới hay mắc phải những lỗi sau đây khi cấu hình NVIC trong STM32F103:
- Quên bật ngắt toàn cục: Mặc dù bạn đã cấu hình ISER, IPR đầy đủ, nhưng nếu không gọi hàm
__enable_irq()(hoặcCPSIE Itrong assembly) thì CPU sẽ không bao giờ phản hồi các yêu cầu ngắt. - Quên bật cờ ngắt ngoại vi: Ví dụ, với Timer, bạn cần phải bật cờ update interrupt trong thanh ghi TIMx_DIER (DMA/Interrupt Enable Register) thì Timer mới gửi yêu cầu ngắt đến NVIC. Nếu không, NVIC sẽ không nhận được tín hiệu.
- Đặt mức ưu tiên quá gần nhau hoặc không hợp lý: Điều này dễ dẫn đến tình trạng ngắt bị ‘starvation’ (ngắt ưu tiên thấp không bao giờ được xử lý) hoặc gây ra các vấn đề về thời gian thực khi ngắt lồng nhau quá sâu, dẫn đến stack overflow. Luôn để khoảng cách an toàn giữa các mức ưu tiên critical.
- Sử dụng sai IRQ Number: Dẫn đến việc bật/tắt ngắt không đúng nguồn. Luôn đối chiếu với bảng Vector Interrupt Table trong RM0008.
- Quên xóa cờ ngắt trong ISR: Hầu hết các ngoại vi (GPIO EXTI, Timer, UART…) yêu cầu bạn phải xóa cờ ngắt (clear interrupt flag) trong hàm ISR. Nếu không xóa, ngắt sẽ liên tục được kích hoạt lại ngay sau khi ISR kết thúc, gây ra vòng lặp vô hạn (infinite loop) trong ISR.
6. Code thực tế chạy trên Keil C
Đây là phần code tổng hợp cho việc cấu hình NVIC trong STM32F103, bao gồm các hàm hỗ trợ bật/tắt và cài đặt ưu tiên ngắt.
// Hàm main ví dụ để kiểm tra cấu hình
int main(void)
{
// Giả định các hàm cấu hình clock, GPIO, SysTick đã có
// SystemClock_Config();
// GPIO_LED_Config();
// GPIO_Button_Config();
// SysTick_Config_1ms();
// Cấu hình ngắt cho TIM2
// TIM2 IRQ Number = 28 (tham khảo bảng Vector Interrupt Table)
NVIC_Interrupt_Enable(28); // Bật ngắt TIM2
NVIC_Set_Interrupt_Priority(28, 5); // Đặt mức ưu tiên cho TIM2 là 5
// Sau đó, cần cấu hình Timer và bật ngắt Timer
// Ví dụ:
// TIM2->DIER.UIE = 1; // Bật cờ ngắt Update Interrupt cho TIM2
// TIM2->CR1.CEN = 1; // Bật Timer 2
__enable_irq(); // Bật ngắt toàn cục (MUST HAVE)
while (1) {
// Chương trình chính chạy ở đây
}
}
// Ví dụ hàm xử lý ngắt cho TIM2 (phải đúng tên)
// Vị trí thực hiện các tác vụ khi ngắt Timer 2 xảy ra
void TIM2_IRQHandler(void) {
// Xóa cờ ngắt của Timer để tránh ngắt lặp lại
// Ví dụ: if (TIM2->SR.UIF) { TIM2->SR.UIF = 0; }
}
7. Cách kiểm tra cấu hình (Debugging)
Để đảm bảo cấu hình NVIC trong STM32F103 của bạn hoạt động chính xác, việc kiểm tra bằng công cụ debug là rất cần thiết. Keil uVision cung cấp khả năng xem trực tiếp trạng thái các thanh ghi.
Ví dụ cấu hình TIM2 IRQ priority = 5
int main(void)
{
SystemClock_Config(); // Initialize system clock
GPIO_LED_Config();
GPIO_Button_Config();
SysTick_Config_1ms();
NVIC_Interrupt_Enable(TIM2_IRQ); // Giả định TIM2_IRQ = 28
NVIC_Set_Interrupt_Priority(TIM2_IRQ, 5);
while (1) {
}
}
Bật bảng trạng thái thanh ghi NVIC: Từ giao diện uVision, Chọn Menu > Peripherals > System Viewer > NVIC.

Chú thích: Mở cửa số KEIL C xem thanh ghi NVIC
TIMER2 IRQ nằm ở vị trí thứ 28 trong bảng vector ngắt:

Chú thích: Kiểm tra cấu hình mức độ ưu tiên TIMER2 thanh ghi NVIC
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.
8. Kết luận
Cấu hình NVIC trong STM32F103 là một kỹ năng nền tảng và không thể thiếu đối với bất kỳ kỹ sư nhúng nào làm việc với dòng vi điều khiển này. Bằng cách nắm vững kiến trúc của NVIC, hiểu rõ chức năng của từng thanh ghi (ISER, ICER, IPR, v.v.), và biết cách ánh xạ IRQ một cách chính xác, bạn sẽ có khả năng xây dựng các hệ thống ngắt mạnh mẽ, phản hồi nhanh và đáng tin cậy.
Việc lập trình ở mức thanh ghi không chỉ giúp bạn tối ưu hóa hiệu năng mà còn mang lại sự hiểu biết sâu sắc về cách hoạt động của vi điều khiển, điều mà các thư viện abstraction như HAL khó có thể cung cấp. Hãy tiếp tục thực hành với các ví dụ cụ thể và đừng ngần ngại tham khảo tài liệu chính thức từ STMicroelectronics và ARM để đào sâu kiến thức.
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).
Tham khảo mã nguồn đầy đủ tại đây.
