自学内容网 自学内容网

嵌入式系统启动代码和外设代码的汇编过程

文章目录

1. start.S

2. 芯片的启动流程

STM32 的启动流程

BootLoader 的作用

代码运行流程

3. 外设代码的启动流程

机器码存储和解释

外设初始化

main主函数


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

  • 比较寄存器 r1r2 的值。
  • 检查当前 BSS 段的位置是否到达了 BSS 段的结束地址。

17. bne clean

  • 如果比较结果不相等(即 r1 未达到 r2),跳转回 clean 标签,继续初始化下一个存储单元。

18. mov pc, lr

  • 将链接寄存器 lr 的值移动到程序计数器 pc,即函数返回。
  • 在调用 clean_bss 函数之前,调用者的返回地址已经存储在 lr 中,这样函数执行完毕后可以返回调用者继续执行。

2. 芯片的启动流程

STM32 的启动流程

STM32 启动过程包括以下几个关键步骤:

  1. 系统上电复位:STM32 芯片上电后,首先执行芯片内部的引导程序(BootLoader)。
  2. BootLoader:BootLoader 根据芯片配置,将程序从 Flash 或其他存储介质加载到 RAM 中。
  3. 系统初始化:STM32 的启动代码(通常是汇编代码)会初始化栈指针、清除 BSS 段,并进行其他必要的系统初始化。
  4. 进入主程序:初始化完成后,跳转到 main 函数,开始执行应用程序代码。

BootLoader 的作用

STM32 的 BootLoader 主要用于在芯片上电复位后,执行初始化和加载应用程序的功能。

  1. 加载程序:将 Flash 中的应用程序代码加载到 RAM 中(如果需要)。
  2. 初始化系统:根据系统配置初始化时钟、外设和其他系统资源。
  3. 跳转到应用程序:跳转到应用程序的入口点(通常是 main 函数)。

代码运行流程

  1. 上电复位后:STM32 芯片上电复位后,首先执行内部 BootLoader 程序。
  2. 加载应用程序:BootLoader 将 Flash 中的应用程序代码加载到 RAM 中。
  3. 系统初始化
    • 初始化栈指针:将栈指针(SP)初始化为适当的地址(例如,RAM 顶部地址)。
    • 清除 BSS 段:通过调用 clean_bss 函数,将 BSS 段中的数据清零。
    • 初始化系统资源:根据具体的应用需求初始化时钟、外设等资源。
  4. 进入主程序:初始化完成后,跳转到 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;
}

汇编指令解释:

  1. push {r7, lr}: 保存现场,将r7和lr寄存器的值压入栈中。
  2. bl 8010003c <led_init>: 跳转到led_init函数,进行初始化操作。
  3. movs r0, #1: 将立即数1加载到寄存器r0中。
  4. bl 801000f8 <led_ctl>: 跳转到led_ctl函数,传递参数r0(1)。
  5. movw r0, #16960 ; 0x4240movt r0, #15: 将立即数0xF4240加载到寄存器r0中,作为delay函数的参数。
  6. bl 8010014c <delay>: 跳转到delay函数,传递参数r0(0xF4240)。
  7. movs r0, #0: 将立即数0加载到寄存器r0中。
  8. bl 801000f8 <led_ctl>: 跳转到led_ctl函数,传递参数r0(0)。
  9. movw r0, #16960 ; 0x4240movt r0, #15: 将立即数0xF4240加载到寄存器r0中,作为delay函数的参数。
  10. bl 8010014c <delay>: 跳转到delay函数,传递参数r0(0xF4240)。
  11. 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)!