目录
1. 外部中断
1.1 外部中断概述
1.2 GPIO外部中断
2. 实验任务
3. 硬件原理
4. 利用STM32CubeMX创建MDK工程
5.在MDK中自建驱动库的工程设置
5.1创建用户函数
5.2修改中断回调函数
5.3 main函数修改:
6.调试与验证
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).如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行哪个中断。
STM32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组位一个单位的,同组间的外部中断同一时间只能使用一个。比如说,PA0,PB0,PC0,PD0,PE0,PF0,PG0这些为1组,如果我们使用PA0作为外部中断源,那么别的就不能够再使用了,在此情况下,我们智能使用类似于PB1,PC2这种末端序号不同的外部中断源。每一组使用一个中断标志EXTIx。EXTI0 – EXTI4这5个外部中断有着自己的单独的中断响应函数,EXTI5-9共用一个中断响应函数,EXTI10-15共用一个中断响应函数。 对于中断的控制,STM32有一个专用的管理机构:NVIC。
利用STM32CubeMX,创建MDK工程,采用外部中断方式触发按键,实现对LED的控制。WK_UP 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,效果同 WK_UP; KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
1) 指示灯 DS0、 DS1分别连接到PA8和PD2.
2) 3个按键: KEY0、 KEY1 和 KEY_UP,分别连接到PA13、PA15、PA0。
选择File下的New Project:
选择芯片类型(本文为stm32f103RBt6),选择下边的item,然后Start Project:
点击左侧的System Core下的SYS,将Debug设置为Serial Wire:
配置时钟:将RCC下的HSE设置为Crystal/Ceramic Resonator
结合开发版的硬件电路,进行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内部设置上下拉:
各IO口设置后的参数见上图。
参数说明:
配置NVIC
中断优先级分组规则Priority Group默认为4个比特位,一般情况下不改。勾选刚刚配置的外部中断线0和13,并配置抢占优先级Preemption Priority 和响应优先级Sub Priority。
结合开发版的硬件电路,选择Clock Configuration,做如下配置:
项目配置:
在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件。取消勾选Use lastest available version,选择其他版本:
代码生成设置:
在Code Generate中选择第二个,然后Generate Code,即生成代码:
可以打开MDK工程编辑了。
点击上图中的Open Floder,打开工程文件夹。在工程文件夹内部新建“BSP” 文件夹:
在BSP文件夹内建立自定义驱动的新文件夹:
其中Key文件夹内为键盘程序key.c和key.h,文件内容同上节博文:(140条消息) 基础篇005. 按键控制_笑春风oO的博客-CSDN博客
Global文件夹内建立程序user.c和user.h:
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=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
//延时nms
//nms:要延时的ms数
void delay_ms(uint16_t nms)
{
uint32_t i;
for(i=0;i>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
/**
* @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控制功能
//具体实现思想,参考<>第五章(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”,见下图:
提示:课程中后续的用户自建驱动库均保存在本文件夹,以后的范例不会如此详细阐述,相关内容请到本节参考。
添加文件步骤见下图:
添加文件后效果图:
执行完上述步骤后,在左侧的Project项目框中,可以看到BSP项目和user.c和key.c文件:
点击工具栏中的魔法棒按钮,选择C++选项,在
在打开的对话框中添加BSP文件夹,步骤见下图:
完成后的效果如下:
连续点击OK,回到主界面。
打开stm32f1xx_it.c中断服务函数文件,找到EXTI0中断的服务函数EXTI0_IRQHandler()。
该中断服务函数里面调用了GPIO外部中断处理函数HAL_GPIO_EXTI_IRQHandler()。编译工程后,在函数HAL_GPIO_EXTI_IRQHandler()上点右键,既可跳转到该函数:
其主要作用就是判断是几号线中断,清除中断标识位,然后调用中断回调函数 HAL_GPIO_EXTI_Callback()。
外部中断回调函数HAL_GPIO_EXTI_Callback():
该函数是弱函数,__weak是一个弱化标识,带有这个的函数就是一个弱化函数,你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数;
UNUSED(GPIO_Pin):是一个防报错的定义,当传进来的GPIO端口号没有做任何处理的时候,编译器也不会报出警告。
中断回调函数:
在stm32f1xx_it.c文件的最下面:
添加中断回调函数:HAL_GPIO_EXTI_Callback()
// 中断服务程序。在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文件中添加头文件:
打开Keil文件后,点击Application,在 main.c 文件里的 while(1) 循环内的
/* USER CODE BEGIN Includes */
和
/* USER CODE END Includes */
之间添加以下代码:
注意:添加头文件时,一定要输入路径!
在 main.c 文件里的 while(1) 循环内的
/* USER CODE BEGIN WHILE */
和
/* USER CODE END WHILE */
之间添加以下代码:
编译工程,直到输出0个错误:
再次提示:
由于MDK5.37版本以后,不在默认安装AC5,由最新版的STM32CubeMX生成的MDK代码,默认编译器是采用的Version 5。因此,如果您使用的5.37以上版本的MDK,请将编译器设置为Version 6,方法如下:
如果你需要AC5编译器,请参考如下博文安装设置:
Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载:
Keil MDK5.37以上版本自行添加AC5(ARMCC)编译器的方法_armcc下载_笑春风oO的博客-CSDN博客
如果您需要虚拟仿真调试,请参考专栏如下博文的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
将程序下载到开发板进行验证:
7.总结