STM32 GPIO中断机制详解(进阶篇)

什么是中断

所谓中断,就是CPU在执行某个任务的时候,遇到了突发事件,CPU需要先处理好这个突发事件,而“中断”就是这样一个突发事件,只有当CPU执行完这个突发事件才能继续之前的任务。

说到这,你肯定有疑问,CPU为什么不把这个“中断”在最开始的时候就成为CPU的主线任务呢,而非得半路杀出个程咬金式的。

这还真有些讲究,从CPU的角度来讲,我堂堂CPU,是单片机最主要的模块,朕平时有大量的工程要跑,你这“外部中断”的事儿又不是集中在某一时间段,还得朕等着你“中断”的号召,朕才能开始干你那活,太没有效率了;再者,站在“中断”的角度来说,我这个只需要CPU你过来处理一会儿就行,速度很快,一直用CPU来处理反而是小题大做。一般速度很快的程序都会放在中断函数里,靠CPU的话无法跟上。所以,一般在中断函数内要避免使用Delay这类耗时的延迟函数

怎么使用GPIO的外部中断

先明确一点,要执行中断函数的是CPU,被执行的是中断。

那么中断怎么才能被CPU执行呢?

在stm32中有很多的中断,而CPU的任务主要是运算,这时候就需要一个“管家”来管理这些中断的优先级,好让CPU知道它该执行哪个中断,这个管家的名字叫做“NVIC”.这些外设的中断最终都会先通过NVIC,然后再交给CPU执行

对于NVIC,它需要选择一个组,每个优先级要选取值是几,然后按照下图方式的优先级顺序依次让CPU执行相应中断,下面的几位代表着他们的优先顺序,抢占优先级高于响应优先级(后面写代码时会提及这部分怎么写)

分组方式 抢占优先级 响应优先级
分组0 0位,取值为0 4位,取值为0~15
分组1 1位,取值为0~1 3位,取值为0~7
分组2 2位,取值为0~3 2位,取值为0~3
分组3 3位,取值为0~7 1位,取值为0~1
分组4 4位,取值为0~15 0位,取值为0

有人会觉得,感觉还是不太理解怎么让中断产生,然后让CPU执行呢,下面以外部中断为例子来讲解

外部中断的介绍
•EXTI(Extern Interrupt)外部中断

•EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序

•支持的触发方式:上升沿/下降沿/双边沿/软件触发

•支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断,看下面的图就知道,GPIOA、GPIOB\GPIOC······这些只有一个能由AFIO通过那里的“16”出来,不会出现pinA0和PINB0同时产生中断的现象

•通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒

•触发响应方式:中断响应/事件响应(右下角处的20条通道通向的其他外设)
注意:5 ~ 9为一个通道,10~15为一个通道,涉及具体是哪个的时候需要在NVIC另外判断

AFIO的介绍

•在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择
下图是数据选择器选择一个口,然后对应输出一个数据,总共选择16个

下面举几个例子来实操上面说的
案例1:由红外传感器进行数字加减,数字显示在OLED上
模块化红外传感器代码

先复习GPIO的使用方法
1.使用RCC开启GPIO的时钟
2.使用GPIO_Init函数初始化GPIO
定义一个结构体,模式选8种中的一个,引脚选择所需要的,速度可以选50的
3.使用输出或输入的函数控制GPIO口
在这里AFIO没有初始化,不知道为什么,但EXTI、NVIC还是需要上述全部流程

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
uint16_t CountSensor_Count;


void CountSensor_Init()
{
	//1.使用RCC开启GPIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);     //选择GPIOB进入AFIO,因为自己红外传感器在面包板上的接线口在B14引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);      //对AFIO打开使能

    //2.建立一个GPIO_InitTypeDef类型的结构体,命名为GPIO_InitStructure
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//选择上拉输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//选择24引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度一般就选50MHZ
	GPIO_Init(GPIOB,&GPIO_InitStructure);//调用GPIO的初始化函数
	//配置AFIO的数据选择器,选择我们需要的中断引脚,在这里是GPIOB14
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	//EXTI的初始化
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line =EXTI_Line14 ;
	EXTI_InitStructure.EXTI_LineCmd =ENABLE ;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//决定触发方式
	EXTI_Init(&EXTI_InitStructure);
	//NVIC选择哪组(这里选哪个都一样,没特殊要求)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//对NVIC初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//选通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//打开使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1 ;//跟选的组有关
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //跟选的组有关
	NVIC_Init(&NVIC_InitStructure);
	
}
//通过这个函数获取每一次触发中断后的计数值
uint16_t CountSensor_Get()
{
	return CountSensor_Count;
}
//GPIOB14的外部中断函数
void EXTI15_10_IRQHandler()
{
	if(EXTI_GetITStatus(EXTI_Line14) == SET )
	{
		
		CountSensor_Count++;
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

注意:中断函数不需要声明
main.c

#include "stm32f10x.h"                  // Device header
#include"Delay.h"
#include"OLED.h"
#include "CountSensor.h"

int main(void)
{
	OLED_Init();
	CountSensor_Init();
	
	OLED_ShowString(1,1,"Count:");
	
	while(1) 
	{
		OLED_ShowNum(1,7,CountSensor_Get(),5);
		
	}
}


案例2:旋转控制数字变化
在这里,我用的是GPIOB1和GPIOB0着两个引脚
#include "stm32f10x.h"                  // Device header

int16_t Encoder_Count;

void Encoder_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	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_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
	
	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_Rising;
	EXTI_Init(&EXTI_InitStructure);
	//设置一个组就行
	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_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()
{
	int16_t temp;
		temp = Encoder_Count;
	Encoder_Count = 0;
	return temp;
}


void EXTI0_IRQHandler()
{
	if(EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0 )
		{
			Encoder_Count--;
		}
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}

void EXTI1_IRQHandler()
{
	if(EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0 )
		{
			Encoder_Count++;
		}
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}

main.c

#include "stm32f10x.h"                  // Device header
#include"Delay.h"
#include"OLED.h"
#include "Encoder.h"

int16_t Number;

int main(void)
{
	OLED_Init();
	Encoder_Init();
	OLED_ShowString(1,1,"hello wrld!");
	while(1) 
	{
		Number += Encoder_Get();
		OLED_ShowSignedNum(2,1,Number,5);
		
	}
}


总结

对于单片机的学习不能止步于抄写他人的代码,重要的是自己也能看懂代码中的原理,学会自己从理论的知识变成可以运行的代码,路很长,我想我还需要继续学习,与诸君共勉。

作者:light to dawn

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 GPIO中断机制详解(进阶篇)

发表回复