《Cortex M3权威指南》参考(8o27)
======专栏地址======
以STM32F10X为例:
通过上第一图3个红框部分来说明简单的程序运行:
1、M3内核(第二图为其简化视图):程序执行的核心。详细功能流程可参考《Cortex M3权威指南》。
2、Flash
:存储程序,指令,常量等。
3、SRAM
:存储变量。
u8 x = 1;
int main()
{
u8 y;
y = x;
return 0;
}
/*
如上代码的执行顺序便是:
1、M3内核从flash(程序存储在flash)中获取指令。
2、M3内核执行指令。
3、M3内核去读SRAM中x的值。
4、M3内核去写SRAM中y的值。
*/
上述读写操作会将值存储在内核中的寄存器组(如下图)中,而做出这些读写操作就需要一些汇编指令。
注:
R0-R12
(通用寄存器)
R13
(堆栈指针SP
):
主堆栈指针(MSP
):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)
进程堆栈指针(PSP
):由用户的应用程序代码使用。
R14
(连接寄存器LR
):当呼叫一个子程序时,由 R14 存储返回地址。
R15
(程序计数器PC
):指向当前的程序地址。如果修改它的值,就能改变程序的执行流。(联想学习笔记(1)中的最后部分,应该就有大概的RTOS框架了)。
ARM 过程调用标准AAPCS
中比较重要的几条:
1、R0~R3
:argument/scratch register
用来传递参数和返回值,无需进行保护。
2、R4~R11
:在需要使用这几个寄存器时,要先将其中数据入栈保护起来,并在使用结束前将数据出栈恢复。
3、R14(LR)
:在跳转到子函数时LR会储存返回地址,但如果在子函数中再调用别的函数,就要将LR中的地址先入栈保护起来,若再调用依次类推,出栈时也是先将最后入栈的LR地址先恢复,以此类推。
部分指令格式以及注意事项也可参考《Cortex M3权威指南》中的第四章。
ARM 汇编器的最典型书写模式如下所示:
标号
操作码 操作数 1, 操作数 2, … ; 注释。
MOV
指令(move)(传送指令)/*
操作数的表示方式有:立即数,寄存器地址,或寄存器加偏移。
立即数:即立即寻址的一个数据,前面加个#,如0x00002700,去掉#就是直接寻址;
寄存器:加个[],如[R1];
寄存器偏移:[R1,R2],或者[R1,#8],[R1,LSL #8]等。
*/
MOV R0,#050aH ;将十六进制数050a 传送到通用寄存器R0中
MOV PC,LR ;从子程序中返回
LDR
指令(load)(读取数据指令)LDRB
是读取8位的字节数据,LDRH
是16位的半字数据) LDR R0,#0x00002700 ;将数据0x00002700读入寄存器R0。
LDR R0,=0x00002700 ;将存储器地址0x00002700读入到寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的内存单元数据读入寄存器R0。
LDR R0,[R1],#8 ;将存储器地址为R1的内存单元数据读入寄存器R0,然后R1=R1+8。
STR
指令(store)(存储数据指令)STRB
是发送8位的字节数据,STRH
是16位的半字数据)//使用方式可参考指令LDR
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,然后R1=R1+8。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
ADD
/ SUB
/ MUL
指令(算术运算指令)//加
ADD R0,R1,#4 ;R0 = R1 + 4
//减
SUB R0,R1,R2 ;R0 = R1 - R2
//乘
MUL R0,R1,R2 ;R0=R1*R2
CMP
指令(COMPARE)(比较指令) CMP R0,R1 ;比较R0,R1
B
/ BL
指令(branch / branch&link)(跳转指令) B main ;跳转到main地址处执行
MOV PC,LR
指令跳回来,这实际上就是C语言调用子函数的用法。 BL delay
PUSH
/ POP
指令(压栈 / 出栈指令)汇编里把一段内存空间定义为一个栈,栈总是先进后出(FILO
)。
图自CM3权威手册。
PUSH {R0 } ;把 R0 存入栈 & 调整 SP
PUSH {R1} ;把 R1 存入栈 & 调整 SP
PUSH {R2} ;把 R2 存入栈 & 调整 SP
//PUSH {R0-R2,R4, R6} ;压入 R0-R2,R4,以及 R6
… ;执行 程序 的功能,中途可以改变 R0-R2 的值
POP {R2} ;恢复 R2 早先的值 & 再次调整 SP
POP {R1} ;恢复 R1 早先的值 & 再次调整 SP
POP {R0} ;恢复 R0 早先的值 & 再次调整 SP
/*
PUSH {R0-R2, LR}
POP {R0-R2, PC} ;PC即是程序下一步要执行的地址,因此直接绕过LR即可
*/
以下示例代码是在keil环境,用模拟debug进行,MCU:STM32F103
int add(int a,int b)
{
return a+b;
}
int main(void)
{
int a=1;
int b=2;
int c;
c = add(a,b);
return c;
}
//main函数的disassembly对应关系
//int main(void)
0x080001B0 4770 BX lr
// {
0x080001B2 B530 PUSH {r4-r5,lr}
// int a=1;
0x080001B4 2301 MOVS r3,#0x01
// int b=2;
// int c;
0x080001B6 2402 MOVS r4,#0x02
// c = add(a,b);
0x080001B8 4621 MOV r1,r4
0x080001BA 4618 MOV r0,r3
0x080001BC F7FFFFF6 BL.W add (0x080001AC)
/*int add(int a,int b){
0x080001AC 4602 MOV r2,r0
return a+b;
0x080001AE 1850 ADDS r0,r2,r1
}*/
0x080001C0 4605 MOV r5,r0
// return c;
0x080001C2 4628 MOV r0,r5
// }
0x080001C4 BD30 POP {r4-r5,pc}
汇编码拆解:
注:
MOV加了S表示这个MOV指令会影响CPSR(当前程序状态寄存器)中的标志位。
BX lr //= MOV PC,LR
PUSH {r4-r5,lr} //把r4,r5和lr中的数据压入栈,在这之后可以操作这几个寄存器
/*
入栈顺序:
高地址
LR的数据
R5的数据
R4的数据 <- 堆栈指针SP指向最后一个被压入堆栈的32位数值,在下一次压栈时,SP先自减4,再存入新的数值。
低地址
*/
MOVS r3,#0x01 ;把1赋值给r3 //r3=1
MOVS r4,#0x02 ;把2赋值给r4 //r4=2
/*
如果a,b声明时用了volatile关键字,那么会把r3(a),r4(b)的值STR到栈中,这里没有
*/
MOV r1,r4 ;把r4赋值给r1 //r1=r4=2
MOV r0,r3 ;把r3赋值给r0 //r0=r3=1
BL.W add (0x080001AC) ;跳转到add函数地址0x080001AC,并存储当前地址到lr
/*进入add函数
MOV r2,r0 ;把r0赋值给r2 //r2=r0=1
ADDS r0,r2,r1 ;r0=r2+r1=1+2
(MOV PC,LR)
*/
MOV r5,r0 ;r5=r0=3
MOV r0,r5 ;return r0=r5=3
POP {r4-r5,pc} ;把先前入栈的数据弹出恢复到r4,r5,pc
/*
出栈顺序:
高地址
原先入栈的R4的数据 ->先从SP指针处读出上一次被压入的值,再把SP指针自增4。
原先入栈的R5的数据
原先入栈的LR的数据 //LR弹出到PC
*/
//将上述代码的c变量用volatile声明就会发现汇编的时候会将得到的c的值压入栈
//volatile int c;
0x080001B6 2402 MOVS r4,#0x02
//c = add(a,b);
0x080001B8 4621 MOV r1,r4
0x080001BA 4618 MOV r0,r3
0x080001BC F7FFFFF6 BL.W add (0x080001AC)
0x080001C0 9000 STR r0,[sp,#0x00] ;当前r0值存入当前sp(即r4) //c=r4=r0=r1+r2=3
//return c;
0x080001C2 9800 LDR r0,[sp,#0x00] ;读取当前sp指向的栈值到r0 //r0=c=3
改该部分可参考《Cortex M3权威指南》的第9章中断/异常的处理。
由于中断会随时产生,假设现在在红色箭头出产生了中断,那么在处理中断函数时就可能导致r0~r3
的值被改变,因此中断产生前必须保存所有寄存器的值,而产生中断就会调用中断函数,在调用函数时会保证r4~r11
受到保护(参考前面的AAPCS
),那么就只需要由硬件保存其他几种寄存器。
注意:这里PC才是中断返回的地址,LR只是之前储存的链接地址。
数据总线执行入栈操作的时候,指令总线(I‐Code)正在从向量表中找到该中断函数的入口地址,然后执行该中断函数。
中断返回时先将之前保存的所有寄存器数值出栈,然后将NVIC寄存器中的中断标志位进行硬件清除。