STM32 LibOpenCM3:EXTI 外部中斷

系列:簡單入門 LibOpenCM3 STM32 嵌入式系統開發 Posted on 2022-09-21

前言

在之前的文章中我們使用輪詢的方式來讀取目前的按鈕狀態,但這種方式的效率不是很好,在需要讀取按鈕狀態等情況下,我們可以使用外部中斷(External Interrupt,EXTI),讓 CPU 可以去忙其它事情,等到按鈕被按下時會產生中斷事件,才去執行按鈕被按下時要處理的事。

這次要我們的目標功能是每次按下按鈕後,LED 的閃爍速度就會變化。

正文

首先一樣以 Nucleo-F446RE 做示範。

首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/ 資料夾中新增並開啓 main.c 檔案。

完整程式

 1/**
 2 *  @file  main.c
 3 *  @brief EXTI example for STM32 Nucleo-F446RE.
 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
11/* User LED (LD2) connected to Arduino-D13 pin. */
12#define RCC_LED_GPIO (RCC_GPIOA)
13#define GPIO_LED_PORT (GPIOA)
14#define GPIO_LED_PIN (GPIO5)
15
16/* User button (B1) connected to PC13. */
17#define RCC_BUTTON_GPIO (RCC_GPIOC)
18#define GPIO_BUTTON_PORT (GPIOC)
19#define GPIO_BUTTON_PIN (GPIO13)
20#define EXTI_BUTTON_SOURCE (EXTI13)
21#define NVIC_BUTTON_IRQ (NVIC_EXTI15_10_IRQ)
22
23#define DELAY_VALUE_A ((uint32_t)500000)
24#define DELAY_VALUE_B ((uint32_t)200000)
25
26uint32_t delay_value = DELAY_VALUE_A;
27
28static void delay(uint32_t value)
29{
30  for (uint32_t i = 0; i < value; i++)
31  {
32    __asm__("nop"); /* Do nothing. */
33  }
34}
35
36static void led_setup(void)
37{
38  /* Set LED pin to output push-pull. */
39  gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN);
40  gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);
41}
42
43static void button_setup(void)
44{
45  /* Set button pin to input floating. */
46  gpio_mode_setup(GPIO_BUTTON_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_BUTTON_PIN);
47
48  /* Set up interrupt. */
49  nvic_enable_irq(NVIC_BUTTON_IRQ);
50  exti_select_source(EXTI_BUTTON_SOURCE, GPIO_BUTTON_PORT);
51  exti_set_trigger(EXTI_BUTTON_SOURCE, EXTI_TRIGGER_FALLING);
52  exti_enable_request(EXTI_BUTTON_SOURCE);
53}
54
55static void rcc_setup(void)
56{
57  rcc_periph_clock_enable(RCC_LED_GPIO);
58  rcc_periph_clock_enable(RCC_BUTTON_GPIO);
59  rcc_periph_clock_enable(RCC_SYSCFG); /* For EXTI. */
60}
61
62int main(void)
63{
64  rcc_setup();
65  led_setup();
66  button_setup();
67
68  while (1)
69  {
70    gpio_toggle(GPIO_LED_PORT, GPIO_LED_PIN); /* LED on/off. */
71    delay(delay_value);
72  }
73  return 0;
74}
75
76/**
77 * @brief EXTI15~10 Interrupt service routine.
78 * @note User button pressed event.
79 */
80void exti15_10_isr(void)
81{
82  if (exti_get_flag_status(EXTI_BUTTON_SOURCE)) /* Check EXTI line. */
83  {
84    exti_reset_request(EXTI_BUTTON_SOURCE);
85
86    if (delay_value == DELAY_VALUE_A)
87    {
88      delay_value = DELAY_VALUE_B;
89    }
90    else
91    {
92      delay_value = DELAY_VALUE_A;
93    }
94  }
95}

分段說明

Include

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.hgpio.h 外,還要引入 exti.hnvic.h

exti.h 當然就是包含了 EXTI 的各種函式。nvic.h 是嵌套向量中斷控制器(Nested Vectored Interrupt Controller,NVIC),這是一個 ARM Cortex-M 中負責處理中斷的控制器,有用到中斷的話都會需要它。

注意是 libopencm3/cm3/nvic.h,而不是 libopencm3/stm32/nvic.h

設定腳位

 1/* User LED (LD2) connected to Arduino-D13 pin. */
 2#define RCC_LED_GPIO (RCC_GPIOA)
 3#define GPIO_LED_PORT (GPIOA)
 4#define GPIO_LED_PIN (GPIO5)
 5
 6/* User button (B1) connected to PC13. */
 7#define RCC_BUTTON_GPIO (RCC_GPIOC)
 8#define GPIO_BUTTON_PORT (GPIOC)
 9#define GPIO_BUTTON_PIN (GPIO13)
10#define EXTI_BUTTON_SOURCE (EXTI13)
11#define NVIC_BUTTON_IRQ (NVIC_EXTI15_10_IRQ)

這次處理要定義 RCC 與腳位外,還一併設定了按鈕的 IRQ 與 EXTI 來源。因爲按鈕是 PC13,所以 IRQ 是 NVIC_EXTI15_10_IRQ,它負責處理 EXTI 15 ~ 10,而我們實際會觸發的是 EXTI13

設定中斷

 1static void button_setup(void)
 2{
 3  /* 省略部分程式 */
 4
 5  /* Set up interrupt. */
 6  nvic_enable_irq(NVIC_BUTTON_IRQ);
 7  exti_select_source(EXTI_BUTTON_SOURCE, GPIO_BUTTON_PORT);
 8  exti_set_trigger(EXTI_BUTTON_SOURCE, EXTI_TRIGGER_FALLING);
 9  exti_enable_request(EXTI_BUTTON_SOURCE);
10}
  • nvic_enable_irq():致能指定的 IRQ。
  • exti_select_source():選擇 EXTI 的來源。
  • exti_set_trigger():設定觸發方式。這裡使用的是 EXTI_TRIGGER_FALLING,即負緣觸發,還可以選擇 EXTI_TRIGGER_RISING(正緣觸發)或 EXTI_TRIGGER_BOTH(正/負緣都觸發)。
  • exti_enable_request():致能 EXTI IRQ。

中斷服務程式 ISR

 1/**
 2 * @brief EXTI15~10 Interrupt service routine.
 3 * @note User button pressed event.
 4 */
 5void exti15_10_isr(void)
 6{
 7  if (exti_get_flag_status(EXTI_BUTTON_SOURCE)) /* Check EXTI line. */
 8  {
 9    exti_reset_request(EXTI_BUTTON_SOURCE);
10
11    if (delay_value == DELAY_VALUE_A)
12    {
13      delay_value = DELAY_VALUE_B;
14    }
15    else
16    {
17      delay_value = DELAY_VALUE_A;
18    }
19  }
20}

exti_reset_request() 可以用來清除 IRQ flag。

由於 EXTI 15 ~ 10 共用一個 ISR,所以還要再用 exti_get_flag_status() 來讀取 EXTI_PR 暫存器的值,以確定目前是哪一個 EXTI Line 被觸發。

在 LibOpenCM3 中,各個功能的 ISR 函式名稱是固定的,如果打錯的話就無法正常執行。完整的 STM32F4 系列的 ISR 列表在此

RCC

1static void rcc_setup(void)
2{
3  rcc_periph_clock_enable(RCC_LED_GPIO);
4  rcc_periph_clock_enable(RCC_BUTTON_GPIO);
5  rcc_periph_clock_enable(RCC_SYSCFG); /* For EXTI. */
6}

比較要注意的是,RCC 除了 GPIO Port 外,還要致能 RCC_SYSCFG,否則 EXTI 不會工作。

多環境程式(F446RE + F103RB)

由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。

以下列出主要的差異部分,也就是 RCC 與 GPIO 的部分。完整的程式請看 GitHub repo

 1static void rcc_setup(void)
 2{
 3  rcc_periph_clock_enable(RCC_LED_GPIO);
 4  rcc_periph_clock_enable(RCC_BUTTON_GPIO);
 5
 6  /* For EXTI. */
 7#if defined(STM32F1)
 8  rcc_periph_clock_enable(RCC_AFIO);
 9#else
10  rcc_periph_clock_enable(RCC_SYSCFG);
11#endif
12}
 1static void led_setup(void)
 2{
 3  /* Set LED pin to output push-pull. */
 4#if defined(STM32F1)
 5  gpio_set_mode(GPIO_LED_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_LED_PIN);
 6#else
 7  gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN);
 8  gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);
 9#endif
10}
 1static void button_setup(void)
 2{
 3  /* Set button pin to input floating. */
 4#if defined(STM32F1)
 5  gpio_set_mode(GPIO_BUTTON_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_BUTTON_PIN);
 6#else
 7  gpio_mode_setup(GPIO_BUTTON_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_BUTTON_PIN);
 8#endif
 9
10  /* 省略部分程式 */
11}

小結

這次簡單介紹了 EXTI 的實際程式。中斷是很基本也實用的功能,而外部中斷 EXTI 也是中斷中比較單純且常用的,希望大家看完後也會使用 EXTI 了。

實際上 STM32 的中斷還要許多細節我沒寫到,因爲本篇主要還是希望大家可以最快速入門,因此就先省略了。

參考資料

本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽



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

comments powered by Disqus