Skip to content

Công nghệ 9 giây

“Website hướng dẫn, thử nghiệm và giải thích công nghệ theo cách dễ hiểu cho mọi người.”

Menu
  • Home
  • Công nghệ
  • STM32
  • ESP32
  • Altium Desinger
  • Kiến thức điện máy
Menu

BÀI 34 – NVIC TRÊN STM32F103

Posted on August 23, 2026August 23, 2026 by congnghe9s

BÀI 34 – NVIC TRÊN STM32F103

Nested Vectored Interrupt Controller – Bộ điều khiển ngắt của Cortex-M3

Ở Bài 33, chúng ta đã học:

GPIO
  ↓
EXTI
  ↓
NVIC
  ↓
CPU
  ↓
ISR

Trong đó, NVIC là phần rất quan trọng nhưng chúng ta mới chỉ dùng:

NVIC_EnableIRQ(EXTI0_IRQn);

Hôm nay chúng ta sẽ hiểu NVIC thực sự làm gì, tại sao STM32 có nhiều mức ưu tiên interrupt, và CPU biết phải chạy hàm EXTI0_IRQHandler() bằng cách nào.


1. NVIC là gì?

NVIC viết đầy đủ là:

Nested Vectored Interrupt Controller

Có thể hiểu đơn giản:

NVIC là bộ phận đứng giữa các nguồn interrupt và CPU Cortex-M3.

Ví dụ:

       GPIO PA0
          │
          ▼
        EXTI0
          │
          ▼
        NVIC
          │
          ▼
         CPU
          │
          ▼
EXTI0_IRQHandler()

Nhưng NVIC không chỉ xử lý EXTI.

STM32F103 còn có rất nhiều nguồn interrupt:

EXTI
Timer
USART
ADC
SPI
I2C
DMA
USB
RTC
...

Tất cả đều có thể gửi yêu cầu interrupt đến NVIC.


2. Tại sao cần NVIC?

Hãy tưởng tượng CPU đang chạy:

while (1)
{
    Motor_Run();
    LCD_Update();
    Sensor_Read();
}

Trong lúc đó:

USART nhận dữ liệu

hoặc:

Timer hết thời gian

hoặc:

Nút PA0 được nhấn

CPU cần biết:

“Nguồn nào đang yêu cầu interrupt?”

NVIC giúp CPU quản lý việc đó.


3. NVIC giống như “bộ điều phối”

Có thể hình dung:

               ┌── EXTI0
               │
               ├── USART1
               │
               ├── TIM2
               │
               ├── ADC1
               │
               └── SPI1
                      │
                      ▼
                    NVIC
                      │
             ┌────────┴────────┐
             │                 │
          Ưu tiên            Chờ
             │                 │
             ▼                 │
            CPU ◄──────────────┘

NVIC quyết định:

  • interrupt nào được phép;
  • interrupt nào đang pending;
  • interrupt nào có mức ưu tiên cao hơn;
  • interrupt nào được xử lý trước.

4. “Nested” nghĩa là gì?

Đây là một từ rất quan trọng.

Nested = lồng nhau.

Ví dụ CPU đang xử lý:

TIM2_IRQHandler()

nhưng trong lúc đó xuất hiện:

EXTI0 interrupt

Nếu EXTI0 có ưu tiên cao hơn TIM2:

TIM2_IRQHandler()
       │
       │ đang chạy
       ▼
   EXTI0 xảy ra
       │
       ▼
NVIC thấy EXTI0 ưu tiên cao
       │
       ▼
tạm dừng TIM2_IRQHandler()
       │
       ▼
EXTI0_IRQHandler()
       │
       ▼
kết thúc
       │
       ▼
quay lại TIM2_IRQHandler()

Đó chính là nested interrupt.


5. Ví dụ thực tế

Giả sử:

TIM2 = ưu tiên thấp
EXTI0 = ưu tiên cao

CPU:

TIM2_IRQHandler()
       │
       ├──── đang xử lý
       │
       │       PA0 bị nhấn
       │            ↓
       │          EXTI0
       │            ↓
       │           NVIC
       │            ↓
       └──────► EXTI0_IRQHandler()
                       │
                       ▼
                  xử lý xong
                       │
                       ▼
              quay lại TIM2

Đây là một trong những điểm mạnh của Cortex-M3.


6. NVIC có những nhiệm vụ chính nào?

Bạn nên nhớ 5 nhiệm vụ:

① Enable interrupt

Cho phép interrupt.

NVIC_EnableIRQ(EXTI0_IRQn);

② Disable interrupt

Tắt interrupt.

NVIC_DisableIRQ(EXTI0_IRQn);

③ Pending

Đặt một interrupt thành trạng thái đang chờ.


④ Priority

Thiết lập mức ưu tiên.


⑤ Vectoring

Chọn đúng ISR để CPU chạy.

Ví dụ:

EXTI0
  ↓
NVIC
  ↓
EXTI0_IRQHandler()

7. NVIC và EXTI khác nhau như thế nào?

Đây là chỗ người mới học rất dễ nhầm.

EXTI

Phát hiện:

PA0 HIGH → LOW

hoặc:

PA0 LOW → HIGH

NVIC

Quản lý:

"Interrupt này có được CPU xử lý không?"

Do đó:

GPIO
 ↓
EXTI = phát hiện sự kiện
 ↓
NVIC = quản lý interrupt
 ↓
CPU = thực thi

8. NVIC_EnableIRQ() thực sự làm gì?

Ở Bài 33 chúng ta viết:

NVIC_EnableIRQ(EXTI0_IRQn);

Nhìn thì rất đơn giản.

Nhưng bên dưới, NVIC có thanh ghi:

ISER

Tên đầy đủ:

Interrupt Set-Enable Register

Ví dụ interrupt số n.

Khi set bit tương ứng:

ISER bit = 1

thì interrupt được enable.


9. ISER

Có thể hình dung:

ISER:

bit 31 ......................... bit 0
 │                                │
 │                                └── IRQ0
 │
 └────────────────────────────────── IRQ31

Ví dụ:

NVIC->ISER[0] = (1 << 6);

có nghĩa là enable IRQ số 6.

Trong CMSIS, chúng ta thường không cần tự viết như vậy.

Dùng:

NVIC_EnableIRQ(...);

an toàn và dễ đọc hơn.


10. Disable interrupt

NVIC có:

ICER

Interrupt Clear-Enable Register

Ví dụ:

NVIC_DisableIRQ(EXTI0_IRQn);

CPU sẽ không tiếp tục nhận interrupt đó.


11. Pending là gì?

Đây là khái niệm cực kỳ quan trọng.

Giả sử:

CPU đang xử lý TIM2

Trong lúc đó:

EXTI0 xảy ra

CPU chưa thể xử lý ngay nếu đang bị một interrupt ưu tiên cao hơn chiếm quyền.

NVIC ghi nhận:

EXTI0 = PENDING

Nghĩa là:

“Tôi có một interrupt đang chờ.”

Khi CPU có thể xử lý:

PENDING
   ↓
CPU xử lý

12. Thanh ghi ISPR

NVIC có:

ISPR

Interrupt Set-Pending Register

Nó dùng để đặt interrupt ở trạng thái pending.

Ví dụ về mặt ý tưởng:

NVIC_SetPendingIRQ(EXTI0_IRQn);

Không nhất thiết phải có tín hiệu GPIO thật.

Ta có thể software trigger interrupt.


13. Thanh ghi ICPR

NVIC còn có:

ICPR

Interrupt Clear-Pending Register

Dùng để xóa trạng thái pending.

CMSIS cung cấp:

NVIC_ClearPendingIRQ(EXTI0_IRQn);

14. Đây là 4 thanh ghi cực kỳ quan trọng

Bạn nên nhớ:

ISER
 ↓
Enable

ICER
 ↓
Disable

ISPR
 ↓
Set Pending

ICPR
 ↓
Clear Pending

Tóm tắt:

Thanh ghi Ý nghĩa
ISER Enable
ICER Disable
ISPR Set Pending
ICPR Clear Pending

15. Priority – mức ưu tiên

Đây mới là phần thú vị.

Giả sử có:

EXTI0
TIM2
USART1

Nếu cả ba xảy ra gần như cùng lúc, CPU phải quyết định:

Xử lý cái nào trước?

NVIC sử dụng priority.

Ví dụ:

EXTI0  → Priority 1
TIM2   → Priority 3
USART1 → Priority 2

Thì:

EXTI0
  ↓
USART1
  ↓
TIM2

sẽ được ưu tiên theo cấu hình.


16. Priority càng nhỏ càng ưu tiên cao

Đây là điều bạn phải nhớ.

Ví dụ:

Priority 0 → rất cao
Priority 1 → cao
Priority 2 → thấp hơn
Priority 3 → thấp hơn nữa

Không phải:

Priority 3 > Priority 1

mà ngược lại:

0 > 1 > 2 > 3

về mức ưu tiên xử lý.


17. Thiết lập Priority

CMSIS cung cấp:

NVIC_SetPriority(
    EXTI0_IRQn,
    1
);

Ví dụ:

NVIC_SetPriority(EXTI0_IRQn, 1);
NVIC_EnableIRQ(EXTI0_IRQn);

Nên cấu hình:

Priority
   ↓
Enable

18. Ví dụ nhiều interrupt

Giả sử:

NVIC_SetPriority(EXTI0_IRQn, 0);
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_SetPriority(USART1_IRQn, 1);

NVIC_EnableIRQ(EXTI0_IRQn);
NVIC_EnableIRQ(TIM2_IRQn);
NVIC_EnableIRQ(USART1_IRQn);

Ta có:

EXTI0    Priority 0
USART1   Priority 1
TIM2     Priority 2

Nếu tất cả cùng yêu cầu interrupt:

             NVIC
              │
       ┌──────┼──────┐
       ▼      ▼      ▼
     EXTI0  USART1  TIM2
       0       1      2
       │
       ▼
      CPU

EXTI0 sẽ được ưu tiên.


19. Nhưng Priority còn phức tạp hơn một chút

Cortex-M3 có khái niệm:

Preemption Priority

và:

Subpriority

Hai khái niệm này dùng để quyết định:

Preemption

Interrupt nào có thể ngắt interrupt đang chạy.

Subpriority

Nếu các interrupt có cùng khả năng preempt, cái nào được xử lý trước.

Đây là phần chúng ta sẽ đi sâu hơn sau khi bạn nắm chắc khái niệm cơ bản.


20. Vector Table là gì?

Bây giờ đến một phần cực kỳ quan trọng.

Bạn có:

void EXTI0_IRQHandler(void)
{
}

Nhưng CPU làm sao biết:

“EXTI0 phải chạy hàm này?”

Câu trả lời:

Vector Table


21. Vector Table giống như một bảng địa chỉ

Hãy tưởng tượng:

Vector Table

Reset        → địa chỉ Reset_Handler
NMI          → địa chỉ NMI_Handler
HardFault    → địa chỉ HardFault_Handler
...
EXTI0        → địa chỉ EXTI0_IRQHandler
EXTI1        → địa chỉ EXTI1_IRQHandler
...
TIM2         → địa chỉ TIM2_IRQHandler

CPU không cần “tìm kiếm” tên hàm.

Nó chỉ cần lấy:

địa chỉ ISR

từ Vector Table.


22. Hãy hình dung toàn bộ quá trình

Khi nhấn nút PA0:

PA0
 │
 ▼
GPIO
 │
 ▼
EXTI0
 │
 ▼
Pending
 │
 ▼
NVIC
 │
 ▼
CPU
 │
 ▼
Vector Table
 │
 ▼
EXTI0_IRQHandler
 │
 ▼
xử lý
 │
 ▼
return

Đây là một trong những sơ đồ quan trọng nhất của khóa học C + STM32 của chúng ta.


23. CPU không “nhảy” trực tiếp từ GPIO đến hàm

Đây là một hiểu nhầm thường gặp.

Không phải:

GPIO → EXTI0_IRQHandler()

mà thực tế gần giống:

GPIO
 ↓
EXTI
 ↓
NVIC
 ↓
Cortex-M3 interrupt mechanism
 ↓
Vector Table
 ↓
ISR

24. Vì sao ISR có tên đặc biệt?

Ví dụ:

void EXTI0_IRQHandler(void)

Tên này phải đúng với startup file và vector table.

Ví dụ:

void TIM2_IRQHandler(void)

hay:

void USART1_IRQHandler(void)

Nếu bạn viết sai:

void MyInterrupt(void)

thì vector table không tự biết rằng đây là ISR của TIM2.

Đó là lý do phải dùng đúng tên:

TIM2_IRQHandler

25. Một ví dụ hoàn chỉnh

Chúng ta quay lại bài thực hành:

PA0 = Button
PC13 = LED

Cấu hình:

void EXTI_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;

    EXTI->IMR |= (1 << 0);

    EXTI->FTSR |= (1 << 0);

    NVIC_SetPriority(EXTI0_IRQn, 1);

    NVIC_EnableIRQ(EXTI0_IRQn);
}

ISR:

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & (1 << 0))
    {
        GPIOC->ODR ^= (1 << 13);

        EXTI->PR = (1 << 0);
    }
}

Ở đây:

EXTI

phát hiện sự kiện.

NVIC

cho phép và ưu tiên interrupt.

ISR

xử lý interrupt.


26. Một thí nghiệm rất hay

Bạn có thể thử:

NVIC_SetPriority(EXTI0_IRQn, 0);

sau đó:

NVIC_SetPriority(EXTI0_IRQn, 3);

Trong chương trình chỉ có một interrupt thì bạn sẽ không thấy khác biệt rõ ràng.

Tại sao?

Vì priority chỉ thực sự có ý nghĩa khi:

Có từ 2 nguồn interrupt trở lên

cùng hoạt động.


27. Thí nghiệm tiếp theo: hai interrupt

Sau này chúng ta có thể làm:

EXTI0 → Button
TIM2  → Timer

Ví dụ:

EXTI0 Priority = 0
TIM2  Priority = 2

Khi:

TIM2_IRQHandler()

đang chạy:

PA0 nhấn

thì:

EXTI0
 ↓
NVIC
 ↓
Priority cao hơn
 ↓
ngắt TIM2
 ↓
EXTI0_IRQHandler()
 ↓
return
 ↓
TIM2_IRQHandler() tiếp tục

Đây mới là lúc bạn thực sự thấy NVIC hoạt động.


28. NVIC không phải EXTI

Hãy nhớ câu này:

EXTI phát hiện sự kiện, NVIC quản lý interrupt.

Ví dụ:

PA0 thay đổi
     ↓
EXTI0
"Đã có sự kiện!"
     ↓
NVIC
"Interrupt này được phép không?"
"Ưu tiên bao nhiêu?"
     ↓
CPU
"Được, tôi xử lý."

29. Một lỗi rất phổ biến

Bạn viết:

EXTI->IMR |= (1 << 0);
EXTI->FTSR |= (1 << 0);

nhưng quên:

NVIC_EnableIRQ(EXTI0_IRQn);

Kết quả:

PA0
 ↓
EXTI0
 ↓
Có sự kiện
 ↓
NVIC không cho phép
 ↓
CPU không chạy ISR

LED không phản ứng.


30. Lỗi ngược lại

Bạn viết:

NVIC_EnableIRQ(EXTI0_IRQn);

nhưng quên:

EXTI->IMR |= (1 << 0);

Kết quả:

PA0
 ↓
GPIO
 ↓
EXTI không được unmask
 ↓
không tạo interrupt

Cũng không chạy.


31. Lỗi thứ ba

Bạn viết ISR:

void EXTI0_IRQHandler(void)
{
    GPIOC->ODR ^= (1 << 13);
}

nhưng không xóa:

EXTI->PR

Có thể dẫn đến việc interrupt tiếp tục bị xử lý lại.

Vì vậy luôn kiểm tra:

if (EXTI->PR & (1 << 0))
{
    // xử lý

    EXTI->PR = (1 << 0);
}

32. Ba tầng bạn phải thuộc lòng

Từ bây giờ khi debug interrupt STM32, hãy kiểm tra theo 3 tầng:

Tầng 1 – Peripheral

Ví dụ EXTI:

EXTI->IMR
EXTI->RTSR
EXTI->FTSR
EXTI->PR

Tầng 2 – NVIC

NVIC_EnableIRQ(...)
NVIC_SetPriority(...)

Tầng 3 – ISR

void EXTI0_IRQHandler(void)
{
}

Nếu interrupt không chạy, kiểm tra lần lượt 3 tầng này.


33. Bài tập Bài 34

Với Blue Pill của bạn, hãy làm bài sau:

Phần A

PA0 → Button
PC13 → LED onboard

PA0 dùng:

Input Pull-up

Nhấn nút tạo:

Falling Edge

Phần B

Thiết lập:

NVIC_SetPriority(EXTI0_IRQn, 1);

và:

NVIC_EnableIRQ(EXTI0_IRQn);

Phần C

Trong ISR:

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & (1 << 0))
    {
        // xử lý

        EXTI->PR = (1 << 0);
    }
}

Phần D – thử nghiệm

Hãy thử:

NVIC_DisableIRQ(EXTI0_IRQn);

Quan sát nút còn điều khiển LED không.

Sau đó:

NVIC_EnableIRQ(EXTI0_IRQn);

34. Kiến thức cần thuộc sau Bài 34

Nếu tôi hỏi bạn:

NVIC làm nhiệm vụ gì?

Bạn phải trả lời được:

NVIC quản lý các nguồn interrupt của Cortex-M3: enable/disable, pending, priority và chuyển CPU đến ISR tương ứng thông qua cơ chế vector interrupt.

Và nhớ sơ đồ:

        GPIO
          ↓
        EXTI
          ↓
       Pending
          ↓
        NVIC
     ┌────┴────┐
     │ Priority│
     │ Enable  │
     │ Pending │
     └────┬────┘
          ↓
         CPU
          ↓
     Vector Table
          ↓
         ISR
          ↓
       xử lý
          ↓
        return

Bài tiếp theo: BÀI 35 – DEBOUNCE NÚT NHẤN + EXTI + TIMER

Bài 35 sẽ rất thực tế. Chúng ta sẽ giải quyết chính vấn đề ở Bài 33:

Nhấn 1 lần
      ↓
Nút cơ khí dội
      ↓
HIGH LOW HIGH LOW HIGH...
      ↓
EXTI có thể phát sinh nhiều lần
      ↓
LED nhảy loạn

Sau đó chúng ta sẽ tự viết debounce, trước tiên bằng phương pháp đơn giản, rồi tiến tới debounce bằng Timer interrupt. Đây là nền tảng rất hữu ích cho các project Blue Pill thực tế của bạn.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Bạn có thể hướng dẫn tôi quy trình sửa chữa một mainboard laptop cũng như mainboard của máy tính để bàn không?
  • Có những mức nguồn DC nào trên một bo mạch mainboard laptop?Kỹ thuật đốt dòng để kiểm tra nguồn laptop được thực hiện như thế nào?
  • bài tiếp theo hợp lý là: BÀI 63 – SENSOR FUSION THỰC CHIẾN: ENCODER + IMU MPU6050 + EKF + ODOMETRY X,Y,θ TRÊN STM32F103
  • BÀI 62 – EKF (EXTENDED KALMAN FILTER) CHO ROBOT 2 BÁNH: FUSION ENCODER + IMU + ƯỚC LƯỢNG X, Y, θ, V, ω
  • BÀI 61 – KALMAN FILTER CHO ENCODER + IMU: ƯỚC LƯỢNG TỐC ĐỘ, GÓC θ VÀ SENSOR FUSION

Recent Comments

No comments to show.

Archives

  • September 2026
  • August 2026
  • July 2026
  • June 2026
  • May 2026
  • February 2026

Categories

  • Altium Desinger
  • Công nghệ
  • ESP32
  • Khác
  • Kiến thức điện máy
  • STM32
Live 3D Globes Visitor
©2026 Công nghệ 9 giây | Design: Newspaperly WordPress Theme