大家好,欢迎来到IT知识分享网。
目录
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。
2. 实验任务
利用STM32CubeMX,创建MDK工程,采用外部中断方式触发按键,实现对LED的控制。WK_UP 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,效果同 WK_UP; KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
3. 硬件原理
1) 指示灯 DS0、 DS1分别连接到PA8和PD2.
2) 3个按键: KEY0、 KEY1 和 KEY_UP,分别连接到PA13、PA15、PA0。
4. 利用STM32CubeMX创建MDK工程
选择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口设置后的参数见上图。
参数说明:
- 开启下降沿触发中断:即在按下按键时电平由高变为低时触发,则在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。
- 抢占优先级,数字越小,优先级越高
- 若抢占优先级相同,判断子优先级,同样,数字越小,优先级越高
结合开发版的硬件电路,选择Clock Configuration,做如下配置:
项目配置:
在Project Manager下的Project中设置工程名称和工程路径,并选择编译软件。取消勾选Use lastest available version,选择其他版本:
代码生成设置:
在Code Generate中选择第二个,然后Generate Code,即生成代码:
可以打开MDK工程编辑了。
5.在MDK中自建驱动库的工程设置
5.1创建用户函数
点击上图中的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~(最大值即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”,见下图:
提示:课程中后续的用户自建驱动库均保存在本文件夹,以后的范例不会如此详细阐述,相关内容请到本节参考。
添加文件步骤见下图:
添加文件后效果图:
执行完上述步骤后,在左侧的Project项目框中,可以看到BSP项目和user.c和key.c文件:
Include目录设置:
点击工具栏中的魔法棒按钮,选择C++选项,在
在打开的对话框中添加BSP文件夹,步骤见下图:
完成后的效果如下:
连续点击OK,回到主界面。
5.2修改中断回调函数
打开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文件中添加头文件:
5.3 main函数修改:
打开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博客
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/
将程序下载到开发板进行验证:
7.总结
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/122346.html