该文章记录一些我在学习过程中的一些理解,如果有误,恳请指出,感激不尽。
以下是一些我在学习外部中断过程中的一些收获,我们先从中断的理解开始
一.中断
1.不完全定义
中断是CPU对系统发生的某个事件作出的一种反应。 引起中断的事件称为中断源。中断源
向CPU提出处理的请求称为中断请求。发生中断时被打断程序的暂停点称为断点。CPU暂
停现行程序而转为响应中断请求的过程称为中断响应。处理中断源的程序称为中断处理程
序。CPU执行有关的中断处理程序称为中断处理。而返回断点的过程称为中断返回。中断
的实现实行软件和硬件综合完成,硬件部分叫做硬件装置,软件部分称为软件处理程序。
2.中断优先权
在某一时刻有几个中断源同时发出中断请求时,处理器只响应其中优先权最高的中断源。
当处理机正在运行某个中断服务程序期间出现另一个中断源的请求时,如果后者的优先
权低于前者,处理机不予理睬,反之,处理机立即响应后者,进入所谓的“嵌套中断”。中
断优先权的排序按其性质、重要性以及处理的方便性决定,由硬件的优先权仲裁逻辑或软
件的顺序询问程序来实现。
然后,大概理解了中断的概念,就该设计程序了。
二.程序设计思路
首先要明确我们要达到的结果,用三个按键(KEY1和KEY2和WKUP)控制两个外部的发光二极管,其中KEY1和KEY2分别控制LED0和LED1的亮灭,即按动一次开关,LED的状态发生一次反转,而按键WKUP控制两个灯的亮灭,即按动一次LED0和LED1状态都发生反转,并且板子通过串口像电脑发送一定的信息,告知正在正常工作。
总的说就是,板子单向向电脑发送信息,按键按下为外部中断,响应为响应的LED状态发生反转。
1.初始化IO口为输入
就是设置按键,设置三个按键为上拉或下拉输入。
2.开启IO口复用时钟,设置IO口与中断线的映射关系
STM32 的 IO 口与中断线的对应关系需要配置外部中断配置寄存器 EXTICR,这样我
们要先开启复用时钟,然后配置 IO 口与中断线的对应关系,才能把外部中断与中断线
连接起来。
3.开启与该 IO 口相对的线上中断/事件,设置触发条件
配置触发条件(上升沿、下降沿)
4.配置中断分组(NVIC),并使能中断
在 MDK 内,与 NVIC 相关的寄存器,MDK 为其定义了如下的结构体:
typedef struct {
__IO uint32_t ISER[8];/*!< Interrupt Set Enable Register*/
uint32_t RESERVED0[24];
__IO uint32_t ICER[8];/*!< Interrupt Clear Enable Register*/
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8];/*!< Interrupt Set Pending Register*/
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Interrupt Clear Pending Register*/
uint32_t RESERVED3[24];
__IO uint32_t IABR[8];/*!< Interrupt Active bit Register*/
uint32_t RESERVED4[56];
__IO uint8_t IP[240];/*!< Interrupt Priority Register, 8Bit wide */
uint32_t RESERVED5[644];
__O uint32_t STIR;/*!< Software Trigger Interrupt Register*/
} NVIC_Type;
这里我只说一个十分重要的寄存器,IP[240]:全称是:Interrupt Priority Registers,
是一个中断优先级控制的寄存器组。STM32 的中断分组与这个寄存器组密切相关。
IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可
以表示 240 个可屏蔽中断。而 STM32 只用到 了其中的 68 个。IP[67]~IP[0]分别
对应中断 67~0。而每个可屏蔽中断占用的 8bit 并没有全部使 用,而是 只用了高 4
位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级(响应优先级)
在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
组 | [0:4] | [2:2] | [3:1] | [0:4] |
---|---|---|---|---|
0 | 响应 | 响应 | 响应 | 响应 |
1 | 抢占 | 响应 | 响应 | 响应 |
2 | 抢占 | 抢占 | 响应 | 响应 |
3 | 抢占 | 抢占 | 抢占 | 响应 |
4 | 抢占 | 抢占 | 抢占 | 抢占 |
如上表,共4位,可以理解为第n组就是抢占优先级的位数,剩下的4-n位就是响
应优先级的位数。例如设置一个中断为3组,即抢占优先级占3位,响应优先级
占4-3=1位,故3组抢占优先级有2^3=8种,而响应优先级有2^1=2种(切记,优
先级的设定一定不要超过范围,比如原本3位的抢占优先级设置成了0XA,这不
就是明显的溢出吗)。
==抢占优先级的 级别高于响应优先级。而数值越小所代表的优先级就越高。 ==
这里需要注意两点:
第一,如果抢占优先级相同,响应优先级不同,有限执行响应优先级高的;如果两个中断
的抢占优先级和响应优先级都是一样的话,则看 哪个中断先发生就先执行;
第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级
相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断,也就是说相同
抢占优先级的中断没有嵌套。
第三,实际配置中,优先级的数字越小,所对应的优先级越高。
5.编写中断服务函数
三.具体实现和源码
1.中断
exti.h
#ifndef EXTI_H
#define EXTI_H
void EXTI_Init(void);//外部中断初始化
#endif
exti.c
#include "exti.h"
#include "delay.h"
#include "key.h"
#include "led.h"
#include "usart.h"
void EXTI0_IRQHandler(void)//外部中断0服务程序,WKUP按键中断
{
delay_ms(10);//消抖
if(WKUP == 1)//WKUP按下,两个灯反转
{
LED0 = !LED0;
LED1 = !LED1;
}
EXTI->PR = 1<<0;//清除LINE0上的中断标志位
return ;
}
void EXTI9_5_IRQHandler(void)//外部中断9-5服务程序,KEY1按键中断
{
delay_ms(10);//消抖
if(KEY1 == 0)//KEY1按下,LED0反转
LED0 = !LED0;
EXTI->PR = 1<<5;//清除LINE5上的中断标志位
return ;
}
void EXTI15_10_IRQHandler(void)//外部中断15-10服务程序,KEY2按键中断
{
delay_ms(10);//消抖
if(KEY2 == 0)//KEY2按下,LED1反转
LED1 = !LED1;
EXTI->PR = 1<<15;//清除LINE15上的中断标志位
return ;
}
void EXTI_Init(void)//外部中断初始化程序,初始化PA0,PA15,PC5为中断输入
{
KEY_Init(); //按键初始化
Ex_NVIC_Config(GPIO_A,0 ,RTIR); //设置WKUP(PA0)为上升沿触发
Ex_NVIC_Config(GPIO_C,5 ,FTIR); //设置KEY1(PC5)为下降沿触发
Ex_NVIC_Config(GPIO_A,15,FTIR); //设置KEY2(PA15)为下降沿触发
MY_NVIC_Init(2,2,EXTI0_IRQn,2); //抢占2,子优先级2,组2
MY_NVIC_Init(2,1,EXTI9_5_IRQn,2); //抢占2,子优先级1,组2
MY_NVIC_Init(2,0,EXTI15_10_IRQn,2); //抢占2,子优先级0,组2
return ;
}
2.外部灯
led.h
#ifndef LED_H
#define LED_H
#define LED0 PAout(11)//将PA11定为LED0
#define LED1 PAout(12)//将PA12定为LED1
void LED_Init(void);//LED0(PA11)和LED1(PA12)的初始化函数
#endif
led.c
#include "led.h"
#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
/*******************/
/* LED0(PA11) */
/* LED1(PA12) */
/*******************/
void LED_Init()
{
RCC->APB2ENR |= 1<<2;//PA时钟使能
GPIOA->CRH &= 0XFFF00FFF;//LED0(PA11)和LED1(PA12)刷0
GPIOA->CRH |= 0X00033000;//LED0(PA11)和LED1(PA12)设置为推挽输出
LED0 = LED1 = 1;//闪烁初始化
delay_ms(250);
LED0 = LED1 = 0;
delay_ms(250);
LED0 = LED1 = 1;
delay_ms(250);
LED0 = LED1 = 0;
return ;
}
3.按键
key.h
#ifndef KEY_H
#define KEY_H
#define KEY1 PCin(5) //将PC5定义为KEY1
#define KEY2 PAin(15) //将PA15定义为KEY2
#define WKUP PAin(0) //将PA0定义为WKUP
void KEY_Init(void); //KEY1(PC5)和KEY2(PA15)和WKUP(PA0)初始化函数
#endif
key.c
#include "sys.h"
#include "key.h"
#include "delay.h"
/*******************/
/* KEY1(PC5) */
/* KEY2(PA15) */
/* WKUP(PA0) */
/*******************/
void KEY_Init(void)
{
RCC->APB2ENR |= 1<<2;//PA时钟使能
RCC->APB2ENR |= 1<<4;//PC时钟使能
JTAG_Set(SWD_ENABLE);//关闭JTAG,开启SWD
GPIOC->CRL &= 0XFF0FFFFF;//KEY1(PC5)刷0
GPIOC->CRL |= 0X00800000;//KEY1(PC5)设置上拉下拉输入
GPIOC->ODR |= 1<<5; //KEY1(PC5)上拉
GPIOA->CRH &= 0X0FFFFFFF;//KEY2(PA15)刷0
GPIOA->CRH |= 0X80000000;//KEY2(PA15)设置上拉下拉输入
GPIOA->ODR |= 1<<15; //KEY2(PA15)上拉
GPIOA->CRL &= 0XFFFFFFF0;//WKUP(PA0)刷0
GPIOA->CRL |= 0X00000008;//WKUP(PA0)设置上拉下拉输入
//GPIOA->ODR &= 0<<0; //WKUP(PA0)设置下拉,默认下来,可以不要
return ;
}
4.主函数
main.c
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "exti.h"
int main()
{
Stm32_Clock_Init(9);//系统时钟初始化
delay_init(72);//延时初始化
LED_Init();//LED0(PA11)和LED1(PA12)的初始化函数
uart_init(72,9600);//串口初始化
EXTI_Init();//外部中断初始化
while(1)
{
delay_ms(500);
printf("Hello!\r\n");
}
}
最后,感谢你看到这里,如果有错误欢迎指出,感激不尽~
参考资料链接:
原子哥,正点原子
link