抖音代运营方案怎么写,Wordpress优化图片插件,网站前端设计软件,深夜小网站一、ARM的异常处理机制及工程代码结构
1.1异常概念 处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序。 1.2异常处理机制 不同的处…一、ARM的异常处理机制及工程代码结构
1.1异常概念 处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序。 1.2异常处理机制 不同的处理器对异常的处理的流程大体相似但是不同的处理器在具体实现的机制上有所不同比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制.
1.3ARM异常源
概念导致异常产生的事件称为异常源
ARM异常源
FIQ 快速中断请求引脚有效
IRQ 外部中断请求引脚有效
Reset 复位电平有效
Software Interrupt 执行swi指令
Data Abort 数据终止
Prefetch Abort 指令预取终止
Undefined Instruction 遇到不能处理的指令
1.4异常模式 在ARM的基本工作模式中有5个属于异常模式即ARM遇到异常后会切 换成对应的异常模式 1.5ARM异常响应
ARM产生异常后的动作自动完成 1.拷贝CPSR中的内容到对应异常模式下的SPSR_mode 2.修改CPSR的值 2.1.修改中断禁止位禁止相应的中断 2.2.修改模式位进入相应的异常模式 2.3.修改状态位进入ARM状态 3.保存返回地址到对应异常模式下的LR_mode 4.设置PC为相应的异常向量异常向量表对应的地址 1.6异常向量表
异常向量表 异常向量表的本质是内存中的一段代码 表中为每个异常源分配了四个字节的存储空间 遇到异常后处理器自动将PC修改为对应的地址 因为异常向量表空间有限一般我们不会再这里写异常处理程序而是在对应的位置写一条跳 转指令使其跳转到指定的异常处理程序的入口 注ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址
1.7异常返回
ARM异常返回的动作自己编写 1.将SPSR_mode的值复制给CPSR使处理器恢复之前的状态 2.将LR_mode的值复制给PC使程序跳转回被打断的地址继续执行 1.8 IRQ异常举例 注整个过程CPSR保存的永远是当前程序运行状态SPSR只是异常时对原来的CPSR进行备份 二、工程模板代码结构分析 common老师写好的库函数文件里面实现了很多功能比如把所有的寄存器封装比如手搓了一个printf。
interface.c: 我们自己要实现的c源文件
start 启动文件任何芯片一上电执行的第一个程序一定是汇编程序要初始化栈初始化芯片将异常向量表基地址位置改变打开FIQ、IRQ然后跳转到C程序。
makefile 编译规则
map.lds: 链接脚本这个工程模板内有很多的C文件S文件H文件他们编译链接后只生成一个.bin文件写入开发板哪个文件放入哪个位置我们写好的文件在内存中的位置都由它决定
三、中断处理框架搭建
LR保存的是被打断的下一条指令的地址指的是汇编指令一条c语言可能会编译成很多条汇编指令
在遇到IRQ时会跳转到以_start为基地址偏移0x18。 然后我们在 b main 后面来写这个中断服务程序因为b main之前的代码都是启动代码一开始就会执行。而 irq_handler是异常处理程序芯片刚启动时我们不希望它执行我们希望它遇到异常的时候再执行。 但是我们不能直接在这里写因为IRQ模式下有很多寄存器都是和USER模式共用的如果在这里写可定会用到一些寄存器这样就会覆盖掉寄存器中本来的内容返回主程序就不能正确返回这个状态了
所以我们需要先压栈保护现场 这个是时候就可以写了吗还是不能我们来复习一下LR寄存器 R14(LR,Link Register) 链接寄存器一般有以下两种用途 执行跳转指令(BL/BLX)时LR会自动保存跳转指令下一条指令的地址程序需要返回时将LR的值复制到PC即可实现。 产生异常时对应异常模式下的LR会自动保存被异常打断的指令的下一条指令的地址异常处理结束后将LR的值复制到PC可实现程序返回。
原理 当执行跳转指令或产生异常时LR寄存器中不会凭空产生一个返回地址。其原理是当执行跳转指令或产生异常时处理器内部会将PC寄存器中的值拷贝到LR寄存器中然后再将LR寄存器中的值自减4。
BL 当执行BL指令时指令执行过程中处理器内部就会将PC寄存器的值拷贝到LR寄存器然后再将LR寄存器中的值自减4 所以LR寄存器中保存的就是BL指令下一条指令的地址。
该时刻PCN8 LRN4 IRQ中断 当执行一条指令时产生了一个IRQ中断执行这条指令过程中处理器不会保存返回地址而是执行完成后才会保存,但执行完成后PC的值又会自动增4所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址。
该时刻PCN12 LRN8 因为产生IRQ异常后自动保存到LR寄存器中的返回地址是被IRQ打断的指令下一条在下一条指令所以需要我们人为的修复一下。
那么为什么不直接让他返回一个正确的呢因为ARM是精简指令集要想直接返回正确的必须要再加一个电路这样会增加硬件成本所以不如软件修复一条指令就解决了。
由于这是一个非叶子函数在这段程序中可能还会有跳转所以我们干脆把LR也压栈保护一下。 异常处理程序既可以用汇编写也可以通过混合编程用C语言写但前面修改LR和压栈两条指令只能用汇编写。 四、中断处理程序编程
interface.c
#include exynos_4412.h//异常处理程序
void do_irq(void)
{printf(Key2 pressed\n);
}void Delay(unsigned int Time)
{while(Time--);
}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)) | (0x1 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;
}start.S
.text
.global _start
_start:/** Vector table*/ b resetb .b .b .b .b .//从异常向量表再跳转到IRQ的异常处理程序b irq_handlerb .reset:/** Set vector address in CP15 VBAR register*/ ldr r0, _startmcr p15, 0, r0, c12, c0, 0 Set VBAR/** Set the cpu to SVC32 mode, Disable FIQ/IRQ*/ mrs r0, cpsrbic r0, r0, #0x1forr r0, r0, #0xd3msr cpsr ,r0/** Defines access permissions for each coprocessor*/ mov r0, #0xfffffffmcr p15, 0, r0, c1, c0, 2 /** Invalidate L1 I/D */mov r0, #0 Set up for MCRmcr p15, 0, r0, c8, c7, 0 Invalidate TLBsmcr p15, 0, r0, c7, c5, 0 Invalidate icache/** Set the FPEXC EN bit to enable the FPU*/ mov r3, #0x40000000fmxr FPEXC, r3/** Disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002000 Clear bits 13 (--V-)bic r0, r0, #0x00000007 Clear bits 2:0 (-CAM)orr r0, r0, #0x00001000 Set bit 12 (---I) Icacheorr r0, r0, #0x00000002 Set bit 1 (--A-) Alignorr r0, r0, #0x00000800 Set bit 11 (Z---) BTBmcr p15, 0, r0, c1, c0, 0/** Initialize stacks */
init_stack: /*svc mode stack*/msr cpsr, #0xd3ldr sp, _stack_svc_end/*undef mode stack*/msr cpsr, #0xdbldr sp, _stack_und_end/*abort mode stack*/ msr cpsr,#0xd7ldr sp,_stack_abt_end/*irq mode stack*/ msr cpsr,#0xd2ldr sp, _stack_irq_end/*fiq mode stack*/msr cpsr,#0xd1ldr sp, _stack_fiq_end/*user mode stack, enable FIQ/IRQ*/msr cpsr,#0x10ldr sp, _stack_usr_end/*Call main*/b main//IRQ的异常处理程序
irq_handler://因为产生IRQ异常后自动保存到LR中的返回地址是被IRQ打断指令的//下一条再下一条指令的地址所以我们需要人为的去修复一下sub lr, lr, #4//因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组//所以在处理异常之前需要先将之前USER模式下寄存器的值压栈保护stmfd sp!,{r0-r12}//处理异常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只按了一次按键就会一直打印Key2 pressed。 中断挂起寄存器 EXT_INT41_PEND[1]对应GPX1_1引脚会自动置1置1就会把这个中断挂。当你处理完中断返回到main函数此时EXT_INT41_PEND[1]依旧是1不会自动清零所以还会给中断控制器发送中断信号中断控制器还会将中断信号转发给CPU还会触发中断。所以我们需要在CPU处理完中断后把挂起位清零。
中断挂起寄存器比较特殊写1才会清零写0则保持不变。 我们在异常处理程序中对中断挂起寄存器的第一位进行修改则按一次按键只会产生一次中断。 但是有个新问题所有的IRQ异常都会跳到这里那么我们需要区分一下但是CPU不知道是谁发来的所以需要询问中断控制器。 ICCIAR寄存器后面[31:10]位与本次实验无关我们只看[9:0]位中断控制器把几号中断转给CPU就会往这个寄存器的[9:0]位写几。所以我们可以让CPU在处理中断之前先读取这个寄存器的值来写不同的中断处理程序。
#include exynos_4412.h//异常处理程序
void do_irq(void)
{unsigned int IrqNum 0;/*从中断控制器中获取当前中断的中断号*/IrqNum CPU0.ICCIAR 0x3FF;switch(IrqNum){case 0://0号中断的处理程序break;case 1://1号中断的处理程序break;/** ......*/case 57:printf(Key2 pressed\n);/*清除GPIO控制器中的中断挂起位*/EXT_INT41_PEND (1 1);break;/** ......*/case 159://159号中断的处理程序break;default:break;}}void Delay(unsigned int Time)
{while(Time--);
}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)) | (0x1 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;
}此时我们又发现按键只有第一次有效之后再按按键就没有反应。
因为中断控制器不知道CPU0已经处理完中断处理程序了所以并没有将新的中断信号发送给CPU0。 ICCEOIR寄存器本次实验只看[9:0]位CPU处理完中断后会把中断号写入该寄存器。
将当前中断的中断号写回到中断控制器以这种方式来告知中断控制器当前的中断已经处理完成可以发送其他中断
#include exynos_4412.h//异常处理程序
void do_irq(void)
{unsigned int IrqNum 0;/*从中断控制器中获取当前中断的中断号*/IrqNum CPU0.ICCIAR 0x3FF;switch(IrqNum){case 0://0号中断的处理程序break;case 1://1号中断的处理程序break;/** ......*/case 57:printf(Key2 pressed\n);/*清除GPIO控制器中的中断挂起位*/EXT_INT41_PEND (1 1);/*将当前中断的中断号写回到中断控制器以这种方式来告知中断控制器当前的中断已经处理完成可以发送其他中断*/CPU0.ICCEOIR CPU0.ICCEOIR (~(0x3FF)) | 57;break;/** ......*/case 159://159号中断的处理程序break;default:break;}}void Delay(unsigned int Time)
{while(Time--);
}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)) | (0x1 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;
}这次代码我们需要实现按一次按键就产生一次中断但个人原因没有实现也没找出问题后续再解决。 五、中断编程补充
中断和轮询轮询是CPU主动去查看硬件有没有异常产生而中断是硬件主动通知CPU。中断的效率更高一些用的较多。
真正开发时有操作系统我们其实只需要写中断程序然后打开对应中断就可以
FIQ为什么比IRQ快
1FIQ的优先级比IRQ高
2FIQ可以打断IRQ
3FIQ在异常向量表的最末别的中断处理程序需要跳转而FIQ可以直接往后写
4FIQ有四组直接独有的寄存器如果只需要r8-r12的话他不需要压栈保护现场。但是如果用到了r0-r7还是要压栈保护现场的。 作业
1.使用中断的方式检测Key3按键的状态实现按一次按键LED2点亮再次按下LED2熄灭 EINT[10]的中断号是58。 本次实验依旧采用下降沿触发方式GPX1_2对应EXT_INT41_CON[2]。 EXT_INT41_MASK[1]对应GPX1_1的开和关。0x0打开中断0x1关闭中断。我们把它打开。 我们把ICDDCR寄存器写1 监控所有的外部中断并将挂起的中断转发到CPU的接口
ICCDCR寄存器相当于GIC中断控制器的总开关。 ICDISER_CPU寄存器的作用寄存器接收到中断信号通过配置该寄存器对应的位控制该中断信号发送或不发送给CPU。 下面这个寄存器是ICDIPTR_CPU它的作用是为每一个中断选择处理他的CPU。 哪一位置1中断信号就发给哪个CPU处理。但4412是一个四核的CPU所以高四位是没有用的。 一共需要40个寄存器来管理这160个中断归属于哪一个CPU处理
想把58号中断交给CPU0处理则把偏移地址为0x838的寄存器的[23:16]写为00000001即可。 ICCICR_CPUn寄存器是中断控制器和CPU之间的接口他就像一个开关用哪个CPU就要打开哪个。 EXT_INT41_PEND[2]对应GPX1_2引脚会自动置1置1就会把这个中断挂。当你处理完中断返回到main函数此时EXT_INT41_PEND[2]依旧是1不会自动清零所以还会给中断控制器发送中断信号中断控制器还会将中断信号转发给CPU还会触发中断。所以我们需要在CPU处理完中断后把挂起位清零。
中断挂起寄存器比较特殊写1才会清零写0则保持不变。 ICCIAR寄存器后面[31:10]位与本次实验无关我们只看[9:0]位中断控制器把几号中断转给CPU就会往这个寄存器的[9:0]位写几。所以我们可以让CPU在处理中断之前先读取这个寄存器的值来写不同的中断处理程序。 ICCEOIR寄存器本次实验只看[9:0]位CPU处理完中断后会把中断号写入该寄存器。
将当前中断的中断号写回到中断控制器以这种方式来告知中断控制器当前的中断已经处理完成可以发送其他中断
#include exynos_4412.hunsigned int flag 1;//异常处理程序
void do_irq(void)
{unsigned int IrqNum;/*从中断控制器中获取当前中断的中断号*/IrqNum CPU0.ICCIAR 0x3FF;switch(IrqNum){case 0://0号中断的处理程序break;case 1://1号中断的处理程序break;/** ......*/case 58:printf(Key3 pressed\n);if(flag 1){GPX2.DAT GPX2.DAT | (1 7);flag 0;}else{GPX2.DAT GPX2.DAT (~(1 7));flag 1;}/*清除GPIO控制器中的中断挂起位*/EXT_INT41_PEND (1 2);/*将当前中断的中断号写回到中断控制器以这种方式来告知中断控制器当前的中断已经处理完成可以发送其他中断*/CPU0.ICCEOIR CPU0.ICCEOIR (~(0x3FF)) | 58;break;/** ......*/case 159://159号中断的处理程序break;default:break;}}void Delay(unsigned int Time)
{while(Time--);
}int main()
{/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*//*将GPX1_2设置成中断功能*/GPX1.CON GPX1.CON | (0xF 8);/*设置GPX1_2中断触发方式下降沿触发*/EXT_INT41_CON EXT_INT41_CON (~(0x7 8)) | (0x2 8);/*使能GPX1_2的中断功能*/EXT_INT41_MASK EXT_INT41_MASK (~(1 2)); /*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*//*全局使能中断控制器使其能够接收外部设备产生的中断信号并转发给CPU接口*/ICDDCR ICDDCR | 1;/*在中断控制器中使能58号中断使中断控制器在接收到58号中断后能将其进一步转发到CPU接口*/ICDISER.ICDISER1 ICDISER.ICDISER1 | (1 26);/*选择CPU0来处理58号中断*/ICDIPTR.ICDIPTR14 ICDIPTR.ICDIPTR14 (~(0xFF 16)) | (0x1 16);/*将中断控制器和CPU0之间的接口使能使得中断控制器转发的信号能够到达CPU0*/CPU0.ICCICR CPU0.ICCICR | 1;GPX2.CON GPX2.CON (~(0xF 28)) | (0x1 28);//LED2while(1){/*点亮LED2*/GPX2.DAT GPX2.DAT | (1 7);/*延时*/Delay(1000000);/*熄灭LED2*/GPX2.DAT GPX2.DAT (~(1 7));/*延时*/Delay(1000000);}return 0;
}