STM32之中断详解

一、 中断

1. 中断是什么

中断(Interrupt)。在 STM32 寄存器开发里,要是寄存器标志以 IE(Interrupt Enable,中断使能)结尾,大多数时候是用于对某个中断进行开启或者关闭的控制。

中断操作是 STM32 里针对特定的事件或者行为,去执行特定处理操作的机制,它需要【触发机制】和【注册行为】。

  • 【触发机制】:程序在执行过程中,当中断出现时,会触发其他操作。
  • 【注册行为】:注册在当前 STM32 执行过程中,所关注的中断内容是哪一个。

常用概念

  • 断点:注册的中断触发位置,一般和电平跳变、寄存器标志位变化相对应。
  • 中断源:当前中断注册的功能函数或者中断服务函数。
  • 压栈:中断触发时,会把断点对应的函数进行压栈操作,进而转换为后台函数。

2. Cortex - M3 中断分类

Cortex - M3 内核中断分类:

  • 内核中断 / 内部中断:属于芯片内部的中断,也可称之为【异常(Exception)】。它能应对程序在正常运行时,出现不同于常规的数据情况,这些情况可以是错误,也可以是标记。通常关注的是寄存器中的中断标志位。
  • 外设中断:Cortex - M3 内核预留了外部中断控制线,能够通过外部中断控制线的高低电平切换,在出现上升沿和下降沿时进行中断触发。

3. 外部中断 / EXTI

3.1 外部中断 / EXTI 概述

  • 外部中断线一共有 EXTI0 ~ EXTI19。
  • 其中 EXTI0 ~ EXTI15 对应 GPIO 口,规则是 EXTI0 对应所有 GPIO 组中的 0 引脚,例如 PA0 ~ PGO。
  • 其他 EXTI16 ~ EXTI19 暂时不使用。

3.2 外部中断判断逻辑框图

  • 外部中断线通过
    • 上升沿触发选择寄存器
    • 下降沿触发选择寄存器
  • 决定当前中断的触发方式。需要根据电路分析,找到默认的电平情况下,选择合理的触发方式。

3.3 按键功能实现外部中断控制 EXTI

3.3.1 按键原理图

3.3.2 AFIO 时钟使能
  • 根据分析,当前 EXTI 外部中断线的控制需要涉及 AFIO 相关寄存器。AFIO 是 IO 引脚复用控制寄存器。
  • 其中 AFIO_EXTICR1 ~ AFIO_EXTICR4 用于控制外部中断线相关内容。
  • AFIO 对应的时钟是 APB2,需要进行使能操作。

3.3.3 外部中断线和按键对应关系
  • 按键涉及到的引脚分别对应的外部中断线为:
    • KEY_UP ==> PA0 ==> EXTI0
    • KEY_2 ==> PE2 ==> EXTI2
    • KEY_1 ==> PE3 ==> EXTI3
    • KEY_0 ==> PE4 ==> EXTI4

以下寄存器操作赋值完成,对应当前外部中断线控制 / 监控:

3.3.4 外部中断上升沿或者下降沿触发寄存器配置
  • 以下两个寄存器用于控制当前触发的中断方式
  • EXTI_RTSR 控制对应外部中断控制线触发方式为上升沿触发
  • EXTI_FTSR 控制对应外部中断控制线触发方式为下降沿触

3.3.5 外部中断屏蔽寄存器
        外部中断屏蔽寄存器是用于控制对应外部中断线,是否运行被 MCU 处理。如果需要使用对应的外部中断控制,必须开启中断允许。
3.3.6 中断挂起寄存器
        利用 EXTI_PR 寄存器明确当前中断是否触发,并且在处理之后,给予 write 1 操作,机械能挂起寄 存器复位。

4. IRQn IRQHandler

4.1 IRQn 中断请求编号

4.2 IRQHandl

5. 外部中断控制实现

5.1 外部中断配置函数使能函数

void Key_EXTI_Interrupt_Enable(void)
{
    // 因为 Key_Init 已经完成了 KEY 对应 GPIO 配置,此处仅配置中断相关
    
    // 1. 使能AFIO时钟,用于外部中断引脚映射
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;  // 更清晰的宏定义方式
    
    // 2. 配置外部中断线与GPIO的映射关系
    // EXTI0 -> PA0,EXTI2 -> PE2,EXTI3 -> PE3
    AFIO->EXTICR[0] &= ~(0x0F << 0);    // 清除EXTI0原有配置
    AFIO->EXTICR[0] |= 0x00 << 0;       // EXTI0映射到PA0 (0000)
    AFIO->EXTICR[0] &= ~(0x0F << 8);    // 清除EXTI2原有配置
    AFIO->EXTICR[0] |= 0x04 << 8;       // EXTI2映射到PE2 (0100)
    AFIO->EXTICR[0] &= ~(0x0F << 12);   // 清除EXTI3原有配置
    AFIO->EXTICR[0] |= 0x04 << 12;      // EXTI3映射到PE3 (0100)
    
    // EXTI4 -> PE4
    AFIO->EXTICR[1] &= ~(0x0F << 0);    // 清除EXTI4原有配置
    AFIO->EXTICR[1] |= 0x04 << 0;       // EXTI4映射到PE4 (0100)
    
    // 3. 配置中断触发方式
    EXTI->RTSR |= 0x01;                 // EXTI0 上升沿触发 (KEY_UP)
    EXTI->FTSR |= (1 << 2) | (1 << 3) | (1 << 4);  // EXTI2/3/4 下降沿触发 (KEY2/1/0)
    
    // 4. 使能外部中断线
    EXTI->IMR |= (1 << 0) | (1 << 2) | (1 << 3) | (1 << 4);  // 使能EXTI0/2/3/4中断
    
    // 5. 配置NVIC中断优先级并使能中断
    NVIC_SetPriority(EXTI0_IRQn, 1);    // 设置EXTI0中断优先级
    NVIC_SetPriority(EXTI2_IRQn, 1);    // 设置EXTI2中断优先级
    NVIC_SetPriority(EXTI3_IRQn, 1);    // 设置EXTI3中断优先级
    NVIC_SetPriority(EXTI4_IRQn, 1);    // 设置EXTI4中断优先级
    
    NVIC_EnableIRQ(EXTI0_IRQn);         // 使能EXTI0中断
    NVIC_EnableIRQ(EXTI2_IRQn);         // 使能EXTI2中断
    NVIC_EnableIRQ(EXTI3_IRQn);         // 使能EXTI3中断
    NVIC_EnableIRQ(EXTI4_IRQn);         // 使能EXTI4中断
}

5.2 外部中断处理函数实现

        外部中断处理函数是 Cortex-M 内核声明的标准函数,无需二次声明,仅需要完成对应的实现即可。
/*
EXTI0_IRQHandler ==> 对应PA0引脚(KEY_UP按键)的中断处理
*/
void EXTI0_IRQHandler(void)
{
    // 检查是否是EXTI0触发的中断
    if (EXTI->PR & EXTI_PR_PR0)
    {
        // 清除中断标志位(写1清除)
        EXTI->PR = EXTI_PR_PR0;
        
        // 翻转LED0状态(假设LED0连接到PB5)
        if (GPIOB->ODR & (1 << 5))
        {
            Led0_Ctrl(0);  // 关闭LED0
        }
        else
        {
            Led0_Ctrl(1);  // 打开LED0
        }
    }
}

/*
EXTI2_IRQHandler ==> 对应PE2引脚(KEY2按键)的中断处理
*/
void EXTI2_IRQHandler(void)
{
    // 检查是否是EXTI2触发的中断
    if (EXTI->PR & EXTI_PR_PR2)
    {
        // 清除中断标志位(写1清除)
        EXTI->PR = EXTI_PR_PR2;
        
        // 翻转蜂鸣器状态(假设蜂鸣器连接到PB8)
        if (GPIOB->ODR & (1 << 8))
        {
            Beep_Ctrl(0);  // 关闭蜂鸣器
        }
        else
        {
            Beep_Ctrl(1);  // 打开蜂鸣器
        }
    }
}

6. 内部中断

6.1 内部中断概述

  • 内部中断在 Cortex-M 内核中被称为异常,通常需要关注指定寄存器的中断标志位。

  • 内部中断中,有三个具有最高优先级的中断(优先级数值越小,优先等级越高):

    1. RESET(复位)
      • 优先级:-3(最高优先级)
      • 特性:优先级不可修改
    2. NMI(不可屏蔽中断)
      • 优先级:-2
      • 特性:优先级不可修改
    3. HardFault(硬件错误中断)
      • 优先级:-1
      • 特性:优先级不可修改
  • 在内部中断中,SysTick(系统滴答定时器) 是较为重要的一种,常用于实现延时、任务调度等功能。

6.2 内部中断对应 IRQn IRQHandler

        内部中断对应 IRQn ,根据原码分析,缺少 RESET HardFault 对应的 IRQn ,是因为 RESET HardFault 对应的中断处理方式为硬件处理方式,与软件内容无关。

7. 中断优先级

7.1 中断优先级概述

中断优先级由占先优先级次级优先级两部分组成:

  • 占先优先级:当多个中断同时触发时,MCU 优先依据占先优先级判断执行顺序。占先优先级高的中断会立即执行,其余中断进入等待状态,遵循 “占先优先级谁高谁执行” 原则。
  • 次级优先级:若多个中断的占先优先级相同,MCU 则通过次级优先级决定执行顺序。需注意,若某中断已开始执行,次级优先级无法打断当前执行流程,即 “同步进入时谁高谁执行,中断执行中不得抢占” 。

7.2 中断优先级配置


7.3 涉及到的函数

1. NVIC_SetPriorityGrouping(全局配置)
void NVIC_SetPriorityGrouping(uint32_t bits);
  • 功能:设置系统全局的中断优先级分组方案,决定「占先优先级」和「次级优先级」的位数分配。
  • 关键特性
    • 全局性:影响所有中断的优先级解释逻辑,系统中所有中断优先级都基于此分组规则解析。
    • 一次性:通常在系统初始化阶段设置一次,运行中不应随意修改,否则可能导致中断优先级混乱。
    • 基础性:为后续 NVIC_SetPriority 函数提供优先级分组的 “解读依据”,决定优先级数值中哪些位代表占先、哪些位代表次级。
2. NVIC_SetPriority(个体中断配置)
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);
  • 功能:在 NVIC_SetPriorityGrouping 确定的分组规则下,为单个中断源设置具体优先级数值。
  • 关键特性
    • 个体性:仅作用于指定的 IRQn(中断请求号),不影响其他中断。
    • 依赖性priority 数值的具体含义(占先、次级优先级如何拆分)完全由 NVIC_SetPriorityGrouping 的分组规则决定。
    • 多次调用:可针对不同中断源单独调用,灵活配置每个中断的优先级。

案例分析(代码 + 逻辑拆解)

// 1. 全局分组配置:设置占先优先级、次级优先级各占 2 位  
NVIC_SetPriorityGrouping(2);  
// 规则:优先级数值共 4 位(默认优先级位数与内核架构相关,此处按分组逻辑拆分)  
//       高 2 位 = 占先优先级,低 2 位 = 次级优先级  


// 2. 为 EXTI0、EXTI1 配置优先级  
NVIC_SetPriority(EXTI0_IRQn, 5);  // 二进制:0101 → 占先[01],次级[01]  
NVIC_SetPriority(EXTI1_IRQn, 6);  // 二进制:0110 → 占先[01],次级[10]  

// 场景:EXTI0_IRQn、EXTI1_IRQn 同时触发  
// 优先级分析:  
// - 占先优先级:两者均为 01(相同)  
// - 次级优先级:EXTI0 是 01,EXTI1 是 10 → 01(EXTI0)更高  
// 执行顺序:EXTI0_IRQn 先执行  


// 3. 为 EXTI2、EXTI3 配置优先级  
NVIC_SetPriority(EXTI2_IRQn, 2);   // 二进制:0010 → 占先[00],次级[10]  
NVIC_SetPriority(EXTI3_IRQn, 10);  // 二进制:1010 → 占先[10],次级[10]  

// 场景:EXTI3_IRQn 正在执行时,EXTI2_IRQn 触发  
// 优先级分析:  
// - 占先优先级:EXTI2 是 00,EXTI3 是 10 → 00(EXTI2)更高  
// 执行逻辑:EXTI3_IRQn 被“压栈暂停”,先执行 EXTI2_IRQn,执行完再回到 EXTI3 继续  

8. USART 操作中断优化

8.1 USART 中断优化思路

借助 USART 相关的 USART_CR 控制寄存器 和 USART_SR 状态寄存器 ,来识别当前 USART 的工作状态,主要用于解决两类关键问题:

  • 数据发送完成问题:判断串口数据是否已全部发送出去,避免数据发送不完整或重复发送。
  • 数据接收完成问题:识别串口是否接收到完整数据,及时触发接收处理逻辑。

8.2 数据接收完成问题

(1)数据读取完成中断判断

当 USART 接收到数据后,需通过状态寄存器(如 USART_SR 的 RXNE 标志位,含义为 “接收数据寄存器非空” )判断数据接收状态。若该标志置位,说明有新数据接收完成,可触发中断进入数据读取流程,避免轮询等待,提升系统效率。

(2)CR 控制寄存器作用

USART_CR 控制寄存器 用于配置 USART 中断使能。例如,通过设置 USART_CR1 中的 RXNEIE 位(接收非空中断使能),当 USART_SR 的 RXNE 标志置位时,即可触发串口接收中断,进入中断处理函数读取数据,实现 “数据接收完成即响应” 的高效机制 。

一、外部中断与内部异常的区分

  1. 外部中断

    • 触发源来自芯片外部(如 GPIO 引脚电平变化,如按键输入),由外部中断控制器(EXTI)管理。
    • 示例:EXTI0_IRQHandler(PA0 引脚)、EXTI2_IRQHandler(PE2 引脚)等,需配置引脚映射、触发方式(上升沿 / 下降沿)、中断使能等。
  2. 内部异常

    • 触发源来自芯片内部(如内核事件、硬件错误),由 Cortex-M 内核直接管理,属于 “异常” 范畴。
    • 典型案例:RESET(复位,优先级 - 3)、NMI(不可屏蔽中断,优先级 - 2)、HardFault(硬件错误,优先级 - 1),优先级固定且不可修改。

二、中断优先级与 NVIC 配置

  1. 优先级规则

    • 优先级数值越小,等级越高;分为 “占先优先级” 和 “次级优先级”,占先优先级高的中断可抢占正在执行的低占先优先级中断。
  2. NVIC 关键函数

    • NVIC_SetPriorityGrouping:全局配置占先 / 次级优先级的位数分配(仅初始化时设置一次)。
    • NVIC_SetPriority:为单个中断源设置具体优先级(依赖全局分组规则)。

三、USART 相关寄存器与中断

  1. 核心寄存器

    • SR(状态寄存器):反映 USART 工作状态,如RXNE(接收寄存器非空,数据已接收)、IDLE(总线空闲,数据传输结束)。
    • CR(控制寄存器):配置 USART 功能,如RE(接收使能,基础开关)、RXNEIE(RXNE 中断使能,允许数据接收后触发中断)、IDLEIE(空闲中断使能,允许总线空闲时触发中断)。
  2. 中断触发逻辑

    • RXNE 中断RE=1(接收使能)+ RXNE=1(有数据)+ RXNEIE=1(中断使能)→ 触发中断,用于实时接收单个字节。
    • IDLE 中断RE=1 + IDLE=1(总线空闲)+ IDLEIE=1 → 触发中断,用于判断一帧数据传输结束。
  3. USART 中断处理函数

    • USART1_IRQHandler:同时响应 RXNE 中断(存储单个字节到缓冲区)和 IDLE 中断(标记数据接收完成并回显),需在函数中通过 SR 寄存器标志位区分具体中断源。

8.3 完整代码实现

// 1. USART1中断使能与配置函数
void USART1_Interrupt_Enable(void)
{
    // 使能两种中断:
    // - IDLEIE(位4):数据总线空闲中断使能
    // - RXNEIE(位5):接收数据寄存器非空中断使能
    USART1->CR1 |= (0x01 << 4) | (0x01 << 5);
    
    // 设置USART1中断优先级
    // 全局优先级分组为2的情况下,优先级值0001表示:
    // - 占先优先级:0(高2位)
    // - 次级优先级:1(低2位)
    NVIC_SetPriority(USART1_IRQn, 1);
    
    // 使能USART1对应的中断请求
    NVIC_EnableIRQ(USART1_IRQn);
}

// 2. 数据存储结构体定义
#define DATA_SIZE (256)  // 缓冲区大小定义

typedef struct usart1_data
{
    u8  data[DATA_SIZE];  // 接收数据缓冲区
    u8  flag;             // 数据处理标志位(1表示接收完成并回显)
    u16 count;            // 有效接收字节数
} USART1_Data;

extern USART1_Data usart1_val;  // 外部声明,供其他模块使用

// 3. USART1中断处理函数(响应RXNE和IDLE中断)
void USART1_IRQHandler(void)
{
    u32 val = 0;  // 临时变量,用于清除IDLE标志位
    
    // 若数据已处理完成(flag=1),清空缓冲区准备下次接收
    if (usart1_val.flag)
    {
        memset(&usart1_val, 0, sizeof(USART1_Data));
    }
    
    // 处理RXNE中断(接收到单个字节)
    if (USART1->SR & (0x01 << 5))  // 检测RXNE标志位(位5)
    {
        // 将接收寄存器数据存入缓冲区,计数+1
        usart1_val.data[usart1_val.count++] = USART1->DR;
        
        // 若缓冲区已满,回显数据并标记接收完成
        if (DATA_SIZE == usart1_val.count)
        {
            USART1_SendBuffer(usart1_val.data, usart1_val.count);
            usart1_val.flag = 1;
        }
    }
    
    // 处理IDLE中断(总线空闲,一帧数据接收完成)
    if (USART1->SR & (0x01 << 4))  // 检测IDLE标志位(位4)
    {
        // 标记数据接收完成
        usart1_val.flag = 1;
        
        // 按官方要求清除IDLE标志位:先读SR,再读DR
        val = USART1->SR;
        val = USART1->DR;
        
        // 回显接收到的完整数据到PC端
        USART1_SendBuffer(usart1_val.data, usart1_val.count);
    }
}

https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值