一、引言
在嵌入式系统的广袤世界里,中断就如同一位高效的调度员,发挥着举足轻重的作用。想象一下,一个嵌入式系统就像一个繁忙的工厂,CPU 如同工厂里的核心工人,负责执行各种任务。如果没有中断机制,CPU 就需要不断地检查各个设备的状态,就像工人要不断停下手中的工作去询问其他部门的工作进展,这无疑会浪费大量的时间和精力。而中断的出现,就像是给每个设备都配备了一个紧急呼叫按钮,当设备有紧急事情需要处理时,比如传感器检测到了异常数据、外部设备发送了重要信息,就可以通过中断信号通知 CPU。CPU 收到信号后,会暂停当前正在执行的任务,优先处理中断事件,处理完成后再返回原来的任务继续执行。这样一来,CPU 就可以更加高效地工作,系统的响应速度和实时性也得到了极大的提升。
今天我们要深入探讨的主角 ——STM32F103C8T6,它作为一款被广泛应用于各种嵌入式项目的微控制器,其外部中断功能更是为开发者们提供了强大的工具。无论是智能硬件开发中对外部信号的实时响应,还是工业控制领域里对设备状态的及时监测,STM32F103C8T6 的外部中断都能大显身手。接下来,就让我们一起揭开它神秘的面纱,深入了解其外部中断的原理、配置与应用。
二、中断基础概念
(一)中断定义
中断,简单来说,就是在主程序运行过程中,当出现特定的中断触发条件(即中断源)时,CPU 会暂停当前正在执行的程序,转而去执行中断服务程序。这个过程就好比你正在专心写作业,突然手机响了(中断源),你会暂停写作业(暂停主程序),去查看手机消息(执行中断服务程序),看完消息后再回来继续写作业(返回主程序继续执行) 。在 STM32F103C8T6 中,中断源可以是外部设备的信号变化,如按键按下、传感器数据更新,也可以是内部定时器溢出、串口数据接收等事件。中断的出现,使得系统能够及时响应各种异步事件,大大提高了系统的实时性和效率。
(二)中断优先级
当有多个中断源同时向 CPU 申请中断时,就需要有一个裁决机制来决定 CPU 先响应哪个中断。这就引入了中断优先级的概念。STM32F103C8T6 的中断优先级由 NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)进行管理。NVIC 为每个中断通道都分配了一个优先级,优先级数值越小,表示优先级越高。例如,假设同时有按键中断和定时器中断申请,而按键中断的优先级设置为 1,定时器中断的优先级设置为 2,那么 CPU 会优先响应按键中断,待按键中断处理完成后,才会去处理定时器中断。通过合理设置中断优先级,可以确保重要的中断事件能够得到及时处理,避免因低优先级中断的干扰而导致关键任务延迟。
(三)中断嵌套
中断嵌套是指在一个中断服务程序执行过程中,又有更高优先级的中断源申请中断,此时 CPU 会暂停当前正在执行的中断服务程序,转而去处理更高优先级的中断服务程序,待处理完成后,再返回到原来被中断的中断服务程序继续执行。这就像你在处理手机消息(执行中断服务程序)时,突然来了一个紧急电话(更高优先级的中断源),你会暂停处理消息(暂停当前中断服务程序),先去接电话(处理更高优先级的中断服务程序),接完电话后再回来继续处理消息(返回原来的中断服务程序继续执行)。在 STM32F103C8T6 中,要实现中断嵌套,需要正确配置 NVIC 的优先级分组,将抢占优先级设置为不同的值,使得高优先级的中断能够打断低优先级的中断。例如,将外部中断 0 的抢占优先级设置为 1,外部中断 1 的抢占优先级设置为 2,当外部中断 0 正在执行中断服务程序时,如果外部中断 1 产生中断请求,由于外部中断 0 的抢占优先级更高,所以不会被外部中断 1 打断;反之,如果外部中断 1 正在执行中断服务程序,外部中断 0 产生中断请求,那么外部中断 1 的中断服务程序会被暂停,优先执行外部中断 0 的中断服务程序。
(四)中断执行流程
中断请求:当某个中断源产生特定事件时,如按键按下、定时器溢出等,会向 CPU 发送中断请求信号。这个信号就像是一个紧急通知,告诉 CPU 有事情需要它立即处理。
中断响应:CPU 在每个指令周期结束时,会检查是否有中断请求。如果有,且中断允许位被设置(即中断未被屏蔽),CPU 会暂停当前正在执行的程序,将当前的程序计数器(PC)、程序状态寄存器(PSR)等重要寄存器的值压入堆栈进行保存,这一步就像是在离开之前记录好自己的位置和状态。然后,CPU 根据中断向量表找到对应的中断服务程序的入口地址,跳转到中断服务程序开始执行。中断向量表就像是一个索引表,记录了每个中断源对应的中断服务程序的入口地址。
中断服务:在中断服务程序中,首先会保护现场,即将其他需要用到的寄存器的值也压入堆栈保存,防止这些数据在中断处理过程中被破坏。然后,根据中断源的具体需求,执行相应的处理代码,比如读取传感器数据、处理按键事件等。处理完成后,再恢复现场,将之前压入堆栈的寄存器的值弹出,恢复到原来的状态。
中断返回:当中断服务程序执行完毕,最后会执行中断返回指令。这时候,CPU 会从堆栈中弹出之前保存的程序计数器(PC)和程序状态寄存器(PSR)的值,回到原来被中断的程序处继续执行,就像完成了紧急任务后,又回到原来的工作中。
三、STM32 中断系统
(一)STM32 中断资源
STM32F103C8T6 拥有丰富的中断资源,为嵌入式系统的多样化需求提供了有力支持。它包含多达 60 个可屏蔽中断通道 ,涵盖了众多关键的外设中断。
其中,EXTI(External Interrupt/Event Controller,外部中断 / 事件控制器)是其外部中断的核心。EXTI 可以监测指定 GPIO 口的电平信号,当指定 GPIO 口产生电平变化时,能立即向 NVIC 发出中断申请。例如,在智能安防系统中,我们可以将外部入侵检测传感器连接到 STM32F103C8T6 的某个 GPIO 口,并通过 EXTI 配置为上升沿触发中断。当传感器检测到有人入侵,输出电平发生变化时,就会触发中断,通知系统进行相应的处理,如启动报警装置、记录入侵时间等。
除了 EXTI,定时器中断(TIM)也是常用的中断资源之一。STM32F103C8T6 的定时器功能强大,有基本定时器(TIM6、TIM7)、通用定时器(TIM2、TIM3、TIM4、TIM5)和高级定时器(TIM1)。这些定时器可以用于精确计时、PWM 信号生成、事件触发等多种场景。以工业电机控制为例,通用定时器可以配置为 PWM 模式,生成不同占空比的 PWM 信号来控制电机的转速和转向。同时,通过定时器中断,可以实现周期性地读取电机的运行状态,如电流、温度等,以便及时调整控制策略,确保电机的稳定运行。
此外,ADC(Analog - to - Digital Converter,模数转换器)中断在数据采集领域发挥着重要作用。当 ADC 完成一次模数转换时,会触发中断,通知 CPU 读取转换后的数据。在环境监测项目中,通过 ADC 中断,可以实时采集温度、湿度、光照等传感器的模拟信号,并将其转换为数字信号进行处理和存储。
还有串口通信(USART)中断、SPI 通信中断、I2C 通信中断等,为 STM32F103C8T6 与外部设备的数据交互提供了高效的中断处理机制。在智能家居系统中,通过串口通信中断,STM32 可以与各类智能设备进行通信,接收设备的状态信息,发送控制指令,实现家居设备的智能化控制。
(二)NVIC(嵌套向量中断控制器)
1. 基本结构
NVIC 作为 Cortex - M3 内核的重要组成部分,就像是 CPU 的得力助手,负责管理中断的方方面面。它的结构设计精妙,与 CPU 和中断源之间有着紧密的连接 。从硬件层面来看,NVIC 一端连接着众多的中断源,这些中断源来自于 STM32F103C8T6 的各个外设,如前面提到的 EXTI、TIM、ADC 等;另一端则与 CPU 紧密相连,直接参与中断的裁决和处理流程。
当多个中断源同时向 CPU 申请中断时,NVIC 就发挥了关键的调度作用。它不会让 CPU 直接面对混乱的中断请求,而是像一个有序的调度员,先对中断请求进行收集和整理。NVIC 根据每个中断源预先设定的优先级,对这些中断请求进行排序,然后将优先级最高的中断请求传递给 CPU。这样一来,CPU 只需专注于处理 NVIC 筛选后的中断,大大提高了中断处理的效率和准确性。
例如,假设同时有定时器中断和串口中断申请,NVIC 会先查看它们的优先级设置。如果定时器中断的优先级高于串口中断,NVIC 就会将定时器中断请求传递给 CPU,让 CPU 优先处理定时器中断。在这个过程中,串口中断请求会被暂时挂起,等待 CPU 处理完当前的高优先级中断。
2. 优先级分组
在 NVIC 中,中断优先级分为抢占优先级和响应优先级,这两个概念对于理解中断的处理顺序至关重要。
抢占优先级:它决定了中断是否可以打断其他正在执行的中断,实现中断嵌套。简单来说,具有高抢占优先级的中断可以在低抢占优先级的中断处理过程中被响应。例如,在一个实时控制系统中,可能有一个紧急故障检测中断,其抢占优先级设置得很高。当系统正在处理普通的定时器中断时,如果紧急故障检测中断发生,由于其抢占优先级更高,就会立即打断定时器中断的处理,转而执行紧急故障检测中断服务程序。这样可以确保系统能够及时响应紧急事件,避免因处理普通中断而延误对关键问题的处理。
响应优先级:当两个中断源的抢占优先级相同时,响应优先级就发挥了作用。它决定了在抢占优先级相同的情况下,中断的响应顺序。也就是说,当两个抢占优先级相同的中断同时到达时,响应优先级高的中断会先被处理。例如,假设有两个串口中断,它们的抢占优先级都设置为相同的值,但其中一个串口中断的响应优先级设置为 1,另一个设置为 2。那么当这两个串口中断同时发生时,响应优先级为 1 的串口中断会先被处理,处理完成后才会处理响应优先级为 2 的串口中断。
STM32F103C8T6 通过对优先级寄存器的 4 位进行分组设置,来确定抢占优先级和响应优先级的具体位数。总共有 5 种分组模式:
第 0 组:所有 4 位用于指定响应优先级,此时没有抢占优先级的区分,所有中断按照响应优先级的高低顺序处理。
第 1 组:最高 1 位用于指定抢占优先级,最低 3 位用于指定响应优先级。这样可以有 2 个抢占优先级级别(0 和 1),8 个响应优先级级别(0 - 7)。
第 2 组:最高 2 位用于指定抢占优先级,最低 2 位用于指定响应优先级。有 4 个抢占优先级级别(0 - 3),4 个响应优先级级别(0 - 3)。
第 3 组:最高 3 位用于指定抢占优先级,最低 1 位用于指定响应优先级。有 8 个抢占优先级级别(0 - 7),2 个响应优先级级别(0 和 1)。
第 4 组:所有 4 位用于指定抢占优先级,没有响应优先级的区分,中断按照抢占优先级的高低顺序处理。
在实际应用中,我们可以根据项目的需求,使用固件库函数NVIC_PriorityGroupConfig()
来选择合适的优先级分组模式。例如,如果我们的项目中需要较多的抢占优先级级别来处理不同紧急程度的中断,就可以选择第 3 组或第 4 组;如果项目中对响应优先级的区分要求较高,而抢占优先级的需求相对简单,就可以选择第 1 组或第 2 组。通过合理配置优先级分组和每个中断源的优先级,我们能够优化系统的中断处理机制,提高系统的性能和稳定性。
四、EXTI(外部中断)
(一)EXTI 介绍
工作原理:EXTI(External Interrupt/Event Controller)作为 STM32F103C8T6 中负责外部中断的关键部件,其工作原理精妙而高效。它就像一个时刻保持警惕的信号监测员,持续监测着指定 GPIO 口的电平信号 。当指定 GPIO 口的电平发生变化时,比如从低电平变为高电平(上升沿),或者从高电平变为低电平(下降沿),EXTI 会迅速捕捉到这个变化,并立即向 NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)发出中断申请。NVIC 在收到申请后,会根据预先设定的中断优先级进行裁决,决定是否中断当前正在运行的 CPU 主程序。如果裁决结果是允许中断,那么 CPU 就会暂停主程序的执行,转而执行 EXTI 对应的中断服务程序。在中断服务程序中,开发者可以编写相应的代码来处理这个中断事件,比如读取传感器数据、控制外部设备等。处理完成后,CPU 会返回原来被中断的主程序位置,继续执行主程序。
触发方式:
上升沿触发:当 GPIO 口的电平从低电平跳变为高电平时,触发中断。例如,在一个智能照明系统中,我们可以将人体红外传感器连接到 STM32F103C8T6 的某个 GPIO 口,并配置为上升沿触发中断。当人体红外传感器检测到有人靠近时,输出电平从低电平变为高电平,触发中断,通知系统打开灯光。
下降沿触发:当 GPIO 口的电平从高电平跳变为低电平时,触发中断。比如在一个电子门锁系统中,按键的按下动作会使 GPIO 口的电平从高电平变为低电平,通过配置下降沿触发中断,系统可以及时响应按键操作,进行开锁或其他相关处理。
双边沿触发:无论是上升沿还是下降沿,只要 GPIO 口的电平发生跳变,就会触发中断。在一些需要精确检测信号变化的场景中,如测量旋转编码器的脉冲信号,双边沿触发可以提高检测的精度和效率。因为旋转编码器在旋转过程中,其输出信号会在上升沿和下降沿都携带重要的位置信息,通过双边沿触发中断,系统能够更全面地获取这些信息,从而准确计算出旋转的角度和速度。
软件触发:即使 GPIO 口的电平没有发生实际变化,也可以通过编写代码来触发中断。这种触发方式在一些特殊的应用场景中非常有用,比如在系统初始化阶段,需要模拟一个外部中断事件来进行一些初始化操作;或者在调试过程中,方便开发者手动触发中断,检查中断服务程序的正确性。
支持 GPIO 口与通道数:STM32F103C8T6 的 EXTI 支持所有的 GPIO 口触发中断,这为开发者提供了极大的灵活性。然而,需要注意的是,相同编号的 Pin(引脚)不能同时触发中断。例如,GPIOA 的 Pin0 和 GPIOB 的 Pin0 虽然是不同端口的相同编号引脚,但它们不能同时被配置为触发中断,因为它们共用同一个中断线。EXTI 总共支持 16 个 GPIO_Pin 的中断触发,分别对应 PA0 - PA15、PB0 - PB15 等端口的引脚。此外,EXTI 还支持一些特殊的中断源,如 PVD(Power Voltage Detector,电源电压检测器)输出、RTC(Real - Time Clock,实时时钟)闹钟、USB 唤醒、以太网唤醒等,这些特殊中断源进一步丰富了 EXTI 的应用场景。以 RTC 闹钟为例,在一个定时提醒的应用中,通过配置 RTC 闹钟触发 EXTI 中断,系统可以在设定的时间点触发中断,执行相应的提醒操作,如发出声音、显示提示信息等。
触发响应方式:
中断响应:这是最常见的触发响应方式。当 EXTI 监测到指定 GPIO 口的电平变化并触发中断后,CPU 会暂停当前正在执行的主程序,保存当前的程序状态(如寄存器的值、程序计数器的值等),然后跳转到对应的中断服务程序执行。在中断服务程序中,完成对中断事件的处理后,再恢复之前保存的程序状态,返回主程序继续执行。中断响应方式适用于需要及时处理的异步事件,比如按键按下、传感器数据更新等,确保系统能够对这些事件做出快速响应。
事件响应:与中断响应不同,事件响应不会触发 CPU 执行中断服务程序,而是直接触发其他外设的操作,属于外设之间的联合工作。在事件响应过程中,当 EXTI 检测到满足条件的信号变化时,会通过硬件电路直接产生一个脉冲信号,这个脉冲信号可以被其他外设(如定时器 TIM、模拟数字转换器 ADC 等)接收并利用。例如,在一个数据采集系统中,当外部传感器产生一个触发信号时,EXTI 通过事件响应方式将这个信号直接传递给 ADC,触发 ADC 开始进行数据转换,而不需要 CPU 的干预。这种方式可以减少 CPU 的负担,提高系统的整体运行效率,尤其适用于那些对实时性要求较高、但不需要 CPU 进行复杂处理的场景。
(二)EXTI 基本结构与 AFIO 复用 IO 接口
基本结构:EXTI 的内部结构设计巧妙,各个部分协同工作,实现了高效的外部中断处理。
边沿检测电路:这是 EXTI 的信号感知部分,它就像一个敏锐的探测器,根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。当输入线的信号发生边沿跳变时,边沿检测电路会根据寄存器的设置来判断是否输出有效信号。如果设置为上升沿触发,那么只有当信号从低电平变为高电平时,边沿检测电路才会输出有效信号 1 给后续电路;如果设置为下降沿触发,则只有信号从高电平变为低电平时输出有效信号;若设置为双边沿触发,那么上升沿和下降沿都会使边沿检测电路输出有效信号。
或门电路:或门电路的作用是整合边沿检测电路和软件中断事件寄存器(EXTI_SWIER)的信号。它的一个输入来自边沿检测电路,另一个输入来自 EXTI_SWIER。由于或门的特性是只要有一个输入为 1,输出就为 1,所以当边沿检测电路检测到边沿跳变输出有效信号,或者通过软件设置 EXTI_SWIER 的相应位使其输出有效信号时,或门电路就会输出 1 给后面的与门电路。这就为中断的触发提供了两种途径,一种是硬件的边沿触发,另一种是软件的触发,增加了中断触发的灵活性。
与门电路:与门电路在中断控制中起到了关键的开关作用。它有两个输入,一个来自或门电路,另一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求两个输入都为 1 时才输出 1,这就意味着只有当 EXTI_IMR 设置为 1(即允许中断),并且或门电路输出有效信号 1 时,与门电路才会输出 1。如果 EXTI_IMR 设置为 0(禁止中断),那么无论或门电路的输出如何,与门电路的输出都为 0,从而不会产生中断。这种设计使得开发者可以通过控制 EXTI_IMR 来灵活地开启或关闭中断,方便对中断进行管理。
挂起寄存器(EXTI_PR):上与门电路输出的信号会被保存到挂起寄存器(EXTI_PR)内。当确定上与门电路输出为 1 时,就会把 EXTI_PR 对应位置 1。这个位置 1 表示有中断事件发生且尚未被处理,CPU 可以通过读取 EXTI_PR 寄存器来判断是否有中断等待处理。在中断处理完成后,需要将 EXTI_PR 中相应的位清零,以表示该中断已经被处理。
AFIO 复用 IO 接口:AFIO(Alternate - Function I/O,复用功能 I/O)在 STM32F103C8T6 中扮演着重要的角色,它主要用于引脚复用功能的选择和重定义 。在 STM32 中,AFIO 主要完成两个关键任务:
复用功能引脚重映射:STM32 的一些外设功能(如 SPI、USART、I2C 等)需要使用特定的 GPIO 引脚作为复用功能引脚。通过 AFIO 的复用功能引脚重映射,开发者可以将这些外设的功能映射到不同的 GPIO 引脚上,增加了引脚使用的灵活性。例如,在一个项目中,原本 SPI 接口使用的是 PA5、PA6、PA7 引脚,但由于这些引脚在其他功能中也有重要用途,通过 AFIO 的重映射功能,可以将 SPI 接口的功能映射到 PB3、PB4、PB5 等引脚上,使得硬件设计更加灵活,避免了引脚资源的冲突。
中断引脚选择:AFIO 在 EXTI 的中断引脚选择中起着不可或缺的作用。前面提到,所有 GPIO 口都可以触发 EXTI 中断,但相同 Pin 不能同时触发中断。AFIO 负责将不同 GPIO 口的中断信号进行选择和映射,确保每个中断信号能够正确地传输到 EXTI 控制器。例如,当我们配置 PA0 引脚触发外部中断时,AFIO 会将 PA0 的中断信号连接到 EXTI 对应的中断线,使得 EXTI 能够监测到 PA0 的电平变化并触发中断。这种中断引脚选择功能使得 EXTI 能够有效地管理多个 GPIO 口的中断请求,提高了系统的中断处理能力。
(三)EXTI 框图解析
结合 EXTI 的框图,我们可以更加直观地理解其工作过程。
输入线:EXTI 控制器拥有 19 个中断 / 事件输入线,这些输入线是 EXTI 与外部世界沟通的桥梁。它们可以通过寄存器设置为任意一个 GPIO 口,也可以连接一些外设的事件输出。这些输入线负责接收外部的信号变化,无论是 GPIO 口的电平跳变,还是外设产生的特定事件信号,都通过这些输入线进入 EXTI 控制器。
边沿检测:输入线的信号首先进入边沿检测电路。如前文所述,边沿检测电路根据 EXTI_RTSR 和 EXTI_FTSR 的设置,对输入信号进行边沿检测。假设我们将某个输入线配置为上升沿触发,当该输入线的信号从低电平变为高电平时,边沿检测电路会捕捉到这个上升沿,输出有效信号 1 给或门电路;如果信号没有发生符合条件的边沿跳变,边沿检测电路则输出无效信号 0。
中断屏蔽:或门电路的输出信号会与中断屏蔽寄存器(EXTI_IMR)的对应位进行与运算。如果 EXTI_IMR 中对应位设置为 1,那么或门电路的输出信号能够顺利通过与门电路,进而触发中断;如果 EXTI_IMR 中对应位设置为 0,无论或门电路输出是 1 还是 0,与门电路的输出都为 0,即不会触发中断。这就好比一个开关,EXTI_IMR 控制着中断是否能够被触发。
挂起寄存器:当中断成功触发,上与门电路输出的信号会被保存到挂起寄存器(EXTI_PR)中。EXTI_PR 中对应位被置 1,表示有中断事件发生且处于挂起状态,等待 CPU 处理。CPU 在适当的时候读取 EXTI_PR 寄存器,判断是否有中断等待处理,并根据中断服务程序的设置进行相应的处理。在中断处理完成后,需要通过软件将 EXTI_PR 中对应的挂起位清零,以表示该中断已经被处理完毕,避免重复处理。
五、STM32F103C8T6 外部中断配置步骤
(一)初始化 GPIO 引脚
使能 GPIO 时钟:在使用 GPIO 引脚之前,首先要使能对应 GPIO 端口的时钟。以使用 GPIOA 端口的 PA0 引脚作为外部中断输入为例,通过 RCC(Reset and Clock Control,复位和时钟控制)寄存器来使能 GPIOA 的时钟。在标准库中,可以使用函数RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
来实现,其中RCC_APB2Periph_GPIOA
表示 GPIOA 端口的时钟,ENABLE
表示使能该时钟。
配置 GPIO 模式:将用于外部中断的 GPIO 引脚配置为输入模式。GPIO_InitTypeDef 结构体用于配置 GPIO 的各种参数,如引脚号、模式、速度等。对于 PA0 引脚,设置其模式为浮空输入或上拉 / 下拉输入。若采用上拉输入模式,代码如下:
GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度GPIO_Init(GPIOA, &GPIO_InitStructure);
这里GPIO_Pin_0
指定了 PA0 引脚,GPIO_Mode_IPU
表示上拉输入模式,GPIO_Speed_50MHz
设置了引脚的输出速度为 50MHz 。如果选择浮空输入模式,则将GPIO_Mode
设置为GPIO_Mode_IN_FLOATING
;若为下拉输入模式,设置为GPIO_Mode_IPD
。不同的输入模式适用于不同的应用场景,例如,当外部信号源能够可靠地提供高电平和低电平时,可以使用浮空输入;而上拉输入适用于外部信号源默认处于低电平,需要通过上拉电阻使其在无信号输入时保持高电平的情况;下拉输入则相反,适用于外部信号源默认处于高电平,需要通过下拉电阻使其在无信号输入时保持低电平的情况。
(二)配置外部中断线路
使能 AFIO 时钟:AFIO(Alternate - Function I/O,复用功能 I/O)用于选择中断引脚,因此需要先使能 AFIO 的时钟。使用函数RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
来使能 AFIO 时钟。
选择中断引脚:通过GPIO_EXTILineConfig()
函数将 GPIO 引脚映射到对应的外部中断线上。例如,将 PA0 引脚映射到 EXTI Line0 中断线,代码为GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
,其中GPIO_PortSourceGPIOA
表示 GPIO 端口源为 GPIOA,GPIO_PinSource0
表示引脚源为 PA0。
配置 EXTI 参数:使用 EXTI_InitTypeDef 结构体来配置外部中断的触发条件、模式和使能状态。
EXTI_InitTypeDef EXTI_InitStructure;EXTI_InitStructure.EXTI_Line = EXTI_Line0; // 选择EXTI Line0EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 配置为中断模式EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能中断线EXTI_Init(&EXTI_InitStructure);
这里EXTI_Line0
指定了中断线为 Line0,EXTI_Mode_Interrupt
表示配置为中断模式,EXTI_Trigger_Rising
表示上升沿触发中断,如果需要下降沿触发,将其设置为EXTI_Trigger_Falling
;双边沿触发则设置为EXTI_Trigger_Rising_Falling
。EXTI_LineCmd = ENABLE
表示使能该中断线,只有使能后,中断线才能正常工作。
- 配置中断优先级:使用 NVIC_InitTypeDef 结构体来配置中断优先级。首先要设置优先级分组,确定抢占优先级和响应优先级的位数。例如,设置为第 2 组,即 2 位抢占优先级和 2 位响应优先级,代码为
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
。然后配置 EXTI 中断的优先级,假设配置 EXTI0 中断的抢占优先级为 0,响应优先级为 0,代码如下:
NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 选择EXTI0中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级为0NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级为0NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道NVIC_Init(&NVIC_InitStructure);
NVIC_IRQChannel = EXTI0_IRQn
指定了中断通道为 EXTI0,NVIC_IRQChannelPreemptionPriority
设置抢占优先级,NVIC_IRQChannelSubPriority
设置响应优先级,NVIC_IRQChannelCmd = ENABLE
使能该中断通道。通过合理设置优先级,可以确保在多个中断同时发生时,系统能够按照预定的顺序进行处理。
(三)编写中断服务程序(ISR)
当中断触发时,CPU 会自动跳转到对应的中断服务程序执行。中断服务程序的名称是固定的,例如,EXTI0 中断的服务程序名为EXTI0_IRQHandler
。在中断服务程序中,首先要检查中断标志位,以确保是当前中断线触发的中断。例如:
void EXTI0_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line0)!= RESET) // 检查EXTI Line0的中断标志位{// 在这里编写中断处理代码,例如读取传感器数据、控制设备等// 假设连接了一个温度传感器,读取温度数据并进行处理uint16_t temperature = ReadTemperatureSensor(); // 读取温度传感器数据的函数if (temperature > 30) // 假设温度阈值为30度{// 控制风扇启动,进行散热ControlFan(ENABLE);}else{// 控制风扇停止ControlFan(DISABLE);}// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}}
在上述代码中,EXTI_GetITStatus(EXTI_Line0)
用于获取 EXTI Line0 的中断标志位状态,如果不为RESET
,则表示该中断线触发了中断。在中断处理代码部分,可以根据实际需求编写相应的功能代码,如读取传感器数据、控制设备的开关、执行特定的算法等。这里假设连接了一个温度传感器,通过自定义函数ReadTemperatureSensor()
读取温度数据,并根据温度值控制风扇的启动和停止。处理完成后,使用EXTI_ClearITPendingBit(EXTI_Line0)
清除中断标志位,以便下一次中断能够正常触发。
(四)清除中断标志位
在中断服务程序执行完毕后,清除中断标志位是非常重要的一步。中断标志位用于表示中断事件的发生,如果不清除,当中断处理完成后,该标志位仍然会保持置位状态,导致 CPU 认为中断事件仍然存在,可能会再次进入中断服务程序,形成死循环,或者影响后续正常中断的触发。在 STM32F103C8T6 中,清除外部中断标志位可以使用EXTI_ClearITPendingBit()
函数 。例如,对于 EXTI Line0 中断,使用EXTI_ClearITPendingBit(EXTI_Line0);
来清除其对应的中断标志位。该函数会将 EXTI 挂起寄存器(EXTI_PR)中对应中断线的标志位清零,从而使系统能够正确识别下一次中断事件的发生。除了在中断服务程序中手动清除中断标志位外,在一些特殊情况下,如系统初始化阶段,也可能需要清除中断标志位,以确保系统的正常运行。
六、代码示例与实践
(一)完整代码展示
下面是一个使用 STM32F103C8T6 的 PA0 引脚作为外部中断输入,控制 LED 状态的完整代码示例:
#include "stm32f10x.h"// 函数声明void GPIO_Configuration(void);void EXTI_Configuration(void);void NVIC_Configuration(void);void Delay(uint32_t nCount);// 主函数int main(void){// 配置GPIOGPIO_Configuration();// 配置外部中断EXTI_Configuration();// 配置中断优先级NVIC_Configuration();while (1){// 主循环可以执行其他任务}}// 配置GPIOvoid GPIO_Configuration(void){GPIO_InitTypeDef GPIO_InitStructure;// 使能GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置PA0为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置PA1为推挽输出,用于控制LEDGPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);}// 配置外部中断void EXTI_Configuration(void){EXTI_InitTypeDef EXTI_InitStructure;// 使能AFIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 将PA0映射到EXTI Line0GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);// 配置EXTI Line0为上升沿触发中断EXTI_InitStructure.EXTI_Line = EXTI_Line0;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);}// 配置中断优先级void NVIC_Configuration(void){NVIC_InitTypeDef NVIC_InitStructure;// 设置优先级分组为第2组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 配置EXTI0中断通道NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}// 简单的延时函数void Delay(uint32_t nCount){for (; nCount!= 0; nCount--);}// 外部中断0服务函数void EXTI0_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line0)!= RESET){// 消抖处理Delay(0xFFFFF);// 切换LED状态GPIO_SetBits(GPIOA, GPIO_Pin_1);Delay(0xFFFFF);GPIO_ResetBits(GPIOA, GPIO_Pin_1);// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}}
(二)代码逐行解析
头文件包含:
#include "stm32f10x.h"
包含了 STM32F10x 系列微控制器的标准库头文件,该头文件中定义了所有的寄存器、结构体、宏等,是使用标准库函数的基础。
函数声明:
void GPIO_Configuration(void);void EXTI_Configuration(void);void NVIC_Configuration(void);void Delay(uint32_t nCount);
声明了后续要使用的函数,包括 GPIO 配置函数、外部中断配置函数、中断优先级配置函数和一个简单的延时函数。
主函数:
int main(void){// 配置GPIOGPIO_Configuration();// 配置外部中断EXTI_Configuration();// 配置中断优先级NVIC_Configuration();while (1){// 主循环可以执行其他任务}}
首先调用GPIO_Configuration
函数配置 GPIO 引脚。
接着调用EXTI_Configuration
函数配置外部中断。
然后调用NVIC_Configuration
函数配置中断优先级。
最后进入一个无限循环while(1)
,在这个循环中可以执行其他任务,由于本示例主要关注外部中断,所以循环中暂时为空。
GPIO 配置函数:
void GPIO_Configuration(void){GPIO_InitTypeDef GPIO_InitStructure;// 使能GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置PA0为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置PA1为推挽输出,用于控制LEDGPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);}
定义了一个GPIO_InitTypeDef
类型的结构体变量GPIO_InitStructure
,用于配置 GPIO 的参数。
使用RCC_APB2PeriphClockCmd
函数使能 GPIOA 的时钟,只有使能时钟后,才能对 GPIOA 进行配置。
配置 PA0 为浮空输入模式:
设置GPIO_InitStructure.GPIO_Pin
为GPIO_Pin_0
,指定配置 PA0 引脚。
设置GPIO_InitStructure.GPIO_Mode
为GPIO_Mode_IN_FLOATING
,表示浮空输入模式。
调用GPIO_Init
函数,将配置应用到 GPIOA 上。
配置 PA1 为推挽输出模式,用于控制 LED:
设置GPIO_InitStructure.GPIO_Pin
为GPIO_Pin_1
,指定配置 PA1 引脚。
设置GPIO_InitStructure.GPIO_Mode
为GPIO_Mode_Out_PP
,表示推挽输出模式。
设置GPIO_InitStructure.GPIO_Speed
为GPIO_Speed_50MHz
,设置引脚速度为 50MHz。
再次调用GPIO_Init
函数,将配置应用到 GPIOA 上。
外部中断配置函数:
void EXTI_Configuration(void){EXTI_InitTypeDef EXTI_InitStructure;// 使能AFIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 将PA0映射到EXTI Line0GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);// 配置EXTI Line0为上升沿触发中断EXTI_InitStructure.EXTI_Line = EXTI_Line0;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);}
定义了一个EXTI_InitTypeDef
类型的结构体变量EXTI_InitStructure
,用于配置外部中断的参数。
使用RCC_APB2PeriphClockCmd
函数使能 AFIO 的时钟,AFIO 用于选择中断引脚,所以需要先使能其时钟。
使用GPIO_EXTILineConfig
函数将 PA0 引脚映射到 EXTI Line0 中断线上。
配置 EXTI Line0 为上升沿触发中断:
设置EXTI_InitStructure.EXTI_Line
为EXTI_Line0
,指定配置 EXTI Line0 中断线。
设置EXTI_InitStructure.EXTI_Mode
为EXTI_Mode_Interrupt
,表示配置为中断模式。
设置EXTI_InitStructure.EXTI_Trigger
为EXTI_Trigger_Rising
,表示上升沿触发中断。
设置EXTI_InitStructure.EXTI_LineCmd
为ENABLE
,使能该中断线。
调用EXTI_Init
函数,将配置应用到外部中断控制器上。
中断优先级配置函数:
void NVIC_Configuration(void){NVIC_InitTypeDef NVIC_InitStructure;// 设置优先级分组为第2组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 配置EXTI0中断通道NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}
定义了一个NVIC_InitTypeDef
类型的结构体变量NVIC_InitStructure
,用于配置中断优先级的参数。
使用NVIC_PriorityGroupConfig
函数设置优先级分组为第 2 组,即 2 位抢占优先级和 2 位响应优先级。
配置 EXTI0 中断通道:
设置NVIC_InitStructure.NVIC_IRQChannel
为EXTI0_IRQn
,指定配置 EXTI0 中断通道。
设置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
为 0,表示抢占优先级为 0。
设置NVIC_InitStructure.NVIC_IRQChannelSubPriority
为 0,表示响应优先级为 0。
设置NVIC_InitStructure.NVIC_IRQChannelCmd
为ENABLE
,使能该中断通道。
调用NVIC_Init
函数,将配置应用到 NVIC 上。
延时函数:
void Delay(uint32_t nCount){for (; nCount!= 0; nCount--);}
这是一个简单的延时函数,通过循环nCount
次来实现延时。在实际应用中,这种简单的延时函数精度较低,对于高精度的延时需求,可以使用定时器来实现。
外部中断 0 服务函数:
void EXTI0_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line0)!= RESET){// 消抖处理Delay(0xFFFFF);// 切换LED状态GPIO_SetBits(GPIOA, GPIO_Pin_1);Delay(0xFFFFF);GPIO_ResetBits(GPIOA, GPIO_Pin_1);// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}}
当 EXTI0 中断触发时,CPU 会自动跳转到这个函数执行。
使用EXTI_GetITStatus
函数检查 EXTI Line0 的中断标志位,如果不为RESET
,表示该中断线触发了中断。
进行消抖处理,通过调用Delay
函数延时一段时间,以消除按键抖动可能产生的误触发。
切换 LED 状态,先使用GPIO_SetBits
函数将 PA1 引脚置高,点亮 LED,然后调用Delay
函数延时,再使用GPIO_ResetBits
函数将 PA1 引脚置低,熄灭 LED。
最后使用EXTI_ClearITPendingBit
函数清除 EXTI Line0 的中断标志位,以便下一次中断能够正常触发。
(三)实践操作与调试技巧
硬件连接:
按键连接:将按键的一端连接到 STM32F103C8T6 的 PA0 引脚,另一端接地或接电源(根据按键的类型和配置的输入模式决定)。如果 PA0 配置为浮空输入,按键按下时会改变 PA0 的电平状态,从而触发外部中断。为了防止按键抖动对中断触发的影响,可以在按键两端并联一个 0.1uF 的电容进行硬件消抖。
LED 连接:将 LED 的阳极通过一个限流电阻(如 220Ω)连接到电源(如 3.3V),阴极连接到 STM32F103C8T6 的 PA1 引脚。这样,通过控制 PA1 引脚的电平,就可以控制 LED 的亮灭。在选择限流电阻时,需要根据 LED 的工作电流和电源电压进行计算,以确保 LED 能够正常工作且不会因为电流过大而损坏。
调试工具使用:
Keil MDK:在 Keil MDK 开发环境中,可以使用断点调试功能来逐步执行代码,观察变量的值和程序的执行流程。在代码中设置断点后,点击调试按钮进入调试模式,当程序执行到断点处时会暂停。此时,可以查看寄存器的值,如 GPIO 寄存器、EXTI 寄存器、NVIC 寄存器等,以检查硬件配置是否正确。例如,在外部中断服务函数中设置断点,当外部中断触发时,程序会停在断点处,此时可以查看 EXTI_PR 寄存器的值,确认中断标志位是否被正确置位,以及查看 GPIOA 寄存器的值,确认 LED 的控制引脚电平是否正确切换。
逻辑分析仪:逻辑分析仪可以用于监测 GPIO 引脚的电平变化,帮助分析外部中断的触发情况和按键的动作。将逻辑分析仪的探头连接到 PA0 和 PA1 引脚,在运行程序时,通过逻辑分析仪可以直观地看到 PA0 引脚的电平变化,判断按键是否正常触发中断,以及 PA1 引脚的电平变化,检查 LED 的控制是否正确。如果发现 PA0 引脚的电平变化异常,可能是按键硬件连接有问题,或者是 GPIO 配置不正确;如果 PA1 引脚的电平变化不符合预期,可能是中断服务程序中的 LED 控制代码有误。
常见问题及解决方法:
中断无法触发:
原因:可能是 GPIO 配置错误,如没有正确设置为输入模式,或者 AFIO 配置错误,导致中断引脚映射不正确;也可能是 EXTI 配置错误,如触发方式设置错误,或者中断线未使能;还可能是 NVIC 配置错误,如中断通道未使能,或者优先级设置不当。
解决方法:仔细检查 GPIO、AFIO、EXTI 和 NVIC 的配置代码,确保每一步配置都正确无误。可以使用调试工具查看相关寄存器的值,确认配置是否生效。例如,使用 Keil MDK 的寄存器查看窗口,查看 GPIOx_CRL、AFIO_EXTICR、EXTI_IMR、NVIC_ISER 等寄存器的值,检查引脚模式、中断映射、中断使能等设置是否正确。
中断误触发:
原因:可能是按键抖动导致的,按键在按下和松开的过程中,由于机械触点的弹跳,会产生多次电平变化,从而触发多次中断;也可能是外部干扰,如周围的电磁干扰,导致 GPIO 引脚的电平出现异常波动,触发中断。
解决方法:对于按键抖动问题,可以采用软件消抖或硬件消抖的方法。软件消抖如在中断服务程序中添加延时函数,等待按键稳定后再进行处理;硬件消抖如在按键两端并联电容。对于外部干扰问题,可以采取屏蔽措施,如使用屏蔽线连接按键和 STM32,或者在 GPIO 引脚上添加滤波电容,以减少干扰对电平的影响。
中断优先级混乱:
原因:可能是 NVIC 优先级分组设置错误,或者中断源的抢占优先级和响应优先级设置不合理,导致中断处理顺序不符合预期。
解决方法:重新检查 NVIC 优先级分组和每个中断源的优先级设置,根据项目的需求合理分配优先级。可以参考 NVIC 的优先级分组规则和中断处理机制,确保高优先级的中断能够及时响应,并且不会被低优先级的中断干扰。在设置优先级时,可以使用一些工具或方法来辅助理解和验证,如绘制中断优先级图表,直观地展示各个中断源的优先级关系。
七、应用案例
(一)按键控制 LED 灯
在许多嵌入式项目中,按键控制 LED 灯是一个常见且基础的应用场景,它充分展示了外部中断在实时响应外部事件方面的优势。利用 STM32F103C8T6 的外部中断实现按键控制 LED 灯的功能,具体步骤如下:
硬件连接:将按键的一端连接到 STM32F103C8T6 的某个 GPIO 引脚,如 PA0,另一端接地或接电源(根据按键类型和 GPIO 输入模式而定)。这里假设按键为低电平有效,即按下按键时,PA0 引脚电平被拉低。同时,将 LED 的阳极通过限流电阻连接到电源,阴极连接到另一个 GPIO 引脚,如 PA1。
软件配置:
初始化 GPIO:使能 GPIOA 时钟,将 PA0 配置为浮空输入或上拉 / 下拉输入模式,以检测按键状态;将 PA1 配置为推挽输出模式,用于控制 LED 的亮灭。例如:
GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置PA0为浮空输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置PA1为推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);
配置外部中断:使能 AFIO 时钟,将 PA0 映射到对应的外部中断线(如 EXTI Line0),并配置 EXTI 为下降沿触发中断(因为按键按下时 PA0 电平从高到低变化)。同时,配置 NVIC 中断优先级,确保外部中断能够被及时响应。示例代码如下:
EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);EXTI_InitStructure.EXTI_Line = EXTI_Line0;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
编写中断服务程序:当中断触发时,在中断服务程序中切换 LED 的状态。为了消除按键抖动的影响,可以在中断服务程序中添加消抖处理,如延时一段时间后再次检测按键状态。代码如下:
void EXTI0_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line0)!= RESET){// 消抖处理Delay(0xFFFFF);if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == Bit_RESET){// 切换LED状态GPIO_SetBits(GPIOA, GPIO_Pin_1);Delay(0xFFFFF);GPIO_ResetBits(GPIOA, GPIO_Pin_1);}// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}}
在上述代码中,Delay
函数用于简单的延时消抖,实际应用中可根据按键抖动情况调整延时时间。通过这种方式,当按键按下时,外部中断被触发,中断服务程序执行,实现 LED 状态的切换,从而达到按键控制 LED 灯的目的。
(二)传感器数据采集
在工业控制、智能家居、环境监测等众多领域,实时采集传感器数据是嵌入式系统的重要任务之一。STM32F103C8T6 的外部中断功能为高效采集传感器数据提供了有力支持,以下以红外传感器和旋转编码器为例进行说明。
1. 红外传感器数据采集
红外传感器常用于检测物体的存在、运动状态等。以对射式红外传感器为例,当有物体遮挡红外光线时,传感器的输出电平会发生变化,我们可以利用这个电平变化触发 STM32F103C8T6 的外部中断,从而实现对物体的检测和计数。
硬件连接上,将红外传感器的输出引脚连接到 STM32F103C8T6 的一个 GPIO 引脚,如 PB14。软件配置方面,与按键控制 LED 灯类似,先初始化 GPIO 和外部中断。假设红外传感器为低电平有效,即有物体遮挡时输出低电平,配置如下:
// 初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置外部中断EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);EXTI_InitStructure.EXTI_Line = EXTI_Line14;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
在中断服务程序中,对检测到的遮挡事件进行计数或其他处理:
volatile int count = 0;void EXTI15_10_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line14)!= RESET){// 消抖处理Delay(10);if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == Bit_RESET){count++;}// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line14);}}
通过上述配置,当红外传感器检测到物体遮挡时,会触发外部中断,在中断服务程序中对计数变量count
进行累加,从而实现对物体通过次数的统计。
2. 旋转编码器数据采集
旋转编码器是一种用于测量旋转角度、速度和方向的传感器,常用于电机控制、机器人运动控制等领域。旋转编码器通常有 A、B 两相输出,通过检测 A、B 相脉冲的相位差可以判断旋转方向,通过计数脉冲个数可以计算旋转角度或速度。
硬件连接时,将旋转编码器的 A 相和 B 相分别连接到 STM32F103C8T6 的两个 GPIO 引脚,如 PB0 和 PB1。软件配置如下:
// 初始化GPIOGPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置外部中断EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; // 双边沿触发EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
在中断服务程序中,根据 A、B 相的电平状态判断旋转方向,并进行计数:
volatile int encoder_count = 0;void EXTI0_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line0)!= RESET){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == Bit_RESET){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET){encoder_count--; // 反转计数}}// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line0);}}void EXTI1_IRQHandler(void){if (EXTI_GetITStatus(EXTI_Line1)!= RESET){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET){if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == Bit_RESET){encoder_count++; // 正转计数}}// 清除中断标志位EXTI_ClearITPendingBit(EXTI_Line1);}}
通过上述配置,当旋转编码器旋转时,A、B 相的脉冲变化会触发外部中断,在中断服务程序中根据 A、B 相的电平关系判断旋转方向,并对encoder_count
进行相应的累加或递减,从而实现对旋转编码器数据的采集和处理,为后续的电机控制、运动测量等应用提供数据支持。
八、总结与展望
(一)总结外部中断要点
在嵌入式系统的广阔天地中,STM32F103C8T6 的外部中断功能犹如一颗璀璨的明星,闪耀着独特的光芒。通过对其深入探究,我们清晰地认识到外部中断在系统中的关键作用。
从原理层面来看,中断作为一种特殊的机制,能够在主程序运行的过程中,当特定的中断源出现时,果断暂停主程序的执行,转而执行中断服务程序。这种机制的出现,极大地提高了系统的实时性和效率,使得系统能够及时响应各种异步事件。就像在一场繁忙的交响乐演奏中,中断就像是一个紧急信号,当它响起时,指挥会立即暂停当前的演奏,优先处理紧急情况,处理完毕后再继续进行演奏。
在 STM32F103C8T6 中,中断系统包含了丰富的中断资源,其中 EXTI(外部中断 / 事件控制器)更是外部中断的核心。EXTI 就像一个敏锐的观察者,时刻监测着指定 GPIO 口的电平信号。当这些信号发生变化时,EXTI 会迅速做出反应,向 NVIC(嵌套向量中断控制器)发出中断申请。NVIC 则如同一个公正的裁判,根据预先设定的优先级,对中断请求进行裁决,决定是否中断当前正在运行的 CPU 主程序。
在实际配置过程中,初始化 GPIO 引脚是第一步,这就像是为一场比赛准备好场地和器材。通过使能 GPIO 时钟,将引脚配置为合适的输入模式,为后续的外部中断配置奠定基础。接着,配置外部中断线路,使能 AFIO 时钟,选择中断引脚,并合理配置 EXTI 参数和中断优先级,这一系列操作就像是精心制定比赛规则,确保每个环节都能顺利进行。最后,编写中断服务程序,这是整个外部中断配置的核心部分,就像是比赛中的运动员,根据不同的中断事件,执行相应的处理代码,完成特定的任务。在中断服务程序执行完毕后,清除中断标志位是必不可少的一步,它就像是比赛结束后清理场地,为下一次的比赛做好准备。
在应用方面,STM32F103C8T6 的外部中断展现出了强大的实用性。以按键控制 LED 灯为例,通过将按键连接到特定的 GPIO 引脚,并配置为外部中断输入,当按键按下时,触发中断,在中断服务程序中实现 LED 灯状态的切换。这一简单的应用,充分体现了外部中断在实时响应外部事件方面的优势。在传感器数据采集中,无论是红外传感器还是旋转编码器,都可以利用外部中断来实现数据的高效采集。例如,红外传感器检测到物体遮挡时,触发中断,在中断服务程序中对遮挡事件进行计数;旋转编码器旋转时,其输出的脉冲信号触发外部中断,通过判断脉冲的相位差和个数,实现对旋转角度、速度和方向的测量。
(二)展望未来学习方向
随着嵌入式系统应用领域的不断拓展和技术的飞速发展,中断技术在其中的重要性愈发凸显。对于 STM32F103C8T6 外部中断的学习,只是我们踏入嵌入式中断世界的第一步。在未来的学习中,我们可以从以下几个方向深入探索。
在更复杂的嵌入式系统中,中断的应用场景将更加多样化。例如,在工业自动化控制系统中,可能会涉及多个传感器、执行器以及通信模块的协同工作,这就需要更精细地配置中断优先级和处理机制,以确保系统能够稳定、高效地运行。在智能家居系统中,大量的智能设备需要实时与中央控制器进行通信和交互,中断技术可以实现对各种设备状态变化的及时响应,提升用户体验。我们可以深入研究如何在这些复杂系统中,合理利用中断来优化系统性能,提高系统的可靠性和稳定性。
中断的优化也是未来学习的重要方向。随着系统复杂度的增加,中断响应时间、中断处理效率等问题变得尤为关键。我们可以探索如何通过优化中断服务程序的代码结构,减少不必要的计算和操作,提高中断处理的速度。还可以研究动态优先级调整策略,根据系统的实时状态和任务需求,动态地调整中断的优先级,使得系统能够更好地应对各种突发情况。
在实际项目开发中,还需要考虑中断与其他系统模块的协同工作。例如,中断与定时器、DMA(直接内存访问)等模块的配合,如何实现数据的快速传输和处理,以满足系统对实时性和数据处理能力的要求。同时,结合实时操作系统(RTOS)来管理中断,也是一个值得深入研究的方向。RTOS 可以提供更高效的任务调度和资源管理机制,与中断技术相结合,能够进一步提升嵌入式系统的性能和开发效率。
STM32F103C8T6 的外部中断为我们打开了嵌入式中断技术的大门,未来还有更多的知识和应用等待我们去探索和实践。希望读者能够在后续的学习和实践中,不断积累经验,提升自己在嵌入式系统开发领域的能力。