微控制器的 C 语言启动代码是什么?何时/为何/如何修改?

What is a startup code in C for Microcontrollers ? When / Why / How it should be modified?

这是关于启动代码的一般性问题。我知道它就像引导加载程序或 运行 在重置或加电后调用主函数的第一件事。

但我想知道它的主要/核心功能。

例如(在 google 中搜索),

目标系统重置后立即执行启动代码。 Keil 启动 代码按顺序执行(可选)以下操作:

 Clears internal data memory
 Clears external data memory
 Clears paged external data memory
 Initializes the small model reentrant stack and pointer
 Initializes the large model reentrant stack and pointer
 Initializes the compact model reentrant stack and pointer
 Initializes the 8051 hardware stack pointer
 Transfers control to code that initializes global variables or to the main C function if
there are no initialized global variables

注意:启动代码总是用汇编语言编写,因为它依赖于 CPU 目标。

感谢您的宝贵时间

所有计算机程序都假设它们执行的世界是按照它们的期望设置的。

假设您有一个程序 P,它假定变量 X 的值在 P 启动时为零。

如果您将该程序 P 作为代码启动点放入您的微处理器中,它将无法运行....因为 X 的值不能保证为零。 (RAM 内存位置往往会包含垃圾。)

您通过插入使假设成立的启动代码来解决该问题,例如,您在启动时放置代码以将变量 X 归零,然后将控制权传递给您的程序。无论您的程序需要什么假设,您都需要在启动代码中实现。

现在我写了一个非常笼统的描述。当我们谈论真正的微处理器时,通常有一些必须满足的低级假设:

  • 如果机器有一个堆栈指针,它指向一些实际内存而不是存放垃圾
  • 需要任何 I/O 端口或特殊硬件 "ready to use"(它们通常需要配置;考虑内存库 select 寄存器或优先级中断控制器)
  • 您的程序位置已知

如果需要这三个假设,启动代码将通过以下方式解决它们:

  • 使用已知为 RAM 地址的常量加载 SP 寄存器
  • 执行特殊指令或将各种魔术常量写入必要的I/O端口以配置它们以供使用
  • 将 PC 设置为程序的已知位置(通常是 JMP xxxx 指令)

每个 MCU 控制器程序对世界都有不同的假设。您自定义编码您的启动代码以满足这些假设。

一般来说,这些假设并不多,许多 MCU 程序都可以通过一个小的、精心选择的集合来实现。因此,启动指令往往与程序分开处理(否则,您可以简单地将它们添加到您的程序中,有时人们会这样做)。

您提到启动代码调用 main,因此我假设您是在谈论 C/C++ 应用程序的启动代码。启动代码负责为应用程序设置 C 运行-time 环境。其中包括:

  • 将初始化变量的初始值从 ROM 复制到 RAM
  • 为未初始化的变量清零 RAM
  • 设置堆栈指针
  • 对于 C++ 应用程序,它为任何全局或静态对象实例调用构造函数
  • 最后,调用main

启动代码也可能会进行一些硬件初始化,但这是特定于硬件的,不一定是必需的。例如,如果硬件使用 PLL 来提高时钟频率,则启动代码可能首先设置 PLL,以便其余启动代码全速执行。如果开发板在 address/data 总线上有外部设备,则处理器的外部内存控制器通常在启动代码中配置。或者,如果硬件有看门狗,则启动代码可能会禁用它,这样它就不会在应用程序有机会配置它之前重置。

我通常不会在启动代码中执行特定于应用程序的硬件初始化。相反,我将在调用 main 之后在我的应用程序中初始化 GPIO、定时器和串行端口。

编译器工具链通常为其支持的硬件环境提供启动代码。此示例启动代码可能适用于大多数应用程序,您可能永远不必修改它。但是,如果您的硬件或 运行-time 环境有一些特殊之处,那么您可能需要自定义启动代码。当我不得不自定义启动代码时,我总是采用示例启动代码并对其进行修改以满足我的需要。我不记得曾经从头开始编写启动代码。