外部中断实验(瞎写)
本文以 正点原子 的战舰 为 基础 进行实验, 实验目标为 按键0 熄灭所有, 按键2点亮led0,led1,按键1 蜂鸣器响,原理图如下:
蜂鸣器:
![](https://i-blog.csdnimg.cn/blog_migrate/ae211388408362043ea4621cc62ee293.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a3b5fe6e63807ecc5b0ad1129c5dc3a3.png)
LED:
![](https://i-blog.csdnimg.cn/blog_migrate/e58affe0f138407cb383a9794085658b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c1d1ba55b80240fd2c47864316e00f9c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/78a37725a3e08d68a261e31fee01e646.png)
按键:
![](https://i-blog.csdnimg.cn/blog_migrate/0e4bbd1bd6e0d74567d82ae0f3b05f92.png)
![](https://i-blog.csdnimg.cn/blog_migrate/72c0604799faa41b300f4514937bf181.png)
蜂鸣器和LED 正常配置即可
#ifndef BSP_LED_H
#define BSP_LED_H
#include "stm32f10x.h"
#define BSP_BEEP_PIN GPIO_Pin_8
#define BSP_BEEP_GPIO_PORT GPIOB
#define BSP_BEEP_GPIO_CLK RCC_APB2Periph_GPIOB
#define BSP_BEEP_ON 1
#define BSP_BEEP_OFF 0
#define digitalHi(p,i) {p->BSRR=i;} //设置为高电平
#define digitalLo(p,i) {p->BSRR=i<<16;} //输出低电平
// #define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态
#define BEEP_TOGGLE digitalToggle(BSP_BEEP_GPIO_PORT,BSP_BEEP_PIN)
#define BEEP_ON digitalHi(BSP_BEEP_GPIO_PORT,BSP_BEEP_PIN)
#define BEEP_OFF digitalLo(BSP_BEEP_GPIO_PORT,BSP_BEEP_PIN)
void BEEP_GPIO_Config(void);
#endif // !BSP_LED_H
#include "bsp_beep.h"
/**
* @brief 蜂鸣器初始化
*
*/
void BEEP_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(BSP_BEEP_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = BSP_BEEP_PIN;
GPIO_Init(BSP_BEEP_GPIO_PORT, &GPIO_InitStructure);
}
#ifndef __BSP_LED_H
#define __BSP_LED_H
#include "stm32f10x.h"
//引脚定义
/*******************************************************/
//R 红色灯
#define LED1_PIN GPIO_Pin_5
#define LED1_GPIO_PORT GPIOB
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB
//G 绿色灯
#define LED2_PIN GPIO_Pin_5
#define LED2_GPIO_PORT GPIOE
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOE
// //B 蓝色灯
// #define LED3_PIN GPIO_Pin_8
// #define LED3_GPIO_PORT GPIOF
// #define LED3_GPIO_CLK RCC_AHB1Periph_GPIOF
/************************************************************/
/** 控制LED灯亮灭的宏,
* LED低电平亮,设置ON=0,OFF=1
* 若LED高电平亮,把宏设置成ON=1 ,OFF=0 即可
*/
#define ON 0
#define OFF 1
/* 带参宏,可以像内联函数一样使用 */
#define LED1(a) if (a) \
GPIO_SetBits(LED1_GPIO_PORT,LED1_PIN);\
else \
GPIO_ResetBits(LED1_GPIO_PORT,LED1_PIN)
#define LED2(a) if (a) \
GPIO_SetBits(LED2_GPIO_PORT,LED2_PIN);\
else \
GPIO_ResetBits(LED2_GPIO_PORT,LED2_PIN
/* 直接操作寄存器的方法控制IO */
#define digitalHi(p,i) {p->BSRR=i;} //设置为高电平
#define digitalLo(p,i) {p->BSRR=i<<16;} //输出低电平
#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态
/* 定义控制IO的宏 */
#define LED1_TOGGLE digitalToggle(LED1_GPIO_PORT,LED1_PIN)
#define LED1_OFF digitalHi(LED1_GPIO_PORT,LED1_PIN)
#define LED1_ON digitalLo(LED1_GPIO_PORT,LED1_PIN)
#define LED2_TOGGLE digitalToggle(LED2_GPIO_PORT,LED2_PIN)
#define LED2_OFF digitalHi(LED2_GPIO_PORT,LED2_PIN)
#define LED2_ON digitalLo(LED2_GPIO_PORT,LED2_PIN)
void LED_GPIO_Config(void);
#endif /* __LED_H */
#include "bsp_led.h"
/**
* @brief 初始化控制LED的IO
* @param 无
* @retval 无
*/
void LED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd(LED1_GPIO_CLK | LED2_GPIO_CLK, ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED1_PIN;
/*设置引脚模式为输出模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为2MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED2_PIN;
GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
}
/*********************************************END OF FILE**********************/
然后我们来看一下按键如何处理
103中有20个线路可以被配置成软件中断/事件线 ,其中通用I/O端口以下图的方式连接到16个外部中断/事件线上
![](https://i-blog.csdnimg.cn/blog_migrate/e0ddb364180e791d9d637371bca90f9d.png)
但是预定义的外部中断服务函数只有6个
EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler
0~1各自独占一个, 5~9用一个, 10~15用一个,按键的三个引脚 分别为 GPIOE2~4,正常配置按键
按键的代码有些杂乱,就不贴了。
因为涉及到了外部中断,就和 AFIO 扯上关系了,所以需要使能 AFIO 的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); ,如图 AFIO 中有 与外部中断相关的寄存器
![](https://i-blog.csdnimg.cn/blog_migrate/63a0ce18bc69ba48217c9aa958c81300.png)
上面说了 通用I/O端口连接到16个外部中断/事件线上 ,所以估计是靠AFIO来实现引脚复用的, 所以,在去手册里看库函数之前,来看看 这个寄存器 配置啥的
![](https://i-blog.csdnimg.cn/blog_migrate/f6234cb732ae6e244ba6a8baf011c979.png)
随手摘了一个,其他的差不多,很显然 这就是一个 连接 GPIO端口 和 中断源产生的 引脚 的寄存器, 理论上,初始化完毕寄存器后 AFIO_EXTICR2 的 值应该是 0x04, AFIO_EXTICR1的值因该是0x44
好了,接下来去找一下库函数(个人习惯库函数,因为从野火的407入手的)
凭直觉,这玩意肯定和exti和gpio有关,不是gpio_extixxx 就是 exti_gpioxxx ,果然,函数长这样 void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource) 当然不能这么粗暴的找,直接从 库文件里找 stm32f10x_gpio.h 中最底下 直接看函数 这是我看到的
![](https://i-blog.csdnimg.cn/blog_migrate/e07197a29fd871e3eaed04a7e8ffee52.png)
扫下来好像就是这一个 有 EXTI 的, 看一看.c文件怎么写的
![](https://i-blog.csdnimg.cn/blog_migrate/c3f48c4a21690b31a1dc0d7aae863fb4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0aef13a30aeb62537d912b5d4bf1a94e.png)
看这个英文因该没错了, 选择GPIO引脚作为 中断源使用 ,而且所操作的 也是 AFIO 的 EXTICR寄存器.
brief 是函数功能简介 param这里是介绍参数 retval是返回值
理论上 连接 连接 PE2 为 外部中断源2 的 代码 为 GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
这里说过如何寻找库函数后,后面就不再重复说明了,找到这个函数名后,更详细的信息可以去库文件
![](https://i-blog.csdnimg.cn/blog_migrate/2e85a2c6878c809116d588dc62fd1786.png)
配置完引脚剩下的就是外部中断的部分,嗯? 等等,中断 咱们还有 中断优先级需要配置,嗯……先配置外部中断相关的寄存器吧,默默的打开了参考手册,
又是功能框图
![](https://i-blog.csdnimg.cn/blog_migrate/204db2af80aa18466fcc5468e883d73b.png)
红色的线是产生中断的,蓝色线是产生事件的
信号从输入线流入 边沿检测电路,边沿检测电路会根据上升沿触发选择寄存器(EXTI_RTSR) 和 下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。
边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给 或门,否则输出无效信号 0。
而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变程,可以是上升沿触发、下降沿触发 or 上升沿和下降沿都触发。
或门一个输入来 边沿检测 电路,另外一输入来自软件中断事件寄存器(EXTI_SWIER)。
EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线(我到现在都没明白事件是啥)。
或门 有1即为真,所以这两个输入随便一个有有效信号 1 就可以输出 1 给 上面的与门 或者 给下面的与门 。
上面的与门一个输入 或门 ,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。
与门电路要求输入都为 1 才输出 1,导致的结果如果 EXTI_IMR 设置为 0 时,那不管 或门 电路的输出信号是 1 还是 0,最终编号 与门 电路输出的信号都为 0;
如果 EXTI_IMR 设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。
与门 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定 与门 电路输出为 1 就会把 EXTI_PR 对应位置 1。
EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制
下面那条线看不太明白 所以就 说了,感兴趣自行研究。
上面 涉及 到了 EXTI_RTSR , EXTI_FTSR ,EXTI_IMR , EXTI_SWIER, EXTI_PR ,我们按顺序来看一下
![](https://i-blog.csdnimg.cn/blog_migrate/748195e8e454f13c85be722ab1d9fb4c.png)
由于 EXTI_RTSR , EXTI_FTSR 两个寄存器类似,所以这里只看一个。
按照描述,以及按键电路,如果要设计抬手检测那就需要写 上升沿加测,如果是按下的话,则是下降沿检测
无论配置为哪一种 寄存器的值因该为0x1c 4 3 2 三位置1
![](https://i-blog.csdnimg.cn/blog_migrate/50705dc7579fbe39df784a3e1084dde8.png)
作用是开放 中断 ,所以理论值因该是 4 3 2 置1 所以也是0x1c ,仿真的之后可以特别注意一下,是不是 中断被屏蔽了
![](https://i-blog.csdnimg.cn/blog_migrate/464023fdb48741b6f5f9ca552785f101.png)
这个类似于标志寄存器,当 符合触发源限制时, 触发源锁在的线 置 1 理论上我们这个只会出现3各位置1, 需要手动将该位置0
![](https://i-blog.csdnimg.cn/blog_migrate/f89af03cef257a2521e183cdfd1db825.png)
这个是挂起中断寄存器,因为所有中断都默认挂起,所以默认为0,然后 不需要手动清0(其实,我也不太明白这个寄存器,仿真的时候数据一直没变,奇奇怪怪的,还是我菜了)
NVIC 自行配置优先级
下面就是,代码实现了,函数自行到函数库查找:
函数
![](https://i-blog.csdnimg.cn/blog_migrate/0d5aa8058938889d326ea9cc085379f8.png)
结构体
![](https://i-blog.csdnimg.cn/blog_migrate/a01bc698e9b69902dcca4615bc6bdf26.png)
余下的枚举自行查看 标准库.chm
exti配置文件
#include "bsp_exti.h"
#include "bsp_key.h"
/**
* @brief
*
* @param x 中断源
* @param ParentPriority 父优先级
* @param subPriority 子优先级
*/
static void NVIC_Configuration(enum IRQn x, int ParentPriority, int subPriority)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 配置NVIC为优先级组2 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置中断源:x */
NVIC_InitStructure.NVIC_IRQChannel = x;
/* 配置抢占优先级:3 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
/* 配置子优先级:4 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
/* 使能中断通道 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
#if 1
/**
* @brief EXTI 外部中断引脚初始化
*
*/
static void EXTI_GPIO_Init(void)
{
// 直接用的按键初始化
Key_GPIO_Config();
// GPIO_InitTypeDef GPIO_InitStructure;
// RCC_APB2PeriphClockCmd(EXTI1_INT_GPIO_CLK | EXTI2_INT_GPIO_CLK | EXTI3_INT_GPIO_CLK, ENABLE);
// //选择按键1的引脚
// GPIO_InitStructure.GPIO_Pin = EXTI1_INT_GPIO_PIN;
// GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
// /* 使用上面的结构体初始化按键 */
// GPIO_Init(EXTI1_INT_GPIO_PORT, &GPIO_InitStructure);
// GPIO_InitStructure.GPIO_Pin = EXTI2_INT_GPIO_PIN;
// GPIO_Init(EXTI2_INT_GPIO_PORT, &GPIO_InitStructure);
// GPIO_InitStructure.GPIO_Pin = EXTI3_INT_GPIO_PIN;
// GPIO_Init(EXTI2_INT_GPIO_PORT, &GPIO_InitStructure);
// 使能 AFIO 寄存器
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
}
/**
* @brief EXTI 配置
*
*/
void EXTI_Init_Config(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
// GPIO 配置
EXTI_GPIO_Init();
// 连接中断源
GPIO_EXTILineConfig(EXTI1_INT_EXTI_PORTSOURCE, EXTI1_INT_EXTI_PINSOURCE);
GPIO_EXTILineConfig(EXTI2_INT_EXTI_PORTSOURCE, EXTI2_INT_EXTI_PINSOURCE);
GPIO_EXTILineConfig(EXTI3_INT_EXTI_PORTSOURCE, EXTI3_INT_EXTI_PINSOURCE);
/* 选择 EXTI 中断源 */
EXTI_InitStructure.EXTI_Line = EXTI1_INT_EXTI_LINE;
/* 中断模式 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
/* 下降沿触发 */
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
/* 使能中断/事件线 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
/* 选择 EXTI 中断源 */
EXTI_InitStructure.EXTI_Line = EXTI2_INT_EXTI_LINE;
EXTI_Init(&EXTI_InitStructure);
/* 选择 EXTI 中断源 */
EXTI_InitStructure.EXTI_Line = EXTI3_INT_EXTI_LINE;
EXTI_Init(&EXTI_InitStructure);
// 优先级配置
NVIC_Configuration(EXTI1_INT_EXTI_IRQ, 2, 3);
NVIC_Configuration(EXTI2_INT_EXTI_IRQ, 2, 2);
NVIC_Configuration(EXTI3_INT_EXTI_IRQ, 2, 1);
}
#endif
下面是中断服务函数:
/**
* @brief 按键2
*
*/
void EXTI2_IRQHandler(void)
{
// 消抖
delayms(10);
if (!KEY2)
{// 这里建议使用EXTI_GetITStatus(EXTI_Linex)!=RESET进行判断
//我这里是偷懒,直接用按键
LED1_ON;
LED2_ON;
}
// 消除 PR 寄存器 的标志位
EXTI_ClearITPendingBit(EXTI1_INT_EXTI_LINE);
}
/**
* @brief 按键1
*
*/
void EXTI3_IRQHandler(void)
{
delayms(10);
if (!KEY1)
{
BEEP_ON;
}
EXTI_ClearITPendingBit(EXTI2_INT_EXTI_LINE);
}
/**
* @brief 按键0
*
*/
void EXTI4_IRQHandler(void)
{
delayms(10);
if (!KEY0)
{
LED1_OFF;
LED2_OFF;
BEEP_OFF;
}
EXTI_ClearITPendingBit(EXTI3_INT_EXTI_LINE);
}
主函数我直接留空 初始化后while(1)
到这里,整个实验也就完成了,还有很多没明白的地方,例如事件,
如果无法达到效果,可以注意一下, AFIO是否打开,以及, 中断号是否正确, PR寄存器标志位是否清除.
第一份32的文章,个人能力有限,理解程度,如有错误或建议,望各位大佬留言指出 orz orz orz.