ARM裸机1期加强版\源码文档图片\文档图片\第014课_异常与中断
014_und_exception_014_004/001有一个BUG,把以下字符串多加一个字符,看看程序还能否运行。
und_string:
.string "undefined instruction exception"
尝试分析反汇编,找到原因(实在找不到的话,下一节视频有讲)
014_und_exception_014_004/002有一个BUG,把start.S
中"bl print1"
去掉,看看未定义指令异常会不会发生,如果能自己解决这个BUG,那么绝对学到家了。我用了整整一个上午才发现原因(哈,我先不说原因,后面的视频也没有讲)。这BUG是一个同学发现的,原因是我找出来的。
实际上LINUX系统中app调用的open, read
等函数就是通过执行swi
命令触发异常,在异常处理函数中实现文件的打开、读写功能。
我们可以实现类似的功能,写一个led_ctrl
汇编函数:
a. 它可以接收1个参数
b. 它会在栈中保存参数
c. 它调用swi #val,这个val来自所接收的参数
d. 恢复参数、返回
修改swi异常处理函数,
a. 根据val来点灯、灭灯
修改main
函数,调用 led_ctrl(0),led_ctrl(1)
对于按键S2,使用快中断支持它。
a. 编写FIQ的中断处理函数,实现保存环境、恢复环境的功能
b. 编写按键的中断处理函数,实现点灯、灭灯功能
c. 修改中断控制器,把S2对应的INTMOD
设置为FIQ
最后一个程序用到了函数指针、注册中断等概念。这对C语言的要求越来越高。main
函数中用到这3个初始化函数,
led_init();
interrupt_init();
key_eint_init();
把它们放在一个函数指针数组里,用一个for循环逐个调用
014_und_exception_014_004/001有一个BUG,把以下字符串多加一个字符,看看程序还能否运行。
und_string:
.string “undefined instruction exception”
尝试分析反汇编,找到原因
不能正常运行,查看反汇编码,当字符串为undefined instruction的时候,und_string段及后段汇编A为:
30000020 <und_string>:
30000020: 65646e75 strvsb r6, [r4, #-3701]!
30000024: 656e6966 strvsb r6, [lr, #-2406]!
30000028: 736e6920 cmnvc lr, #524288 ; 0x80000
3000002c: 63757274 cmnvs r5, #1073741831 ; 0x40000007
30000030: 6e6f6974 mcrvs 9, 3, r6, cr15, cr4, {3}
...
30000035 <reset>:
当字符串为undefined instruction exception的时候,und_string段及后段汇编B为:
30000020 <und_string>:
30000020: 65646e75 strvsb r6, [r4, #-3701]!
30000024: 656e6966 strvsb r6, [lr, #-2406]!
30000028: 6e692064 cdpvs 0, 6, cr2, cr9, cr4, {3}
3000002c: 75727473 ldrvcb r7, [r2, #-1139]!
30000030: 6f697463 swivs 0x00697463
30000034: 7865206e stmvcda r5!, {r1, r2, r3, r5, r6, sp}^
30000038: 74706563 ldrvcbt r6, [r0], #-1379
3000003c: 006e6f69 rsbeq r6, lr, r9, ror #30
30000040 <reset>:
明显,汇编A中,由于字符串的长度问题(22字节),导致汇编指令无法自动按4字节对齐,于是reset段的首地址不是4字节对齐,导致调用出错。而汇编B中,字符串长度刚刚好是32字节,汇编指令能够按照4字节自动对齐,reset段首地址就是4字节对齐,因此没有问题
014_und_exception_014_004/002有一个BUG,把start.S中"bl print1"去掉,看看未定义指令异常会不会发生,如果能自己解决这个BUG。
参看ARM指令集手册,发现ARM的所有指令都可以是条件执行,指令格式如下
cond的含义如下 原来的命令: .word 0xdeadc0de /* 未定义指令 */
这个指令中高4位bit[31:28]=1101,对应图中黄色块,只有当Z set, or N set and V clear, or N clear and V set (Z == 1 or N != V)的时候才会被执行,而上面的bl uart0_init
改变了状态位,使得条件不成立,没有执行这个指令,于是没有undefine异常发生
根据手册,修改bit[31:28] = 1110,对应图中红色块,就是无条件执行语句,undefine异常就可以发生
.word 0xe3000000 /* 未定义指令 */
实际上LINUX系统中app调用的open, read等函数就是通过执行swi命令触发异常,在异常处理函数中实现文件的打开、读写功能。
我们可以实现类似的功能,写一个 led_ctrl 汇编函数:
a. 它可以接收1个参数
b. 它会在栈中保存参数
c. 它调用swi #val,这个val来自所接收的参数
d. 恢复参数、返回
修改swi异常处理函数,
a. 根据val来点灯、灭灯
修改main函数,调用 led_ctrl(0),led_ctrl(1)
需要编写汇编的函数,可以反编译先看看有一个int函数参数的汇编程序是怎样的,然后参考它的写法,小白测试后,发现有一个int参数的C函数对应的汇编大概是这样的:
funtion:
mov ip, sp /* IP=SP;保存SP */
stmdb sp!, {fp, ip, lr, pc} /* 先对SP减4,再对fp,ip,lr,pc压栈 */
sub fp, ip, #4 /* fp=ip-4;此时fp指向栈里面的“fp” */
sub sp, sp, #4 /* 开辟一个int内存 */
str r0, [fp, #-16] /* 参数压栈,-16是跳过ip/lr/pc,到第4个位置 */
ldr r3, [fp, #-16] /* 取出刚刚压入的参数 */
/* r3存放着参数,然后进行具体操作 */
ldmia sp, {r3,fp, sp, pc}
有了参考,led_ctrl
就好写了,不过还是不熟悉汇编,功能是实现了,不知道有没有bug,代码如下
.global led_ctrl
led_ctrl:
mov ip, sp /* IP=SP;保存SP */
stmdb sp!, {fp, ip, lr, pc} /* 先对SP减4,再对fp,ip,lr,pc压栈 */
sub fp, ip, #4 /* fp=ip-4;此时fp指向栈里面的“fp” */
sub sp, sp, #4 /* 开辟一个int内存 */
str r0, [fp, #-16] /* 参数压栈,-16是跳过ip/lr/pc,到第4个位置 */
ldr r3, [fp, #-16] /* 取出刚刚压入的参数 */
cmp r3, #0 /* r3和0比较 */
beq swi_zero /* 如果相等,触发swi中断,传参0 */
swi_one:
swi 0x1
b ctrl_flag
swi_zero:
swi 0x0
ctrl_flag:
ldmia sp, {r3,fp, sp, pc}
然后在Start.S汇编do_swi
中添加bl led_set
……
mrs r0,cpsr /* 传参1 */
ldr r1,=swi_string /* 传参2 */
bl print_exception /* 处理异常 */
mov r0, r4 /* 传参 */
bl led_set
……
在led.c中添加led_set()
函数如下
void led_set(unsigned int *p_swi)
{
unsigned int val = *p_swi & ~0xff000000;
if(val == 0)
GPFDAT &= ~(GP_ONE_ROTATE<<GPF4_DATA);
else
GPFDAT |= GP_ONE_ROTATE<<GPF4_DATA;
}
先取出swi呼叫号数,然后根据号数去开关LED(GP_ONE_ROTATE和GPF4_DATA是自己写的宏,和老师写的数值一样)
最后就可以在main()
函数中调用led_ctrl(1)
打开LED,led_ctrl(0)
关闭LED
对于按键S2,使用快中断支持它。
a. 编写FIQ的中断处理函数,实现保存环境、恢复环境的功能
b. 编写按键的中断处理函数,实现点灯、灭灯功能
c. 修改中断控制器,把S2对应的INTMOD设置为FIQ
先开启FIQ,然后设置按键,再添加do_fiq和handle_fiq即可
修改Start.S
bic r0,r0,#(1<<6) /* 对FIQ清0,开启CPU中断总开关 */
修改interrupt_init()
函数,S2为FIQ
INTMOD = 0x01; //设置按键s2为FIQ模式
Start.S中添加do_fiq
do_fiq:
/*
* 1.lr_fiq保存了被中断模式中下一条即将被执行的指令的地址
* 2.SPSR_fiq保存有被中断模式下的CPSR程序状态
* 3.CPSR中的模式被设置为10001,进入fiq模式
* 4.跳到0x1C的地方执行程序do_fiq
*/
ldr sp, =0x33c00000 /* sp_fiq未设置,先设置栈 */
sub lr, lr, #4 /* 根据手册,返回地址lr需要减4 */
stmdb sp!, {r0-r12,lr} /* 保存现场 异常处理中可能修改r0~r12以及返回地址lr先保存*/
mrs r0, cpsr /* 传参1 */
ldr r1, =fiq_string /* 传参2 */
bl handle_fiq /* 处理异常 */
ldmia sp!, {r0-r12,pc}^ /* 恢复现场 ^会把spsr恢复到cpsr*/
fiq_string:
.string "fiq instruction exception"
.align 4 /* 为了让后面的程序4字节对齐 */
key.c中添加handle_fiq()
/**
* @brief 发送按键中断后的服务函数,用于区分中断源并执行对应函数
* @param cpsr:保存现场时的备份寄存器
* @param str:待打印的字符串
* @retval None
*/
void handle_fiq(unsigned int cpsr, char* str)
{
unsigned int val = GPFDAT;
//需要读SRCPND。因为FIQ下,INTOFFSET和INTPND不会被影响
int bit = SRCPND;
//1.信息输出
puts("[FALSE] Exception instruction!\n\r");
puts("CPSR=");
printHex(cpsr);
puts(" ");
puts(str);
puts("\n\r");
//2.FIQ只有一个中断源
//处理中断, 清中断源EINTPEND
if (val & (1<<0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1<<6);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<6);
}
//3.清位,INTPND不会被FQI影响,从源头开始清
SRCPND = (1<<(bit-1));
}
最后一个程序用到了函数指针、注册中断等概念。这对C语言的要求越来越高。main函数中用到这3个初始化函数,
led_init();
interrupt_init();
key_eint_init();
把它们放在一个函数指针数组里,用一个for循环逐个调用
在main函数上方声明类型,定义初始化数组
typedef void(*init_func)(void);
irq_func init_array[32];
unsigned char num_init = 0;
然后main函数中使用
int i;
init_array[num_init++] = key_eint_init;
init_array[num_init++] = leds_init;
init_array[num_init++] = timer0_init;
for(i = 0; i<num_init; i++)
init_array[i]();
百度网盘 提取码:8fto