use*_*767 4 microcontroller arm cortex-m3 stm32 thumb
我是MCU的新手,并试图弄清楚基于arm(Cortex M3-M4)的MCU是如何启动的.因为启动是针对任何SOC的,所以我采用了STM的示例硬件板进行案例研究.
主板:STMicroelectronics - STM32L476 32位.
在该板中,当引导模式为(x0)"从用户闪存引导"时,电路板将0x0000000
地址映射到闪存地址.在闪存上我已经粘贴了我的二进制文件,前4个字节指向矢量表第一个条目,即esp
.现在如果我按下复位按钮ARM文档说PC值将设置为0x00000000
.
CPU通常基于PC -> PC + 1
循环执行指令流.在这种情况下,如果我看到PC值指向esp
,这不是指令.Arm CPU如何做不使用该指令地址的逻辑,但跳转到地址处的值存储0x00000004
?
或者是这种情况:复位会产生一个特殊的硬件中断并导致PC值为at 0x00000004
,如果是这样的话,为什么Arm文档说它将PC值设置为0x00000000
?
参考:http://infocenter.arm.com/help/index.jsp?topic = / com.arm.doc.faqs/ka3761.html
上电复位后ARM寄存器中有哪些值?适用于:ARM1020/22E,ARM1026EJ-S,ARM1136,ARM720T,ARM7EJ-S,ARM7TDMI,ARM7TDMI-S,ARM920/922T,ARM926EJ-S,ARM940T,ARM946E-S,ARM966E-S,ARM9TDMI
应答寄存器R0-R14(包括存储寄存器)和SPSR(在所有模式下)在复位后未定义.
如果内核具有VINITHI或CFGHIVECS输入,当核心保持复位时,程序计数器(PC/R15)将被设置为0x000000或0xFFFF0000.应设置此输入以反映系统中矢量表的基础所在的位置.
当前程序状态寄存器(CPSR)将指示ARM内核已启动处于ARM状态,Supervisor模式同时设置了FIQ和IRQ掩码位.条件代码标志将是未定义的.有关CPSR的详细说明,请参阅"ARM体系结构手册".
cortex-m的启动方式与传统和全尺寸内核启动的方式不同.那些至少用于重置的那些,你指出从地址0x00000000(或备用,如果断言)获取第一条指令,不是真的公平称它为PC值,因为此时PC有些错误,有多个程序计数器正在在r15中产生了一个假的,一个领先取出,一个做预取,没有一个真的是程序计数器.无论如何,无所谓.
在armv7-m文档中记录的cortex-m(对于m3和m4,对于m0和m0 +,请参见armv6-m,尽管它们到目前为止都以相同的方式启动).这些使用向量TABLE而非指令.CORE读取地址0x00000000(如果绑定被断言则为备用),并且32位值被加载到堆栈指针寄存器中.它读取地址0x00000004它检查lsbit(可能不是所有内核都)如果设置则这是一个有效的拇指地址,剥离lsbit(使其为零)并开始获取该地址的重置处理程序的第一条指令所以如果您的闪存开始
0x00000000 : 0x20001000
0x00000004 : 0x00000101
Run Code Online (Sandbox Code Playgroud)
cortex-m将0x20001000放入堆栈指针并从地址0x100获取第一条指令.拇指指令是16位,thumb2扩展是两个16位部分,它不是x86,程序计数器对齐全尺寸处理器,32位指令在对齐地址0x0000,0x0004,0x0008上取得它不会增加pc <= pc + 1; 对于拇指模式或拇指处理器,它是pc = pc + 2.但是提取也不一定是单指令事务,对于完整大小,它们可以每次事务获取4或8个字,技术参考手册中记录的cortex-ms有些可以一次编译或绑定到16位或一次32位.因此,不需要谈论或考虑执行循环获取pc = pc + 1,即使在x86这些天也没有意义.
公平的武器文件通常是好的,与其他一些相比,更好的一面,而不是最好的.与完整大小的手臂异常表不同,cortex-m文档中的向量表并没有完成,可能有/应该刚刚完成像完整大小的事情,但显示它们是向量而不是指令.虽然在armv6-m和armv7-m的建筑参考手册中(我会假设armv8-m也没看过,上周得到了一些零件,但是板子还没有到这里,很快就会知道) ).无法查找像重置这样的单词必须在该手册中查找中断或未定义或硬故障等.
编辑
在这个关于处理器如何开始提取的概念中解开你的想法,它可以是它们添加到设计中的任意地址,然后执行指令确定下一个地址和下一个地址等.
同样理解不像说x86或微芯片pic或avrs等,核心和芯片是两个不同的公司.即使在那些相同的公司设计中,但肯定在IP与已知总线之间存在明确划分的情况下,ARM CORE将在AMBA/AXI/AHB总线上读取地址0x00000004,芯片供应商可以在多个不同的地方镜像该地址他们想要的地方,在这种情况下使用stm32可能实际上没有任何东西在0x00000000,因为他们的文档暗示基于引导引脚它们将它映射到内部引导加载程序,或者它们将它映射到用户应用程序0x08000000(或大多数) stm32's如果有一个例外,我还没有看到它就好了)所以当这种方式绑定并且逻辑具有镜像的那些地址时,你会在0x00000000和0x08000000,0x00000004和0x08000004看到相同的32位值,等等数量有限地址空间.这就是为什么即使链接0x00000000在某种程度上工作(直到你达到可能小于应用程序闪存大小的限制),你会看到大多数人链接为0x08000000,硬件负责其余的,所以你的表真的很想看起来像
0x08000000 : 0x20001000
0x08000004 : 0x08000101
Run Code Online (Sandbox Code Playgroud)
对于一个stm32,至少到目前为止我见过的几十个.
处理器读取0x00000000,镜像到应用程序闪存中的第一项,找到0x20001000,然后读取0x00000004,它被镜像到应用程序闪存中的第二个字,并获得0x08000101,这将导致从0x08000100获取,现在我们正在执行适当的完全映射应用程序Flash地址空间 只要你不改变镜像,我不知道你是否可以在stm32(nxp芯片,你可以,我不知道ti或其他品牌手头).VTOR寄存器中的一些cortex-m内核存在且可变(其他内容固定为0x00000000且您无法更改它),对于stm32,您不需要将其更改为0x08000000,至少是我所知道的所有内容.它只有你自己主动更改零地址空间的镜像,或者如果你说你有自己的引导程序,也许你的应用程序空间是0x08004000并且该应用程序需要一个自己的向量表.然后你要么使用VTOR,要么你构建引导程序向量表,使它运行代码,读取0x08004000处的向量并分支到那些.过去NXP和其他人肯定使用ARMV7TDMI内核,可以让您更改地址零的镜像,因为那些较旧的内核没有可编程的向量表偏移寄存器,可以帮助您解决芯片设计中的问题.采用VTOR的较新ARM内核消除了这种需求,随着时间的推移,芯片供应商可能不会再费心了......
编辑
我不知道你是否有发现板或核心,我认为后者因为前者不可用(希望我知道那个人想要一个.而且/或者我已经有一个它被埋在抽屉里我从来没有这样做过).
所以这是一个有点小程序,你可以试试你的stm32
.cpu cortex-m0
.thumb
.globl _start
_start:
.word 0x20000400
.word reset
.word loop
.word loop
.thumb_func
loop: b loop
.thumb_func
reset:
ldr r0,=0x20000000
mov r2,sp
str r2,[r0]
add r0,r0,#4
mov r2,pc
str r2,[r0]
add r0,r0,#4
mov r1,#0
top:
str r1,[r0]
add r1,r1,#1
b top
Run Code Online (Sandbox Code Playgroud)
建立
arm-none-eabi-as so.s -o so.o
arm-none-eabi-ld -Ttext=0x08000000 so.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf -O binary so.bin
Run Code Online (Sandbox Code Playgroud)
这应该建立在arm-linux-whatever-或者其他手臂 - 无论是什么 - 来自过去10年的binutils的任何工具.
在使用二进制文件之前,反汇编是很重要的,不想让你的芯片变砖(使用stm32有一种方法可以解决问题)
08000000 <_start>:
8000000: 20000400 andcs r0, r0, r0, lsl #8
8000004: 08000013 stmdaeq r0, {r0, r1, r4}
8000008: 08000011 stmdaeq r0, {r0, r4}
800000c: 08000011 stmdaeq r0, {r0, r4}
08000010 <loop>:
8000010: e7fe b.n 8000010 <loop>
08000012 <reset>:
8000012: 4805 ldr r0, [pc, #20] ; (8000028 <top+0x6>)
8000014: 466a mov r2, sp
8000016: 6002 str r2, [r0, #0]
8000018: 3004 adds r0, #4
800001a: 467a mov r2, pc
800001c: 6002 str r2, [r0, #0]
800001e: 3004 adds r0, #4
8000020: 2100 movs r1, #0
08000022 <top>:
8000022: 6001 str r1, [r0, #0]
8000024: 3101 adds r1, #1
8000026: e7fc b.n 8000022 <top>
8000028: 20000000 andcs r0, r0, r0
Run Code Online (Sandbox Code Playgroud)
反汇编程序不知道向量表不是指令,所以你可以忽略它们.
08000000 <_start>:
8000000: 20000400
8000004: 08000013
8000008: 08000011
800000c: 08000011
08000010 <loop>:
8000010: e7fe b.n 8000010 <loop>
08000012 <reset>:
Run Code Online (Sandbox Code Playgroud)
它是否在0x08000000处启动向量表,请检查.我们的堆栈指针初始值是0x00000000,是的,我们有工具放置的复位向量.thumb_func告诉他们以下标签是某些代码/函数/过程/ whatever_not_data的地址,所以他们为我们订购了那个.我们的重置处理程序在地址0x08000012,所以我们想在向量表中看到0x08000013,检查.为了演示目的,我抛出了几个,将它们发送到地址0x08000010的无限循环,因此向量表应该有0x08000011,检查.
因此,假设您有一个核板不是发现,那么您可以将so.bin文件复制到插入时显示的拇指驱动器.
如果您使用openocd通过stlink接口连接到主板现在您可以看到它正在运行(详细信息留待读者了解)
Open On-Chip Debugger
> halt
stm32f0x.cpu: target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000022 msp: 0x20000400
> mdw 0x20000000 20
0x20000000: 20000400 0800001e 0048cd01 200002e7 200002e9 200002eb 200002ed 00000000
0x20000020: 00000000 00000000 00000000 200002f1 200002ef 00000000 200002f3 200002f5
0x20000040: 200002f7 200002f9 200002fb 200002fd
> resume
> halt
stm32f0x.cpu: target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000022 msp: 0x20000400
> mdw 0x20000000 20
0x20000000: 20000400 0800001e 005e168c 200002e7 200002e9 200002eb 200002ed 00000000
0x20000020: 00000000 00000000 00000000 200002f1 200002ef 00000000 200002f3 200002f5
0x20000040: 200002f7 200002f9 200002fb 200002fd
Run Code Online (Sandbox Code Playgroud)
所以我们可以看到堆栈指针有预期的0x20000400
0x20000000: 20000400 0800001e 0048cd01
Run Code Online (Sandbox Code Playgroud)
程序计数器不是一些神奇的东西,他们必须有点伪造它使指令集工作.
800001a: 467a mov r2, pc
Run Code Online (Sandbox Code Playgroud)
如指令集中所定义的,该指令中使用的pc值是该指令地址之前的两条指令,因此0x0800001A + 4 = 0x0800001E这是我们在内存转储中看到的.
第三项是显示我们正在运行的计数器,恢复和停止显示该计数继续运行
0x20000000: 20000400 0800001e 005e168
Run Code Online (Sandbox Code Playgroud)
所以这表明,向量表,初始化堆栈指针,复位向量,代码执行开始,pc的值在程序中的某个点,以及看到程序运行.
.cpu cortex-m0使它为cortex-m系列构建了最兼容的程序,并且mov r0,= 0x20000000作弊,你在评论中发布了相同的功能,它说我想把blah的地址加载到寄存器中标签只是一个地址,他们让你只放一个地址= _estack是一个标签的地址= 0x20000000只是一个被视为地址的数字(地址也只是数字,没有什么神奇的).我可以用一个班次做一个较小的立即或明确地完成了pc相对负载.在这种情况下习惯的力量.
EDIT2
为了让程序员理解芯片是逻辑的,只有一部分是软件/指令驱动的,即使只是在逻辑上做的事情比软件指令本身指示的更多.你想从内存中读取你的指令要求处理器这样做但是在实际芯片中有许多步骤来实际执行,微编码或不编码(ARM没有微编码)有状态机可以执行各个步骤执行每个任务.从寄存器中获取值,计算地址,执行内存事务,这是一些单独的步骤,获取返回值并将其放在寄存器文件中.
.thumb
.globl _start
_start:
.word 0x20001000
.word reset
.word loop
.word loop
.thumb_func
loop: b loop
.thumb_func
reset:
ldr r0,loop_counts
loop_top:
sub r0,r0,#1
bne loop_top
b reset
.align
loop_counts: .word 0x1234
00000000 <_start>:
0: 20001000 andcs r1, r0, r0
4: 00000013 andeq r0, r0, r3, lsl r0
8: 00000011 andeq r0, r0, r1, lsl r0
c: 00000011 andeq r0, r0, r1, lsl r0
00000010 <loop>:
10: e7fe b.n 10 <loop>
00000012 <reset>:
12: 4802 ldr r0, [pc, #8] ; (1c <loop_counts>)
00000014 <loop_top>:
14: 3801 subs r0, #1
16: d1fd bne.n 14 <loop_top>
18: e7fb b.n 12 <reset>
1a: 46c0 nop ; (mov r8, r8)
0000001c <loop_counts>:
1c: 00001234 andeq r1, r0, r4, lsr r2
Run Code Online (Sandbox Code Playgroud)
几乎没有足够的指令集模拟器来运行该程序.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ROMMASK 0xFFFF
#define RAMMASK 0xFFF
unsigned short rom[ROMMASK+1];
unsigned short ram[RAMMASK+1];
unsigned int reg[16];
unsigned int pc;
unsigned int cpsr;
unsigned int inst;
int main ( void )
{
unsigned int ra;
unsigned int rb;
unsigned int rc;
unsigned int rx;
//just putting something there, a real chip might have an MBIST, might not.
memset(reg,0xBA,sizeof(reg));
memset(ram,0xCA,sizeof(ram));
memset(rom,0xFF,sizeof(rom));
//in a real chip the rom/flash would contain the program and not
//need to do anything to it, this sim needs to have the program
//various ways to have done this...
//00000000 <_start>:
rom[0x00>>1]=0x1000; // 0: 20001000 andcs r1, r0, r0
rom[0x02>>1]=0x2000;
rom[0x04>>1]=0x0013; // 4: 00000013 andeq r0, r0, r3, lsl r0
rom[0x06>>1]=0x0000;
rom[0x08>>1]=0x0011; // 8: 00000011 andeq r0, r0, r1, lsl r0
rom[0x0A>>1]=0x0000;
rom[0x0C>>1]=0x0011; // c: 00000011 andeq r0, r0, r1, lsl r0
rom[0x0E>>1]=0x0000;
//
//00000010 <loop>:
rom[0x10>>1]=0xe7fe; // 10: e7fe b.n 10 <loop>
//
//00000012 <reset>:
rom[0x12>>1]=0x4802; // 12: 4802 ldr r0, [pc, #8] ; (1c <loop_counts>)
//
//00000014 <loop_top>:
rom[0x14>>1]=0x3801; // 14: 3801 subs r0, #1
rom[0x16>>1]=0xd1fd; // 16: d1fd bne.n 14 <loop_top>
rom[0x18>>1]=0xe7fb; // 18: e7fb b.n 12 <reset>
rom[0x1A>>1]=0x46c0; // 1a: 46c0 nop ; (mov r8, r8)
//
//0000001c <loop_counts>:
rom[0x1C>>1]=0x0004; // 1c: 00001234 andeq r1, r0, r4, lsr r2
rom[0x1E>>1]=0x0000;
//reset
//THIS IS NOT SOFTWARE DRIVEN LOGIC, IT IS JUST LOGIC
ra=rom[0x00>>1];
rb=rom[0x02>>1];
reg[14]=(rb<<16)|ra;
ra=rom[0x04>>1];
rb=rom[0x06>>1];
rc=(rb<<16)|ra;
if((rc&1)==0) return(1); //normally run a fault handler here
pc=rc&0xFFFFFFFE;
reg[15]=pc+2;
cpsr=0x000000E0;
//run
//THIS PART BELOW IS SOFTWARE DRIVEN LOGIC
//still you can see that each instruction requires some amount of
//non-software driven logic.
//while(1)
for(rx=0;rx<20;rx++)
{
inst=rom[(pc>>1)&ROMMASK];
printf("0x%08X : 0x%04X\n",pc,inst);
reg[15]=pc+4;
pc+=2;
if((inst&0xF800)==0x4800)
{
//LDR
printf("LDR r%02u,[PC+0x%08X]",(inst>>8)&0x7,(inst&0xFF)<<2);
ra=(inst>>0)&0xFF;
rb=reg[15]&0xFFFFFFFC;
ra=rb+(ra<<2);
printf(" {0x%08X}",ra);
rb=rom[((ra>>1)+0)&ROMMASK];
rc=rom[((ra>>1)+1)&ROMMASK];
ra=(inst>>8)&0x07;
reg[ra]=(rc<<16)|rb;
printf(" {0x%08X}\n",reg[ra]);
continue;
}
if((inst&0xF800)==0x3800)
{
//SUB
ra=(inst>>8)&0x07;
rb=(inst>>0)&0xFF;
printf("SUBS r%u,%u ",ra,rb);
rc=reg[ra];
rc-=rb;
reg[ra]=rc;
printf("{0x%08X}\n",rc);
//do flags
if(rc==0) cpsr|=0x80000000; else cpsr&=(~0x80000000); //N flag
//dont need other flags for this example
continue;
}
if((inst&0xF000)==0xD000) //B conditional
{
if(((inst>>8)&0xF)==0x1) //NE
{
ra=(inst>>0)&0xFF;
if(ra&0x80) ra|=0xFFFFFF00;
rb=reg[15]+(ra<<1);
printf("BNE 0x%08X\n",rb);
if((cpsr&0x80000000)==0)
{
pc=rb;
}
continue;
}
}
if((inst&0xF000)==0xE000) //B
{
ra=(inst>>0)&0x7FF;
if(ra&0x400) ra|=0xFFFFF800;
rb=reg[15]+(ra<<1);
printf("B 0x%08X\n",rb);
pc=rb;
continue;
}
printf("UNDEFINED INSTRUCTION 0x%08X: 0x%04X\n",pc-2,inst);
break;
}
return(0);
}
Run Code Online (Sandbox Code Playgroud)
欢迎你讨厌我的编码风格,这是一个为这个问题抛出的蛮力.不,我不为ARM工作,这可以全部从公共文档/信息中提取.我将循环缩短为4个计数,看它是否触及外循环
0x00000012 : 0x4802
LDR r00,[PC+0x00000008] {0x0000001C} {0x00000004}
0x00000014 : 0x3801
SUBS r0,1 {0x00000003}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000002}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000001}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000000}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000018 : 0xE7FB
B 0x00000012
0x00000012 : 0x4802
LDR r00,[PC+0x00000008] {0x0000001C} {0x00000004}
0x00000014 : 0x3801
SUBS r0,1 {0x00000003}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000002}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000001}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000014 : 0x3801
SUBS r0,1 {0x00000000}
0x00000016 : 0xD1FD
BNE 0x00000014
0x00000018 : 0xE7FB
B 0x00000012
Run Code Online (Sandbox Code Playgroud)
也许这有助于这可能会使情况变得更糟.大多数逻辑不是由指令驱动的,每条指令都需要一定数量的逻辑,不计算像读取指令这样的通用逻辑.
如果你添加更多的代码,这个模拟器将打破它只支持这些少数指令和这个循环.
当您对 Arm 处理器的某些行为感到困惑时,要检查的最重要的事情可能是检查适用的架构版本。您会发现大量与 ARM7 和 ARM9 设计相关的非常古老的遗留文档。虽然今天并非所有这些都是错误的,但它可能会非常具有误导性。
具体来说,ARMv6M/ARMv7M/ARMv8M 与所有其他 ARM 架构(在该系列中保持相似)相比,提供了非常不同的异常模型,而许多其他差异则更加渐进或专注于专门领域。
归档时间: |
|
查看次数: |
1401 次 |
最近记录: |