任务目的
使用EXTI(External Interrupt)外部中断方式, 通过中断服务函数对GPIO口进行控制, 使得LED灯可以进行亮灭翻转.
原理图分析
问题分析结束之后还是先来看原理图的分析.
首先是LED灯部分:
由图中可知, 若要控制红灯(PB5), 则只需输出引脚输出为0(低电平)即可.
然后再看下按键部分:
从图中可知, 按键未按时为低电平, 按下按键时为高电平. 符合下拉输入的逻辑.
EXTI外部中断
简介
外部中断指的是通过GPIO检测输入的脉冲变化,从而引起中断. 触发方式为边沿触发.
中断
笔者使用的STM32(F103VET6)单片机使用的是Cortex-M3内核,中断资源丰富.
外部中断/事件控制器包含19个边沿检测器,用于产生中断/事件请求。每个中断线都可以独立地配置它的触发事件(上升沿或下降沿或双边沿),并能够单独地被屏蔽;有一个挂起寄存器维持所有中断请求的状态。 EXTI可以检测到脉冲宽度小于内部APB2的时钟周期。多达112个通用I/O口连接到16个外部中断线.
具体中断向量表不再列出, 详情请参照STM32中文参考手册(132/754).
这里我们需要注意一下, 中断线有EXTI_Line0~EXTI_Line15共16条(其实还有四条,不再详细阐述,详情参照参考手册), 但是中断向量只有EXTI0~EXTI4以及EXTI9_5, EXTI15_10共7个中断向量.使用时需注意这一点。
由上图可以发现,按键所在的PA0引脚处于EXTI_Line0中断线上,中断向量为EXTI0.
NVIC设置
说到中断,就不得不说到NVIC(Nested Vectored Interrupt Controller)嵌套向量中断控制器。因为STM32的中断十分丰富,所以NVIC的出现几乎是必然的。那么谈到NVIC,利用库函数开发的思想,就不难想到结构体的应用。NVIC的结构体定义如下:
typedef struct {
uint8_t NVIC_IRQChannel; /*!< Specifies the IRQ channel to be enabled or disabled.
This parameter can be a value of @ref IRQn_Type
(For the complete STM32 Devices IRQ Channels list, please
refer to stm32f10x.h file) */
uint8_t NVIC_IRQChannelPreemptionPriority; /*!< Specifies the pre-emption priority for the IRQ channel
specified in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
uint8_t NVIC_IRQChannelSubPriority; /*!< Specifies the subpriority level for the IRQ channel specified
in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
FunctionalState NVIC_IRQChannelCmd; /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
will be enabled or disabled.
This parameter can be set either to ENABLE or DISABLE */
} NVIC_InitTypeDef;
NVIC的结构体共有三个成员,描述如下:
结构体成员名 | 介绍 |
---|---|
NVIC_IRQChannel | 需要配置的中断向量名 |
NVIC_IRQChannelCmd | 使能/关闭中断向量 |
NVIC_IRQChannelPreemptionPriority | 中断抢占优先级 |
NVIC_IRQChannelSubPriority | 响应优先级 |
Tip:什么是抢占优先级和响应优先级
中断向量有两个属性:抢占属性和响应属性,属性编号越小,中断优先级级别越高。
抢占优先级运用在中断嵌套之中,而响应优先级运用在中断响应处理方面。下面给出一个例子(参考自Fire的《STM32库开发实战指南》):
以下是三个中断向量:
中断向量 | 抢占优先级 | 响应优先级 |
---|---|---|
A | 0 | 0 |
B | 1 | 0 |
C | 1 | 1 |
当内核正在执行C的中断服务函数时,则它可以被抢占优先级更高的A打断,但不能被B打断。当B和C同时发生中断,则优先处理响应优先级较高的B中断的中断服务函数。
NVIC的优先级组
在配置中断优先级时,由于抢占优先级和响应优先级共同分配一个4位的二进制数,则一共可以有
- 第 0 组: 所有 4 位用来配置响应优先级。即 16 种中断向量具有都不相同的响应优先级。
- 第 1 组:最高 1 位用来配置抢占优先级,低 3 位用来配置响应优先级。表示有 21=2 种级别的抢占优先级(0 级,1 级),有 23=8 种响应优先级,即在 16 种中断向量之中,有8 种中断,其抢占优先级都为 0 级,而它们的响应优先级分别为 0~7,其余 8 种中断向量的抢占优先级则都为 1 级,响应优先级别分别为 0~7。
- 第 2 组:2 位用来配置抢占优先级,2 位用来配置响应优先级。即 22=4 种抢占优先级,22=4 种响应优先级。
- 第 3 组:高 3 位用来配置抢占优先级,最低 1 位用来配置响应优先级。即有 8 种抢占优先级,2 种响应 2 优先级。
- 第 4 组:所有 4 位用来配置抢占优先级,即 NVIC 配置的
24=16 种中断向量都是只有抢占属性,没有响应属性。要配置这些优先级组,可以采用库函数NVIC_PriorityGroupConfig()
,可输入的参数为NVIC_PriorityGroup_0 ~ NVIC_PriorityGroup_4
,分别为以上介绍的 5 种分配组。
更直观的图像版描述如下(其中的占先式优先级即抢占式优先级,副优先级即为响应优先级):
NVIC结构体程序设置
static void NVIC_Configuration(void) {
NVIC_InitTypeDef key_nvic;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
key_nvic.NVIC_IRQChannel = EXTI0_IRQn;
key_nvic.NVIC_IRQChannelCmd = ENABLE;
key_nvic.NVIC_IRQChannelPreemptionPriority = 0;
key_nvic.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&key_nvic);
}
EXTI设置
前面说了好多NVIC的内容,终于写到了EXTI的处理内容。EXTI的设置核心也是结构体的设置。但是别忘了,由于外部中断依托于GPIO,所以也要同时设置GPIO的结构体成员。
注意:外部中断在同一时间内只能响应一个中断.
结构体说明
EXTI的结构体定义如下:
typedef struct {
uint32_t EXTI_Line; /*!< Specifies the EXTI lines to be enabled or disabled.
This parameter can be any combination of @ref EXTI_Lines */
EXTIMode_TypeDef EXTI_Mode; /*!< Specifies the mode for the EXTI lines.
This parameter can be a value of @ref EXTIMode_TypeDef */
EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.
This parameter can be a value of @ref EXTIMode_TypeDef */
FunctionalState EXTI_LineCmd; /*!< Specifies the new state of the selected EXTI lines.
This parameter can be set either to ENABLE or DISABLE */
}EXTI_InitTypeDef;
从中可以看出其共有4个结构体成员,描述如下表:
结构体成员名 | 作用 |
---|---|
EXTI_Line | 明确外部中断线 |
EXTI_LineCmd | 外部中断线使能 |
EXTI_Mode | 外部中断线的模式选择,共有EXTI_Mode_Interrupt 和EXTI_Mode_Event 两种模式,前者为中断触发,后者不会立刻触发中断,而只是在 寄存器上把相应的事件标志位置1,应用这个模式需要不停地查询相应的寄存器 |
EXTI_Trigger |
EXTI_Trigger_Rising :上升沿触发; EXTI_Trigger_Falling :下降沿触发;EXTI_Trigger_Rising_Falling :上升/下降沿触发 |
结构体编程
void key_exti(void) {
GPIO_InitTypeDef key_struct;
EXTI_InitTypeDef key_exti_struct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
NVIC_Configuration();
key_struct.GPIO_Mode = GPIO_Mode_IPD; // pull down input
key_struct.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOA, &key_struct);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
key_exti_struct.EXTI_Line = EXTI_Line0;
key_exti_struct.EXTI_Mode = EXTI_Mode_Interrupt;
key_exti_struct.EXTI_Trigger = EXTI_Trigger_Falling;
key_exti_struct.EXTI_LineCmd = ENABLE;
EXTI_Init(&key_exti_struct);
}
中断服务函数
中断服务函数的定义与51系列单片机不同。必须与规定的中断服务函数名相同。我们可以从启动文件startup_stm32f10x_hd.s
中查询:
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
查得本次中断服务函数的函数名为:EXTI0_IRQHandler
。
编写的中断服务函数为:
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
led_toggle(); // LED状态翻转
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
主函数
int main()
{
led_config();
key_exti();
while (1);
}