【STM32】中断编程入门

目录

一、中断

1.中断处理流程

2.STM32中断和异常向量表

二、STM32F103按键中断点亮LED灯

1.Keil工程建立

2.代码解释

(1)GPIO初始化配置

(2)开启I/O端口的时钟和复用时钟

(3)设置I/O引脚与中断线路的映射关系

(4)初始化EXTI,配置EXTI相关参数并使能

(5)初始化NVIC,配置NVIC参数并使能

(6)编写中断服务程序

3.实物结果

三、采用串口中断方式重写上篇博客的串口通信任务

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

(1)代码实现

(2)代码解释

(3)实物连接和程序烧录

(4)实验结果

2.当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”

(1)代码实现

(2)实验结果

参考博客:


一、中断

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

1.中断处理流程

中断请求、中断响应、中断服务和中断返回

单重中断的中断处理流程

多重中断的中断处理流程

2.STM32中断和异常向量表

中断向量表:当发生了异常或中断,内核要想响应这些异常或中断,就需要知道这些异常或中断的服务程序的入口地址,再由入口地址找到相应的中断服务程序

入口地址一般存放在程序存储器(ROM),默认情况下,Cortex-M3内核的中断向量表从零地址处开始,且每个向量占用4个字节。

二、STM32F103按键中断点亮LED灯

1.Keil工程建立

img

img

添加文件,添加的文件可与上一个博客中串口通信的文件相同,只是需要稍微改一下。具体改动如下:

将LED.c文件的代码更换为如下代码:

#include "stm32f10x.h"              

uint16_t LED_Count;
void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	EXTI_InitTypeDef EXTI_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);  
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);  

	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_Init(GPIOB, &GPIO_InitStruct);

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	
	EXTI_InitStruct.EXTI_Line = EXTI_Line14;
	EXTI_InitStruct.EXTI_LineCmd = ENABLE;
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
	EXTI_Init(&EXTI_InitStruct);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; 
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStruct);
}

void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line14)== SET)
	{
			GPIO_InitTypeDef GPIO_InitStructure;	
			RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
			GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
			GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
			GPIO_Init(GPIOA,&GPIO_InitStructure);
		
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_RESET)
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_5);
		}
	    else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_SET)
		{
			GPIO_ResetBits(GPIOA,GPIO_Pin_5);
		}
		
	EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

将LED.h的代码改为:

#ifndef _LED_H
#define _LED_H

void LED_Init(void);

#endif

将main.c的代码改为:

#include "stm32f10x.h"
#include "LED.h"
int main(void)
{
	LED_Init();
	while(1)
	{
		
	}
} 

2.代码解释

下面我们介绍此次代码中EXTI标准外设库中断配置的步骤

(1)GPIO初始化配置

	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; 
	GPIO_Init(GPIOB, &GPIO_InitStruct);

初始化I/O口为浮空输入模式

(2)开启I/O端口的时钟和复用时钟

GPIO用作EXTI外部中断功能引脚,必须开启复用功能,打开相应引脚的AFIO时钟,通过调用RCC_APB2PeriphClockCmd()函数实现

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

(3)设置I/O引脚与中断线路的映射关系

GPIO引脚作为中断功能引脚用来触发外部中断时,需将GPIO的引脚与相应的中断线关联在一起,使用库函数GPIO_EXTILineConfig来实现这种映射关系。

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);

(4)初始化EXTI,配置EXTI相关参数并使能

    EXTI_InitStruct.EXTI_Line = EXTI_Line14;//设置外部中断线14中断
	EXTI_InitStruct.EXTI_LineCmd = ENABLE; //使能该外部中断线
	EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;//设置为中断模式
	EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//设置为下降沿触发
	EXTI_Init(&EXTI_InitStruct);

(5)初始化NVIC,配置NVIC参数并使能

NVIC相关的配置主要包括配置中断优先级的中断分组,确定各具体中断的抢占优先级和响应优先级的大小。

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; 
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStruct);

(6)编写中断服务程序

中断服务程序主要包括检测中断线路的状态、中断处理的内容和清除相关的中断

void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line14)== SET)//判断某个线上的中断是否发生
	{
			GPIO_InitTypeDef GPIO_InitStructure;	
			RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
			GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
			GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
			GPIO_Init(GPIOA,&GPIO_InitStructure);
		
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_RESET)
		{
			GPIO_SetBits(GPIOA,GPIO_Pin_5);
		}
	    else if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==Bit_SET)
		{
			GPIO_ResetBits(GPIOA,GPIO_Pin_5);
		}
		
	EXTI_ClearITPendingBit(EXTI_Line14);//清除中断标志
	}
}

3.实物结果

三、采用串口中断方式重写上篇博客的串口通信任务

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

Keil工程的建立和上个博客中串口通信的工程建立一致,添加的文件夹也一致。

(1)代码实现

将main.c改为:

#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)代码解释

先通过函数SystemInit()进行系统时钟的初始化。

然后通过函数USART_Configuration()进行串口的配置和一系列设置。

根据接收到的字符来改变send_enabled的值,当接收字符't'时,send_enabled值为1,当接收字符's'时send_enabled值为0。在main函数中判断send_enabled的值。若为1则for循环输出字符串hello windows!。

Delay()用于延时,控制字符串发送速度。

(3)实物连接和程序烧录

(4)实验结果

2.当stm32接收到字符“stop stm32!”时,停止持续发送“hello windows!”; 当接收到字符“go stm32!”时,持续发送“hello windows!”

(1)代码实现

将main.c的代码改为:

#include "stm32f10x.h"
#include "misc.h"
#include <string.h>

#define BUFFER_SIZE 100
volatile char buffer[BUFFER_SIZE];
volatile int buffer_index = 0;
volatile int send_enabled = 0;

void Delay(__IO uint32_t nCount) {
    for (; nCount != 0; nCount--);
}

void USART_Configuration(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);
}

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);
}

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();
    USART_Configuration();
    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(5000000);
    }
}

(2)实验结果

参考博客:

STM32以中断的方式点亮LED小灯(标准库)_stm32按键中断控制led灯-CSDN博客

第12周--中断编程入门-CSDN博客

中断服务程序设计实验 2.7.1 实验目的 1、 熟悉中断的概念、中断响应过程、中断向量表以及系统是如何根据中断类型号在中断向量表中得到该中断对应的中断服务程序的入口地址等内容。 2、 掌握中断服务程序的设计方法。 2.7.2 实验预习要求 1、 复习中断的有关内容。 2、 复习中断服务程序设计方法。 3、 读懂“2.7.4 实验内容”中给出的源程序,以便上机调试。 4、 从“2.7.5 实验习题”中任选一道题目,编写源程序,以便上机调试。 2.7.3 中断服务程序设计方法简介 由2.6节中的中断向量表可以看出,PC中断系统中中断类型可分为三类: 磁盘操作系统DOS提供的中断服务程序,中断类型号从20H到2FH; 系统主板BIOS芯片提供的BIOS中断服务程序,包括系统内部中断中断类型号从00H到07H;系统8级外部中断中断类型号从08H到0FH,它们对应于接在可编程中断控制器8259A的8个中断请求输入端IR0IR7的8个外部中断,其中IR2(中断类型号0AH)为系统保留,用户可通过该引脚实现对用户所需的外部硬件中断的管理;设备驱动程序,中断类型号从10H到1FH等; 用户定义的中断中断类型号从60H到7FH、F1H到FFH。用户可根据实际需要将某些通用性强的子程序功能通过中断服务程序来实现。一旦设置好了这样的中断服务程序,在其他应用程序中就可以调用这些中断服务程序。 设计中断服务程序的步骤如下: 1、 选择一个中断类型号 如果采用硬件中断,则要使用硬件决定的中断类型号。在PC机系统中,使用了一片可编程中断控制器8259A来对外部硬件中断进行管理,具体内容参见教材264页“8259A应用举例”,可以看出IR2为系统保留,其中断类型号为0AH。若用户需要,可将用户所需的外部中断源发来的中断请求信号接到IR2,编写该中断所需的中断服务程序,并将该中断服务程序的入口地址写到中断向量表0AH4AH4+3四个单元中,这样,当接在IR2上的外部中断源发来中断请求信号时,系统就会根据得到的类型号(0AH)到中断向量表中找出其入口地址,并转去执行该中断服务程序。 如果采用软件中断,即利用执行int n指令的方式执行中断服务程序,则可从系统预留给用户的中断类型号60H7FH、F1HFFH中选择一个。 2、 将中断服务程序的入口地址置入中断向量表的相应的四个存储单元中 确定了中断类型号,还要把中断服务程序入口地址置入中断向量表,以保证在中断响应时CPU能自动转入与该类型号相对应的中断服务程序。下面介绍两种将中断服务程序入口地址置入中断向量表的方法。 (1) 直接装入法 用传送指令直接将中断服务程序首地址置入矢量表中。设中断类型号为60H(此类型号对应的矢量表地址为从00180H开始的四个连续存储单元)。 PUSH DS XOR AX,AX MOV DS,AX ;将数据段寄存器清零 MOV AX,OFFSET INT60 ;将中断服务程序INT60所在段内的偏移地址送AX MOV DS:[0180H],AX ;将中断服务程序偏移地址送中断向量表00180H和00181单元 MOV AX,SEG INT60 ;将中断服务程序INT60所在段的段地址送AX MOV DS:[0180H+2],AX ;将中断服务程序所在代码段的段地址送00182H和00183单元 POP DS (2) DOS系统功能调用法 功能号(AH)=25H; 入口参数:(AL)=中断类型号, (DS)=中断服务程序入口地址的段地址 (DX)=中断服务程序入口地址的偏移地址 下面程序段完成中断类型号为60H的入口地址置入。 PUSH DS ;保护DS MOV DX,OFFSET INT60 ;取服务程序偏移地址 MOV AX,SEG INT60 ;取服务程序段地址 MOV DS,AX MOV AH,25H ;送功能号 MOV AL,60H ;送中断类型号 INT 21H ;DOS功能调用 POP DS ;恢复DS 3、 使中断服务程序驻留内存,以便其他应用程序调用 实现这一步骤的必要性在于:一旦中断服务程序驻留内存后,一般程序员使用这一新增的中断调用就如同调用DOS或BIOS的中断子程序一样,只要了解其入口要求和返回参数就可调用。程序驻留在内存后,它占用的存储区就不会被其他软件覆盖。使程序驻留内存,要求该程序以 .COM形式运行,这种种结构的程序要求入口定位于100H,并且数据和代码均在同一个段内,这样,.COM程序就被定位于低地址区,DOS常在低地址区增加驻留程序,而 .EXE程序被定位于高地
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值