STM32——外部中断
外部中断实验(瞎写)
本文以 正点原子 的战舰 为 基础 进行实验, 实验目标为 按键0 熄灭所有, 按键2点亮led0,led1,按键1 蜂鸣器响,原理图如下:
蜂鸣器:
LED:
按键:
蜂鸣器和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个外部中断/事件线上
但是预定义的外部中断服务函数只有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 中有 与外部中断相关的寄存器
上面说了 通用I/O端口连接到16个外部中断/事件线上 ,所以估计是靠AFIO来实现引脚复用的, 所以,在去手册里看库函数之前,来看看 这个寄存器 配置啥的
随手摘了一个,其他的差不多,很显然 这就是一个 连接 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 中最底下 直接看函数 这是我看到的
扫下来好像就是这一个 有 EXTI 的, 看一看.c文件怎么写的
看这个英文因该没错了, 选择GPIO引脚作为 中断源使用 ,而且所操作的 也是 AFIO 的 EXTICR寄存器.
brief 是函数功能简介 param这里是介绍参数 retval是返回值
理论上 连接 连接 PE2 为 外部中断源2 的 代码 为 GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
这里说过如何寻找库函数后,后面就不再重复说明了,找到这个函数名后,更详细的信息可以去库文件 里查找, 个人觉得自己 查看 库文件也行,毕竟文档也是按照注释生成的.
配置完引脚剩下的就是外部中断的部分,嗯? 等等,中断 咱们还有 中断优先级需要配置,嗯……先配置外部中断相关的寄存器吧,默默的打开了参考手册,
又是功能框图
红色的线是产生中断的,蓝色线是产生事件的
信号从输入线流入 边沿检测电路,边沿检测电路会根据上升沿触发选择寄存器(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 ,我们按顺序来看一下
由于 EXTI_RTSR , EXTI_FTSR 两个寄存器类似,所以这里只看一个。
按照描述,以及按键电路,如果要设计抬手检测那就需要写 上升沿加测,如果是按下的话,则是下降沿检测
无论配置为哪一种 寄存器的值因该为0x1c 4 3 2 三位置1
作用是开放 中断 ,所以理论值因该是 4 3 2 置1 所以也是0x1c ,仿真的之后可以特别注意一下,是不是 中断被屏蔽了
这个类似于标志寄存器,当 符合触发源限制时, 触发源锁在的线 置 1 理论上我们这个只会出现3各位置1, 需要手动将该位置0
这个是挂起中断寄存器,因为所有中断都默认挂起,所以默认为0,然后 不需要手动清0(其实,我也不太明白这个寄存器,仿真的时候数据一直没变,奇奇怪怪的,还是我菜了)
NVIC 自行配置优先级
下面就是,代码实现了,函数自行到函数库查找:
函数
结构体
余下的枚举自行查看 标准库.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.