中级篇 第十八课《EXTI - GPIO外部中断》课后作业
中级篇 第十八课题目:
在应用层面对于中断的理解:
在中断编程之前,先浅谈一下实际的运用场景。拿按键举例,要为一个单片机编程实现按键检测然后进行相应操作。如果不使用中断,能否实现?是否会遇到问题?
可以实现。但是会有BUG,最大的问题就是实时性。什么是实时性问题?就是要保证一直在检测按键是否被按下。通常这类程序都是一个循环结构,如果不这么做,
假设程序运行周期是5T,按键检测所需时间为1T。按键检测功能只有在那个“1T”内是有效的,在剩下的“4T”都无效。
如果要保证实时性,一种可能的解决方案是“多线程”,引入操作系统的概念。这就出现了例如UCOS、FreeRTOS、Vxworks这类操作系统。
如果不使用操作系统纯靠编程,也可以实现,但是会比较复杂。可能的方案是利用算法实现时间片轮转,模拟多线程。
对于一些能力较弱的单片机,这些单片机可能不能搭载实时操作系统,自身结构可能也比较简单。曾经我在Arduino单片机上做过按键检测,Arduino结构相对简单,
主函数本身是一个循环体。那个项目除了要在循环体里除了实现按键检测,还要做别的,比如时钟累加、温度检测。这样对于实时性的干扰是很强的,按键总是不灵敏。
当时我没有想到使用中断的方式,由于没有时间模块,定义了三个变量(时、分、秒)来模拟,每一次循环秒+1,判断秒=60的时候,分+1,秒清零。判断分=60的时候,
时+1,分清零。每次循环的延时都是1000ms(使用的是Arduino库函数自带的delay),这样可以实现时间显示,但误差会非常大。我想到的方法是把1秒(1000ms)切成
200个小份,一份5毫秒。每一个5毫秒都检测一下按键,但这样的解决方案也是治根不治本。而使用中断似乎能解决按键实时检测的问题。
EXTI中断编程:
移植点亮led的例程,在User文件夹创建exti文件夹,里面创建exti.c以及它配套的头文件exti.h。
EXTI中断步骤:
1.初始化NVIC,用于处理中断
2.初始化要连接到EXTI的GPIO
3.初始化EXTI用于产生中断/事件
4.编写中断服务函数stm32f10x_it.c
5.main函数
NVIC要先配置,先写NVIC初始化函数
NVIC配置步骤:
1.配置中断优先级分组
2.配置NVIC寄存器,初始化NVIC_InitTypeDef
NVIC_InitTypeDef:
NVIC_IRQChannel:中断源
NVIC_IRQChannelPreemptionPriority:抢占优先级
NVIC_IRQChannelSubPriority:子优先级
NVIC_IRQChannelCmd:使能或者失能
3.编写中断服务函数
NVIC结构体原型存放于misc.h
两个按键分别位于GPIOA P0 和GPIOC P13,分别为两个按键写中断。
1.两个中断都分在优先级组一
2.P0使用的是EXTI0线,中断源是EXTI0_IRQn;P13使用的是EXTI13线,这里需要注意:中断源是EXTI15_10_IRQn 而不是EXTI13_IRQn。
从EXTI5开始,命名就发生变化了。
3.抢占优先级都写1,次优先级P0写1、P13写2。
4.分别使能,使用Init函数初始化结构体
因为NVIC函数只用于exti.c,我们不希望被别的程序调用,在函数名字前面加一个static关键字以保护函数。
然后写EXTI配置函数
1.先调用刚才写好的NVIC配置函数
2.初始化所用到的GPIO
1)使能RCC_APB2时钟
2)设置管脚
3)设置输入模式
用Init函数初始化
为了增加可移植性,关于硬件GPIO连接的部分都写到了头文件的宏定义
GPIO初始化过程,按键使用浮空输入模式,输入没有速度。
3.接下来是EXTI初始化
仿照GPIO初始化步骤
先使能时钟,EXTI使用的是AFIO的时钟
AF代表复用(Alternate Functions),何为复用?打个比方,一间房间有一扇门,里面的人出去要经过这扇门,外边的人进来也要经过这扇门,这扇门就被复用了。
由于按键GPIO输入 和 外部中断EXTI都使用了同一个GPIO的同一个Pin,可以视为GPIO功能复用,所以选择AFIO时钟。(我是这么理解的,网上没有查到具体的)
和GPIO不同的一点,先要用EXTILineConfig函数选择,告诉程序哪个GPIO的哪个Pin接入中断/事件线,形参/实参如图。
同样,硬件相关的内容也被写进了头文件的宏定义
初始化EXTI结构体步骤:
1)EXTI_Line:用于产生 中断/事件 的线
2)EXTI_Mode:EXTI模式(中断/事件)
3)EXTI_Trigger:触发(上升沿/下降沿/上升下降沿)
4)EXTI_LineCmd:使能或者失能(IMR/EMR)
用Init函数初始化
结构体原型在stm32f10x_exti.h可以找到
模式有两种选择,选择“中断模式”
触发方式有三种,选择“上升沿触发”
一图看懂什么是上升沿什么是下降沿:
LineCmd只有使能和失能,要使用当然要使能
EXTI初始化全过程
函数写完需要到头文件声明
离配置完EXTI还剩最后一步,配置中断服务函数,在stm32f10x_it.c末尾添加
这里函数的命名很重要,要和系统配置的一致,不然会出错。
命名方式在启动文件startup_stm32f10x_hd.s可以找到
开始写中断服务函数
先判断中断标志位是否为1,为1则触发中断
定义RESET=0
if的大括号内填写需要执行的操作,这里填写的是LED灯的反转函数。
在执行完中断服务函数以后要对标志位清零,不然就陷入死循环了。
清零函数:
LED取反函数定义:(这部分在led.c内 和之前例程一致)
中断服务函数整体:
主函数只需要调用之前写好的GPIO初始化函数和EXTI初始化函数即可
while 1 为了让其保持运行。
最终代码:
bsp_exti.c:
- #include "bsp_exti.h"
- static void EXTI_NVIC_Config(void)
- {
- NVIC_InitTypeDef NVIC_InitStruct;
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
- NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStruct);
-
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
- NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
- NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStruct);
-
- }
- void EXTI_Key_Config(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- EXTI_InitTypeDef EXTI_InitStruct;
-
- //配置中断优先级
- EXTI_NVIC_Config();
-
- //初始化GPIO
- RCC_APB2PeriphClockCmd(KEY1_INT_GPIO_CLK, ENABLE);
- GPIO_InitStruct.GPIO_Pin = KEY1_INT_GPIO_PIN;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- GPIO_Init(KEY1_INT_GPIO_PORT, &GPIO_InitStruct);
-
- RCC_APB2PeriphClockCmd(KEY2_INT_GPIO_CLK, ENABLE);
- GPIO_InitStruct.GPIO_Pin = KEY2_INT_GPIO_PIN;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- GPIO_Init(KEY2_INT_GPIO_PORT, &GPIO_InitStruct);
-
- //初始化EXTI
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
-
- EXTI_InitStruct.EXTI_Line = EXTI_Line0;
- EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
- EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
- EXTI_InitStruct.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStruct);
-
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);
-
- EXTI_InitStruct.EXTI_Line = EXTI_Line13;
- EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
- EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
- EXTI_InitStruct.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStruct);
- }
复制代码
bsp_exti.h:
- #ifndef __BSP_EXTI_H
- #define __BSP_EXTI_H
- #include "stm32f10x.h"
- #define KEY1_INT_GPIO_PIN GPIO_Pin_0
- #define KEY1_INT_GPIO_PORT GPIOA
- #define KEY1_INT_GPIO_CLK RCC_APB2Periph_GPIOA
- #define KEY2_INT_GPIO_PIN GPIO_Pin_13
- #define KEY2_INT_GPIO_PORT GPIOC
- #define KEY2_INT_GPIO_CLK RCC_APB2Periph_GPIOC
- #define KEY1_INT_EXTI_Line EXTI_Line0
- #define KEY1_INT_GPIO_PortSrc GPIO_PortSourceGPIOA
- #define KEY1_INT_GPIO_PinSrc GPIO_PinSource0
- #define KEY2_INT_EXTI_Line EXTI_Line13
- #define KEY2_INT_GPIO_PortSrc GPIO_PortSourceGPIOC
- #define KEY2_INT_GPIO_PinSrc GPIO_PinSource13
- void EXTI_Key_Config(void);
- #endif /*__BSP_EXTI_H*/
复制代码
stm32f10x_it.c:
- /*....此处省略139行...*/
- void EXTI0_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line0)!=RESET)//检测中断标志位
- {
- LED_TOGGLE(LED_Red);
- }
- EXTI_ClearITPendingBit(EXTI_Line0);//清除中断标志位
- }
- void EXTI15_10_IRQHandler(void)
- {
- if(EXTI_GetITStatus(EXTI_Line13)!=RESET)
- {
- LED_TOGGLE(LED_Green);
- }
- EXTI_ClearITPendingBit(EXTI_Line13);
- }
- /*如果需要实现别的颜色,直接修改括号里的实参*/
复制代码
bsp_led.c:
- #include "bsp_led.h"
- void GPIO_LED_Config(uint32_t CLK, uint16_t PIN, GPIO_TypeDef *PORT)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
-
- RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
-
- GPIO_InitStruct.GPIO_Pin = PIN;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(PORT, &GPIO_InitStruct);
- GPIO_SetBits(PORT,PIN);
- }
复制代码
bsp_led.h:
- #ifndef __BSP_LED_H
- #define __BSP_LED_H
- #include "stm32f10x.h"
- #define LED_Red GPIO_Pin_5
- #define LED_Green GPIO_Pin_0
- #define LED_Blue GPIO_Pin_1
- #define LED_GPIO_PORT GPIOB
- #define LED_GPIO_CLK RCC_APB2Periph_GPIOB
- #define LED_TOGGLE(color) {LED_GPIO_PORT->ODR ^= color;}
- void GPIO_LED_Config(uint32_t CLK, uint16_t PIN, GPIO_TypeDef *PORT);
- #endif /*__BSP_LED_H*/
复制代码
main.c:
- #include "stm32f10x.h" // 相当于51单片机中的 #include <reg51.h>
- #include "bsp_led.h"
- #include "bsp_exti.h"
- int main(void)
- {
- GPIO_LED_Config(LED_GPIO_CLK, LED_Red|LED_Green|LED_Blue, LED_GPIO_PORT);
- EXTI_Key_Config();
- while(1){}
- }
复制代码
|