文章目录
前言
此章主要介绍EXTI外部中断以及如何使用中断。(我写该文章更多是作为学习笔记之用,主要参考视频为《STM32入门教程-2023持续更新中》 作者:江协科技)
1、中断系统
1.1、中断
在STM32中,中断是一种用于响应外部事件或内部异常的机制。中断使得处理器可以在主程序执行的同时处理多个任务,从而提高系统的响应能力和效率。
个人理解:中断就像是突发事件,它会打断你当前所作的事情以便先执行更为紧急的事情,当然在处理完后又会回到原来被暂停的位置。
1.2、 中断优先级
在STM32中,每个中断都会有一个优先级,数值越小优先级越高。
1.3、 中断嵌套
简单讲就是在运行一个中断程序时,又有更高优先级的中断申请中断,CPU会暂停当前中断程序,转而处理新的中断。
2、中断向量表
STM32中断向量表是一个存储中断处理程序地址的表,它是在系统复位时加载到CPU的。中断向量表包含中断处理程序的地址。当发生中断时,CPU会跳转到中断向量表中存储的中断处理程序地址执行对应中断的处理程序。
详见《1-STM32F10x-中文参考手册》第9.1.2章节。
3、NVIC
3.1、NVIC的基本结构
NVIC(Nested Vectored Interrupt Controller)是一个高效的中断控制器,用于管理和控制各种外部和内部中断。
3.2、NVIC优先级分组
NVIC的中断优先级由寄存器的4位(0~15)决定,分为高n位的抢占优先级和低4-n位的响应优先级。
抢占优先级:抢占优先级高的中断可以打断低的中断,以此会产生中断嵌套的现象。
响应优先级:响应优先级类似于排队插队但是它任然会排在当前正在执行的中断后面。
当然当两个优先级相同时会根据中断向量表的优先级排队。
4、EXTI简介
4.1、EXTI外部中断
EXTI可以监控GPIO口的电平信号,当其指定GPIO口产生电平变化时,EXTI立即向NVIC发出中断申请,在NVIC裁决优先级后即可中断CPU主程序。
4.2、支持触发方式
上升沿、下降沿、双边沿、软件触发
4.3、触发响应方式
中断响应、事件响应
4.4、支持的GPIO口
所有GPIO口,但相同的Pin不能同时触发中断(PA1和PB1不能同时)
4.5、EXTI基本结构
![在这里插入图片描述](https://img-blog.csdnimg.cn/30e047a15f8e4f0b83e4946ee3c08f9f.png#pic_center =500xr)
4.6、AFIO复用IO口
AFIO主要用于引脚复用功能的选择和定义
4.7、EXTI框图
- 下降沿触发选择寄存器:这个非常好理解就是在输入波形为下降沿时会触发。
- 上升沿触发选择寄存器:和下降沿触发相同是在输入波形为上升沿时触发。
- 软件中断事件寄存器:它与下面的或门使得任意一个输入1时都会输出1。
- 请求挂起寄存器:本质是一个中断的标志位,可以读取该寄存器知道哪个通道触发中断。
- 中断屏蔽寄存器:和下面的与门构成类似于开关的功能只要它输入0无论另一端是什么都是0。
- 事件屏蔽寄存器:和中断屏蔽寄存器同理。
5、实例
5.1、旋转编码器
用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向。
其输出信号A相与B相都输出方波,顺时针方向旋转时,A相超前B相90°,逆时针方向旋转时,B相超前A相90°。
5.2、旋转编码器原理图
5.3、实物连接图
6、程序编写
6.1、模块化编程
#include "stm32f10x.h" // main.c
#include "OLED.h"
#include "Encode.h"
int16_t Num;
int main(void)
{
OLED_Init(); //初始化OLED屏幕模块
Encode_Init(); //初始化旋转编码器模块
OLED_ShowString(1,1,"Num:");
while(1)
{
Num += Encoder_Get();
OLED_ShowSignedNum(1,5,Num,5);
}
}
#include "stm32f10x.h" // Encode.c
int16_t Encoder_Count; //旋转计数
void Encode_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1 | GPIO_PinSource0);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure); //初始化EXTI
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure); //初始化NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
int16_t Encoder_Get(void)
{
int16_t temp;
temp = Encoder_Count;
Encoder_Count = 0;
return temp;
}
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
{
Encoder_Count --;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0)
{
Encoder_Count ++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}
#ifndef __ENCODE_H //Encode.h
#define __ENCODE_H
void Encode_Init(void);
int16_t Encoder_Get(void);
#endif
总结
中断的初始化需要配置相当多内容,但是只要知道要配置哪些东西,且知道GPIO如何配置就相对简单点,说实话个人感觉自己写的并不好在之后可能会再修改一下。