【精】电子设计基础训练 思考6

本系列给出了作者在课程学习中遇到的问题,并给出自己的思考成果以供大家参考。能力有限,如遇文中有错误、不当之处烦请批评指正。更多问题欢迎在评论区交流!

本文少部分内容引用了互联网上的资料,出处不再一一标明。


结合资料查阅,给出Arduino中loop函数如何实现循环执行的?

首先想到的是查找官网资料,在Arduino网站的DOCUMENTATION-REFERENCE栏目中找到loop()函数的信息。

图1 Arduino网页对loop函数的说明

而网站并未对loop函数的具体执行方式给出任何说明,仅单纯说明该函数是完成初始化后会被循环执行的函数。因此需要进一步深入。查询Arduino Uno开发板的数据得到该板的核心微控制器型号为ATmega328P,进一步查询得到该控制器核心为AVR单片机。下面通过两种方案得到loop函数的实际执行方式。

  • 方案一

可以知道,单片机执行代码需要靠读取已烧写在闪存FLASH可编程只读存储器EEPROM中的二进制程序,因此需要找到Arduino IDE编译后生成的文件。新建一份测试代码文件,如图所示:

图2 测试代码

在两个函数中写入内联汇编指令nop(空指令,不执行任何操作),便于在二进制文件中定位setuploop函数的位置。编译,发现在系统的临时文件夹中生成了sketch_apr17a.ino.elf文件,根据后缀名可知这是编译生成的ELF格式可执行文件,其中就包含了实际要上传到开发板执行的二进制程序。由前面已知Arduino Uno基于AVR单片机,查阅资料可了解到,Arduino IDE调用了avr-gcc套件(位于IDE安装目录下hardware\tools\avr\bin中)执行真正的编译、链接操作,现在使用该套件反汇编生成的二进制文件。执行avr-objdump.exe -d sketch_apr17a.ino.elf > dasm.S命令,得到文件输出dasm.S。打开文件,可以查看测试程序对应的AVR指令集汇编代码。

图3.1 反汇编(开头部分)

图3.2 反汇编(核心代码部分)

从图3.2中发现,写在测试代码loop函数中的两条nop指令被汇编在偏移0x1b0位置(main标号处偏移为0x124nop相对main标号偏移为0x8c),查询AVR指令集可知,位于0x1b60x1bc的跳转指令均满足跳转条件,一旦单片机执行到此处就立即往回跳到nop指令(loop函数开始)处,再往下执行,再回跳……如此实现了loop函数的无限循环。因此,任何写在loop函数中的自定义代码,都会被编译成汇编指令,填入本测试例中两个nop指令的位置,再由breqrjmp指令实现反复循环。

图4 AVR指令集

  • 方案二

方案一暗示了Arduino IDE在编译程序时不仅仅使用了我们创建的代码文件,其下层应该存在完备的代码封装来调用loopsetup函数,且真正生成的可执行程序应该有一个唯一入口点,猜测为main函数(见方案一反汇编部分)。经过翻找,在Arduino提供的AVR开发平台源码或IDE安装目录(hardware\arduino\avr\cores\arduino\main.cpp)中找到了对loop函数的引用,同时也找到了程序真正入口点。

图5 main.cpp文件

非常明显,可以在main.cpp文件中看到熟悉的main入口函数与for(;;)无限循环结构setuploop函数在Arduino开发板中的调用层次自此变得明晰。包含main入口点的编译好的二进制程序被上传至Arduino的FLASHEEPROM)中,AVR单片机启动时先运行内置bootloaderbootloader在完成初始化工作后定位main入口点并跳转,将CPU执行权交与我们的程序,main函数在完成应有的准备后最终进入loop循环

Arduino在编译我们的程序时,实际上编译并链接了更多、更主要的代码封装,这些代码封装才是真正的caller,而我们仅仅是在丰富setuploop这些默认留空的函数的“内心”。

posted @ 2022-05-06 23:07  coder014  阅读(240)  评论(0)    收藏  举报
相关博文:
阅读排行:
· 从被喷“假开源”到登顶 GitHub 热榜,这个开源项目上演王者归来!
· Stack Overflow,轰然倒下!
· Cursor 1.2重磅更新,这个痛点终于被解决了!
· 上周热点回顾(6.30-7.6)
· .NET AI 模板
点击右上角即可分享
微信分享提示