一、压栈、出栈
在单片机中,通常使用堆栈(或称为栈)来保存程序执行时的现场信息。堆栈是一种数据结构,在内存中按照“先进后出”的顺序进行操作。当程序执行到一个函数调用时,它会将当前的指令地址和寄存器等现场信息保存到堆栈中,并跳转到函数的入口处继续执行。当函数执行结束后,它会从堆栈中取回之前保存的现场信息,恢复程序执行的状态。
当发生函数调用、中断处理、缓冲区溢出、递归函数时会进行压栈,结束时会进行出栈。
二、函数调用
函数调用流程:保存现场 --> 函数调用 --> 执行函数 --> 返回结果 --> 恢复现场
保存现场后,进行函数调用时,以下内容依次会被保存到堆栈中:
- 函数返回地址:将下一条指令地址压入堆栈,以便在子程序执行完毕后能够正常返回。
- 函数参数:将调用函数时传递的参数压入堆栈中,参数从右往左按顺序排列。
- 执行环境信息:函数内部定义的局部变量、临时变量、寄存器状态等信息。
执行完后函数的返回值讲存放到之前预留的位置上,并恢复现场,恢复原来的指令地址和执行状态。
三、中断响应
在执行主程序时,突然接收到一个中断信号后,立即暂停当前的任务,转而去处理该中断信号所对应的中断服务函数任务。
每个外设都有一个或多个中断请求引脚(IRQ),每个IRQ又对应一个特定的中断服务函数(ISR),当外设需要向CPU发出请求时,就改变IRQ引脚的电平状态。此时,在单片机中断系统硬件的支持下,CPU会立即停下来执行相对应的中断服务函数,以处理该中断请求。
四、回调函数和函数调用的区别
- 回调函数:一般作为参数传入到其他函数中,在特定条件下被执行。如在主程序中传递一个函数指针,并且由另一个函数或事件触发后异步执行该函数。
- 函数调用:在主程序中直接调用一个函数,,一般由程序员直接调用,并且在一般情况下,是同步的,也就是说,当函数被调用后,在该函数执行完毕前,程序会一直等待。
回调函数的可重用性很高,因为它们不依赖于任何具体的环境。相反,普通的函数可能会涉及到环境变量和局部变量等问题。
五、回调函数和中断函数的区别
- 回调函数:在程序中以普通函数的形式定义,并且可以在任何时候被调用。当回调函数作为参数传递至其他函数或事件处理器时,它既可以同步执行也可以异步执行。
- 中断函数:由硬件或操作系统发起的一种机制,在特定的事件(例如外部设备请求、错误、时钟等)发生时被自动触发。中断函数通常需要快速响应,立即执行,并尽可能地短暂运行,因此其实现上有着很多限制。
虽然回调函数和中断函数都是在事件发生时被调用的,但由于回调函数是普通的程序代码而非由硬件或操作系统直接触发,因此其实现上更加灵活和可控。
六、单片机的编译流程
① 编译预处理:读取 C 源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。
- 宏定义指令,如#define Name TokenString,#undef等。
对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的 Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。- 条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉- 头文件包含指令,如#include "FileName"或者#include <FileName>等。
在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。
采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。- 特殊符号,预编译程序可以识别一些特殊的符号。
② 编译器编译:编译器对预处理后的源代码进行语法分析、语义检查等操作,并将其翻译成汇编代码。
③ 汇编器汇编:将汇编代码转换成机器指令,生成目标文件(OBJ文件)。
④ 链接:将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体,一般有 elf、coff、mach-o 等格式。
⑤ 转换文件:最后生成可直接下载到芯片并且能够运行在芯片上的 hex 或 bin 文件。
Linux 下交叉编译流程
① ~ ③ 将 led.s 编译为对应的 .o 文件
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
-g
:产生调试信息-c
:编译源文件,但是不链接-o
:指定编译产生的文件名字
④ 将众多的 .o 文件链接到一个指定的链接位置,本实验链接地址为:0X87800000
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
-Ttext
:指定链接地址-o
:指定链接生成的 elf 文件名,命名为 led.elf
⑤ 将 .elf 文件转换为 .bin 文件
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
-O
:指定以什么格式输出,“ binary”表示以二进制格式输出-S
:不要复制源文件中的重定位信息和符号信息-g
:不复制源文件中的调试信息
七、ARM 下的 elf 格式
elf 文件是一种可执行文件和可链接目标文件通用的二进制格式。它包含了程序代码、数据、符号表、重定位信息等重要的元素,可以在ARM平台上运行并与操作系统进行交互。
在ARM下,elf 文件有三种类型:
①:可重定位文件(Relocatable File):根据链接地址,确定代码和数据的位置、大小和属性等,最终将可执行文件拷贝到指定的链接地址进行执行。
②:可执行文件(Executable File):经过链接后生成的最终可执行文件。
③:共享库(Shared Object File):类似于Windows下的DLL(动态链接库),多个程序共享同一份代码和数据段。
elf 文件头部描述了文件类型、目标体系结构、入口地址等信息。在头部后面是各个段(Section)和段头表(Section Header Table),段是一个逻辑单元,描述了不同类型的内容。例如.text段包含编译器生成的指令代码,.data段包含静态数据等。每个段都有一个对应的段头表项来描述它们在内存中应该放置的位置和大小等信息。
八、ARM 下程序启动流程
在 ARM 下执行程序,一般是 ARM 处理器会先从烧入程序里读取 elf 文件头部(即可重定位文件,从中可得知链接地址,明确可执行文件该拷贝到 DDR、Flash 或 EEPROM 等非易失性存储器中的地址),ARM 处理器再进行可执行文件的拷贝,在拷贝过程中,ARM 处理器还需要处理一些细节,比如解压缩、填充空白区域等,以保证可执行文件能正确运行,然后跳至可执行文件启动烧入的程序,一般可执行文件的启动文件为汇编文件,在其设置中断向量表,初始化 sp 指针等操作后,再跳至 C 语言中的 main 函数。文章来源:https://www.toymoban.com/news/detail-416586.html
九、ARM 下的汇编调用 C 语言函数传参
ARM 下的汇编调用 C 语言函数传参有两种方法:文章来源地址https://www.toymoban.com/news/detail-416586.html
- 参数依次存储在寄存器 r0、r1、r2 、r3中和栈中:前四个参数分别从左到右存储在 r0、r1、r2 和 r3 寄存器中,如果还有更多的参数,则需要将它们依次压入栈中。C 语言函数在执行时,可以通过访问 SP 寄存器来获取栈中的参数值。
- 参数全部存储在栈中:所有函数参数都按照从右到左的顺序压入栈中,然后 C 语言函数在执行时通过访问 SP 寄存器来获取栈中的参数值。
C 语言函数的返回值一般存在r0寄存器中。
到了这里,关于【散文诗】单片机程序基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!