2025年基础篇006. 外部中断

基础篇006. 外部中断目录 1 外部中断 1 1 外部中断概述 1 2 GPIO 外部中断 2 实验任务 3 硬件原理 4 利用 STM32CubeMX 创建 MDK 工程 5 在 MDK 中自建驱动库的工程设置 5 1 创建用户函数 5 2 修改中断回调函数 5 3 main 函数修改 6 调试与验证 1 外部中断 1 1

大家好,我是讯享网,很高兴认识大家。

目录

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.总结

 

 

 

 

小讯
上一篇 2025-03-13 22:23
下一篇 2025-04-07 23:34

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/19973.html