2021/10/14 智能家具 嵌入式实训 第四天 中断 (2)
外部中断概述
STM32的每个IO都可以作为外部中断输入。
STM32的中断控制器支持19个外部中断/事件请求:
线0~15:对应外部IO口的输入中断。
线16:连接到PVD输出。
线17:连接到RTC闹钟事件。
线18:连接到USB唤醒事件。
每个外部中断线可以独立的配置触发方式(上升沿,下降沿或者双边沿触发),触发/屏蔽,专用的状态位。
从上面可以看出,STM32供IO使用的中断线只有16个,但是STM32F10x系列的IO口多达上百个,STM32F103ZET6(112),
STM32F103RCT6(51),那么中断线怎么跟io口对应呢?
通过该图可知,每一个中断线都对应了7组中的对应编号的IO口(这边以ZET6:112个IO=16个 * 7组)。
有如下关系:
GPIOx.0映射到EXTI0
GPIOx.1映射到EXTI1
…
GPIOx.15映射到EXTI15
注意:每个时间只能有一个I/O可映射到对应的中断线。
外部中断服务函数
是不是16个中断线就可以分配16个中断服务函数呢?
IO口外部中断在中断向量表中只分配了7个中断向量,也就是只能使用7个中断服务函数(下表少写了EXTI0)
从表中可以看出,外部中断线5~9分配一个中断向量,共用一个服务函数外部中断线10 ~15分配一个中断向量,共用一个中断服务函数。
中断服务函数列表
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
外部中断设置步骤
①映射
②中断使能、触发模式等使能方式。
③编写中断服务函数
一般配置步骤:
①初始化IO口为输入。
GPIO_Init();
② 开启IO口复用时钟。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
③设置IO口与中断线的映射关系。
void GPIO_EXTILineConfig();
④初始化线上中断,设置触发条件等。
EXTI_Init();
⑤配置中断分组(NVIC),并使能中断。
NVIC_Init();
⑥编写中断服务函数。
EXTIx_IRQHandler();
⑦清除中断标志位
EXTI_ClearITPendingBit();
外部中断常用库函数
①void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
//设置IO口与中断线的映射关系
exp: GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
②void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
//初始化中断线:触发方式等
③ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
//判断中断线中断状态,是否发生
④void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
//清除中断线上的中断标志位
* 接下来写外部中断的函数 exit.c
先把exit.h写完
#ifndef _EXTI_H_ #define _EXTI_H_ #include "stm32f10x.h" #include "stdio.h" #include "key.h" #include "beep.h" extern uint8_t key1_status; /* 函数接口 */ void EXTI0_Config(void); #endif
再写exit.c
①初始化IO口为输入。
GPIO_Init();
就是使能
按键
可以直接引用 key,h led.h
GPIO_InitTypeDef GPIO_InitStruce; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOC , ENABLE); GPIO_InitStruce.GPIO_Mode=GPIO_Mode_IPU;//上拉输入 GPIO_InitStruce.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6; GPIO_InitStruce.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOE, &GPIO_InitStruce); GPIO_InitStruce.GPIO_Mode=GPIO_Mode_IPD;//下拉输入 GPIO_InitStruce.GPIO_Pin=GPIO_Pin_0; GPIO_InitStruce.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruce);
>可省略
第二步:开启IO口复用时钟
增加:RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO , ENABLE);
第三步:设置IO口与中断线的映射关系
该函数存在于:stm32f10x_gpio.h中
增加:GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
看按键的连接口 这里是KEY1 PA0 映射线
//配置中断线和IO管脚的映射关系(PA0 -- 中断线0)
第四步:初始化线上中断,设置触发条件等。
该函数存在于:stm32f10x_exti.h中
增加:
//2. 配置中断线 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line0; //中断线0 EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中断线使能 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发 EXTI_Init(&EXTI_InitStructure);
第五步:配置中断分组(NVIC),并使能中断。
该函数存在于:misc.h中
通道的参数是在stm32f10x,h中找到IRQn结尾的。
增加:
//3. 配置NVIC //NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC中断优先级组(一般写在主函数开始处) NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; //选择中断通道 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //中断通道使能 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; //占先优先级 0 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; //次级优先级 0 NVIC_Init(&NVIC_InitStruct);
这边要注意,在这同时要在main.c中先写下该函数NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置NVIC中断分组,2位抢占优先级,2位响应优先级
,设置优先级后,这边填写的抢占优先级才有效。
下面写中断服务函数
编写中断服务函数&清除中断标志位
该函数在startup_stm32f10x_hd.s中:EXTI4_IRQHandler
故可在exti,c文件中编写一下程序:
先定义一下
uint8_t key1_status = 0;
key1的初始值
基本的中断服务函数
void EXTI0_IRQHandler(void) //中断服务函数 { Delay_nop_nms(10);//延时10ms if(KEY1==0){ //如果KEY1按下 BEEP_TOGGLE; } EXTI_ClearITPendingBit(EXTI_Line0); //清除中断标志位 }
void EXTI0_IRQHandler(void) //中断服务函数 { if(EXTI_GetITStatus(EXTI_Line0)) //判断中断是否发生(PR挂起寄存器) { EXTI_ClearITPendingBit(EXTI_Line0); //清除中断标志位 if(KEY1)//按下 key1_status = 1; else//抬起 { BEEP_TOGGLE; //LED1_TOGGLE; key1_status = 2; } } }
然后编写主函数
#include "main.h" /*********************************************************************************** 函数功能:main函数 形参:无 返回值:无 ***********************************************************************************/ int main(void) { Systick_Config(72000); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC中断优先级组 LED_Config(ON); Beep_Config(OFF); KEY_Config(); EXTI0_Config(); while(1) { LED_Twinkle(); if(key1_status==2) { key1_status=0; BEEP_TOGGLE; } } }
就相当于key1按下 key1_status为1
抬起后 key1_status为2
这样
if(key1_status==2)
key1_status=0;
可以表示一个按下抬起的过程