基础篇006. 外部中断

基础篇006. 外部中断本文详细介绍了 STM32 的外部中断机制 包括中断概述 GPIO 外部中断的设置 以及如何利用 STM32CubeMX 创建 MDK 工程来实现基于外部中断的按键控制 LED 的功能

大家好,欢迎来到IT知识分享网。

目录

1. 外部中断

1.1 外部中断概述

1.2 GPIO外部中断

2. 实验任务

3. 硬件原理

4. 利用STM32CubeMX创建MDK工程

5.在MDK中自建驱动库的工程设置

5.1创建用户函数

5.2修改中断回调函数

5.3 main函数修改:

6.调试与验证


 

1. 外部中断

1.1 外部中断概述

ARM Coetex-M3内核共支持256个中断,其中16个内部中断,240个外部中断和可编程的256级中断优先级的设置。STM32目前支持的中断共84个(16个内部+68个外部),还有16级可编程的中断优先级的设置,仅使用中断优先级设置8bit中的高4位。

STM32F4 的每个 IO 都可以作为部中断的中断输入口。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。

STM32可支持68个中断通道,已经固定分配给相应的外部设备,每个中断通道都具备自己的中断优先级控制字节PRI_n(8位,但是STM32中只使用4位,高4位有效),每4个通道的8位中断优先级控制字构成一个32位的优先级寄存器。68个通道的优先级控制字至少构成17个32位的优先级寄存器.

4bit的中断优先级可以分成2组,从高位看,前面定义的是抢占式优先级,后面是响应优先级。按照这种分组,4bit一共可以分成5组:

第0组:所有4bit用于指定响应优先级;

第1组:最高1位用于指定抢占式优先级,后面3位用于指定响应优先级;

第2组:最高2位用于指定抢占式优先级,后面2位用于指定响应优先级;

第3组:最高3位用于指定抢占式优先级,后面1位用于指定响应优先级;

第4组:所有4位用于指定抢占式优先级。

抢占优先级和响应优先级的联系和区别(重要原则):

(1).高优先级的抢占优先级可以打断正在进行的低抢占优先级中断的;

(2).抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;

(3).抢占优先级相同的中断,当两个中断同时发生的情况下,哪一个的响应优先级高,哪个先执行;

(4).如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行哪个中断。

1.2 GPIO外部中断

STM32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组位一个单位的,同组间的外部中断同一时间只能使用一个。比如说,PA0,PB0,PC0,PD0,PE0,PF0,PG0这些为1组,如果我们使用PA0作为外部中断源,那么别的就不能够再使用了,在此情况下,我们智能使用类似于PB1,PC2这种末端序号不同的外部中断源。每一组使用一个中断标志EXTIx。EXTI0 – EXTI4这5个外部中断有着自己的单独的中断响应函数,EXTI5-9共用一个中断响应函数,EXTI10-15共用一个中断响应函数。 对于中断的控制,STM32有一个专用的管理机构:NVIC。

72ce0a5c51704e139ce6f66c63607bf9.png

2. 实验任务

利用STM32CubeMX,创建MDK工程,采用外部中断方式触发按键,实现对LED的控制。WK_UP 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,效果同 WK_UP; KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。

3. 硬件原理

3db9904b135b495db062ead628359d6c.png

 

3edb94d1d0db408391608530d516a446.png

1) 指示灯 DS0、 DS1分别连接到PA8和PD2.

2) 3个按键: KEY0、 KEY1 和 KEY_UP,分别连接到PA13、PA15、PA0。

4. 利用STM32CubeMX创建MDK工程

选择File下的New Project:

cbe0969e8205488488c58c6dcd14d40b.png

选择芯片类型(本文为stm32f103RBt6),选择下边的item,然后Start Project:

4f79cbcd25c34a7490920d58cff912b2.png

点击左侧的System Core下的SYS,将Debug设置为Serial Wire:

6684ca83b856425dad8f4b0e559da9c4.png

配置时钟:将RCC下的HSE设置为Crystal/Ceramic Resonator

34a9b98df2ec4948b6d6c6dd2122ba9e.png

结合开发版的硬件电路,进行GPIO设置

选择GPIO,依次将PA8、PD2设置为GPIO_Output,3个按键对应的IO口设置为输入,KEY0、 KEY1 和 KEY_UP,分别连接到PA13、PA15、PA0。

KEY0(PA13)和KEY1(PA15)是低电平有效的,而WK_UP(PA0)是高电平有效的,需要将PA13、PA15、PA0设置为GPIO_EXTI0、GPIO_EXTI13、GPIO_EXTI15。

,在STM32内部设置上下拉:

bb57ee8f0e1b4a248fc6fb6c6bef25fc.png

各IO口设置后的参数见上图。

参数说明:

  • 开启下降沿触发中断:即在按下按键时电平由高变为低时触发,则在GPIO mode中选择External Interrupt Mode with Falling edge trigger detection
  • 开启上升沿触发中断:即在按下按键后松开时电平由低变为高时触发,则在GPIO mode中选择External Interrupt Mode with Rising edge trigger detection
  • 开启下降沿上升沿都触发中断:即在按下时触发,松开时再次触发,则在GPIO mode中选择External Interrupt Mode with Rising/Falling edge trigger detection
  • 如果硬件上已外部上拉或下拉,则在GPIO Pull-up/Pull-down中选择No pull-up and no pull-down既不上拉也不下拉。
  • 如果硬件外部没有上拉,则在GPIO Pull-up/Pull-down 中选择Pull-up内部上拉电阻。

 

配置NVIC

中断优先级分组规则Priority Group默认为4个比特位,一般情况下不改。勾选刚刚配置的外部中断线0和13,并配置抢占优先级Preemption Priority 和响应优先级Sub Priority。

  • 抢占优先级,数字越小,优先级越高
  • 若抢占优先级相同,判断子优先级,同样,数字越小,优先级越高

a29e805efec143309a1af768abd8b7d0.png

 

 

结合开发版的硬件电路,选择Clock Configuration,做如下配置:

fccced0ec6c24e628f99a947c2a061e5.png

 

项目配置:

在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件。取消勾选Use lastest available version,选择其他版本:

a33025a98b204935a26207aefd09d817.png

代码生成设置:

9d93e95e5d384b3baa55eb9c6be1cb1f.png在Code Generate中选择第二个,然后Generate Code,即生成代码:dcb93932070b486baf94da833013c4ad.png

6ba4253bb9ee46bea14e20caae2812da.png

可以打开MDK工程编辑了。

 

5.在MDK中自建驱动库的工程设置

5.1创建用户函数

点击上图中的Open Floder,打开工程文件夹。在工程文件夹内部新建“BSP” 文件夹:

69dc7b9aabb74f8d85ae4f13c8005d08.png

在BSP文件夹内建立自定义驱动的新文件夹:

7c73c4ccefcc4ad49808b31abfd695ab.png

其中Key文件夹内为键盘程序key.c和key.h,文件内容同上节博文:(140条消息) 基础篇005. 按键控制_笑春风oO的博客-CSDN博客

9740ac90502e4857b98df874b25c4c6c.png

Global文件夹内建立程序user.c和user.h:

afa0a73d763342a1aa7c0779055b9a88.png

user.c和user.h文件定义了IO接口函数和延时等函数,可在所有工程中使用。

user.c代码:

#include "global/user.h" // #ifdef USE_FULL_ASSERT //当编译提示出错的时候此函数用来报告错误的文件和所在行 //file:指向源文件 //line:指向在文件中的行数 void assert_failed(uint8_t* file, uint32_t line) { while (1) { } } #endif // ! ------延时函数------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //ALIENTEK STM32F429开发板 //使用SysTick的普通计数模式对延迟进行管理(支持ucosii/ucosiii) //包括delay_us,delay_ms // static uint32_t fac_us=0; //us延时倍乘数 //初始化延迟函数 //当使用ucos的时候,此函数会初始化ucos的时钟节拍 //SYSTICK的时钟固定为AHB时钟 //SYSCLK:系统时钟频率 void delay_init(uint8_t SYSCLK) { HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK fac_us=SYSCLK; //不论是否使用OS,fac_us都需要使用 } //延时nus //nus为要延时的us数. //nus:0~(最大值即2^32/fac_us@fac_us=22.5) void delay_us(uint32_t nus) { uint32_t ticks; uint32_t told,tnow,tcnt=0; uint32_t reload=SysTick->LOAD; //LOAD的值 ticks=nus*fac_us; //需要的节拍数 told=SysTick->VAL; //刚进入时的计数器值 while(1) { tnow=SysTick->VAL; if(tnow!=told) { if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了. else tcnt+=reload-tnow+told; told=tnow; if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. } }; } //延时nms //nms:要延时的ms数 void delay_ms(uint16_t nms) { uint32_t i; for(i=0;i<nms;i++) delay_us(1000); } // ! ------软件延时函数------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> / * @DESCRIPTION: us级纯软件延时函数,不使用定时器 * @INPUT ARGS : none * @OUTPUT ARGS: none * @RETURNS : none * @NOTES : F407内部时钟为168MHz时,每个指令周期约6ns。 * @param {uint32_t} t_us */ #define INS_CPU_CYCLES 8 //一条自增减指令所需的CPU周期数 #define ADJ_CPU_CYCLES 62 //延时函数自身需要的CPU周期数(根据需要调整) void delaySoft_us(uint32_t t_us) { uint32_t count; count = (HAL_RCC_GetHCLKFreq()/*t_us - ADJ_CPU_CYCLES)/INS_CPU_CYCLES; while(count--); } / * @DESCRIPTION: ns级纯软件延时函数,不使用定时器,延时不准,需要调试 * @INPUT ARGS : none * @OUTPUT ARGS: none * @RETURNS : none * @NOTES : F407内部时钟为168MHz时,每个指令周期约6ns。 * @param {uint32_t} t_ns */ void delaySoft_ns(uint32_t t_ns) { do { ; } while(t_ns--); } // ! ------汇编指令------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> //THUMB指令不支持汇编内联 //采用如下方法实现执行汇编指令WFI #if defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= ) //AC6编译器 //以下为汇编函数(AC6) void WFI_SET(void) //执行WFI指令 { __ASM volatile("WFI"); } void INTX_DISABLE(void) //关闭所有中断 { __ASM volatile("CPSID I"); __ASM volatile("BX LR"); } void INTX_ENABLE(void) //开启所有中断 { __ASM volatile("CPSIE I"); __ASM volatile("BX LR"); } void MSR_MSP(uint32_t addr) //设置堆栈地址 { __ASM volatile("MSR MSP, r0"); __ASM volatile("BX r14"); } #elif defined ( __CC_ARM ) //AC5编译器 __asm void WFI_SET(void) { WFI; } //关闭所有中断(但是不包括fault和NMI中断) __asm void INTX_DISABLE(void) { CPSID I BX LR } //开启所有中断 __asm void INTX_ENABLE(void) { CPSIE I BX LR } //设置栈顶地址 //addr:栈顶地址 __asm void MSR_MSP(uint32_t addr) { MSR MSP, r0 //set Main Stack value BX r14 } #endif 

user.h代码:

#ifndef __USER_H #define __USER_H #ifdef __cplusplus extern "C" { #endif #include "main.h" //#define uchar unsigned char typedef unsigned char uchar; // ! --定义位带操作-->>> //位带操作,实现51类似的GPIO控制功能 //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了. //IO口操作宏定义 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x+((addr &0xFFFFF)<<5)+(bitnum<<2)) #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) //IO口地址映射 #define GPIOA_ODR_Addr (GPIOA_BASE+20) //0x #define GPIOB_ODR_Addr (GPIOB_BASE+20) //0x #define GPIOC_ODR_Addr (GPIOC_BASE+20) //0x #define GPIOD_ODR_Addr (GPIOD_BASE+20) //0x40020C14 // #define GPIOE_ODR_Addr (GPIOE_BASE+20) //0x // #define GPIOF_ODR_Addr (GPIOF_BASE+20) //0x // #define GPIOG_ODR_Addr (GPIOG_BASE+20) //0x // #define GPIOH_ODR_Addr (GPIOH_BASE+20) //0x40021C14 // #define GPIOI_ODR_Addr (GPIOI_BASE+20) //0x // #define GPIOJ_ODR_ADDr (GPIOJ_BASE+20) //0x // #define GPIOK_ODR_ADDr (GPIOK_BASE+20) //0x #define GPIOA_IDR_Addr (GPIOA_BASE+16) //0x #define GPIOB_IDR_Addr (GPIOB_BASE+16) //0x #define GPIOC_IDR_Addr (GPIOC_BASE+16) //0x #define GPIOD_IDR_Addr (GPIOD_BASE+16) //0x40020C10 // #define GPIOE_IDR_Addr (GPIOE_BASE+16) //0x // #define GPIOF_IDR_Addr (GPIOF_BASE+16) //0x // #define GPIOG_IDR_Addr (GPIOG_BASE+16) //0x // #define GPIOH_IDR_Addr (GPIOH_BASE+16) //0x40021C10 // #define GPIOI_IDR_Addr (GPIOI_BASE+16) //0x // #define GPIOJ_IDR_Addr (GPIOJ_BASE+16) //0x // #define GPIOK_IDR_Addr (GPIOK_BASE+16) //0x //IO口操作,只对单一的IO口! //确保n的值小于16! #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出 #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入 #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出 #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入 #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出 #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入 #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出 #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入 // #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出 // #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入 // #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出 // #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入 // #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出 // #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入 // #define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n) //输出 // #define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n) //输入 // #define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n) //输出 // #define PIin(n) BIT_ADDR(GPIOI_IDR_Addr,n) //输入 // #define PJout(n) BIT_ADDR(GPIOJ_ODR_Addr,n) //输出 // #define PJin(n) BIT_ADDR(GPIOJ_IDR_Addr,n) //输入 // #define PKout(n) BIT_ADDR(GPIOK_ODR_Addr,n) //输出 // #define PKin(n) BIT_ADDR(GPIOK_IDR_Addr,n) //输入 // ! --汇编函数声明-->>> void WFI_SET(void); //执行WFI指令 void INTX_DISABLE(void);//关闭所有中断 void INTX_ENABLE(void); //开启所有中断 void MSR_MSP(uint32_t addr); //设置堆栈地址 // ! --延时函数声明-->>> void delay_init(uint8_t SYSCLK); void delay_ms(uint16_t nms); void delay_us(uint32_t nus); void delaySoft_ns(uint32_t t_ns); //ns级纯软件延时函数,不使用定时器,延时不准,需要调试 void delaySoft_us(uint32_t t_us); #ifdef __cplusplus } #endif #endif /*__ USER_H__ */ 

点击菜单栏中的“Project\Manage\ Project Items…”,或者点击工具栏中的品字形按钮(见下图中的①),在Groups选项中创建驱动文件的工作目录,取名为“BSP”,见下图:

484871cb09cb49d1888fdaed58365952.png

提示:课程中后续的用户自建驱动库均保存在本文件夹,以后的范例不会如此详细阐述,相关内容请到本节参考。

添加文件步骤见下图:

26f112f7ddb44364a0afc9dbc740730b.png

添加文件后效果图:

8626242aee084dd1a45a46ca91641f58.png

执行完上述步骤后,在左侧的Project项目框中,可以看到BSP项目和user.c和key.c文件:

baac4a027e384563b9da537c324be0c2.pngInclude目录设置:

点击工具栏中的魔法棒按钮,选择C++选项,在

872f7fa970a64e38bdc7f9e91ae4df4f.png

在打开的对话框中添加BSP文件夹,步骤见下图:

fb408e96c6a1464e83c37692870192c0.png

完成后的效果如下:

18474ded24e9462d896acdddca887b40.png

连续点击OK,回到主界面。

5.2修改中断回调函数

打开stm32f1xx_it.c中断服务函数文件,找到EXTI0中断的服务函数EXTI0_IRQHandler()。

9babd15466f540fca0eb7b1c7afabb5f.png

该中断服务函数里面调用了GPIO外部中断处理函数HAL_GPIO_EXTI_IRQHandler()。编译工程后,在函数HAL_GPIO_EXTI_IRQHandler()上点右键,既可跳转到该函数:

5485d1e8a2304bcab5595c209da8d198.png

其主要作用就是判断是几号线中断,清除中断标识位,然后调用中断回调函数 HAL_GPIO_EXTI_Callback()。

beb350fce3b14ea0a1bab7728d06121d.png

外部中断回调函数HAL_GPIO_EXTI_Callback():

该函数是弱函数,__weak是一个弱化标识,带有这个的函数就是一个弱化函数,你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数;

UNUSED(GPIO_Pin):是一个防报错的定义,当传进来的GPIO端口号没有做任何处理的时候,编译器也不会报出警告。

 

中断回调函数:

在stm32f1xx_it.c文件的最下面:

49bb7e336cdf465485b8a19d64f04daf.png

添加中断回调函数:HAL_GPIO_EXTI_Callback()

49521d96744b4471898e6b3f23c24b92.png

// 中断服务程序。在HAL库中所有的外部中断服务函数都会调用此函数 // GPIO_Pin:中断引脚号 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { // GPIO_PinState pinStatus; delay_ms(100); // 消抖 switch (GPIO_Pin) { case GPIO_PIN_0: if (WK_UP == 1) { // 控制LED0,LED1互斥点亮 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8); // LED0 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8)) { HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); } } break; case GPIO_PIN_13: if (KEY0 == 0) // 控制LED0翻转 { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8); // LED0 } break; case GPIO_PIN_15: if (KEY1 == 0) { HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_2); // 控制LED1翻转 } break; } } 

在stm32f1xx_it.c文件中添加头文件:

27a6b09446b040598b5a2b63767303e8.png

 

5.3 main函数修改:

打开Keil文件后,点击Application,在 main.c 文件里的 while(1) 循环内的

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

之间添加以下代码:

d5af26b154624c709b0495a92ee9b2b8.png

注意:添加头文件时,一定要输入路径!

 

在 main.c 文件里的 while(1) 循环内的

/* USER CODE BEGIN WHILE */

/* USER CODE END WHILE */

之间添加以下代码:

12739a39da524a28975efa70b9f5688a.png

 

编译工程,直到输出0个错误:

8c021f005c0b41c38a94b69a5930cf31.png

再次提示:

由于MDK5.37版本以后,不在默认安装AC5,由最新版的STM32CubeMX生成的MDK代码,默认编译器是采用的Version 5。因此,如果您使用的5.37以上版本的MDK,请将编译器设置为Version 6,方法如下:

889f004c4ead4fe2934e01c22783d1c7.png

 

如果你需要AC5编译器,请参考如下博文安装设置:

Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载:

Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载_笑春风oO的博客-CSDN博客

6.调试与验证

如果您需要虚拟仿真调试,请参考专栏如下博文的5.1节:

基础篇003. 使用STM32CubeMX创建MDK工程,实现流水灯的仿真与下载验证:

https://blog.csdn.net/qcmyqcmy/article/details/

如果您需要在Proteus中仿真调试,请参考本专栏的博文:

基础篇004. 采用Proteus + STM32CubeMX + MDK-ARM学习流水灯:

https://blog.csdn.net/qcmyqcmy/article/details/

将程序下载到开发板进行验证:

 

6a95712de17f477b8fec320aef987677.jpeg

 

7.总结

 

 

 

 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/122346.html

(0)
上一篇 2025-10-17 12:26
下一篇 2025-10-17 12:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信