一、ARM对异常(中断)的使用过程:
1、初始化
1)设置中断源,让其可以产生中断。
2)设置中断控制器(屏蔽、优先级等)。
3)设置CPU总开关,使能中断。
2、 执行用户程序
3、 产生中断
中断信号–>中断控制器–>CPU
4 、检查
CPU每执行完一条指令,都会检查有无中断、异常产生。
5、处理
发现有异常、中断产生,开始处理。
对于不同的异常,会跳去不同的地址去执行程序。
二、不同架构处理器对中断的处理流程
2.1 Cortex-M3、M4系列
2.1.1 中断处理流程
Cortex-M3、M4中断发生后,
1、保存现场、硬件完成
2、分辨中断、硬件完成
3、跳转到向量表,硬件完成
4、恢复现场,软件触发、硬件恢复
硬件做了绝大部分工作,软件上只需要提供一个中断函数即可。
2.1.2 异常向量表
下面是Cortex-M3、M4的中断向量表,异常向量表里面放的是某一中断具体的函数地址,当中断发生后就会到对应的地址去执行代码。
2.2 Cortex-A7系列
2.2.1 中断处理流程
Cortex-A7中断发生后,
1、切换模式:硬件
2、跳转执行:硬件件
3、保存现场:软件
4、分辨中断源、处理:软件
5、恢复现场:软件
硬件只负责切换模式和跳转到某一中断指令执行。
2.2.2 异常向量表
Cortex-M3、M4对于每一个异常都有一个对应的中断函数,但Cortex-A7不是,Cortex-A7里面放的是跳转指令。他是把多个同模式的中断放在一起,成为一个大类,然后在用软件分辨中断。某一类异常放一个中断入口。
关于中断的一些知识,在之前的F103学习笔记中介绍过,这里不再讲解。
三、保存现场
虽然不同架构的中断处理流程有所不同,但是主要的步骤都是一样的,分为以下三步。
1、保存现场
2、分辨异常,调用对应的异常处理函数
3、恢复现场
对于不用的处理器,具体的处理工作有差别:
保存现场:cortex M3/M4里是硬件完成,cortex A7等是软件实现
分辨异常/中断:cortex M3/M4里是硬件完成,cortex A7等是软件实现
调用处理函数:cortex M3/M4里是硬件来调用,cortex A7等是软件自己去调用
恢复现场:cortex M3/M4里是软件触发、硬件实现,cortex A7等是软件实现
不管是硬件还是软件实现,第一步都是保存现场。
保存现场的目的是确保函数调用前后,CPU内部寄存器中的数据不被改变。
例如func_A调用func_B,调用之前,R0=1,调用func_B之后,在func_B内部更改了R0,R0=2,这样func_B结束,程序回到func_A的时候,R0的值被改变了,func_A就会出错。
保存现场就是保存CPI内部寄存器的值。根据ARM的ATPCS规则知道,R0-R3是用来传递参数的,R4-R11用来保存局部变量的,还有几个特殊寄存器。
对于cortex-M3、M4内核来说,R0-R3、R12、LR、PSR是由硬件帮你保存。剩余的寄存器就需要自己保存了。
ARM处理器中有这些寄存器:
在arm中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。
约定R0-R15寄存器的用途:
R0-R3:函数之间传递参数
R4-R11:保存局部变量
R12-R15:特殊寄存器
除了上面的16个寄存器还有一个程序状态寄存器PSR。
R0-R15和PSR,就是所谓的现场。每个函数都可能会用到这17个寄存器,所以每次函数的现场都是不一样的,在函数调用时必须要先保存此函数的现场,然后在被调用函数执行完毕,把R0-R15、PSR寄存器的数值根据之前保存的现场在恢复回来,这样程序才能正常返回运行。
当发生异常时,进入异常处理函数前,需要保存现场,但是不是把R0-R15、PSR所有寄存器都保存下来,对于不同的处理器,会有不同的保存方式。
1、 对于M3/M4
1.1 硬件保存现场
发生异常时,硬件自动把R0-R3、R12、LR、PC、PSR这几个寄存器的数据保存在栈中。这里注意右边栈空间,后面会用到这个知识。
所以有硬件帮我们保存了一些寄存器,除了上面的8个寄存器之外,如果还用到了其他的寄存器,就需要我们自己保存了,比如R4-R11。
1.2 返回地址
异常中断服务函数执行完毕后,需要返回原来的地址,那么是返回LR中的地址吗?
可以看到,在进入异常函数前,LR赋值0x55555555,进入异常函数之后,LR寄存器的值变为了0xFFFFFFF9,0xFFFFFFF9明显不是个正常的数值。这是为什么?
查询资料知道,cortex-M3、M4在进入异常处理函数前,会把LR设置为一个特殊的值EXC_RETURN。当异常函数执行完毕,PC值指向返回地址LR,然后PC寄存器发现LR=EXC_RETURN,这是就会触发异常返回机制:从栈里恢复R0-R3,R12,LR,PC,PSR等寄存器的值。PC值就变为入栈时的值,程序继续向下执行。
处理模式:执行中断服务程序等异常处理时,处于处理模式
线程模式:执行普通应用程序代码时,处于线程模式
前面说过,Cortex-M3、M4有两个SP栈,SP_process和SP_main
线程栈:SP_process有些RTOS在运行用户程序时会使用
主栈:SP_main,默认使用
2、对于A7
它寄存器如下:
A7与Cortex-M3、M4相比,他分成了许多工作模式,每种模式有自己的专属寄存器(上图蓝色部分),比如工作在IRQ模式下,他的LR和SP寄存器会使用蓝色的专属寄存器,这样保存和恢复数据不用操作原寄存器,能够减少操作指令,提高效率。
四、几种异常实验
下面是Cortex-M3、M4支持的所有异常,共15个:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
异常根据原因分为不同种类,比如总线错误异常、使用错误异常、硬件错误异常等。当发生某一异常时,程序就会跳转到对应的异常服务函数去执行。
1、未定义指令异常
未定义指令,就是没有被定义的指令,CPU无法识别的指令。当CPU执行到未定义指令的时候就会产生异常。
例如:
DCD 0xffffffff ;这就是一条异常指令
当CPU执行到上述指令时,会发生异常,然后程序就会跳转到对应的异常中断函数去执行。在对应异常中断函数中就可以打印信息进行调试了。
中断函数:
void HardFault_Handler(void)
{
puts("HardFault_Handler is running !\r\n");
}
void UsageFault_Handler(unsigned int * stack)
{
puts("UsageFault_Handler is running !\r\n");
}
实验结果:
可以看到有两个不太正常的现象:
1、执行的是HardFault_Handler
函数而不是UsageFault_Handler
函数,根据异常向量表可以看到,发生未定义指令情况执行对应的函数应该是UsageFault_Handler
。
2、中断函数一直被反复执行,一般认为应该是执行一次就可以了。
为什么执行HardFault_Handler
ARM Cortex-M3与Cortex-M4权威指南里面介绍到:
综上可知,所有的异常默认的中断函数都是HardFault_Handler
,这样就解释了为什么执行的是HardFault_Handler
函数了。
未定义指令属于"处理器操作相关的错误",如果没有使能"Usage Fault",就会触发"Hard Fault"。所以要使能Usage Fault。
使能Usage Fault:
找到官方提供的文件:core_cm3.h,里面定义了一个SCB结构体。
SCB是系统控制块,里面包含了许多寄存器,其中的SHCSR就是使能Usage Fault的。下面红框中是SCB的基地址。
下面是权威指南中的使能Usage Fault的操作过程:
使能代码:
void usage_fault_enable(void)
{
SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
SCB->SHCSR |= (1<<18); //设置第18个bit
}
实验结果:
可以看到,异常执行的是UsageFault_Handler
函数。
为什么会一直调用UsageFault_Handler
函数
查手册看到SCB中有个寄存器CFSR。
从上面看到,CFSR的bit16位,当他为1的时候,栈里面保存的PC值指向引起当前异常的未定义指令。
之前说过cortex-M3中,函数跳转前,硬件自动保存一些寄存器的值,其中就有返回地址值,此时返回地址就是未定义指令的地址。
当异常中断函数执行完毕,程序会从刚才引起异常的未定义指令重新再次运行,这样就又会导致程序执行异常中断函数。这就是为什么UsageFault_Handler
函数会被一直循环调用。
所以需要在函数中修改返回地址的值,让PC指向下一条指令。
C函数没办法传递SP,所以要编写汇编函数传递栈指针SP,然后在调用C函数更改SP中的PC值。
DCD UsageFault_Handler_asm ; Usage Fault Handler
UsageFault_Handler_asm PROC
MOV R0,SP ;把SP栈指针传递给R0,调用C函数stack就指向了SP
B UsageFault_Handler ;不能使用BL,因为BL会改变LR寄存器的值,异常函数跳转的LR保存的是一个特殊值
void UsageFault_Handler(unsigned int * stack)
{
puts("UsageFault_Handler is running !\r\n");
stack[6] += 4; //PC指向下一条指令
}
返回地址保存在SP的第7块,对应的stack就是stack[6]。
之前说过,函数第一个参数保存在R0中,所以汇编函数中把SP传递给R0,如果C函数有两个参数比如
void UsageFault_Handler(volatile unsigned int i,unsigned int * stack)
此时就应该把SP保存在R1中。
MOV R1,SP
输出结果:
可以看到UsageFault_Handler
执行一次就结束了。
网上其他资料说对于UsageFault_Handler
函数还需要清除CFSR寄存器,在函数中有两步操作1、清除usage fault;2、让PC指向下一条指令。
void UsageFault_Handler(unsigned int * stack)
{
SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
puts("UsageFault_Handler is running !\r\n");
//清除usage fault
SCB->CFSR = SCB->CFSR;
//PC指向下一条指令
stack[6] += 4;
}
但是我经过上面实验发现不清除usage fault也就是不执行SCB->CFSR = SCB->CFSR;
,依然可以正常运行程序。可能是为了应对某些情况需要清除,具体原因我也不清除。
在汇编文件中设置某些寄存器的值,验证发生异常中断时,R0等寄存器的数据保存在SP中。
ldr r0,=0
ldr r1,=0x11111111
ldr r2,=0x22222222
ldr r3,=0x33333333
ldr r12,=0x44444444
ldr lr,=0x55555555
DCD 0xffffffff ;未定义指令
void UsageFault_Handler(unsigned int * stack)
{
SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
puts("UsageFault_Handler is running !\r\n");
put_s_hex("R0 = ",stack[0]);
put_s_hex("R1 = ",stack[1]);
put_s_hex("R2 = ",stack[2]);
put_s_hex("R3 = ",stack[3]);
put_s_hex("R12 = ",stack[4]);
put_s_hex("LR = ",stack[5]);
put_s_hex("PC = ",stack[6]);
put_s_hex("PSR = ",stack[7]);
stack[6] += 4;
}
输出结果:
可以看到,上面的寄存器中的值确实被保存在了栈里面然后传递给C函数了。
在keil中进行debug调试:
这里注意SP栈指针的值,查看内存0x2000FFE0中的数据:
可以看到前面6个是我们设置的值,第七个返回地址是0x08000068。
可以看到DCD 0xffffffff的地址正是0x08000060+4+4=0x08000068。
打开反汇编文件,也可以看到DCD 0xffffffff指令的地址是0x08000068。
说明SP确实保存了R0等寄存器的值。
2、 SVC异常
SVC(请求管理调用)常用于OS中使用,使用SVC指令故意触发异常,从而调用内核的异常处理函数,使程序进入内核。
使用SVC机制可用于实现应用任务访问系统资源的API。
从权威指南中看到SVC指令的用法,在内核里面可以根据参数执行对应的函数。
在操作系统中,比如各类RTOS或者Linux,都会使用SVC
指令故意触发异常,从而导致内核的异常处理函数被调用,进而去使用内核的服务。
实验:
在汇编文件中加入SVC指令:
SVC #1
异常服务函数:
void SVC_Handler(void)
{
puts("SVC_Handler is running !\r\n");
}
可以看到,执行了SVC_Handler
函数,并且这个异常函数只执行一次,与上面的未定义指令异常不同。
可以看到栈中的返回地址是0x08000052。
打开反汇编文件,可以看到0x08000052正是SVC异常指令的下一条指令。所以异常服务函数执行完毕就会返回到异常指令的下一条指令继续执行。
关于SVC异常的具体内容可以查询Cortex-M3、M4权威指南10.3章节。
3、SysTick异常
SysTick也属于一种异常,这个定时器是被嵌入在CPU上的,对于不同类型的单片机,只要内部的CPU是同一类型的,那么SysTick就都是一样的,关于SysTick的程序代码就可以保持一致,无需修改。
关于SysTick的相关内容在之前的单片机学习笔记7–SysTick定时器中有所介绍,这里就不再细致讲解了。
SysTick的主要寄存器是上面红框中的三个。
SysTick->LOAD:重装载值
SysTick->VAL:当前计数值
SysTick->CTRL:使能定时器、设置触发沿、使能异常中断等
实验:
用SysTick实现LED闪烁:
这里我没有设置系统时钟,所以系统时钟是默认HSI输入,也就是8MHz。
重装载值计算公式:
S
y
s
T
i
c
k
−
>
L
O
A
D
=
T
i
m
e
/
(
1
/
C
l
k
)
SysTick->LOAD = Time/(1/Clk)
SysTick−>LOAD=Time/(1/Clk)
其中Time是周期,单位是秒,也就是每隔Time时间产生一次中断,Clk是时钟。这样让SysTick每隔1s产生一次中断,LOAD的值就是1/(1/8000000) = 8000000。对于其他的定时器也可以这样算。
从上图可以知道SysTick的寄存器地址,下面简单介绍一下。
1、SysTick->CTRL
从上边可以看到,这个寄存器一共有4个bit可以使用。
bit0 | 使能SySTick | 0:不使能;1:使能 |
---|---|---|
bit1 | 使能SySTick中断 | 0:不使能;1:使能 |
bit2 | 选择SySTick时钟源 | 0:AHB/8:1:AHB |
bit16 | 计数完成标志位 | 当SySTick计数到0,置1 |
2、Sys Tick->LOAD
这个寄存器就是保存重装载值的,当计数到0时,会自动把LOAD寄存器的值加载在VAL寄存器中。
3、SysTick->VAL
VAL寄存器保存了SysTick当前的计数值,对VAL寄存器进行写操作,会将此寄存器清零并且清除CRTL寄存器中的bit1标志位。
当向下计数器systick->VAL减到0时,会发生下面的事件:
1、systick->CTRL的TICKINT位使能systick异常、ENABLE位使能态后由硬件置位,但若由于所需的处理任务花费时间太长而导致悬起状态再次置位,则需要清除systick的悬起状态SCB->ICSR |= 1<<25,防止再次悬起。
又加载LOAD值到VAL重新递减计数:systick->VAL = systick->LOAD
systick->CTRL的bit16位置1,若读取该位,会被自动清零
程序代码:
void SystemTickInit(void)
{
unsigned int * pSys_CTRL = ( unsigned int * )(0xE000E010);
unsigned int * pSys_LOAD = ( unsigned int * )(0xE000E014);
unsigned int * pSys_VAL = ( unsigned int * )(0xE000E018);
unsigned int * pSys_CALIB = ( unsigned int * )(0xE000E01C);
*pSys_VAL = 0; //清空VAL寄存器同时也把CTRL中的标志位清除
*pSys_LOAD = 8000000 - 1; //设置计数重装载值为8000000 - 1,计数到0用时1s
*pSys_CTRL |= (1<<2) | (1<<1) | (1<<0); //选择AHB时钟,使能中断,使能SysYick
}
void SysTick_Handler(void)
{
static int led_state = 0;
unsigned int * pGPIOOdr = ( unsigned int * )(0x40010C00 + 0X0c);
SCB_Type * SCB = (SCB_Type *)SCB_BASE_ADDR;
if(led_state == 0)
{
*pGPIOOdr |= (1<<0);//灯亮
}
else
{
*pGPIOOdr &= ~(1<<0);//灯灭
}
led_state = ~led_state;
//异常进入活动态时, 其悬起位被硬件清除(但若由于所需的处理任务花费时间太长而导致悬起状态再次置位则需清除)
//SCB->ICSR |= (1<<25); //清除异常,若不清除,函数返回会再次执行异常函数
}
五、中断
关于中断的一些知识可以看单片机学习笔记8–按键和外部中断,里面有介绍。下面对于一些之前介绍过的东西不做详细讲解。
中断也属于一种异常,异常无法被屏蔽,中断可以被屏蔽。单片机有许多中断,下面是STM32F103启动文件中的向量表,其中上边是异常,下边是中断。
之前介绍过,Cortex-M3、M4的中断向量表是一个个函数,发生异常或者中断时跳转到对应函数。而A7不同,A7的向量表对应的是一条条指令。
1、中断硬件框架
一个中断的完整结构包括三个部分:
1、中断源:产生中断,发出中断信号
2、中断控制器:所有中断信号都汇聚在中断控制器中,由中断控制器统一控制,设置中断的优先级,选择最高优先级的中断发送给CPU。不同内核的中断控制器也互不相同,比如Cortex-M3、M4中叫做NVIC,Cortex-A7中是GIC。
3、CPU:每执行完一条指令,都会判断是否有中断发生。CPU内部也有一个寄存器,用来设置使能/禁止中断,是中断处理的总开关。
2、STM32F103中断
所以如果要配置完整的中断要配置中断控制器和CPU内部的寄存器,如果中断源还有其他的控制器则还需要另行配置,比如STM32F103的GPIO中断,他们有一个外部中断控制器EXTI。
以GPIO为例,要想实现外部中断功能,对上面三部分都要进行配置。
一、中断源配置
1、GPIO控制寄存器:负责选择某个GPIO引脚作为中断的输入源。
每组GPIO有0-15共16个引脚,使用4个寄存器控制。每组GPIO的同一引脚在一个中断线上,同一时间只能使用一个,也就是使用PA0作为中断源,那么PX0(X=B-G)都不能作为中断源使用。
AFIO_EXTICR1负责PIN0-PIN3的控制,同理AFIO_EXTICR2负责PIN4-PIN7的控制,AFIO_EXTICR3负责PIN8-PIN11的控制,AFIO_EXTICR4负责PIN12-PIN15的控制。
2、EXTI控制器:负责设置触发沿是上升沿触发还是下降沿触发、使能还是屏蔽中断。
外部中断线一共有20个,除了16个GPIO的,还有其他四条,这里不做介绍。
从寄存器描述可知,EXTI_IMR的哪个bit位为1,那么对应的中断就会被使能。
从寄存器描述可知,EXTI_RTSR的哪个bit位为1,那么对应的中断会选择为上升沿触发中断模式。
从寄存器描述可知,EXTI_FTSR的哪个bit位为1,那么对应的中断会选择为上升沿触发中断模式。
主要寄存器就上面这些,还有几个其他的寄存器,这里不在一一介绍。
到此中断源配置完毕,下面进行NVIC配置。
二、NVIC配置
NVIC负责为每个中断分配优先级,同时在所有中断中选出优先级最高的中断发送给CPU。
NVIC相关寄存器如下:
其中主要用到的有:ISER、ICPR。
ISER寄存器是设置中断使能的,写1使能中断。
ICPR寄存器是清除中断的挂起状态的。
注意:上边的这些寄存器不是1个,而是类似数组有多个。比如ISER包括ISER0,ISER1,ISER2。里面的每一位对应一个中断。
三、CPU
CPU内部有一个所有中断的总开关,如果不使能这个bit,那么中断就不会起作用。
可以看到,CPU内部的特殊寄存器中,FAULTMASK、PRIMASK、BASEPRI是关于异常的寄存器。
PRIMASK
这个寄存器只有bit0有效,置1的时候屏蔽所有可配置优先级的中断。也就是清零使能中断。
在汇编文件中可以使用这些指令来设置它:
CPSIE I ; 清除PRIMASK,使能中断
CPSID I ; 设置PRIMASK,禁止中断
或者:
MOV R0, #1
MSR PRIMASK R0 ; 将1写入PRIMASK禁止所有中断
MOV R0, #0
MSR PRIMASK, R0 ; 将0写入PRIMASK使能所有中断
FAULTMASK
FAULTMASK寄存器也是只有bit0有效,置1会把所有异常都屏蔽,除了NMI。
汇编文件中可以使用这些指令来设置它:
CPSIE F ; 清除FAULTMASK
CPSID F ; 设置FAULTMASK
或者:
MOV R0, #1
MSR FAULTMASK R0 ; 将1写入FAULTMASK禁止中断
MOV R0, #0
MSR FAULTMASK, R0 ; 将0写入FAULTMASK使能中断
BASEPRI
BASEPRI寄存器只有bit4-bit7有效,根据此寄存器的数值屏蔽特定优先级范围的中断。
例如bit4-bit7的数值是0x60,则屏蔽优先级高于0x60的中断,只允许优先级低于0x60的中断执行。写0则此寄存器不发挥作用。
汇编文件中可以使用这些指令来设置它:
MOVS R0, #0x60
MSR BASEPRI, R0 ; 禁止优先级高于0x60的中断
MRS R0, BASEPRI ; 读取BASEPRI
MOVS R0, #0
MSR BASEPRI, R0 ; 取消BASEPRI屏蔽
六、按键中断实验
经过上面的介绍我们可以知道配置中断的流程了。
1、设置中断源:
使用按键KEY1–PA0引脚。设置GPIO输入模式。
使能外部中断线EXTI0,选择PA0为中断源。
EXTI设置选择双边沿触发模式,开放PA0中断请求。
typedef struct
{
volatile unsigned int EVCR;
volatile unsigned int MAPR;
volatile unsigned int EXTICR[4];
volatile unsigned int RESERVED;
volatile unsigned int MAPR2;
}AFIO_REG_TYPE;
typedef struct
{
volatile unsigned int IMR;
volatile unsigned int EMR;
volatile unsigned int RTSR;
volatile unsigned int FTSR;
volatile unsigned int SWIER;
volatile unsigned int PR;
}EXTI_REG_TYPE;
void key_init()
{
AFIO_REG_TYPE *AFIO_EXTI = (AFIO_REG_TYPE*)(0x40010000);
unsigned int * pAPB2En = ( unsigned int * )(0x40021000 + 0x18);
unsigned int * pGPIOACrl = ( unsigned int * )(0x40010800 + 0x00);
*pAPB2En |= (3<<2);//开启GPIOA时钟
*pGPIOACrl |= (1<<2);//设置PA0输入模式
AFIO_EXTI->EXTICR[0] &= 0x0f;//设置PA0为中断源
}
void exti_init(void)
{
EXTI_REG_TYPE *EXTIStu = (EXTI_REG_TYPE*)(0x40010400);
EXTIStu->IMR |= (1<<0); //开放PA0中断请求
EXTIStu->FTSR |= (1<<0); //下降沿触发
EXTIStu->RTSR |= (1<<0); //上升沿触发
}
2、NVIC设置
使能对应的中断。
typedef struct
{
volatile unsigned int ISER[8];
volatile unsigned int RESERVED0[24];
volatile unsigned int ICER[8];
volatile unsigned int RSERVED1[24];
volatile unsigned int ISPR[8];
volatile unsigned int RESERVED2[24];
volatile unsigned int ICPR[8];
volatile unsigned int RESERVED3[24];
volatile unsigned int IABR[8];
volatile unsigned int RESERVED4[56];
volatile unsigned int IP[240];
volatile unsigned int RESERVED5[644];
volatile unsigned int STIR;
}NVIC_Type;
void nvic_init(void)
{
NVIC_Type *pStuNvic = (NVIC_Type*)(0xE000E100);
pStuNvic->ISER[0] |= (1<<6);//EXTI0_IRQHandler中断函数编号是22,22-15=7,对应的是bit6
}
汇编中的EXTI0_IRQHandler中断函数,可以看到共有15个异常,EXTI0_IRQHandler
他是异常#22,在ISER中就是bit6。
__Vectors DCD 0
DCD Reset_Handler ; Reset Handler
DCD 0 ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; MPU Fault Handler
DCD 0 ; Bus Fault Handler
DCD UsageFault_Handler_asm ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD 0 ; Debug Monitor Handler
DCD 0 ; Reserved
DCD 0 ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
;外部中断
DCD 0 ; Window Watchdog
DCD 0 ; PVD through EXTI Line detect
DCD 0 ; Tamper
DCD 0 ; RTC
DCD 0 ; Flash
DCD 0 ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
3、CPU中断使能
CPU使能要在汇编文件中操作,具体方法上面介绍了。
;使能CPU中断
CPSIE I
经过上面三步中断就配置完成了,下面看实验现象。
void EXTI0_IRQHandler(void)
{
unsigned int * pGPIOIdr = ( unsigned int * )(0x40010800 + 0x08);
if((* pGPIOIdr) & (1<<0) == 1) //按键按下
{
puts("KEY1 has been pressed! \n\r");
}
else
{
puts("KEY1 released! \n\r");
}
//清除标志位
exti_clear_flag(6);
nvic_clear_flag(6);
}
实验结果:
注意:若不清除标志位,会一直调用EXTI0_IRQHandler
中断服务函数。清除标志位的时候最主要的是把中断源的标志位清除也就是EXTI的标志位。