【精】电子设计基础训练 思考6
本系列给出了作者在课程学习中遇到的问题,并给出自己的思考成果以供大家参考。能力有限,如遇文中有错误、不当之处烦请批评指正。更多问题欢迎在评论区交流!
本文少部分内容引用了互联网上的资料,出处不再一一标明。
结合资料查阅,给出Arduino中loop函数如何实现循环执行的?
首先想到的是查找官网资料,在Arduino网站的DOCUMENTATION-REFERENCE栏目中找到loop()
函数的信息。
图1 Arduino网页对loop
函数的说明
而网站并未对loop
函数的具体执行方式给出任何说明,仅单纯说明该函数是完成初始化后会被循环执行的函数。因此需要进一步深入。查询Arduino Uno开发板的数据得到该板的核心微控制器型号为ATmega328P,进一步查询得到该控制器核心为AVR单片机。下面通过两种方案得到loop
函数的实际执行方式。
-
方案一
可以知道,单片机执行代码需要靠读取已烧写在闪存FLASH(可编程只读存储器EEPROM)中的二进制程序,因此需要找到Arduino IDE编译后生成的文件。新建一份测试代码文件,如图所示:
图2 测试代码
在两个函数中写入内联汇编指令nop
(空指令,不执行任何操作),便于在二进制文件中定位setup
与loop
函数的位置。编译,发现在系统的临时文件夹中生成了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
标号处偏移为0x124
,nop
相对main
标号偏移为0x8c
),查询AVR指令集可知,位于0x1b6
、0x1bc
的跳转指令均满足跳转条件,一旦单片机执行到此处就立即往回跳到nop
指令(loop
函数开始)处,再往下执行,再回跳……如此实现了loop
函数的无限循环。因此,任何写在loop
函数中的自定义代码,都会被编译成汇编指令,填入本测试例中两个nop
指令的位置,再由breq
或rjmp
指令实现反复循环。
图4 AVR指令集
-
方案二
方案一暗示了Arduino IDE在编译程序时不仅仅使用了我们创建的代码文件,其下层应该存在完备的代码封装来调用loop
与setup
函数,且真正生成的可执行程序应该有一个唯一入口点,猜测为main
函数(见方案一反汇编部分)。经过翻找,在Arduino提供的AVR开发平台源码或IDE安装目录(hardware\arduino\avr\cores\arduino\main.cpp)中找到了对loop
函数的引用,同时也找到了程序真正入口点。
图5 main.cpp文件
非常明显,可以在main.cpp文件中看到熟悉的main
入口函数与for(;;)
无限循环结构,setup
和loop
函数在Arduino开发板中的调用层次自此变得明晰。包含main
入口点的编译好的二进制程序被上传至Arduino的FLASH(EEPROM)中,AVR单片机启动时先运行内置bootloader,bootloader在完成初始化工作后定位main
入口点并跳转,将CPU执行权交与我们的程序,main
函数在完成应有的准备后最终进入loop
循环。
Arduino在编译我们的程序时,实际上编译并链接了更多、更主要的代码封装,这些代码封装才是真正的caller,而我们仅仅是在丰富setup
和loop
这些默认留空的函数的“内心”。
【推荐】FlashTable:表单开发界的极速跑车,让你的开发效率一路狂飙
【推荐】Flutter适配HarmonyOS 5知识地图,实战解析+高频避坑指南
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从被喷“假开源”到登顶 GitHub 热榜,这个开源项目上演王者归来!
· Stack Overflow,轰然倒下!
· Cursor 1.2重磅更新,这个痛点终于被解决了!
· 上周热点回顾(6.30-7.6)
· .NET AI 模板