🌱 STM32 - 9. Lập trình Thanh ghi STM32 Ngắt Ngoài - External Interrupt

🌱 STM32 - 9. Lập trình Thanh ghi STM32 Ngắt Ngoài - External Interrupt

    Muốn cấu hình External Interrupt, cần phải cấu hình chân GPIO tương ứng, bộ điều khiển ngắt NVIC và bộ External Interrupt (EXTI). External Interrupt thường được sử dụng để phát hiện các sự kiện sườn lên/xuống trên các chân, trong các mục đích cần thiết (thay vì kiểm tra theo kiểu polling). Một ví dụ thường sử dụng External Interrupt là sử dụng nút bấm.

STM32F401 NUCLEO Board

Hình 1: Board STM32F401 NUCLEO với các chân GPIO

Kiến thức cần thiết

Trước khi cấu hình External Interrupt, các bạn cần tìm hiểu về các thanh ghi ngoại vi GPIO, EXTI và NVIC, cũng như các kiến thức liên quan đến 3 bộ này. Các bài viết trước về Core và STM32 về External Interrupt và NVIC:

Cùng với đó là tham khảo địa chỉ các thanh ghi ngoại vi trong Reference Manual (RM) cũng như tài liệu về Core. Chúng ta sẽ trải qua các bước cấu hình sau để cấu hình một chân ở chế độ External Interrupt.

👉 Define địa chỉ và khai báo các thanh ghi

EXTI Register Definition

Hình 2: Khai báo các thanh ghi liên quan đến EXTI

    Các thanh ghi cần sử dụng ở đây bao gồm:

  • Thanh ghi cấu hình clock cho bus AHB1 (cấp cho GPIOA) - RCC_AHB1ENR và APB2 (cấp cho EXTI) - RCC_APB2ENR.
  • Thanh ghi cấu hình Input cho GPIOA - GPIOx_MODER.
  • Thanh ghi cấu hình EXTI bao gồm thanh ghi interrupt mask EXTI_IMR, thanh ghi cấu hình sườn lên/xuống (ở đây là sườn lên) EXTI_FTSR, thanh ghi pending EXTI_PR.
  • Thanh ghi cấu hình NVIC, ở đây chỉ sử dụng thanh ghi Enable IRQ (Ngoài ra còn có các thanh ghi cấu hình độ ưu tiên của các line ngắt, nếu như có nhiều ngắt).
#define RCC_BASE        0x40023800
#define GPIOA_BASE      0x40020000
#define SYSCFG_BASE     0x40013800
#define EXTI_BASE       0x40013C00
#define NVIC_BASE       0xE000E100

#define RCC_AHB1ENR     (*(volatile unsigned int *)(RCC_BASE + 0x30))
#define RCC_APB2ENR     (*(volatile unsigned int *)(RCC_BASE + 0x44))
#define GPIOA_MODER     (*(volatile unsigned int *)(GPIOA_BASE + 0x00))
#define SYSCFG_EXTICR1  (*(volatile unsigned int *)(SYSCFG_BASE + 0x08))
#define EXTI_IMR        (*(volatile unsigned int *)(EXTI_BASE + 0x00))
#define EXTI_RTSR       (*(volatile unsigned int *)(EXTI_BASE + 0x08))
#define EXTI_PR         (*(volatile unsigned int *)(EXTI_BASE + 0x14))
#define NVIC_ISER0      (*(volatile unsigned int *)(NVIC_BASE + 0x00))
    

👉 Cấu hình mode cho GPIO và EXTI

EXTI Configuration

Hình 3: Cấu hình GPIO và EXTI cho ngắt ngoài

Ở bước này chúng ta cần làm theo các bước:

  • Cấu hình cho phép Clock GPIOA và Clock cho EXTI.
  • Cấu hình mode cho GPIOA (Ở đây mình để là Input Floating, người ta thường sử dụng Input Pullup cho các nút nhấn).
  • Cấu hình sườn lên/xuống cho EXTI (ở đây mình chọn là sườn lên).
  • Cấu hình cho phép EXTI line tương ứng (ở đây là line 0).
  • Cấu hình Enable NVIC.
RCC_AHB1ENR |= (1 << 0);  // Enable Clock GPIOA
RCC_APB2ENR |= (1 << 14); // Enable Clock SYSCFG

GPIOA_MODER &= ~(3 << 0); // Clear Mode PA0
GPIOA_MODER |= (0 << 0);  // Input Mode PA0

SYSCFG_EXTICR1 &= ~(0xF << 0); // Select PA0 for EXTI0
EXTI_RTSR |= (1 << 0);         // Rising Trigger Enable EXTI0
EXTI_IMR |= (1 << 0);          // Interrupt Mask Enable EXTI0

NVIC_ISER0 |= (1 << 6);        // Enable IRQ6 (EXTI0)
    

👉 Viết chương trình con phục vụ ngắt - ISR - Interrupt Service Routine

EXTI ISR

Hình 4: Chương trình con phục vụ ngắt EXTI0_IRQHandler

    Chương trình con phục vụ ngắt sẽ sử dụng tên hàm có định nghĩa sẵn trong file Startup. Ở đây mình dùng External Interrupt trên line 0, vì vậy, tên chương trình con phục vụ ngắt sẽ có tên: EXTI0_IRQHandler (void).

    Trong chương trình con phục vụ ngắt, mình sẽ có thể làm bất cứ điều gì mình thích 😂 Đùa thôi, một điều cần lưu ý trong ISR đó là bạn nên để chương trình trong đây càng ngắn càng tốt. Bởi vì nếu nó quá dài, thì có thể chiếm dụng thời gian của chương trình chính (hoặc chương trình dài có nguy cơ gây ra treo).

    Sau khi thực hiện điều mình thích rồi, bạn cần xóa ngắt của mình khỏi hàng đợi Pending, bằng cách set bit tương ứng trên thanh ghi EXTI_PR lên một.

void EXTI0_IRQHandler(void)
{
    if (EXTI_PR & (1 << 0)) // Check Pending Bit
    {
        // Do something here
        EXTI_PR |= (1 << 0); // Clear Pending Bit
    }
}
    

👉 Ví dụ cụ thể

    Dưới đây là đoạn mã mẫu để cấu hình EXTI0 (PA0) phát hiện sườn lên và bật/tắt LED (PA5) khi ngắt xảy ra:

#include "stm32f4xx.h"

#define GPIOA_EN    (1U << 0)  // Enable clock for GPIOA
#define SYSCFG_EN   (1U << 14) // Enable clock for SYSCFG
#define EXTI0_IRQ   6          // EXTI0 interrupt number

void EXTI0_IRQHandler(void) {
    if (EXTI->PR & (1U << 0)) { // Check if EXTI0 pending bit is set
        GPIOA->ODR ^= (1U << 5); // Toggle PA5 (LED)
        EXTI->PR = (1U << 0);    // Clear pending bit
    }
}

int main(void) {
    // 1. Enable clock for GPIOA and SYSCFG
    RCC->AHB1ENR |= GPIOA_EN;
    RCC->APB2ENR |= SYSCFG_EN;

    // 2. Configure PA0 as input
    GPIOA->MODER &= ~(3U << 0); // Clear bits for PA0
    // No pull-up/pull-down for simplicity (floating)

    // 3. Configure PA5 as output (LED)
    GPIOA->MODER &= ~(3U << 10); // Clear bits for PA5
    GPIOA->MODER |= (1U << 10);  // Set PA5 as output

    // 4. Configure EXTI0 for PA0
    SYSCFG->EXTICR[0] &= ~(0xF << 0); // Select PA0 for EXTI0
    EXTI->IMR |= (1U << 0);           // Enable EXTI0 interrupt
    EXTI->RTSR |= (1U << 0);          // Enable rising edge trigger

    // 5. Enable NVIC for EXTI0
    NVIC->ISER[0] |= (1U << EXTI0_IRQ);

    while (1) {
        // Main loop does nothing
    }
}
    

👉 Kết quả mong đợi

    Khi chạy chương trình trên board STM32F401 (ví dụ: NUCLEO-F401RE): - Mỗi lần tín hiệu trên PA0 chuyển từ thấp lên cao (ví dụ: nhấn nút bấm nếu có pull-down), LED trên PA5 sẽ bật/tắt luân phiên. - Chương trình chính không làm gì (vòng lặp rỗng), mọi xử lý diễn ra trong ISR.

👉 Lưu ý khi lập trình ISR

  • Giữ ISR ngắn gọn: Tránh thực hiện các tác vụ nặng (như delay dài) trong ISR để không ảnh hưởng đến chương trình chính.
  • Xoá Pending Bit: Luôn xoá bit trong EXTI_PR sau khi xử lý để tránh ngắt lặp vô hạn.
  • Tránh nhiễu: Nếu dùng nút bấm, nên bật pull-up/pull-down trong GPIO_PUPDR để tránh tín hiệu nhiễu.
  • Kiểm tra cẩn thận: Đảm bảo NVIC đã được bật cho IRQ tương ứng (ở đây là EXTI0_IRQn).

    Phần còn lại là việc chúng ta thiết kế chương trình main theo ý các bạn thôi là xong!!!

>>>>>> Follow ngay <<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

10 Nhận xét

  1. Trả lời
    1. ok em, anh k có nhiều time lắm nên mỗi ngày 1 2 bài thôi :v

      Xóa
    2. E gọi thẳng RCC->AHB1ENR không define các thanh ghi có sao không A ?

      Xóa
    3. Đó là nếu em có thư viện STD trong project rồi thì được, còn project anh đang hướng dùng thanh ghi. Thư viện ngta đã define sẵn cho mình rồi, ví dụ Struct pointer RCC ở địa chỉ nào. Nếu không dùng thư viện thì mình sẽ tự define như anh làm đó.

      Xóa
  2. mấy cái ngắt ADC, UART,... là của mode Event à anh?

    Trả lờiXóa
    Trả lời
    1. cái đó cũng là ngắt nha em, còn event là một tính năng không gọi đến các ISR, thường dùng trong power mode

      Xóa
  3. cái thanh ghi ISER của core NVIC xem ở đâu vậy anh

    Trả lờiXóa
    Trả lời
    1. Với các thanh ghi của NVIC em xem trong tài liệu Cortex M Generic User Guide nhé

      Xóa
  4. *pNVICIRQEnReg |= (1<<6) là Enable NVIC trên chân nào vậy ạ, em xem qua thanh ghi pNVICIRQEnReg thấy có tới 32 bit nhưng mà không biết các bit đó khác nhau như nào ạ, em cảm ơn anh

    Trả lờiXóa
    Trả lời
    1. Mỗi bit trên thanh ghi này tương ứng 1 line ngắt đó e, ví dụ thanh ghi NVIC_ER0 thì bit 5 tương ứng với ngắt có IRQ number = 5

      Xóa
Mới hơn Cũ hơn
//