只分析 《点亮LED灯》中的一段
1.C语言代码
GPIOB->BRR = GPIO_Pin_11;//PB11输出低电平
等价于
*(int*)(0x40010c14)= 0x0800; //向地址0x40010c14上写0x0800
2.汇编代码
0x080001D6 F44F6000 MOV r0,#0x800
0x080001DA 4908 LDR r1,[pc,#32] ; @0x080001FC
0x080001DC 6008 STR r0,[r1,#0x00]
。。。。
0x080001FC 0C14 DCW 0x0C14
0x080001FE 4001 DCW 0x4001
3.代码分析
1)mov指令
MOV{条件}{S} 目的寄存器,源操作数
MOV指令可完成从另一个寄存器、被移位的寄存器或立即数赋值到目的寄存器。其中S选项为指令的操作结果是否操作CPSR中的条件标志位,当没有S选项时指令不更新CPSR中的条件标志位结果。
MOV R0,R1 ; R0 = R1;
MOV PC,R14 ;PC = R14;
MOV R0,R1,LSL#3 ;R0=R1<<3;
0x080001D6 F44F6000 MOV r0,#0x800 即:将0x800放在r0寄存器中
2)LDR指令
LDR指令的格式:
LDR{条件} 目的寄存器 <存储器地址>
作用:将 存储器地址 所指地址处连续的4个字节(1个字)的数据传送到目的寄存器中。
LDR指令的寻址方式比较灵活,实例如下:
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0。
LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+R2的值存入R1。
LDR R0,[R1],#8 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+8的值存入R1。
LDR R0,[R1,R2]! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将R1+R2的值存入R1。
LDR R0,[R1,LSL #3] ;将存储器地址为R1*8的字数据读入寄存器R0。
LDR R0,[R1,R2,LSL #2] ;将存储器地址为R1+R2*4的字数据读入寄存器R0。
LDR R0,[R1,,R2,LSL #2]! ;将存储器地址为R1+R2*4的字数据读入寄存器R0,并将R1+R2*4的值存入R1。
LDR R0,[R1],R2,LSL #2 ;将存储器地址为R1的字数据读入寄存器R0,并将R1+R2*4的值存入R1。
LDR R0,Label ;Label为程序标号,Label必须是当前指令的-4~4KB范围内。
要注意的是
LDR Rd,[Rn],#0x04 ;这里Rd不允许是R15。
另外LDRB 的指令格式与LDR相似,只不过它是将存储器地址中的8位(1个字节)读到目的寄存器中。
LDRH的指令格式也与LDR相似,它是将内存中的16位(半字)读到目的寄存器中。
LDR R0,=0xff
这里的LDR不是arm指令,而是伪指令。这个时候与MOVE很相似,只不过MOV指令后的立即数是有限制的。这个立即数必须是0X00-OXFF范围内的数经过偶数次右移得到的数,所以MOV用起来比较麻烦,因为有些数不那么容易看出来是否合法。
0x080001DA 4908 LDR r1,[pc,#32] ; @0x080001FC 即:将存储器地址为(当前存储器地址+32)的字数据读入寄存器R1。在cortex-m3权威指南的第六章中有一句话,“由于流水线的存在,以及处于对Thumb代码兼容的考虑,读取PC会返回当前指令地址+4。这个偏移量总是4,不管是执行16位指令还是32位指令,这样就保证了在thumb和thumb2之间的一致性”。也就是说,CM3要么每次读取两条16位的指令,要么每次读取一条32位的指令。
这里出现了一个让人疑惑的事情,就是按照上面所述进行计算的话.pc=0x080001DA +4=0x080001DE,然后执行LDR r1,[pc,#32] ;得到的地址是0x080001FE。这与反编译得到的结果不一样,然后我纠结了一天。
查看《深入理解计算机系统》,在里面找到一张图,也不知道自己理解的对不对,拿出来与大家分享自己的想法。
假设A阶段取指,B阶段译码,C阶段执行。
第一条指令进入C阶的时候,也就是第三个时间点(time=300)时,这个时候C开始执行,有可能已经开始取PC值。但此时第三条指令刚刚开始进入A阶段(取指),B阶段(开始译码)。A寄存器(PC寄存器)的值还没有更改,也就是说还是第二指令的地址,即PC=当前指令地址+2。得到PC值为0x080001DC。
然后进行下一步,第一条指令的C阶段接着执行,A、B阶段跟进,到最后time=359的时候,也就是第4阶段,A取指结束,B译码结束,C执行结束,等多了359这个时间点,刷新寄存器。PC=当前指令地址+4。
如果是这么思考的话,就能和指令手册上面所述内容对应上了。 得到地址0x080001FC。将这个地址的字数据放入R1寄存器中。
找到0x080001FC。这个地址的内容是:
0x080001FC 0C14 DCW 0x0C14
0x080001FE 4001 DCW 0x4001
这里有个注意的点是:大小端问题。
[注意]:STM32是小端模式,意思是他把低字节数据保存在低地址,高字节数据保存在高地址。大端模式在保存的时候,他把低字节数据保存在高地址,高字节数据保存在低地址。(按照我们的书写习惯,我觉得大端模式,我们看着舒服。)
那么其实这里的数据就是0x40010C14,这个数据其实就是BRR寄存器的地址。
3)STR指令
STR指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR
指令用于从源寄存器中将一个
32
位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令
LDR
。
指令示例:
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
0x080001DC 6008 STR r0,[r1,#0x00]
它将0x800送入地址为0x40010C14的存储器中,起到了设置寄存器的目的。