前言:中断属于7种异常的1种,这节主要讲CPU接收到中断之后怎么处理,回顾之前的知识。
目录
1 异常
1.1 概念
1.2 异常处理机制
1.3 ARM异常源
1.4 异常模式
1.5 ARM异常响应
1.6 异常向量表
1.7 异常返回
编辑 1.8 IRQ异常举例
2 中断处理框架搭建
2.1 代码框架介绍
2.2 当中断信号来,ARM如何处理
2.2.1 以BL为例说明LR如何保存下一条指令的地址。
2.2.2 IRQ异常LR寄存器说明如何保存下一条地址指令。
2.3 编写IRQ异常处理程序
3 补充
1 异常
1.1 概念
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生
这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件
异常事件处理完成之后再返回到被异常打断的点继续执行程序
1.2 异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
1.3 ARM异常源
导致异常产生的事件称为异常源
FIQ、IRQ作为外部中断,驱动IRQ用得较多,FIQ速度较快。
1.4 异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式
1.5 ARM异常响应
2.1中相应的中断的意思:高优先级的可以打断低优先级的,但是平级中断不能被打断
真正处理跳转在第4点,自动修改成了向量表对应的地址。
1.6 异常向量表
ARM的异常向量表的基地址默认在0x00地址,但可以通过配置协处理器来修改其地址,如基址修改为0x40000000,那么IRQ的地址对应0x40000018
1.7 异常返回
ARM异常返回的动作(自己编写)
1.8 IRQ异常举例
2 中断处理框架搭建
2.1 代码框架介绍
common中写得是头文件和源文件,包含.c和.s的可以调用,如exynos_4412.h是寄存器的封装
start中放得是启动代码start.s,启动的汇编代码
makefile用于编译规则
map.lds链接脚本,用于生成文件的排版和格式
最终编译所有.c、.s等组成了一个.bin的文件。所有.c .s的源文件的内容的排版由map.lds决定。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x40008000;
. = ALIGN(4);
.text :
{
start/start.o(.text)
*(.text)
}
. = ALIGN(4);
.rodata :
{ *(.rodata) }
. = ALIGN(4);
.data :
{ *(.data) }
. = ALIGN(4);
.bss :
{ *(.bss) }
}
协处理器cp15中有个寄存器c12,往里面写什么地址,异常向量表就在什么地址
r0已经存了start的地址,把r0给c12,当arm遇到异常时就会跳转到异常向量表
目前没有写跳转指令 b.代表跳转到自身,占用了32个字节。
详细内容参考往期内容:
lv11 嵌入式开发 C工程与寄存器封装 10-CSDN博客
2.2 当中断信号来,ARM如何处理
回顾:
ARM寄存器
注:在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用
LR寄存器两种用途:
2.2.1 以BL为例说明LR如何保存下一条指令的地址。
当正在执行BL ADDR指令的时候,BL指令的下一条指令正在译码(三级流水线)BL指令的下下条正在取址。PC寄存器指向的永远是在取址的指令,CPU会执行BL的时候,会把PC的值减4的地址拷贝到LR
2.2.2 IRQ异常LR寄存器说明如何保存下一条地址指令。
2.3 编写IRQ异常处理程序
start.s
处理函数中做了几件事:
- 修正LR返回地址
- 压栈保护现场
- 跳转异常处理程序
- 恢复现场
处理跳转异常处理程序,只能使用汇编编写,因为C语言没有压栈出栈的操作函数
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
/*
* 从异常向量表再跳转到IRQ的异常处理程序
*/
b irq_handler
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
/*
* IRQ的异常处理程序,需要写在main函数之后,否则就执行了。
*/
irq_handler:
/*
* 因为产生IRQ异常后ARM自动保存到LR中的返回地址是被IRQ打断的指令
* 的下一条再下一条指令的地址,所以我们需要人为的去修正一下
*/
sub lr, lr, #4
/*
* 因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组
* 所以在处理异常之前需要先将之前寄存器中的值压栈保护
*/
stmfd sp!, {r0-r12,lr}
/*
* 跳转到do_irq处理异常
*/
bl do_irq
/*
* 异常返回
* 1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
* 2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前
* 3.将栈中保存的LR寄存器的值出栈给PC,使程序跳转回被异常打断的点继续执行
*/
ldmfd sp!,{r0-r12,pc}^
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
编写do_irq
获取中断号
告诉中断控制器,当前中断已经处理完成,可以发送其他中断
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//IRQ异常处理
void do_irq(void)
{
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号,只需要10位*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位,不清楚结束会跳转到main3然后再持续执行上面的中断处理函数*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
int main()
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}
注:清除GPIO控制器中GPX1_1的中断挂起标志位,不清楚会持续发送给CPU。写1清零。
正在开发的时候linux或者其他操作系统会帮我们写好,重点理解中断和轮询处理流程。
3 补充
frq速度快的原因:
- 优先级高
- 异常向量表最后一位,可以不写跳转指令,直接接着写处理函数
- frq模式下r8-r12是私有的,如果用了r8-r12可以不用压栈,但如果用了r0-r7寄存器,那么还是需要压栈的。