[LibOpenCM3 × STM32教學-2] 按鈕觸發外部中斷 EXTI

系列:LibOpenCM3 × STM32教學 Posted on 2021-11-28

我在 2022 年 9 月重新寫了與本文內容相近的文章,建議可以觀看新文章:

前言

LibOpenCM3 是一個 Open-Source 的 ARM Cortex-M3 微控制器底層硬體函式庫,支援包含 STM32 在內的多種微控制器。

本文將以 STM32F103RB(Nucleo F103RB)作為示範,介紹如何使用 LibOpenCM3 寫出 STM32 的外部中斷(External interrupt,EXTI)。

正文

外部中斷最基本的應用就是按鈕。雖然可以使用輪詢的方式來感測按鈕是否有觸發,但這種做法不但消耗資源,也不保險(觸發當下可能剛好錯過輪詢),而使用外部中斷就不會有這樣的問題。

本文示範一個以按鈕觸發的外部中斷,每次按下按鈕時就會觸發指定的外部中斷,讓 LED 進行一次開或關。

程式全文

 1/**
 2 * @file   main.c
 3 * @brief  Basic button external interrupt(EXTI).
 4 */
 5
 6#include <libopencm3/stm32/rcc.h>
 7#include <libopencm3/stm32/gpio.h>
 8#include <libopencm3/stm32/exti.h>
 9#include <libopencm3/cm3/nvic.h>
10
11void led_setup(void)
12{
13  rcc_periph_clock_enable(RCC_GPIOA);
14  gpio_set_mode(GPIOA,
15                GPIO_MODE_OUTPUT_2_MHZ,
16                GPIO_CNF_OUTPUT_PUSHPULL,
17                GPIO5);
18}
19
20
21void button_setup(void)
22{
23  rcc_periph_clock_enable(RCC_GPIOC);
24  rcc_periph_clock_enable(RCC_AFIO);
25
26  nvic_enable_irq(NVIC_EXTI15_10_IRQ);
27
28  gpio_set_mode(GPIOC,
29                GPIO_MODE_INPUT,
30                GPIO_CNF_INPUT_FLOAT,
31                GPIO13);
32
33  exti_select_source(EXTI13, GPIOC);
34  exti_set_trigger(EXTI13, EXTI_TRIGGER_FALLING);
35  exti_enable_request(EXTI13);
36}
37
38/**
39 * @brief EXTI15~10 Interrupt service routine.
40 */
41void exti15_10_isr(void)
42{
43  exti_reset_request(EXTI13);
44  gpio_toggle(GPIOA, GPIO5);
45}
46
47int main(void)
48{
49  led_setup();
50  button_setup();
51
52  while (1)
53  {
54    __asm__("nop");
55  }
56
57  return 0;
58}

程式說明

引入函式庫

1#include <libopencm3/stm32/rcc.h>
2#include <libopencm3/stm32/gpio.h>
3#include <libopencm3/stm32/exti.h>
4#include <libopencm3/cm3/nvic.h>

除了基本的「RCC」與「GPIO」函式庫外,要使用外部中斷還需要「EXTI」與「NVIC」這兩個函式庫。

其中「EXTI」包含了外部中斷(External interrupt)的相關功能。「NVIC」是「Nested vectored interrupt controller」的意思,是 ARM Cortex-M3 中負責管理所有中斷的核心功能,包含了中斷優先級、中斷請求(Interrupt request,IRQ)、中斷旗標等。

注意是 libopencm3/cm3/nvic.h 而非 libopencm3/stm32/nvic.h

設定 LED

1void led_setup(void)
2{
3  rcc_periph_clock_enable(RCC_GPIOA);
4  gpio_set_mode(GPIOA,
5                GPIO_MODE_OUTPUT_2_MHZ,
6                GPIO_CNF_OUTPUT_PUSHPULL,
7                GPIO5);
8}

函數 led_setup() 負責設定 LED。

  • rcc_periph_clock_enable() 用來致能目標 LED 所在 GPIO Port-A 的 Clock。
  • gpio_set_mode() 設定 LED 所在的 PA5 為最高速度 2 MHz 的推輓式(Push-Pull)輸出。

設定按鈕及 EXTI

 1void button_setup(void)
 2{
 3  rcc_periph_clock_enable(RCC_GPIOC);
 4  rcc_periph_clock_enable(RCC_AFIO);
 5
 6  nvic_enable_irq(NVIC_EXTI15_10_IRQ);
 7
 8  gpio_set_mode(GPIOC,
 9                GPIO_MODE_INPUT,
10                GPIO_CNF_INPUT_FLOAT,
11                GPIO13);
12
13  exti_select_source(EXTI13, GPIOC);
14  exti_set_trigger(EXTI13, EXTI_TRIGGER_FALLING);
15  exti_enable_request(EXTI13);
16}

函數 button_setup() 負責按鈕的相關設定。

  • RCC
    • rcc_periph_clock_enable(RCC_GPIOC) 致能按鈕本身所在的 GPIO Port-C 的 Clock。
    • rcc_periph_clock_enable(RCC_AFIO) 致能 Alternate function I/O(AFIO) 的 Clock。使用外部中斷必須啟用 AFIO。
  • nvic_enable_irq(NVIC_EXTI15_10_IRQ) 致能「EXTI-10 到 15」的中斷請求(Interrupt request,IRQ)。我使用的 STM32 中 EXTI-10 到 15 的 IRQ 是共用的,它們都會對應到相同的中斷服務程序(ISR)。我要使用的是 EXTI-13,所以要對「EXTI15_10」進行設定。
  • gpio_set_mode() 將按鈕所在的 PC13 設定成浮接輸入(Input float)模式。
  • EXTI
    • exti_select_source(EXTI13, GPIOC) 選擇 EXTI 的來源為 「EXIT-13」,「GPIO Port-C」,也就是「PC13」。
    • exti_set_trigger(EXTI13, EXTI_TRIGGER_FALLING) 設定 「EXTI-13」的觸發方式為「Falling(負緣)」。
    • exti_enable_request(EXTI13) 致能「EXTI-13」的中斷請求。

中斷服務程序 ISR

1/**
2 * @brief EXTI15~10 Interrupt service routine.
3 */
4void exti15_10_isr(void)
5{
6  exti_reset_request(EXTI13);
7  gpio_toggle(GPIOA, GPIO5);
8}

函數 exti15_10_isr() 是「EXTI-10 到 15」的中斷服務程序(Interrupt service routine,ISR)。當「EXTI-10 到 15」發生中斷時就會執行這裡的程式。此函數名稱是規定好的,不能打錯。

  • exti_reset_request(EXTI13) 會清除來自「EXTI-13」的中斷請求旗標。
  • gpio_toggle(GPIOA, GPIO5) 是讓 LED(PA5)的輸出反轉,進行 LED 的開關,也就是我們要的功能——每次按下按鈕 LED 就開或關一次。

主程式

 1int main(void)
 2{
 3  led_setup();
 4  button_setup();
 5
 6  while (1)
 7  {
 8    __asm__("nop");
 9  }
10
11  return 0;
12}

主程式先依序呼叫 led_setup()button_setup() 來完成設定,隨後就進入一個無限空迴圈,等待中斷的觸發。

__asm__("nop") 會嵌入組合語言的「nop(無操作)」指令。

結語

本次文章內介紹的程式我也有放在 GitHub 上,可以直接載下來並使用 PlatformIO 開始專案。

相關文章



留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)

comments powered by Disqus