嵌入式系统启动代码和外设代码的汇编过程
文章目录
1. start.S
start.S 通常是指用于嵌入式系统启动代码的汇编文件。这个文件负责在嵌入式系统上电或重置时的初始设置和引导过程。
这段代码是一个ARM汇编程序,用于设置栈指针、清理BSS段并调用主函数。
这段汇编代码的执行过程为:
-
设置栈指针:
- 确保程序在开始执行时有一个正确的栈空间,栈用于函数调用和本地变量的存储。
-
清除 BSS 段:
- 初始化 BSS 段,将所有未初始化的全局和静态变量设置为零。这是为了确保这些变量在程序开始时处于已知状态。
-
调用主程序:
- 调用
main
函数,这是程序的主入口点,开始执行实际的应用逻辑。
- 调用
-
处理程序结束后的行为:
- 在主程序
main
函数返回后,程序进入一个无限循环halt
,以防止程序继续执行无效代码或返回到不安全的地址。
- 在主程序
.text
.global _start
_start:
//设置栈
ldr sp,=0x80200000
bl clean_bss
bl main
halt:
b halt
clean_bss:
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr
1. .text
- 表示下面的代码将放在代码段中,这是汇编程序的一个标准部分,意味着这些代码是可执行的。
2. .global _start
- 声明
_start
是一个全局符号,使得链接器在链接其他模块时可以识别这个符号。它通常表示程序的入口点。
3. _start:
- 这是一个标签,标记程序开始执行的地方。程序启动时会从这个位置开始执行。
4. ldr sp, =0x80200000
- 这行代码将值
0x80200000
加载到栈指针(sp
)寄存器中。设置栈顶位置是程序初始化的一部分,确保在函数调用过程中有足够的空间用于保存返回地址和局部变量。 - 设置栈指针的原因:在调用 C 函数时,需要使用栈来保存现场(如寄存器值),以及传递参数。栈是函数调用和返回过程中重要的数据结构。
5. bl clean_bss
bl
是 "Branch with Link" 的缩写,表示跳转到标签clean_bss
所在的位置,并保存返回地址到链接寄存器(lr
)中。- 这行代码实际上是调用
clean_bss
函数。这个函数的作用是清除 BSS 段(未初始化的全局变量区域)。
6. bl main
- 这行代码调用 C 语言的
main
函数。同样地,bl
指令会跳转到main
函数的代码,并保存返回地址到lr
寄存器中。 - 在嵌入式程序中,
main
通常是主程序的入口函数,负责执行程序的主要逻辑。
7. halt:
- 这是一个标签,标记一个无限循环的起点。这个循环用于在程序执行完
main
函数后停止程序的运行,等待外部指令。
8. b halt
b
是 "Branch" 的缩写,表示无条件跳转到halt
标签的位置。- 这行代码使程序进入一个无限循环,防止程序在
main
函数返回后继续执行无意义的代码,通常用于嵌入式系统中,等待复位或中断。
9. clean_bss:
- 这是一个标签,标记
clean_bss
函数的开始位置。函数的入口。
10. ldr r1, =__bss_start
- 将符号
__bss_start
的地址加载到寄存器r1
中。 __bss_start
是编译器生成的符号,表示 BSS 段的起始地址。- BSS 段用于存储未初始化的全局和静态变量。
11. ldr r2, =__bss_end
- 将符号
__bss_end
的地址加载到寄存器r2
中。 __bss_end
是编译器生成的符号,表示 BSS 段的结束地址。
12. mov r3, #0
- 将立即数
0
加载到寄存器r3
中。 - 这个寄存器用于在 BSS 段中写入 0,以初始化未初始化的全局和静态变量。
13. clean:
- 这是一个标签,标记一个循环的开始位置。
14. str r3, [r1]
- 将寄存器
r3
的值(即0
)存储到寄存器r1
所指向的内存地址中。 - 初始化当前 BSS 段位置的值为
0
。
15. add r1, r1, #4
- 将寄存器
r1
的值增加4
,指向下一个需要初始化的位置。 - 因为每个存储单元是 4 个字节,所以增加 4。
16. cmp r1, r2
- 比较寄存器
r1
和r2
的值。 - 检查当前 BSS 段的位置是否到达了 BSS 段的结束地址。
17. bne clean
- 如果比较结果不相等(即
r1
未达到r2
),跳转回clean
标签,继续初始化下一个存储单元。
18. mov pc, lr
- 将链接寄存器
lr
的值移动到程序计数器pc
,即函数返回。 - 在调用
clean_bss
函数之前,调用者的返回地址已经存储在lr
中,这样函数执行完毕后可以返回调用者继续执行。
2. 芯片的启动流程
STM32 的启动流程
STM32 启动过程包括以下几个关键步骤:
- 系统上电复位:STM32 芯片上电后,首先执行芯片内部的引导程序(BootLoader)。
- BootLoader:BootLoader 根据芯片配置,将程序从 Flash 或其他存储介质加载到 RAM 中。
- 系统初始化:STM32 的启动代码(通常是汇编代码)会初始化栈指针、清除 BSS 段,并进行其他必要的系统初始化。
- 进入主程序:初始化完成后,跳转到
main
函数,开始执行应用程序代码。
BootLoader 的作用
STM32 的 BootLoader 主要用于在芯片上电复位后,执行初始化和加载应用程序的功能。
- 加载程序:将 Flash 中的应用程序代码加载到 RAM 中(如果需要)。
- 初始化系统:根据系统配置初始化时钟、外设和其他系统资源。
- 跳转到应用程序:跳转到应用程序的入口点(通常是
main
函数)。
代码运行流程
- 上电复位后:STM32 芯片上电复位后,首先执行内部 BootLoader 程序。
- 加载应用程序:BootLoader 将 Flash 中的应用程序代码加载到 RAM 中。
- 系统初始化:
- 初始化栈指针:将栈指针(SP)初始化为适当的地址(例如,RAM 顶部地址)。
- 清除 BSS 段:通过调用
clean_bss
函数,将 BSS 段中的数据清零。 - 初始化系统资源:根据具体的应用需求初始化时钟、外设等资源。
- 进入主程序:初始化完成后,跳转到
main
函数,开始执行应用程序代码。
3. 外设代码的启动流程
比如点灯程序led的代码
在启动过程中,BootLoader 会将程序代码(如 led.bin
)从存储介质加载到内存中,CPU 从内存地址 0x08000000
开始执行机器码。在 STM32 中,Flash 通常映射到起始地址 0x08000000
。每条机器码是 32 位/4 字节,此处的机器码就是 led.bin
中的机器码。
例如,在 STM32 中,程序被加载到内存中后,CPU 开始从地址 0x08000000
开始执行机器码。假设 led.bin
被加载到此地址,下面是其机器码的分析:
.global _start
_start:
ldr sp, =0x20000000 // 初始化栈指针
bl clean_bss
bl main
halt:
b halt
clean_bss:
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr
机器码存储和解释
对于指令 ldr sp, =0x20000000
,假设其机器码为 e59fd000
,存储在小端模式下如下:
- 地址
0x08000000
存储:00
- 地址
0x08000001
存储:d0
- 地址
0x08000002
存储:9f
- 地址
0x08000003
存储:e5
小端模式(Little Endian)是指数据的最低有效字节(LSB)存储在内存的最低地址处,而最高有效字节(MSB)存储在最高地址处。这个存储方式广泛用于 ARM 架构的处理器,包括 STM32 。
在 STM32 中,默认也是使用小端模式。因此,同样的指令在 STM32 中的存储方式也会是相同的,即以小端模式存储。
外设初始化
但是bin文件的机器码不方便阅读,所以一般会通过arm-linux-gnueabihf-objdump进行反汇编,得到容易读的led.dis文件。
机器码:
内存地址 机器码 指令
80100000: <_start>:
80100000: e59fd028 ldr sp, [pc, #40] ; 80100030 <clean+0x14>
80100004: eb000001 bl 80100010 <clean_bss>
80100008: fa000057 blx 8010016c <main>
8010000c <halt>:
8010000c: eafffffe b 8010000c <halt>
80100010 <clean_bss>:
80100010: e59f101c ldr r1, [pc, #28] ; 80100034 <clean+0x18>
80100014: e59f201c ldr r2, [pc, #28] ; 80100038 <clean+0x1c>
80100018: e3a03000 mov r3, #0
8010001c <clean>:
8010001c: e5813000 str r3, [r1]
80100020: e2811004 add r1, r1, #4
80100024: e1510002 cmp r1, r2
80100028: 1afffffb bne 8010001c <clean>
8010002c: e1a0f00e mov pc, lr
80100030: eorhi r0, r0, r0
80100034: mulshi r0, ip, r1
80100038: andshi r0, r0, ip, lsr #3
8010003c <led_init>:
8010003c: b480 push {r7}
汇编代码:
.text
.global _start
_start:
// 设置栈
ldr sp,=0x80200000
bl clean_bss
bl main
halt:
b halt
clean_bss:
// 清除BSS段
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr
- 第一条指令设置栈指针。
- 第二条指令跳转到
clean_bss
函数。 - clean_bss 函数清除 BSS 段,将段内存设置为 0。
- 最后返回到
main
函数。
main主函数
反汇编代码:
8010016c <main>:
8010016c: b580 push {r7, lr}
8010016e: af00 add r7, sp, #0
80100170: f7ff ff64 bl 8010003c <led_init>
80100174: 2001 movs r0, #1
80100176: f7ff ffbf bl 801000f8 <led_ctl>
8010017a: f244 2040 movw r0, #16960 ; 0x4240
8010017e: f2c0 000f movt r0, #15
80100182: f7ff ffe3 bl 8010014c <delay>
80100186: 2000 movs r0, #0
80100188: f7ff ffb6 bl 801000f8 <led_ctl>
8010018c: f244 2040 movw r0, #16960 ; 0x4240
80100190: f2c0 000f movt r0, #15
80100194: f7ff ffda bl 8010014c <delay>
80100198: e7ec b.n 80100174 <main+0x8>
C代码:
int main()
{
led_init();
while(1)
{
led_ctl(1);
delay(1000000);
led_ctl(0);
delay(1000000);
}
return 0;
}
汇编指令解释:
push {r7, lr}
: 保存现场,将r7和lr寄存器的值压入栈中。bl 8010003c <led_init>
: 跳转到led_init函数,进行初始化操作。movs r0, #1
: 将立即数1加载到寄存器r0中。bl 801000f8 <led_ctl>
: 跳转到led_ctl函数,传递参数r0(1)。movw r0, #16960 ; 0x4240
和movt r0, #15
: 将立即数0xF4240加载到寄存器r0中,作为delay函数的参数。bl 8010014c <delay>
: 跳转到delay函数,传递参数r0(0xF4240)。movs r0, #0
: 将立即数0加载到寄存器r0中。bl 801000f8 <led_ctl>
: 跳转到led_ctl函数,传递参数r0(0)。movw r0, #16960 ; 0x4240
和movt r0, #15
: 将立即数0xF4240加载到寄存器r0中,作为delay函数的参数。bl 8010014c <delay>
: 跳转到delay函数,传递参数r0(0xF4240)。b.n 80100174 <main+0x8>
: 跳转回主循环的开始,形成无限循环。
整体流程:
- 进入
main()
函数,保存现场信息。 - 调用
led_init()
函数进行初始化。 - 进入无限循环
while(1)
:- 调用
led_ctl(1)
函数,点亮LED。 - 调用
delay(1000000)
函数,延时。 - 调用
led_ctl(0)
函数,关闭LED。 - 调用
delay(1000000)
函数,延时。
- 调用
- 通过
b.n
指令跳转回循环体开头,重复上述过程。
原文地址:https://blog.csdn.net/TENET123/article/details/140256269
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!