1 引 言
本次实验旨在深入探究STM32F103单片机中断的应用。通过实验,我们将全面掌握STM32F103中断的定义、NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)的使用和开发,以及外部中断(EXTI)的使用和开发。中断作为一种关键的处理机制,在嵌入式系统中起着至关重要的作用,能够有效地响应外部事件并实现对系统的灵活控制。通过本次实验,我们将深入理解中断的原理和实现方式,为进一步的STM32F103单片机开发奠定坚实的基础。
2 实验目的
- 掌握 STM32F103 中断的定义
- 掌握 STM32F103 的 NVIC 使用和开发
- 掌握 STM32F103 的 EXTI 使用和开发
3 实验内容
3.1了解中断
图1 中断执行流程
3.2了解NVIC
NVIC: Nested Vectored Interrupt Controller,嵌套向量中断控制器。
针对两个中断同时发生问题,Cortex-M3内核有一个专门管理中断的外设 NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器),通过优先级控制中断的嵌套和调度。
NVIC是一个总的中断控制器,无论是来在内核的异常还是外设的外部中断,都由 NVIC 统一进行管理。Reset(复位)、NMI(Non Maskable Interrupt,不可屏蔽中断)、HardFault(硬件异常)的优先级是固定的,且优先级是负数,也就是最高的(优先级数字越小,优先级越高)。剩下的异常或中断,都是可以通过修改 NVIC 的寄存器调整优先级(但不能设置为负数)。
NVIC 的中断优先级由优先级寄存器的 4 位(0~15)决定,这 4 位可以进行切分,分为高 n 位的抢占优先级和低 4-n 位的响应优先级。抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队。
3.3了解EXIT
EXTI(External interrupt/event controller,外部中断/事件控制器)
EXTI 可以监测指定 GPIO 口的电平信号,当其指定的 GPIO 口产生电平变化时,EXTI 将立即向 NVIC 发出中断申请,经过 NVIC 裁决后即可中断 CPU 主程序,使 CPU 执行 EXTI 对应的中断程序
支持的 GPIO 口:所有 GPIO 口,但相同的 Pin 不能同时触发中断(数字不能相同,不能 PA0\PB0 同时存在,但是可以 PA0/PA1 存在)
通道数:16 个GPIO_Pin,外加 PVD 输出、RTC 闹钟、USB 唤醒、以太网唤醒
触发响应方式:中断响应/事件响应
支持的触发方式:上升沿/下降沿/双边沿/软件触发
3.4事件和中断的关系
中断一般会让 CPU 进入中断处理函数,而事件没有对应的处理函数,一般是靠硬件自己实现关联操作。
事件:表示检测到有触发事件发生了。
中断:有某个事件发生并产生中断,并跳转到对应的中断处理程序中。
事件可以触发中断,也可以不触发。中断有可能被更优先的中断屏蔽,事件不会。事件本质上就是一个触发信号,是用来触发特定的外设模块或核心本身(例如唤醒操作)。
图2 EXTI 外部中断/事件控制器基本结构
3.5了解Exit的框架
流程:
1) 打开时钟 RCC
2) 初始化用来产生中断的 GPIO,选择输入模式
3) 配置 AFIO,选定使用某一路的 GPIO
4) 初始化 EXTI,选择触发方式,上升或下降沿触发
5) 配置 NVIC,排优先级,包括抢占优先级和子优先级
进入 CPU 执行程序.
3.6了解AFIO
图3 GPIO_EXTILineConfig函数
GPIO_EXTILineConfig 函数用来指定中断/事件线的输入源,它实际是设定外部中断配置寄存器的 AFIO_EXTICRx 值,该函数接收两个参数,第一个参数指定 GPIO 端口源,第二个参数为选择对应 GPIO 引脚源编号。
图4 按键和EXIT宏定义
bsp_exti.c 和 bsp_exti.h 文件用来存放 EXTI 驱动程序及相关宏定义
在上面的宏定义中,我们除了开 GPIO 的端口时钟外,我们还打开了 AFIO 的时钟,这是因为配置 EXTI信号源的时候需要用到 AFIO的外部中断控制寄存器 AFIO_EXTICRx。
4 深入解析
思考一(用按键中断让蜂鸣器发出声音)
1.日常电话在拨号过程中,按键的同时会有不同的按键声音。实验要求:基于STMF103单片机实现两个按键声音和按键检测功能,本次实验要求采用中断的方式。
在主函数配置好蜂鸣器和按键中断,然后在中断发生后会进入的函数中写蜂鸣器的响应。
图5 main.c
图6 bsp_exit.h解析
图7 bsp_exit.c—配置NVIC
图8 bsp_exit.c—配置按键中断
图9 stm32f10x_it.c
图10 stm32f10x_it.c
图11 在中断函数中写所需要进行的功能
main.c
#include "stm32f10x.h"
#include "bsp_exti.h"
#include "bsp_beep.h"
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
BEEP_GPIO_Config();
/* 初始化EXTI中断,按下按键会触发中断,
* 触发中断会进入stm32f4xx_it.c文件中的函数
* KEY1_IRQHandler和KEY2_IRQHandler,处理中断,反转LED灯。
*/
EXTI_Key_Config();
/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
while(1)
{
}
}
/*********************************************END OF FILE**********************/
bsp_exti.c
#include "bsp_exti.h"
/**
* @brief 配置嵌套向量中断控制器NVIC
* @param 无
* @retval 无
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置NVIC为优先级组1 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置中断源:按键1 */
NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
/* 配置抢占优先级 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
/* 配置子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 配置中断源:按键2,其他使用上面相关配置 */
NVIC_InitStructure.NVIC_IRQChannel = KEY2_INT_EXTI_IRQ;
/* 配置抢占优先级 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 配置子优先级 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 配置 IO为EXTI中断口,并设置中断优先级
* @param 无
* @retval 无
*/
void EXTI_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/*开启按键GPIO口的时钟*/
RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK,ENABLE);
RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK,ENABLE);
/* 配置 NVIC 中断*/
NVIC_Configuration();
/*--------------------------KEY1配置-----------------------------*/
/* 选择按键用到的GPIO */
GPIO_InitStructure.GPIO_Pin = KEY1_INT_GPIO_PIN;
/* 配置为浮空输入 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStructure);
/* 选择EXTI的信号源 */
GPIO_EXTILineConfig(KEY1_INT_EXTI_PORTSOURCE, KEY1_INT_EXTI_PINSOURCE);
EXTI_InitStructure.EXTI_Line = KEY1_INT_EXTI_LINE;
/* EXTI为中断模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 上升沿中断 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
/* 使能中断 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/*--------------------------KEY2配置-----------------------------*/
/* 选择按键用到的GPIO */
GPIO_InitStructure.GPIO_Pin = KEY2_INT_GPIO_PIN;
/* 配置为浮空输入 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStructure);
/* 选择EXTI的信号源 */
GPIO_EXTILineConfig(KEY2_INT_EXTI_PORTSOURCE, KEY2_INT_EXTI_PINSOURCE);
EXTI_InitStructure.EXTI_Line = KEY2_INT_EXTI_LINE;
/* EXTI为中断模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 下降沿中断 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
/* 使能中断 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
/*********************************************END OF FILE**********************/
bsp_exti.h
#ifndef __EXTI_H
#define __EXTI_H
#include "stm32f10x.h"
//引脚定义
#define KEY1_INT_GPIO_PORT GPIOA
#define KEY1_INT_GPIO_CLK (RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO)
#define KEY1_INT_GPIO_PIN GPIO_Pin_0
#define KEY1_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOA
#define KEY1_INT_EXTI_PINSOURCE GPIO_PinSource0
#define KEY1_INT_EXTI_LINE EXTI_Line0
#define KEY1_INT_EXTI_IRQ EXTI0_IRQn
#define KEY1_IRQHandler EXTI0_IRQHandler
#define KEY2_INT_GPIO_PORT GPIOC
#define KEY2_INT_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO)
#define KEY2_INT_GPIO_PIN GPIO_Pin_13
#define KEY2_INT_EXTI_PORTSOURCE GPIO_PortSourceGPIOC
#define KEY2_INT_EXTI_PINSOURCE GPIO_PinSource13
#define KEY2_INT_EXTI_LINE EXTI_Line13
#define KEY2_INT_EXTI_IRQ EXTI15_10_IRQn
#define KEY2_IRQHandler EXTI15_10_IRQHandler
void EXTI_Key_Config(void);
#endif /* __EXTI_H */
bsp_beep.c
#include "./beep/bsp_beep.h"
/**
* @brief 初始化控制蜂鸣器的IO
* @param 无
* @retval 无
*/
void BEEP_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启控制蜂鸣器的GPIO的端口时钟*/
RCC_APB2PeriphClockCmd( BEEP_GPIO_CLK, ENABLE);
/*选择要控制蜂鸣器的GPIO*/
GPIO_InitStructure.GPIO_Pin = BEEP_GPIO_PIN;
/*设置GPIO模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置GPIO速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化控制蜂鸣器的GPIO*/
GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStructure);
/* 关闭蜂鸣器*/
GPIO_PinLockConfig(GPIOC, BEEP_GPIO_PIN);
}
/*********************************************END OF FILE**********************/
bsp_beep.h
#ifndef __BEEP_H
#define __BEEP_H
#include "stm32f10x.h"
/* 定义蜂鸣器连接的GPIO端口, 用户只需要修改下面的代码即可改变控制的蜂鸣器引脚 */
#define BEEP_GPIO_PORT GPIOC /* GPIO端口 */
#define BEEP_GPIO_CLK RCC_APB2Periph_GPIOC /* GPIO端口时钟 */
#define BEEP_GPIO_PIN GPIO_Pin_0 /* 连接到蜂鸣器的GPIO */
/* 高电平时,蜂鸣器响 */
#define ON_beep 1
#define OFF_beep 0
/* 带参宏,可以像内联函数一样使用 */
#define BEEP(a) if (a) \
GPIO_SetBits(BEEP_GPIO_PORT,BEEP_GPIO_PIN);\
else \
GPIO_ResetBits(BEEP_GPIO_PORT,BEEP_GPIO_PIN)
void BEEP_GPIO_Config(void);
#endif /* __BEEP_H */
stm32f10x_it.c
#include "stm32f10x_it.h"
#include "bsp_led.h"
#include "bsp_exti.h"
#include "bsp_beep.h"
void SysTick_Delay_us( __IO uint32_t us) ;
void Delay(__IO uint32_t nCount);
u32 yanshi;
u16 e;
void Sound(u16 frq)
{
u32 time;
if(frq != 1000)
//if(frq != 1000):条件判断语句,判断音调的频率是否不等于 1000 Hz。
{
// time = 500000/((u32)frq);
time = 100000/((u32)frq);
//根据音调的频率计算延时时间。通常情况下,频率越高,延时时间越短,声音越高。
// PBeep = 1;
BEEP( ON );//打开蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置1
SysTick_Delay_us(time);
// PBeep = 0;
BEEP( OFF );//关闭蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置0
SysTick_Delay_us(time);
}else
SysTick_Delay_us(1000);
//time = 100000/((u32)frq);:根据音调的频率计算延时时间。通常情况下,频率越高,延时时间越短,声音越高。
}
/** @addtogroup STM32F10x_StdPeriph_Template
* @{
*/
/**
* @brief This function handles NMI exception.
* @param None
* @retval None
*/
void NMI_Handler(void)
{
}
/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
while (1)
{
}
}
/**
* @brief This function handles Memory Manage exception.
* @param None
* @retval None
*/
void MemManage_Handler(void)
{
/* Go to infinite loop when Memory Manage exception occurs */
while (1)
{
}
}
/**
* @brief This function handles Bus Fault exception.
* @param None
* @retval None
*/
void BusFault_Handler(void)
{
/* Go to infinite loop when Bus Fault exception occurs */
while (1)
{
}
}
/**
* @brief This function handles Usage Fault exception.
* @param None
* @retval None
*/
void UsageFault_Handler(void)
{
/* Go to infinite loop when Usage Fault exception occurs */
while (1)
{
}
}
/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
void SVC_Handler(void)
{
}
/**
* @brief This function handles Debug Monitor exception.
* @param None
* @retval None
*/
void DebugMon_Handler(void)
{
}
/**
* @brief This function handles PendSVC exception.
* @param None
* @retval None
*/
void PendSV_Handler(void)
{
}
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
}
void KEY1_IRQHandler(void)
{
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
yanshi = 4;//10; 4; 2
// LED2 取反
for(e=0;e<(u16)(2*262/yanshi);e++){
//在内部循环中,计算发声的次数,通过 (u16)time[i] * tone[music[i]] / yanshi 来确定。
//这里将音符持续时间乘以音符对应的频率,再除以延时因子 yanshi,得到需要发声的次数。
Sound(262);
}
BEEP( OFF );
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
}
void KEY2_IRQHandler(void)
{
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET)
{
yanshi = 4;//10; 4; 2
// LED2 取反
for(e=0;e<(u16)(2*462/yanshi);e++){
//在内部循环中,计算发声的次数,通过 (u16)time[i] * tone[music[i]] / yanshi 来确定。
//这里将音符持续时间乘以音符对应的频率,再除以延时因子 yanshi,得到需要发声的次数。
Sound(462);
}
BEEP( OFF );
//清除中断标志位
EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);
}
}
/**
* @brief This function handles PPP interrupt request.
* @param None
* @retval None
*/
/*void PPP_IRQHandler(void)
{
}*/
/**
* @}
*/
void SysTick_Delay_us( __IO uint32_t us)
{
uint32_t i;
SysTick_Config(SystemCoreClock/1000000);
for (i=0; i<us; i++)
{
// 当计数器的值减小到 0 的时候,CRTL 寄存器的位 16 会置 1
// 当置 1 时,读取该位会清 0
while ( !((SysTick->CTRL)&(1<<16)) );
}
SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
}
思考二(使用NVIC优先级)
2.找到例程“18-EXTI-外部中断”程序的优先级设定程序,并改变上述外部中断的优先级(设置至少两种不同优先级),运行程序观察实验现象。如何实现按键 1 和按键2 设置在不同的优先级, 设计程序并实现按键 1 的优先级比按键 2 的优先级高。
运行程序的实验现象是:为了实验现象明显和便于观察我们在两个按键中断函数中都使用了while(1)函数
①当先按下按键1时会发出按键1的声音,再按下按键2时还是持续按键1的声音
②当先按下按键2时会发出按键2的声音,再按下按键1时声音变成了按键1的声音
图12 优先级分组
图13 加while函数
图14 实现按键1的优先级高于按键2