基础篇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 外部中断概述

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~190887435(最大值即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()/1000000*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 >= 6010050)  	//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)+0x2000000+((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) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14
// #define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014
// #define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414
// #define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814
// #define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14
// #define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014
// #define GPIOJ_ODR_ADDr    (GPIOJ_BASE+20) //0x40022414
// #define GPIOK_ODR_ADDr    (GPIOK_BASE+20) //0x40022814

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10
// #define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010
// #define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410
// #define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810
// #define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10
// #define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010
// #define GPIOJ_IDR_Addr    (GPIOJ_BASE+16) //0x40022410
// #define GPIOK_IDR_Addr    (GPIOK_BASE+16) //0x40022810

//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/129159801

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

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

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

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

 

6a95712de17f477b8fec320aef987677.jpeg

 

7.总结

 

 

 

 

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值