怎么控制STM32跳转到指定程序:
- 首先,使用标号加goto语句可以使程序强制跳转,而goto的原理实际上是汇编语言里面的强制跳转。
- 我们看STM32的启动文件,发现里面有这两句程序:
- 其中:
LDR R0 ,=一个函数名:意思是把这个函数的地址放到寄存器R0里面。
BLX R0和BX R0:意思都是跳转到以R0寄存器所存的地址。
STM32的LR寄存器和PC寄存器:
- Cortex-M3架构共有R0-R15共16个寄存器,每个寄存器都是32位的。
R15叫做PC寄存器,意思为程序计数器,内部存的是程序当前执行到了哪个地址。
R14叫做LR寄存器,意思为连接寄存器,在执行子函数前,LR会保存执行完子函数后,应该回到哪个地方。
验证如下: - 我们在主函数执行里面打一个断点,当程序执行到断点处时,我们发现断点处的PC指针是0X080002A2。如下:
- 由于历史原因,最初的16位单片机用的指令是Thumb指令,为16位指令。后来,ARM推出了32位的ARM指令。16位的指令更适合空间有限的嵌入式处理器,而32位的指令则更加灵活、功能丰富。因此,有些单片机在运行时可以根据需求切换指令状态。如下图:
- 后来诞生了Thumb-2指令集,它实现了16位指令和32位指令的共存。而Cortex-M3架构则就是采用Thumb-2指令集。由于16位指令长度是2字节,32位指令长度是4字节,因此其PC指针地址是按照2字节(16位)对齐的。换言之其最低位一定是0。
- 我们发现第一个断点处PC指针:0X080002A2符合这个特点。
- 下面是PC寄存器对齐说明。
- 接着回到我们打的断点处。
- 断点处的上一句执行的是函数raw_os_init()。
- 按照我们的推论,在跳转到函数**raw_os_init()前,LR寄存器会记住执行完raw_os_init()函数后,PC指针应该回到哪个地址。
因此我们在raw_os_init()函数里面打断点,看执行子函数raw_os_init()**时LR是不是存的是执行完子函数后应该返回的地址。如下图:
- 在执行子函数raw_os_init()时,我们发现LR里面存的是0X080002A3,而执行完改函数后应当回到的地址是0X080002A2,我们发现比我们在
temppp += 1;
断点处看到的PC指针大了1。这是为什么呢? - 首先我们观察LR寄存器内存放的值:0X080002A3,其最低位为1,显然不可能是直接存放的程序地址,因为程序地址需要2字节对齐,最低位为0。
- 我们继续看资料发现,原来LR寄存器的最低位是标识指令长度状态的。
- 在以LR存放的地址进行跳转时,会将最低位与0。而与0后0X080002A3就变为了0X080002A2,即要回到的地址。
对指令感兴趣的可以继续往下看:
在《权威指南》一书中,有以下说明:
- 首先是Cortex-M3架构的创新性指令集:
Cortex-M3架构的优点之一就是采用了Thumb-2指令集,其同时支持16位指令和32位指令共存,而不会混淆。
而只所以不会混淆,并不可能是存在什么魔法能够让CPU自动分辨指令长度,必定存在一个标志位,来标识每一条指令的类别是16位还是32位。
- 我们对比LR和PC特点,发现PC最低位一定是0,那么LR存放的数据中,最低位就相当于多余的,因此我们可以拿LR最低位来做标志位,来标识一些信息。恰好,我们上边16位指令还是32位指令缺少个标志位,因此,LR寄存器的最低位是Thumb状态标志位,如下:
2023年3月31日删除下面错误:
这是因为我们在temppp += 1;打断点时,是还没执行temppp += 1;语句的PC指针地址,则执行temppp += 1;时PC指针值应该是0X080002A2 + 1=0X080002A3,刚好和LR寄存器中存的值一样。
2024年1月12日更正下面错误:
换言之其最低2位一定全是0,我们发现0X080002A2符合这个特点
结语:
LR寄存器存的是:执行子函数后,PC指针应该跳转回去的地方。