中断的概念: 中断是当单片机的CPU在执行程序时,外部或内部发生了一个随机事件,导致CPU暂时中断正在执行的程序,转去执行一段特殊的服务程序也就是中断服务子程序或中断处理程序。当处理完服务程序后,返回到被中断的程序继续执行,这样的一个过程就被称为中断,引发这个中断的事件被称为中断源。中断在stm32中还被分为相应的优先级,低优先级的中断会被高优先级中断所中断,即为中断的嵌套。在Crotex-M3内核中支持256个中断,其中包含16个内核中断以及240个外部中断。STM32F10X芯片中只用了其中的84个中断通道,包含16个内核中断和68个可屏蔽中断。
中断的优先级: STM32的每个中断通道都有一个中断优先级控制字节(8位二进制数据,可设置为0~255,数值越小,优先级越高。在STM32F103中只用其中高4位数据),其用于表达优先级的高4位又被分为抢占式优先级和响应式优先级。在抢占式优先级相同情况下,高响应优先级的中断优先被响应。优先级相同时,按照中断响应的顺序执行服务程序,越靠前的先执行。中断的相关功能需要通过NVIC(嵌套向量中断器)来配置。
中断的配置: 首先使能某个外设中断;然后设置中断优先级分组,初始化NVIC_InitTypeDef结构体,设置抢占优先级和响应优先级,使能中断请求;最后编写中断服务函数。

1、外部中断编程
1.1 EXTI的结构
在STM32F10X芯片中外部中断/事件控制器(EXTI)有20个用于产生中断/事件请求的边沿检测器,其对应的每一根输入线都可以单独进行配置,选择“中断”或“事件”类型以及触发事件(上升沿触发、边沿触发和下降沿触发),还可以对中断开启屏蔽。外部中断/事件控制器(EXTI)的结构如下图中所示:
在这里插入图片描述

EXTI主要有两个功能,一个用来产生中断,另一个被用来产生事件。
(1)产生中断的线路由输入线开始,经边沿检测电路,到一个或门电路再到一个与门电路,最后至NVIC中断控制器结束。输入线可以通过寄存器设置为任何一个GPIO引脚,也可以是外设的时间,在这部分主要是传递一个电平变化的信号;边沿检测电路可以对触发方式进行选择,有上升沿触发、下降沿触发以及边沿触发三种方式;或门电路使得中断不仅可以由外部电路进行触发也可以通过软件来启动中断/事件;与门电路用来决定是否产生中断,只有中断屏蔽寄存器端和或门电路端的有效信号均为1时,中断才会被产生;最后将挂起寄存器内容输入到NVIC中,实现系统中断事件的控制。
(2)产生事件的前三步与(1)相同,后经过一个与门电路,再经过一个脉冲发生器电路后结束。其中与门电路是用来决定是否产生事件,只有事件屏蔽寄存器端和或门电路端的有效信号均为1时,事件才会被产生;脉冲发生器电路只有在与门电路为有效信号1时才会输出一个脉冲信号;脉冲信号可被用于其他外设电路的使用,如定时器、ADC等等。
从EXTI框图中可以看出,中断的输出是NVIC控制器,从而会导致系统运行中断服务函数,处于软件层面;时间的输出是脉冲信号流向其他外设电路,处于硬件层面。

1.2 外部中断/事件线 映射
STM32F10X的EXTI对用连接的外设如下表中所示:

从表中可以看出EXTI可供外部IO使用的中断线有16根,但STM32F103芯片的IO口数量却有很多,每个GPIO端口均有16个管脚对应了16根中断线。中断线每次只能连接在一个IO口上,因此就需要用到外部中断的映射功能,通过AFIO(复用IO)的外部中断配置寄存器1的EXTIx[3:0]位来决定对应的中断线映射到哪个GPIO端口上。对于中断映射到GPIO端口的配置函数在stm32f10x_gpio.c和stm32f10x_gpio.h中,所以在使用标准库进行开发时需要将这两个文件添加到工程中。

1.3 外部中断/事件线 映射
EXTI相关的库函数在stm32f10x_exti.c和stm32f10x_exti.h文件中,使用库函数对外部中断进行配置的步骤如下:

使能IO口时钟,配置IO模式为输入;
开启AFIO时钟,设置IO口与中断线的映射关系;
首先需要使能AFIO时钟,其是挂载在APB2总线上的;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

然后将GPIO映射到对应的中断线上;

GPIO_EXTILineConfig(GPIO_PortSourceGPIOx, GPIO_PinSourceY]);//Y是0~15号中断线
  1. 配置中断分组(NVIC),使能中断;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//EXTI0 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;		//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; 			//子优先级
NVIC_Init(&NVIC_InitStructure);
  1. 初始化EXTI,选择触发方式;
    配置好NVIC后就需要对中断线上的中断进行初始化,需要调用void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)函数,其中结构体EXTI_InitTypeDef的成员变量有:
typedef struct
{
	uint32_t EXTI_Line; 				//中断/事件线
	EXTIMode_TypeDef EXTI_Mode; 		//EXTI模式
	EXTITrigger_TypeDef EXTI_Trigger; 	//EXTI 触发方式
	FunctionalState EXTI_LineCmd; 		//中断线使能或失能
}

EXTI_Line: 可配置参数为EXTI0~EXTI20;
EXTI_Mode: 可配置为EXTI_Mode_Interrupt(中断模式)和EXTI_Mode_Event(事件模式);
EXTI_Trigger: 可配置为EXTI_Trigger_Rising(上升沿触发)、EXTI_Trigger_Falling(下降沿触发)以及EXTI_Trigger_Rising_Falling(边沿触发);
EXTI_LineCmd: 配置ENABLE为使能,DISABLE为失能。
5. 编写EXTI中断服务函数
中断服务函数就是当外部中断发生后,需要执行的一段用户程序,可根据需求进行编写。

1.4 应用示例
在本小节中使用一个按键来触发外部中断,在服务函数中执行点亮LED的程序,这个功能是很简单的,但也能够解释清楚外部中断的配置过程,详细的代码如下所示(其中LED文件的代码在上一章节中已经给出):
exit.h

#ifndef _exti_H
#define _exti_H

#include "system.h"
void MY_EXTI_Init(void);

#endif

exit.c

#include "exti.h"
#include "SysTick.h"
#include "key.h"
#include "led.h"
void MY_EXTI_Init()
{
	NVIC_InitTypeDef NVIC_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
	
	NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=EXTI3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=EXTI4_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line0;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
	EXTI_Init(&EXTI_InitStructure);
	
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStructure);
	
}
void EXTI0_IRQHandler()
{
	if(EXTI_GetFlagStatus(EXTI_Line0)==1)
	{
		delay_ms(10);
		if(K_UP==1)
		{
			led2=0;
		}	
	}
	EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI2_IRQHandler()
{
	if(EXTI_GetFlagStatus(EXTI_Line2)==1)
	{
		delay_ms(10);
		if(K_LEFT==0)
		{
			led3=1;
		}	
	}
	EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI3_IRQHandler()
{
	if(EXTI_GetFlagStatus(EXTI_Line3)==1)
	{
		delay_ms(10);
		if(K_DOWN==0)
		{
			led2=1;
		}	
	}
	EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI4_IRQHandler()
{
	if(EXTI_GetFlagStatus(EXTI_Line4)==1)
	{
		delay_ms(10);
		if(K_RIGHT==0)
		{
			led3=0;
		}	
	}
	EXTI_ClearITPendingBit(EXTI_Line4);
}

key.h

#ifndef _key_H
#define _key_H

#include "system.h"

#define KEY_UP_Pin 		GPIO_Pin_0
#define KEY_LEFT_Pin 	GPIO_Pin_2
#define KEY_DOWN_Pin 	GPIO_Pin_3
#define KEY_RIGHT_Pin 	GPIO_Pin_4

#define KEY_UP_PORT 	GPIOA
#define KEY_PORT 		GPIOE

#define K_UP PAin(0)
#define K_DOWN PEin(3)
#define K_LEFT PEin(2)
#define K_RIGHT PEin(4)

#define KEY_UP 1
#define KEY_LEFT 3
#define KEY_DOWN 2
#define KEY_RIGHT 4

void KEY_Init(void);
u8 KEY_Scan(u8 mode);

#endif

key.c

#include "key.h"
#include "SysTick.h"
void KEY_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
	
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(KEY_UP_PORT,&GPIO_InitStructure);
	
	
	GPIO_InitStructure.GPIO_Pin=KEY_LEFT_Pin|KEY_DOWN_Pin|KEY_RIGHT_Pin;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(KEY_PORT,&GPIO_InitStructure);
}


u8 KEY_Scan(u8 mode)
{
	static u8 key=1;
	if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0))
	{
		delay_ms(10);
		key=0;
		if(K_UP==1)
		{
			return KEY_UP;
		}
		else if(K_DOWN==0)
		{
			return KEY_DOWN;
		}
		else if(K_LEFT==0)
		{
			return KEY_LEFT;
		}
		else if(K_RIGHT==0)
		{
			return KEY_RIGHT;
		}
	}
	else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1)
	{
		key=1;
	}
	if(mode==1)
	{
		key=1;
	}
	return 0;
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "exti.h"
#include "key.h"

u8 i=0;
int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	MY_EXTI_Init();
	LED_Init();
	KEY_Init();
	
	while(1)
	{
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		delay_ms(10);
	}
}

2、 定时器中断编程
STM32F1的定时器系统由2个基本定时器(TIM6、TIM7)、4个通用定时器(TIM2~TIM5)和2个高级定时器(TIM1、TIM8)所组成。基本定时器同51单片机内的定时器类似,功能较为简单;通用定时器在其基础上增加了输入捕获与输出比较功能;高级定时器又在通用定时器基础上增加了可编程死去互补输出、重复计数、带刹车(断路)的功能主要针对于工业电机的控制。

2.1 通用定时器
STM32F1的通用定时器内有一个16位自动重载计数器(CNT)由可编程预分频器(PSC)驱动,其可用于测量输入信号的脉冲宽度(输入捕获)或者生产输出波形(输出比较和PWM)。使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几微秒到几毫秒之间调整,此外每一个定时器都是独立的,之间不互相共享任何资源。通用定时器TIM2~TIM5具备如下功能:

16位向上、向下、向上/向下自动装载计数器(TIMx_CNT);
16位可编程预分频器,计数器时钟频率的分频系数为1~65535;
4个独立通道(TIMx_CH1~4),通道可以被用作输入捕获、输出比较、PWM生成和单脉冲模式输出功能;
可以使用外部信号(TIMx_ETR)控制定时器,可实现多个定时器互连的同步电路;
发生更新(计数器溢出、初始化)、触发时间(计数器启动、停止、初始化或者由内部/外部触发计数)、输入捕获以及输出比较事件时会产生中断/DMA请求;
支持针对定位的增量(正交)编码器和霍尔传感器电路
触发输入作为外部时钟或者按周期的电流管理。
2.2 定时器配置步骤
定时器相关的库函数在stm32f10x_tim,c和stm32f10x_tim.h文件中,使用库函数对定时器进行配置的详细步骤如下:

使能定时器时钟;
定时器是挂载在APB1总线上的设备,因此使能定时器可以调用函数:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIMx, ENABLE);
初始化定时器参数,包含了自动重装值,分频系数,计数方式等;
调用函数:void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
TIM_TimeBaseInitTypeDef为一个结构体类型,包含了定时器初始化的成员变量:
typedef struct
{
    uint16_t TIM_Prescaler;            //定时器预分频器
    unit16_t TIM_CouterMode;        //计数模式
    unit16_t TIM_Period;            //定时器周期
    unit16_t TIM_ClockDivision;        //时钟分频
    unit8_t TIM_RepetitionCounter;    //重复计数器

TIM_Prescaler: 时钟源经过该预分频器后输出的是定时器时钟,设置范围为0~65535;
TIM_CouterMode: 可设置为TIM_CounterMode_Up(向上)、TIM_CounterMode_Down(向下)以及中心对齐计数模式;
TIM_Period:设置定时器自动重载计数值,范围为0~65536;
TIM_ClockDivision: 时钟分频因子,设置定时器时钟CK_INT频率与数字滤波器采样时钟频率分频比;
TIM_RepetitionCounter: 重复计数器,简单的控制PWM输出个数。

设置定时器中断类型,使能定时器;
调用函数:void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
TIM_IT 用来设置定时器中断类型,包含TIM_IT_Update(更新中断)、TIM_IT_Trigger(触发中断)以及输入捕获中断等等;
FunctionalState 用来使能或使能定时器中断,ENABLE和DISABLE。

设置定时器中断优先级,使能定时器中断通道;
对NVIC初始化,如前一节中所示。

开启定时器;
调用函数:void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

编写定时器中断服务函数
由于定时器中断类型有很多,因此在中断服务函数中需要通过状态寄存器的值来判断此次中断属于哪一种类型,然后再执行相应的用户程序。

2.3 应用示例
本次实验通过TIM4定时器的更新中断控制LED灯实现不断闪烁的功能,详细的代码如下所示;


time.h

#ifndef _time_H
#define _time_H

#include "system.h"

void TIM4_Init(u16 pre,u16 psc);

#endif

time.c

#include "time.h"
#include "led.h"

void TIM4_Init(u16 pre,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
	
	TIM_TimeBaseInitStructure.TIM_Period=pre;//ÖÜÆÚ
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
	TIM_ClearITPendingBit(TIM4,TIM_CounterMode_Up);
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM4,ENABLE);
	
}
void TIM4_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM4,TIM_IT_Update)==1)
	{
		led2=!led2;
	}
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
}

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "time.h"

u8 i=0;

int main()
{
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	LED_Init();
	TIM4_Init(1000,36000-1);//500ms
	
	
	while(1)
	{
		i++;
		if(i%20==0)
		{
			led1=!led1;
		}
		delay_ms(10);
	}

}