关于外部中断的AFIO,NVIC的基础知识请参考《stm32标准库入门教程》
链接: link
1、选择外部引脚配置
如上图所示:外部中断配置寄存器AFIO_EXTICR(1~4)中选择EXTI(0 ~ 15),在选择好的EXTI中填入对应的数值选择引脚。
本次实验:我们需要对引脚PB0进行外部中断检测,来控制LED灯的亮灭,则选择的寄存器是外部中断配置寄存器1(AFIO_EXTICR1),选择寄存器中的位0~位3(EXTI0)填入数值0001(选择PB0)
实物连接如下图所示:
具体的配置代码如下:
/* 1. 开启时钟:GPIOB和AFIO */
/* 1.1 GPIOB */
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
/* 1.2 AFIO */
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
/* 2. 将GPIOB0配置为上拉输入:MODE = 00,CNF = 10,上拉:ODR = 1 */
GPIOB->CRL &= ~GPIO_CRL_MODE0;
GPIOB->CRL &= ~GPIO_CRL_CNF0_0;
GPIOB->CRL |= GPIO_CRL_CNF0_1;
GPIOB->ODR |= GPIO_ODR_ODR0;//ODR = 1:表示上拉
/* 3. 配置AFIO ,选用EXTI0*/
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0_PB;//其中EXTIR[0]是EXTIR数组的第一个元素,
//AFIO_EXTICR1_EXTI0_PB = 0001
2、上升沿触发/下降沿触发
/* 1. 配置EXTI:下降沿触发,使能EXTI中断屏蔽 */
EXTI->FTSR |= EXTI_FTSR_TR0;//配置为下降沿触发
EXTI->IMR |= EXTI_IMR_MR0;//使能EXTI开关,只有使能这个开关中断信号才能传递到NVIC
3、NVIC的配置
NVIC是中断管理器,将请求来的中断进行优先级分组等操作。NVIC和RCC是处于CPU内部,挂载在系统总线AHB上面的,单片机一上电AHB的时钟信号都开启了的,所以不需要我们在用代码开启时钟,关于总线时钟可以参考《stm32标准库入门教程》中的时钟树那一篇。
由于NVIC在参考手册里面没有关于它的寄存器介绍,但是在core_cm3.c的文件里面有关于NVIC的配置函数,如下图所示:
其中中断源的来源如下图所示:
由上图得出,中断源和下图的中断向量表的排序是一比一对应的。
如上图中断向量表所示:表中黑色部分的中断源是由CPU内部产生的,而其他的都是由片上外设产生的,若没有配置NVIC的优先级,那么则按照表中的默认优先级进行执行中断。其他黑色部分的中断源来自于CPU内部,则无需在使能NVIC_EnableIRQ(),因为只有前面位置值为正数才需要使能NVIC_EnableIRQ()。
/* 5. 配置NVIC */
/* 5.1 优先级分组*/
NVIC_SetPriorityGrouping(0x04);//设置4位抢占优先级,0位子优先级
/* 5.2 优先级赋值*/
NVIC_SetPriority(EXTI0_IRQn,0);
/* 5.3 使能中断源请求*/
NVIC_EnableIRQ(EXTI0_IRQn);
综上的分析:总结的外部中断的大体结构如下图所示:
4、完整代码
①Key.c的文件代码如下:
#include "stm32f10x.h" // Device header
#include "Key.h"
#include "LED.h"
#include "Delay.h"
/*
* 引脚的初始化:
* 1、给按键对应的引脚IO口工作模式:上拉输入
* 2、配置AFIO(复用外部中断)
* 3、配置EXTI(外部中断控制器)
* 4、配置NVIC
*/
void Key_EXTI_Init(void)
{
/* 1. 开启时钟:GPIOB和AFIO */
/* 1.1 GPIOB */
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
/* 1.2 AFIO */
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
/* 2. 将GPIOB0配置为上拉输入:MODE = 00,CNF = 10,上拉:ODR = 1 */
GPIOB->CRL &= ~GPIO_CRL_MODE0;
GPIOB->CRL &= ~GPIO_CRL_CNF0_0;
GPIOB->CRL |= GPIO_CRL_CNF0_1;
GPIOB->ODR |= GPIO_ODR_ODR0;//ODR = 1:表示上拉
/* 3. 配置AFIO ,选用EXTI0*/
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0_PB;
/* 4. 配置EXTI:下降沿触发,使能EXTI中断屏蔽 */
EXTI->FTSR |= EXTI_FTSR_TR0;
EXTI->IMR |= EXTI_IMR_MR0;
/* 5. 配置NVIC */
/* 5.1 优先级分组*/
NVIC_SetPriorityGrouping(0x04);
/* 5.2 优先级赋值*/
NVIC_SetPriority(EXTI0_IRQn,0);
/* 5.3 使能中断请求*/
NVIC_EnableIRQ(EXTI0_IRQn);
}
/*
* 中断服务函数:按下按键时,执行一次中断函数
*/
void EXTI0_IRQHandler(void)
{
/* 除中断挂起标志位 */
EXTI->PR |= EXTI_PR_PR0;
Delay_ms(10);//在中断里面添加延迟是不被允许的,但是这里是学习中断,我们忽略
if((GPIOB->IDR & GPIO_IDR_IDR0) != 0)//检测输入引脚是否被真的按下
{
//不等于0说明还在抖动
}
else
{
/* LED0翻转 */
LED_Turn(LED0);
}
}
②LED.c文件的代码如下:
#include "stm32f10x.h" // Device header
#include "LED.h"
/*
* PA0~PA7引脚的初始化
*/
void LED_Init(uint32_t PA_x)
{
/* 1. 打开GPIOA的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2. 将PA0~PA7配置为通用输出开漏模式 */
if(PA_x != PA_ALL)
{
GPIOA->CRL |= PA_x;
GPIOA->CRL &= ~(PA_x << 2);
}else{
GPIOA->CRL |= PA_ALL;
GPIOA->CRL &= PA_ALL;
}
}
/*
* 点亮LEDx
*/
void LED_ON(uint16_t LEDx)
{
GPIOA->ODR |= LEDx;
}
/*
* 熄灭LEDx
*/
void LED_OFF(uint16_t LEDx)
{
GPIOA->ODR &= ~LEDx;
}
/*
* 翻转LEDx
*/
void LED_Turn(uint16_t LEDx)
{
/* 如果是关闭那么就打开,如果是打开那么就关闭 */
if((GPIOA->ODR & LEDx) == 0)//关闭
{
LED_ON(LEDx);
}else{
LED_OFF(LEDx);
}
}
③LED.h的文件代码如下:
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h" // Device header
#define PA_0 0x00000003 //GPIO_CRH_MODE0
#define PA_1 0x00000030 //GPIO_CRH_MODE1
#define PA_2 0x00000300 //GPIO_CRH_MODE2
#define PA_3 0x00003000 //GPIO_CRH_MODE3
#define PA_4 0x00030000 //GPIO_CRH_MODE4
#define PA_5 0x00300000 //GPIO_CRH_MODE5
#define PA_6 0x03000000 //GPIO_CRH_MODE6
#define PA_7 0x30000000 //GPIO_CRH_MODE7
#define PA_ALL 0x33333333 //GPIO_CRH_MODE
#define LED0 GPIO_ODR_ODR0
#define LED1 GPIO_ODR_ODR1
#define LED2 GPIO_ODR_ODR2
#define LED3 GPIO_ODR_ODR3
#define LED4 GPIO_ODR_ODR4
#define LED5 GPIO_ODR_ODR5
#define LED6 GPIO_ODR_ODR6
#define LED7 GPIO_ODR_ODR7
void LED_Init(uint32_t PA_x);
void LED_ON(uint16_t LEDx);
void LED_OFF(uint16_t LEDx);
void LED_Turn(uint16_t LEDx);
#endif
④主函数文件的代码如下:
#include "stm32f10x.h"
#include "Delay.h"
#include "LED.h"
#include "Key.h"
/*
使用外部中断按键控制LED
*/
int main(void)
{
LED_Init(PA_0);//初始化PA0引脚
Key_EXTI_Init();//初始化PB0引脚的EXTI
while(1)
{
}
}
实物演示效果如下:
外部中断