当前位置: 代码网 > 科技>人工智能 > STM32 中断编程入门

STM32 中断编程入门

2024年08月02日 人工智能 我要评论
STM32微控制器的中断系统是其功能强大和灵活性的重要组成部分。中断允许微控制器在执行主程序的同时,及时响应外部事件或内部条件的变化,从而实现高效的实时控制和数据处理。核心的中断控制器是NVIC(Nested Vectored Interrupt Controller),负责管理和分发所有的中断请求,并支持优先级分组,使开发人员能够为不同的中断源设置不同的优先级。STM32支持多种类型的中断,包括外部中断、定时器中断、串口中断和DMA中断。

目录

一、中断系统

1、中断的原理

2、中断类型

外部中断

定时器中断

dma中断

3、中断处理函数

中断标志位清除

中断服务程序退出 

二、实际应用

中断控制led 

任务要求

代码示例

中断控制串口通信

任务要求1

代码示例

任务要求2

代码示例

总结


一、中断系统

stm32微控制器的中断系统是其功能强大和灵活性的重要组成部分。中断允许微控制器在执行主程序的同时,及时响应外部事件或内部条件的变化,从而实现高效的实时控制和数据处理。核心的中断控制器是nvic(nested vectored interrupt controller),负责管理和分发所有的中断请求,并支持优先级分组,使开发人员能够为不同的中断源设置不同的优先级。stm32支持多种类型的中断,包括外部中断、定时器中断、串口中断和dma中断。每种中断类型都有特定的配置方式和中断服务程序编写规范,以确保及时和有效地处理相应的事件。中断使能和中断优先级设置是配置中断系统的关键步骤,同时需要编写高效的中断服务程序,以便快速响应并尽快恢复主程序的执行。这些特性使得stm32在广泛的嵌入式应用中表现出色,为实时控制和数据处理提供了强大支持。

1、中断的原理

下面通过一个生活中的例子,帮助更好的去理解中断:

可以看到图中,由最开始的看书,转到最后的去卫生间,这个过程中,看书就受到了中断。我们将看书看作主程序,快递电话、肚子疼视为中断源,取快递和去卫生间视为中断服务程序 ,但是通过箭头可以看到,最后还是返回到了看书的 “主程序” ,所以,中断还存在返回,我们叫做中断返回。

在计算机中,执行程序过程中,当出现异常情况(断电等)或特殊请求(数据传输等)时,计算机暂停现行程序的运行,转向对这些异常情况或特殊请求进行处理,处理完毕后再返回到现行程序的中断处,继续执行原程序,这就是“中断”。

中断的主要处理流程为:中断请求——>中断响应——>中断服务——>中断返回

中断请求:中断请求是中断源向cpu发出中断请求信号,此时中断控制系统的中断请求寄存器被置位,向cpu请求中断

中断响应:cpu的中断系统判断中断源的中断请求是否符合中断响应条件,如果符合条件,则暂时中断当前程序并控制程序跳转到中断服务程序

中断服务:为处理中断而编写的程序称为中断服务程序,是由开发人员针对具体中断所要实现的功能进行设计和编写的,需要由开发人员来实现

中断返回:cpu退出中断服务程序,返回到中断请求响应之前被中止的位置继续执行主程序。这部分操作同样由硬件来实现,不需要开发人员进行处理

 当发生了异常或中断,内核要想响应这些异常或中断,就需要知道这些异常或中断的服务程序的入口地址,再由入口地址找到相应的中断服务程序,由中断入口地址组成的表称作中断向量表(如下图)。

stm32中断系统的结构和工作原理如下:

中断请求来源:stm32的中断请求可以来自外部和内部两个方面。外部中断是由gpio口引脚的电平或边沿信号变化触发,而内部中断通常是由硬件模块(如定时器、adc)或软件产生的。

nvic控制器:在stm32中,所有中断请求都由nvic(nested vectored interrupt controller)控制器进行管理和调度。nvic是一个基于向量表的中断控制器,通过优先级和向量表来实现对中断请求的管理。

中断分组:stm32将中断分为多个组别,每个组别包含一组中断请求。不同组别的中断请求可以具有不同的优先级,并且可以使用优先级抢占和屏蔽机制来确保系统的实时性和可靠性。stm32中断分组方式可选为0~4个前缀,用于设定中断优先级组和亚组。

中断服务程序:当中断事件发生后,cpu会暂停当前任务并跳转到相应的中断服务程序,处理该事件。中断服务程序通常包括以下几个步骤:保存cpu寄存器的值(包括堆栈指针、程序计数器等)处理中断请求(根据外部或内部中断的类型进行相应的处理,如清除标志位、读取数据等操作)执行用户自定义代码(根据实际需求执行用户自定义的代码段)恢复cpu寄存器的值(将保存在堆栈中的寄存器值恢复到其原始状态,以便cpu继续执行之前的任务)

中断优先级:stm32中,所有中断请求都具有唯一的编号(irqn),并且可以根据编号和中断分组方式确定其优先级。优先级高的中断可以打断正在执行的低优先级中断,从而确保系统的实时性和可靠性。如果多个中断请求的优先级相同,则可以使用优先级抢占机制来确定响应顺序

2、中断类型

外部中断

exti(外部中断/事件控制器)支持19个外部中断/事件请求,每个中断/事件都有独立的触发和屏蔽设置,具有中断模式和事件模式两种设置模式。

其是一种通过配置gpio引脚并使用exti线路实现的事件处理机制。在初始化gpio引脚为输入并设置相应的中断触发方式后,可以通过编写中断服务程序来响应外部事件。例如,配置gpio引脚为上升沿触发,当引脚接收到上升沿信号时,会触发预先定义的中断服务程序,以便快速处理事件。这种机制使得stm32能够高效地监听和响应外部触发事件,广泛应用于各种应用场景中。

  • 输入线:exti有19个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个gpio,也可以是一些外设事件。
  • 边沿检测电路:它会根据上升沿触发选择寄存器(exti_rtsr)和下降沿出发选择器(exti_ftsr)对应的设置来控制信号触发。

上升沿触发选择寄存器:要配置stm32微控制器的外部中断以在上升沿触发时响应,首先需通过gpio的配置寄存器(如gpioxcrh或gpioxcrl)将相应引脚设置为输入模式。接着,在extix_rtsr寄存器中设置相应的位来使能对应的外部中断线x的上升沿触发。最后,在nvic中使能对应外部中断的中断处理。这些步骤确保了当引脚接收到上升沿信号时,系统能够及时调用预定义的中断服务程序来处理事件。

下降沿触发选择寄存器:要配置stm32微控制器的外部中断以在下降沿触发时响应,首先需通过gpio的配置寄存器(如gpiox_crh或gpiox_crl)将相应引脚设置为输入模式。接着,在extix_ftsr寄存器中设置相应的位来使能对应的外部中断线x的下降沿触发。最后,在nvic中使能对应外部中断的中断处理。这些步骤确保了当引脚接收到下降沿信号时,系统能够及时调用预定义的中断服务程序来处理事件。

gpio的中断是以组为单位的,同组的外部中断公用一条外部中断线。    

例如:pa0、pb0、pc0、pd0、pe0、pf0、pg0这些为一组,如果使用pa0作为外部中断源,那么pb0、pc0、pd0、pe0、pf0、pg0就不能同时再作为外部中断使用了,在此情况下,只能使用类似于pb1、pc2这种末端序号不同的外部中断源。

gpio引脚和外部中断线的映射关系图如下:

定时器中断

stm32微控制器的定时器是关键的外设,用于生成精确的时间延迟和周期性任务。通过选择合适的定时器类型(如通用定时器tim或基本定时器tim6/tim7),配置工作模式和中断触发条件,可以实现定时器中断功能。配置过程包括设置时钟源、计数器初值和自动重装载寄存器,以及使能中断并编写相应的中断服务程序。这些步骤确保了定时器可以在达到预设计数值时产生中断请求,从而实现精确的时间控制和周期性任务执行,适用于实时操作系统、通信协议和其他时间敏感应用。

要配置stm32微控制器的定时器中断,首先选择适合需求的定时器(如tim1、tim2等),配置其工作模式时钟源计数周期。通过使能定时器中断控制寄存器中的更新中断位(uie),允许定时器溢出时产生中断请求。

  • 时钟和预分频设置:选择适当的时钟源和预分频器,以确定定时器的计数频率。
  • 计数器设置:设置定时器的计数器初值和自动重装载寄存器(arr),确定定时器的计数周期。
  • 中断使能:通过使能定时器中断使能寄存器中的相应中断使能位(如uie),允许定时器溢出时产生中断请求。

然后编写中断服务程序来处理定时器中断事件,包括清除中断标志执行特定的定时任务重新配置定时器。最后,确保在主程序中使能全局中断,以确保定时器中断能够正常触发和处理。这些步骤能够有效配置和利用stm32定时器中断功能,用于实现各种时间相关的应用和功能需求。

dma中断

 在stm32微控制器中,dma(直接存储器访问)提供了高效的数据传输机制,允许外设和内存之间直接交换数据,无需cpu的干预,从而提升系统效率和响应速度。dma传输完成时可触发中断通知cpu,通过使能dma中断并配置中断服务程序,可以实现在数据传输完成时执行额外操作或启动后续任务,适用于实时数据处理、高速数据采集和图形显示等应用场景,有效优化系统性能和数据处理效率。

3、中断处理函数

中断标志位清除

void exti0_irqhandler(void)
{
	if (exti_getitstatus(exti_line0) != reset)
	{
		exti_clearitpendingbit(exti_line0);
		// 接中断服务程序代码
	}
}

中断服务程序退出 

void exti0_irqhandler(void)
{
	if (exti_getitstatus(exti_line0) != reset)
	{
		exti_clearitpendingbit(exti_line0);
		// 接中断服务程序代码
	}
	nvic_clearpendingirq(exti0_irqn);

二、实际应用

中断控制led 

任务要求

用stm32f103核心板的gpioa端一管脚接一个led,gpiob端口一引脚接一个开关(用杜邦线模拟代替)。采用中断模式编程,当开关接高电平时,led亮灯;接低电平时,led灭灯。如果完成后,尝试在main函数while循环中加入一个串口每隔1s 发送一次字符的代码片段,观察按键中断对串口发送是否会带来干扰或延迟。

代码示例

led.c

#include "stm32f10x.h"                  // device header
 
/**
  * 函    数:led初始化
  * 参    数:无
  * 返 回 值:无
  */
void led_init(void)
{
	/*开启时钟*/
	rcc_apb2periphclockcmd(rcc_apb2periph_gpioa, enable);		//开启gpioa的时钟
	
	/*gpio初始化*/
	gpio_inittypedef gpio_initstructure;
	gpio_initstructure.gpio_mode = gpio_mode_out_pp;
	gpio_initstructure.gpio_pin = gpio_pin_all;
	gpio_initstructure.gpio_speed = gpio_speed_50mhz;
	gpio_init(gpioa, &gpio_initstructure);		
 
    gpio_resetbits(gpioa, gpio_pin_0);
}

 exti_key.c

#include "exti_key.h"
#include "misc.h"
 
void exti_key_init(void)
{   
    gpio_inittypedef gpio_initstructure;
    rcc_apb2periphclockcmd(rcc_apb2periph_gpiob | rcc_apb2periph_afio, enable);
    gpio_initstructure.gpio_pin = gpio_pin_1; // 使用 b 口的引脚 1
    gpio_initstructure.gpio_mode = gpio_mode_in_floating;
    gpio_init(gpiob, &gpio_initstructure);
    nvic_inittypedef nvic_initstructure;
    nvic_prioritygroupconfig(nvic_prioritygroup_2);
    nvic_initstructure.nvic_irqchannel = exti1_irqn; // 使用与 gpiob 引脚 1 相关的外部中断通道
    nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;
    nvic_initstructure.nvic_irqchannelsubpriority = 1;
    nvic_initstructure.nvic_irqchannelcmd = enable;
    nvic_init(&nvic_initstructure); 
    exti_inittypedef exti_initstructure;
    exti_clearitpendingbit(exti_line1);  
    gpio_extilineconfig(gpio_portsourcegpiob, gpio_pinsource1); // 将 gpiob 和引脚 1 配置为外部中断
    exti_initstructure.exti_line = exti_line1;
    exti_initstructure.exti_mode = exti_mode_interrupt;
    exti_initstructure.exti_trigger = exti_trigger_falling;
    exti_initstructure.exti_linecmd = enable;
    exti_init(&exti_initstructure);
}

main.c

#include "stm32f10x.h"                  // device header
#include "led.h"
#include "exti_key.h"
 
int main(void)
{
	led_init();
    gpio_resetbits(gpioa,gpio_pin_0);
	exti_key_init();
 
	while (1)
	{
	}
}
//void exti1_irqhandler(void)
//{
//    if(exti_getitstatus(exti_line1) != reset)
//    {
//        gpio_writebit(gpioa,gpio_pin_0,(bitaction)((1-gpio_readoutputdatabit(gpioa,gpio_pin_0))));
//        exti_clearitpendingbit(exti_line1);
//    }
//}
//两种方法
uint8_t led = 1;
 
void exti1_irqhandler(void)
{
      if(exti_getitstatus(exti_line1) != reset)
      {
		led = ~led; //状态翻转
        //如果等于1,则pb1复位点亮,否则置1熄灭
        if(led == 1)
            gpio_resetbits(gpioa,gpio_pin_0);
        else
            gpio_setbits(gpioa,gpio_pin_0);    
     }
     exti_clearitpendingbit(exti_line1); //清除exti1的中断标志位
}

即可实现中断控制led灯亮灭。

中断控制串口通信

任务要求1

当stm32接收到1个字符“s”时,停止持续发送“hello windows!”; 当接收到1个字符“t”时,持续发送“hello windows!”

代码示例

#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
 
volatile uint8_t send_enabled = 0;  // 全局变量,控制发送行为
 
void usart_configuration(void) {
    usart_inittypedef usart_initstructure;
    gpio_inittypedef gpio_initstructure;
 
    // 打开 gpio 与 usart 端口的时钟
    rcc_apb2periphclockcmd(rcc_apb2periph_gpioa | rcc_apb2periph_usart1, enable);
 
    // 配置 usart1 tx (pa.09) 为复用推挽输出
    gpio_initstructure.gpio_pin = gpio_pin_9;
    gpio_initstructure.gpio_mode = gpio_mode_af_pp;
    gpio_initstructure.gpio_speed = gpio_speed_50mhz;
    gpio_init(gpioa, &gpio_initstructure);
 
    // 配置 usart1 rx (pa.10) 为浮空输入
    gpio_initstructure.gpio_pin = gpio_pin_10;
    gpio_initstructure.gpio_mode = gpio_mode_in_floating;
    gpio_init(gpioa, &gpio_initstructure);
 
    // 配置 usart 参数
    usart_initstructure.usart_baudrate = 9600;
    usart_initstructure.usart_wordlength = usart_wordlength_8b;
    usart_initstructure.usart_stopbits = usart_stopbits_1;
    usart_initstructure.usart_parity = usart_parity_no;
    usart_initstructure.usart_hardwareflowcontrol = usart_hardwareflowcontrol_none;
    usart_initstructure.usart_mode = usart_mode_rx | usart_mode_tx;
    usart_init(usart1, &usart_initstructure);
 
    // 使能 usart
    usart_cmd(usart1, enable);
 
    // 使能接收中断
    usart_itconfig(usart1, usart_it_rxne, enable);
 
    // 配置 nvic
    nvic_inittypedef nvic_initstructure;
    nvic_initstructure.nvic_irqchannel = usart1_irqn;
    nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;
    nvic_initstructure.nvic_irqchannelsubpriority = 0;
    nvic_initstructure.nvic_irqchannelcmd = enable;
    nvic_init(&nvic_initstructure);
}
 
void usart1_irqhandler(void) {
    if(usart_getitstatus(usart1, usart_it_rxne) != reset) {
        char data = usart_receivedata(usart1);
        if(data == 's') {  // 接收到 's' 停止发送
            send_enabled = 0;
        } else if (data == 't') {  // 接收到 't' 开始发送
            send_enabled = 1;
        }
        usart_clearitpendingbit(usart1, usart_it_rxne);
    }
}
 
void delay(__io uint32_t ncount) {
    for(; ncount != 0; ncount--);
}
 
int main(void) {
    systeminit();
    usart_configuration();
 
    char *str = "hello windows!\r\n";
    while(1) {
        if(send_enabled) {
            for(uint32_t i = 0; i < strlen(str); i++) {
                while(usart_getflagstatus(usart1, usart_flag_txe) == reset);
                usart_senddata(usart1, str[i]);
            }
        }
        delay(5000000);
    }
}

任务要求2

当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”(提示:要将接收到的连续字符保存到一个字符数组里,进行判别匹配。写一个接收字符串的函数。)

代码示例

nvic.c

#include "stm32f10x.h"                  // device header
 
 
void nvic_configuration(void) {
    nvic_inittypedef nvic_initstructure;
 
    nvic_prioritygroupconfig(nvic_prioritygroup_0);
    nvic_initstructure.nvic_irqchannel = usart1_irqn;
    nvic_initstructure.nvic_irqchannelsubpriority = 0;
    nvic_initstructure.nvic_irqchannelpreemptionpriority = 0;
    nvic_initstructure.nvic_irqchannelcmd = enable;
    nvic_init(&nvic_initstructure);
}
 

serial.c

#include "stm32f10x.h"                  // device header
#include <stdio.h>
#include <stdarg.h>
 
/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void serial_init(void)
{
	/*开启时钟*/
	usart_inittypedef usart_initstructure;
    gpio_inittypedef gpio_initstructure;
 
    rcc_apb2periphclockcmd(rcc_apb2periph_gpioa | rcc_apb2periph_usart1, enable);
 
    // usart tx (pa.09) 配置为复用推挽输出
    gpio_initstructure.gpio_pin = gpio_pin_9;
    gpio_initstructure.gpio_mode = gpio_mode_af_pp;
    gpio_initstructure.gpio_speed = gpio_speed_50mhz;
    gpio_init(gpioa, &gpio_initstructure);
 
    // usart rx (pa.10) 配置为浮空输入
    gpio_initstructure.gpio_pin = gpio_pin_10;
    gpio_initstructure.gpio_mode = gpio_mode_in_floating;
    gpio_init(gpioa, &gpio_initstructure);
 
    usart_initstructure.usart_baudrate = 9600;
    usart_initstructure.usart_wordlength = usart_wordlength_8b;
    usart_initstructure.usart_stopbits = usart_stopbits_1;
    usart_initstructure.usart_parity = usart_parity_no;
    usart_initstructure.usart_hardwareflowcontrol = usart_hardwareflowcontrol_none;
    usart_initstructure.usart_mode = usart_mode_rx | usart_mode_tx;
    usart_init(usart1, &usart_initstructure);
 
    usart_itconfig(usart1, usart_it_rxne, enable); // 开启接收中断
    usart_cmd(usart1, enable);
}

main.c

#include "stm32f10x.h"
#include "misc.h"
#include <string.h>
#include "delay.h"
#include "serial.h"
#include "nvic.h"
 
 
#define buffer_size 100
volatile char buffer[buffer_size];
volatile int buffer_index = 0;
volatile int send_enabled = 0;
 
 
void usart1_irqhandler(void) {
    if (usart_getitstatus(usart1, usart_it_rxne) != reset) {
        char data = (char)usart_receivedata(usart1);
        if (buffer_index < buffer_size - 1) {
            buffer[buffer_index++] = data;
            buffer[buffer_index] = '\0';  // 保持字符串结尾
 
            char* temp_buffer = (char*)buffer; // 创建一个非 volatile 指针
 
            if (strstr(temp_buffer, "stop stm32!") != null) {
                send_enabled = 0;
                buffer_index = 0;  // 清空缓冲区
            } else if (strstr(temp_buffer, "go stm32!") != null) {
                send_enabled = 1;
                buffer_index = 0;  // 清空缓冲区
            }
        }
    }
}
 
int main(void) {
    systeminit();
    serial_init();
    nvic_configuration();
 
    char *str = "hello windows!\r\n";
    while (1) {
        if (send_enabled) {
            for (uint32_t i = 0; i < strlen(str); i++) {
                while (usart_getflagstatus(usart1, usart_flag_txe) == reset);
                usart_senddata(usart1, str[i]);
            }
        }
        delay_ms(500);
    }
}

最后使用串口助手即可(野火以及其他串口助手均可)。

总结

本章内容理解上不存在太多有问题的地方,对于中断的理解更像是正51单片机的另一个翻版,对于实践过程中的问题,远远多于理论理解,关于软件的操作,环境的配置,串口的调试运行,都是之前学习的逐渐累积,在学习上,没有一蹴而就,要脚踏实地,做好每一步,才可以更好更快,更高效率完成任务。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com