Chapter 14 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki

14. EXTI & NVIC β€” External Interrupt

Goal

Ch07μ—μ„œλŠ” while(1) μ•ˆμ—μ„œ PC13 button을 계속 μ½λŠ” Polling 방식을 μ‚¬μš©ν–ˆλ‹€.

μ΄λ²ˆμ—λŠ” B1을 λˆ„λ₯΄λŠ” μˆœκ°„ hardware interruptκ°€ λ°œμƒν•˜λ„λ‘ κ΅¬μ„±ν•œλ‹€.

B1 Press
   ↓
PC13 LOW β†’ HIGH
   ↓
EXTI13
   ↓
Pending Request
   ↓
NVIC
   ↓
Cortex-M4
   ↓
EXTI15_10_IRQHandler()
   ↓
UART Message
+
LD1 Toggle

Board:

B1  β†’ PC13
LD1 β†’ PB0

이번 NUCLEO-F439ZI의 B1은:

Released β†’ LOW
Pressed  β†’ HIGH

μ΄λ―€λ‘œ Rising Edgeλ₯Ό μ‚¬μš©ν•œλ‹€.


1. Polling vs Interrupt

Ch07:

while (1)
{
    if (get_btn_state())
    {
        led_on();
    }
}

CPUκ°€ 계속:

Button?
Button?
Button?
Button?
...

을 ν™•μΈν•œλ‹€.

이번 Ch14:

CPU doing other work
       ↓

Button Event
       ↓
Interrupt
       ↓
ISR

CPUκ°€ button을 계속 읽을 ν•„μš”κ°€ μ—†λ‹€.


2. Interrupt Components

이번 μ‹€μŠ΅μ—μ„œ ν•„μš”ν•œ ꡬ성:

GPIO
 ↓
SYSCFG
 ↓
EXTI
 ↓
NVIC
 ↓
Vector Table
 ↓
ISR

각 μ—­ν• :

Block μ—­ν• 
GPIO PC13 pin input
SYSCFG PC13을 EXTI13에 μ—°κ²°
EXTI edge κ²€μΆœ 및 pending 생성
NVIC CPU interrupt enable / priority 관리
Vector Table IRQ와 ISR μ£Όμ†Œ μ—°κ²°
ISR μ‹€μ œ interrupt 처리 μ½”λ“œ

이번 μ„€μ •:

IMR13  = 1
β†’ EXTI13 interrupt unmask

RTSR13 = 1
β†’ Rising edge detect

FTSR13 = 0
β†’ Falling edge unused

6. Practice Code

Project:

Inc/
β”œβ”€β”€ gpio.h
β”œβ”€β”€ gpio_exti.h
└── uart.h

Src/
β”œβ”€β”€ gpio.c
β”œβ”€β”€ gpio_exti.c
β”œβ”€β”€ uart.c
└── main.c

Ch07 gpio.c와 Ch10 uart.cλ₯Ό μž¬μ‚¬μš©ν•œλ‹€.


7. gpio_exti.h

#ifndef GPIO_EXTI_H__
#define GPIO_EXTI_H__

#include <stdint.h>
#include "stm32f4xx.h"

/*
 * EXTI line 13 mask.
 *
 * PC13 is connected to:
 *
 *     EXTI13
 */
#define LINE13   (1U << 13)


/*
 * Configure PC13 as an external interrupt source.
 *
 * Board:
 *
 *     B1 β†’ PC13
 *
 * Trigger:
 *
 *     Rising Edge
 */
void pc13_exti_init(void);

#endif

8. gpio_exti.c

#include "gpio_exti.h"

/*
 * ============================================================
 * RCC Clock Enable Bits
 * ============================================================
 */

/*
 * RCC AHB1ENR bit2:
 *
 * GPIOC clock enable.
 *
 * B1 is connected to PC13.
 */
#define GPIOCEN    (1U << 2)


/*
 * RCC APB2ENR bit14:
 *
 * SYSCFG peripheral clock enable.
 *
 * SYSCFG is required to connect a GPIO port
 * to an EXTI line.
 */
#define SYSCFGEN   (1U << 14)


/*
 * ============================================================
 * PC13 EXTI Initialization
 * ============================================================
 */

void pc13_exti_init(void)
{
    /*
     * --------------------------------------------------------
     * Step 1. Disable Global Interrupts Temporarily
     * --------------------------------------------------------
     *
     * Several registers must be configured in sequence.
     *
     * We do not want an interrupt to occur while
     * configuration is only partially complete.
     */
    __disable_irq();


    /*
     * --------------------------------------------------------
     * Step 2. Enable GPIOC Clock
     * --------------------------------------------------------
     *
     * PC13 belongs to GPIOC.
     */
    RCC->AHB1ENR |= GPIOCEN;


    /*
     * --------------------------------------------------------
     * Step 3. Configure PC13 as Input
     * --------------------------------------------------------
     *
     * MODER uses 2 bits per pin.
     *
     * PC13:
     *
     *     MODER[27:26]
     *
     * Input mode:
     *
     *     00
     */

    GPIOC->MODER &= ~(1U << 26);
    GPIOC->MODER &= ~(1U << 27);


    /*
     * --------------------------------------------------------
     * Step 4. Enable SYSCFG Clock
     * --------------------------------------------------------
     *
     * SYSCFG is on APB2.
     */
    RCC->APB2ENR |= SYSCFGEN;


    /*
     * --------------------------------------------------------
     * Step 5. Connect PC13 β†’ EXTI13
     * --------------------------------------------------------
     *
     * EXTI12 ~ EXTI15 are controlled by:
     *
     *     EXTICR[3]
     *
     * EXTI13 field selects which GPIO port
     * supplies line13.
     *
     * Port encoding:
     *
     *     A = 0000
     *     B = 0001
     *     C = 0010
     *
     * For EXTI13:
     *
     *     EXTICR3[7:4]
     *
     * Port C = 0010
     *
     * Original practice code:
     *
     *     SYSCFG->EXTICR[3] |= (1U << 5);
     *
     * Reset value is 0000, so setting bit5 produces:
     *
     *     0010 = Port C
     */
    SYSCFG->EXTICR[3] |= (1U << 5);


    /*
     * --------------------------------------------------------
     * Step 6. Unmask EXTI13
     * --------------------------------------------------------
     *
     * IMR:
     *
     *     0 β†’ interrupt masked
     *     1 β†’ interrupt allowed
     */
    EXTI->IMR |= LINE13;


    /*
     * --------------------------------------------------------
     * Step 7. Select Rising Edge
     * --------------------------------------------------------
     *
     * NUCLEO-F439ZI B1:
     *
     *     Released = LOW
     *     Pressed  = HIGH
     *
     * Button press therefore produces:
     *
     *     LOW β†’ HIGH
     *
     * so Rising Trigger Selection Register
     * is used.
     */
    EXTI->RTSR |= LINE13;


    /*
     * Optional defensive configuration:
     *
     * Make sure falling-edge trigger is disabled.
     */
    EXTI->FTSR &= ~LINE13;


    /*
     * --------------------------------------------------------
     * Step 8. Enable IRQ in NVIC
     * --------------------------------------------------------
     *
     * EXTI lines 10 ~ 15 share:
     *
     *     EXTI15_10_IRQn
     */
    NVIC_EnableIRQ(EXTI15_10_IRQn);


    /*
     * --------------------------------------------------------
     * Step 9. Enable Global Interrupts
     * --------------------------------------------------------
     *
     * Initialization is complete.
     */
    __enable_irq();
}

9. main.c

#include "gpio.h"
#include "gpio_exti.h"
#include "uart.h"


/*
 * ============================================================
 * EXTI Callback
 * ============================================================
 *
 * This function contains the application action
 * performed when B1 is pressed.
 */
static void exti_callback(void)
{
    /*
     * Send a message through USART3.
     *
     * Expected terminal output:
     *
     *     BTN Pressed...
     */
    uart_write_string("BTN Pressed...\r\n");


    /*
     * Toggle LD1 connected to PB0.
     *
     * Every valid button interrupt changes
     * the LED state.
     */
    led_toggle();
}


int main(void)
{
    /*
     * --------------------------------------------------------
     * Step 1. Initialize USART3
     * --------------------------------------------------------
     *
     * Used to print the button event.
     */
    uart_init();


    /*
     * --------------------------------------------------------
     * Step 2. Initialize LD1
     * --------------------------------------------------------
     *
     * PB0 β†’ Output
     */
    led_init();


    /*
     * --------------------------------------------------------
     * Step 3. Configure PC13 EXTI
     * --------------------------------------------------------
     *
     * B1 press:
     *
     *     PC13 Rising Edge
     *        ↓
     *     EXTI13
     *        ↓
     *     NVIC
     *        ↓
     *     ISR
     */
    pc13_exti_init();


    while (1)
    {
        /*
         * No button polling.
         *
         * The CPU does NOT repeatedly execute:
         *
         *     get_btn_state()
         *
         * Button processing happens only when
         * an interrupt occurs.
         *
         * A later low-power version could use:
         *
         *     __WFI();
         *
         * here to sleep until an interrupt arrives.
         */
    }
}


/*
 * ============================================================
 * EXTI10 ~ EXTI15 Shared Interrupt Handler
 * ============================================================
 */

void EXTI15_10_IRQHandler(void)
{
    /*
     * --------------------------------------------------------
     * Step 1. Identify the Interrupt Source
     * --------------------------------------------------------
     *
     * EXTI10 ~ EXTI15 share one ISR.
     *
     * Therefore check whether EXTI13 is actually pending.
     */
    if ((EXTI->PR & LINE13) != 0U)
    {
        /*
         * ----------------------------------------------------
         * Step 2. Clear Pending Flag
         * ----------------------------------------------------
         *
         * EXTI PR is W1C:
         *
         *     Write 1 β†’ clear pending bit
         *     Write 0 β†’ no effect
         *
         * IMPORTANT:
         *
         * Use direct assignment:
         *
         *     EXTI->PR = LINE13;
         *
         * instead of:
         *
         *     EXTI->PR |= LINE13;
         *
         * because '|=' performs a Read-Modify-Write.
         *
         * Since PR is W1C, reading multiple pending bits
         * and writing them back as 1 could accidentally
         * clear other pending EXTI lines.
         */
        EXTI->PR = LINE13;


        /*
         * ----------------------------------------------------
         * Step 3. Execute Application Callback
         * ----------------------------------------------------
         *
         * Pending is cleared BEFORE the callback.
         *
         * If another edge occurs later,
         * hardware can set PR again.
         */
        exti_callback();
    }
}

10. Important Correction β€” PR Is W1C

원본 μ‹€μŠ΅ μ½”λ“œλŠ”:

EXTI->PR |= LINE13;

μ΄μ—ˆλ‹€.

κΈ°λŠ₯적으둜 line13 ν•˜λ‚˜λ§Œ pending인 λ‹¨μˆœ μ‹€μŠ΅μ—μ„œλŠ” 정상 λ™μž‘ν–ˆκ³  μ‹€μ œ board test도 PASSν–ˆλ‹€.

ν•˜μ§€λ§Œ register νŠΉμ„±μ„ κ³ λ €ν•˜λ©΄ μ΅œμ’… μ½”λ“œλŠ”:

EXTI->PR = LINE13;

이 더 μ μ ˆν•˜λ‹€.

μ™œλƒν•˜λ©΄ PR은:

W1C
=
Write 1 to Clear

register이기 λ•Œλ¬Έμ΄λ‹€.

|=λŠ” μ‹€μ œλ‘œ:

Read PR
   ↓
OR LINE13
   ↓
Write PR

λ₯Ό μˆ˜ν–‰ν•œλ‹€.

λ§Œμ•½ 읽은 μˆœκ°„:

PR[13] = 1
PR[12] = 1

μ΄μ—ˆλ‹€λ©΄:

Read
β†’ bit13 = 1
β†’ bit12 = 1

Write back
β†’ bit13 = 1 β†’ clear
β†’ bit12 = 1 β†’ clear

κ°€ λ˜μ–΄ μ²˜λ¦¬ν•˜μ§€ μ•Šμ€ EXTI12κΉŒμ§€ clear될 μœ„ν—˜μ΄ μžˆλ‹€.

κ·Έλž˜μ„œ W1C registerμ—μ„œλŠ” 보톡:

EXTI->PR = LINE13;

처럼 clearν•˜λ €λŠ” bitμ—λ§Œ 1을 직접 writeν•œλ‹€.


11. Complete Interrupt Flow

B1 Released
PC13 = 0

      ↓ User presses B1

PC13 = 1

      ↓

LOW β†’ HIGH

      ↓

EXTI13 Rising Edge Detect

      ↓

PR[13] = 1

      ↓

EXTI15_10 IRQ Request

      ↓

NVIC

      ↓

Cortex-M4 pauses normal execution

      ↓

Vector Table Lookup

      ↓

EXTI15_10_IRQHandler()

      ↓

Check PR[13]

      ↓

Clear PR[13]

      ↓

exti_callback()

      β”œβ”€β”€ UART Print
      └── LED Toggle

      ↓

Return From Interrupt

      ↓

main() resumes

12. NVIC

NVIC:

Nested
Vectored
Interrupt
Controller

μ—­ν• :

Interrupt Enable
Interrupt Disable
Priority
Pending
Nested Interrupt

이번 μ½”λ“œλŠ”:

NVIC_EnableIRQ(EXTI15_10_IRQn);

을 μ‚¬μš©ν•œλ‹€.

즉:

EXTI peripheral

μ—μ„œ interrupt requestκ°€ λ§Œλ“€μ–΄μ Έλ„ NVICμ—μ„œ ν•΄λ‹Ή IRQκ°€ enableλ˜μ–΄ μžˆμ–΄μ•Ό CPUκΉŒμ§€ μ „λ‹¬λœλ‹€.


13. Vector Table

IRQκ°€ λ“€μ–΄μ˜€λ©΄ Cortex-M4λŠ” ν•¨μˆ˜ 이름을 κ²€μƒ‰ν•˜λŠ” 것이 μ•„λ‹ˆλ‹€.

Startup file에 μžˆλŠ” Vector Table에 ISR addressκ°€ μ €μž₯λ˜μ–΄ μžˆλ‹€.

κ°œλ…μ μœΌλ‘œ:

Vector Table

...
EXTI15_10_IRQn
      ↓
EXTI15_10_IRQHandler address
...

λ”°λΌμ„œ μš°λ¦¬κ°€:

void EXTI15_10_IRQHandler(void)

λ₯Ό κ΅¬ν˜„ν•˜λ©΄ startup의 weak handler λŒ€μ‹  이 ν•¨μˆ˜κ°€ μ‚¬μš©λœλ‹€.


14. Why One Handler for 6 Lines?

EXTI10~15λŠ” ν•˜λ‚˜μ˜ IRQλ₯Ό κ³΅μœ ν•œλ‹€.

EXTI10 ─┐
EXTI11 ──
EXTI12 ──
EXTI13 ─┼── EXTI15_10_IRQHandler
EXTI14 ──
EXTI15 β”€β”˜

λ”°λΌμ„œ ISR λ‚΄λΆ€μ—μ„œ:

if ((EXTI->PR & LINE13) != 0U)

처럼 μ‹€μ œ pending sourceλ₯Ό 확인해야 ν•œλ‹€.

μ—¬λŸ¬ line을 μ‚¬μš©ν•  경우:

if (EXTI->PR & LINE10)
{
    ...
}

if (EXTI->PR & LINE13)
{
    ...
}

if (EXTI->PR & LINE15)
{
    ...
}

처럼 ꡬ뢄할 수 μžˆλ‹€.


15. Rising Edge

이번 board:

Not Pressed
PC13 = LOW

Pressed
PC13 = HIGH

λ”°λΌμ„œ button press:

0 β†’ 1

μ΄λ―€λ‘œ:

EXTI->RTSR |= LINE13;

을 μ‚¬μš©ν•œλ‹€.

μ›μ„œμ²˜λŸΌ active-LOW button이면:

1 β†’ 0

μ΄λ―€λ‘œ FTSR을 μ‚¬μš©ν–ˆμ„ 수 μžˆλ‹€.

즉 interrupt edgeλŠ” board 회둜 polarity에 따라 κ²°μ •ν•΄μ•Ό ν•œλ‹€.


16. Why Clear Pending First?

ISR:

if (EXTI->PR & LINE13)
{
    EXTI->PR = LINE13;
    exti_callback();
}

처럼 pending flagλ₯Ό callback 전에 μ§€μš΄λ‹€.

이유:

IRQ λ°œμƒ
 ↓
PR = 1
 ↓
ISR μ§„μž…
 ↓
PR clear
 ↓
Application processing

PR을 μ•ˆ μ§€μš°λ©΄ ISR μ’…λ£Œ 후에도:

Pending = 1

이 남을 수 μžˆμ–΄ λ‹€μ‹œ interruptκ°€ λ°œμƒν•  수 μžˆλ‹€.


17. ISR Should Be Short

이번 μ‹€μŠ΅μ—μ„œλŠ” 이해λ₯Ό μœ„ν•΄ ISR μ•ˆμ—μ„œ:

UART Output
LED Toggle

κΉŒμ§€ μˆ˜ν–‰ν•œλ‹€.

ν•˜μ§€λ§Œ 일반적인 embedded designμ—μ„œλŠ” ISR을 짧게 μœ μ§€ν•˜λŠ” 것이 μ’‹λ‹€.

예:

volatile uint8_t g_btn_pressed;

void EXTI15_10_IRQHandler(void)
{
    if (EXTI->PR & LINE13)
    {
        EXTI->PR = LINE13;

        g_btn_pressed = 1U;
    }
}

main:

while (1)
{
    if (g_btn_pressed)
    {
        g_btn_pressed = 0U;

        uart_write_string("BTN Pressed...\r\n");
        led_toggle();
    }
}

이 방식은:

ISR
β†’ Event capture only

main
β†’ Heavy processing

으둜 역할을 λΆ„λ¦¬ν•œλ‹€.

이번 Ch14 원본 μ‹€μŠ΅μ€ interrupt λ™μž‘μ„ 직접 ν™•μΈν•˜κΈ° μœ„ν•œ λ‹¨μˆœ κ΅¬μ‘°μ΄λ―€λ‘œ callback을 ISRμ—μ„œ λ°”λ‘œ μˆ˜ν–‰ν–ˆλ‹€.


18. Button Bounce

EXTIμ—μ„œλŠ” mechanical switch bounceκ°€ 더 λˆˆμ— 띌 수 μžˆλ‹€.

ν•œ 번 λˆ„λ₯Έλ‹€κ³  해도 μ‹€μ œ waveform:

LOW
 ↓
HIGH
 ↓
LOW
 ↓
HIGH
 ↓
LOW
 ↓
HIGH

처럼 μ—¬λŸ¬ edgeκ°€ 생길 수 μžˆλ‹€.

λ”°λΌμ„œ μ‹€μ œλ‘œλŠ” ν•œ 번 λˆŒλ €λŠ”λ°:

BTN Pressed...
BTN Pressed...
BTN Pressed...

κ°€ μ—¬λŸ¬ 번 λ°œμƒν•  κ°€λŠ₯성이 μžˆλ‹€.

이λ₯Ό λ§‰μœΌλ €λ©΄:

Software Debounce
Timer Debounce
State Machine

등을 μΆ”κ°€ν•  수 μžˆλ‹€.

이번 chapter의 핡심은 EXTI + NVIC ꡬ쑰라 debounceλŠ” μΆ”κ°€ν•˜μ§€ μ•ŠλŠ”λ‹€.


19. Build / Board Result

κΈ°μ‘΄ μ‹€μŠ΅:

Build    : PASS
Download : PASS
Run      : PASS

μ‹€μ œ board:

B1 Press
   ↓
LD1 Toggle
   ↓
UART:
BTN Pressed...

확인 μ™„λ£Œ.

Program size:

text = 1272 bytes
data = 0 bytes
bss  = 2560 bytes

κ²½κ³  없이 build됐닀.


20. Verification

1. Build

make clean
make

Expected:

Build PASS

text = 1272
data = 0
bss  = 2560

2. GPIO

Debugger:

GPIOC->MODER

Expected:

PC13 MODER[27:26]
=
00

3. SYSCFG

확인:

SYSCFG->EXTICR[3]

EXTI13 field:

0010
=
Port C

즉:

EXTI13 ← PC13

4. EXTI

확인:

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

Expected:

IMR[13]  = 1
RTSR[13] = 1

button press 직후:

PR[13] = 1

ISR 처리 ν›„:

PR[13] = 0

5. Breakpoint

Breakpoint:

void EXTI15_10_IRQHandler(void)

B1을 λˆ„λ₯΄λ©΄ CPUκ°€ ν•΄λ‹Ή ISR둜 μ§„μž…ν•˜λŠ”μ§€ ν™•μΈν•œλ‹€.

Watch:

EXTI->PR
GPIOC->IDR
GPIOB->ODR

6. UART

Expected:

BTN Pressed...

button을 λˆ„λ₯Ό λ•Œλ§ˆλ‹€ 좜λ ₯λœλ‹€.


7. LED

Expected:

Press #1
β†’ LD1 ON

Press #2
β†’ LD1 OFF

Press #3
β†’ LD1 ON

21. Screenshots to Add

images/ch14_build.png
images/ch14_exticr.png
images/ch14_exti_registers.png
images/ch14_isr_breakpoint.png
images/ch14_uart.png
images/ch14_button_led.gif

κ°€μž₯ 쒋은 증거 ꡬ성:

B1 Press
+
ISR Breakpoint
+
PR[13]
+
UART "BTN Pressed..."
+
LD1 Toggle

22. Key Points

PC13
 ↓
SYSCFG
 ↓
EXTI13
 ↓
Rising Edge
 ↓
PR[13]
 ↓
NVIC
 ↓
Vector Table
 ↓
EXTI15_10_IRQHandler
 ↓
LED + UART
  • Ch07 polling을 EXTI interrupt ꡬ쑰둜 λ³€κ²½ν–ˆλ‹€.
  • PC13은 EXTI13을 μ‚¬μš©ν•œλ‹€.
  • SYSCFG->EXTICR[3]μ—μ„œ EXTI13을 Port C에 μ—°κ²°ν•œλ‹€.
  • F439ZI B1은 active-HIGHμ΄λ―€λ‘œ Rising Edgeλ₯Ό μ‚¬μš©ν•œλ‹€.
  • IMRμ—μ„œ EXTI13 interruptλ₯Ό unmaskν•œλ‹€.
  • NVIC_EnableIRQ()둜 CPU interrupt input을 enableν•œλ‹€.
  • EXTI10~15λŠ” EXTI15_10_IRQHandler()λ₯Ό κ³΅μœ ν•œλ‹€.
  • ISRμ—μ„œ PR을 확인해 μ‹€μ œ interrupt sourceλ₯Ό κ΅¬λΆ„ν•œλ‹€.
  • PR은 W1C registerμ΄λ―€λ‘œ EXTI->PR = LINE13 ν˜•νƒœλ‘œ clearν•˜λŠ” 것이 μ•ˆμ „ν•˜λ‹€.
  • μ‹€μ œ boardμ—μ„œ B1 β†’ LD1 toggle + UART outputκΉŒμ§€ 검증 μ™„λ£Œν–ˆλ‹€.
⚠️ **GitHub.com Fallback** ⚠️