目录
5.2.3 options for target(目标选项) 与 Manage Project(管理项目)
1. 前存储空间 0x0000_0000~0x1FFF_FFFF(512MB)
(1.1)CODE区 0x0000_0000~0x07F F_FFFF(128MB)
(1.2) Flash 0x0800_0000~0x0F FF_FFFF(128MB),这个芯片只用到~0x083E FFFF(4032KB,即3M)
(1.3) SRAM1 映射区 0x1000_0000~0x1000_FFFF(64KB)
(1.4)Bootloader flash 0x1FFF_0000~0x1FFF_BFFF(48KB)
(1.5)User system data ? 0x1FFF_C000~0x1FFF_CFFF(4KB)
2. SRAM 0x2000_0000~0x200F_FFFF(1024KB)
3. 外设空间 0x4000 0000~0xDFFF FFFF(2GB)
Betaflight的体系相当庞大包括一套系统调用算法、各类飞行控制及飞行辅助算法、完整的地面通信体系、完整的外部设备接口系统等,而由于Betaflight源码是在Linux环境下进行编译的,arm-none-eabi-gcc交叉编译器与Keil自带的ARMCC编译器也有许多不同,具体体现在:
1. Linux下支持的一些Linux C语言库函数,Keil下可能不支持,如静态断言等。
2. 两者的汇编语法不同,导致.s汇编启动文件也有出入。
3. Keil下是没有.ld链接脚本之类的文件的,导致其中定义的变量不能使用。
综上所述,Betaflight移植到Keil的第一步便是解决由于编译环境不同引起的一系列问题,本篇的目的是:
1. 解决Linux与Keil环境不同引起的一系列编译方面的问题。
2. 移植Betaflight下所有的lib文件与src中关于系统调用算法有关的文件到Keil中并正常编译运行,具体包括系统调用初始化与系统调用扫描。
5.1 环境搭建
5.1.1 MDK安装:
安装过程参考 /2、参考资料/STM32F1开发指南(精英版)-寄存器版本_V1.2文档。软件包位于 /5、软件资料/MDK5
5.1.2 芯片Pack包安装:
参考 \6、AT32官方资料\(1)教程\(1)AT32F435_437固件库BSP&Pack应用指南.pdf , pack包位于\6、AT32官方资料\Pack包_下载器Keil5_AT32MCU_AddOn_V2.1.9.zip
5.1.3 J-LINK OB:
对软件的下载与调试这里使用J-LINK OB(与普通J-link功能相同,但价格低的多),淘宝链接:
Jlink驱动安装,参考 /5、软件资料/J-LINK OB全套资料
5.1.4 另外使用Jlink调试过程中可能出现如下报错:
错误的提示:
其错误提示为:Encountered an improper argument(翻译过来就是遇到不恰当的争论)
错误原因
错误原因其实是因为我们在调试完结束时候,有断点(红色圆点)还没有去掉,所以我们一点击停止调试之后,keil就会马上弹出这个错误,然后你就会发现你的keil关不掉了。。。,别问我怎么知道的,因为我自己崩了不下二三十次,试了keil的两个不同版本都会出现这种情况(当然不是所有版本都这样,因为本人只试了两个版本),直到我发现只有把断点(红色圆点)全部去掉,关调试的时候才不会弹出这种提示和导致keil崩。新版本的Keil可能已经解决该问题。
解决方法:
如果想要规避这种情况的话,在你调试结束时候记得检查是否有断点没有去掉!!!重要的事说三次~
同时,这里跟你说一下,如果已经弹出如上图所示提示,keil可能已经崩了,正常是关不了了,我基本上是通过任务管理器强行关掉的。然后再打开工程就好,并且你再调试时候那个没有去掉的红点依然在,记得这次把点去了就好。
参考:解决调试时候出现的“Encountered an improper argument”错误-CSDN博客
5.1.5 官方例程试验
其实AT32官方是有给移植好的例程的,只是与其他软件应用相混杂,不太好用。这里可以用官方例程来试试环境是否搭建成功啦。打开目录
6、AT32官方资料\BSP_板级支持包_AT32F435\AT32F435_437_Firmware_Library_V2.1.2\project\at_start_f435\templates\mdk_v5
下的keil工程文件,编译、设置Debug为Jlink,由于AT32飞控Led与AT32官方start开发板的led使用了相同的引脚,下载成功后飞控的两个led灯会呈流水灯一样闪烁。
另外,当使用AT32 keil工程设置Debug为jlink时可能会出现如下提示:
|
该提示表示在Jlink驱动中查找不到AT32F435VMT7这个设备,这里先点击Yes来手动选择芯片设备。
然后会出现如下界面:
|
这里 Cotex-M4 -> OK。
至于具体原因,来看下AT32飞控参数:
CPU: AT32VMT7
主频: 288MHz
引脚: 100 Pin(“V”)
Flash: 4032 KB(“M”)
RAM: 384 KB
内核:32位ARM® Cortex®-M4内核
故选择Cotex-M4这个广义设备。
5.2 移植前准备
5.2.1 AT32官方资料简介
AT32的官方资料者可以在https://www.arterytek.com/cn/product/AT32F435.jsp#Resource 中下载。
这里清月将下载的资料都放在了6、AT32官方资料 中,其中ISP工具要介绍一下,这是一个程序下载软件,
有UI界面(6、AT32官方资料\Tool_工具\Artery_ISP_Programmer_V2.0.08\Artery ISP Programmer_V2.0.08 下的ArteryISPProgrammer.exe)
与 命令行版本(G:\Practice\(2)Linux飞控\(4)作品\基于AT32的Betaflight飞行控制器\6、AT32官方资料\Tool_工具\Artery_ISP_Console_Win_V3.0.05\Artery_ISP_Console_Win_V3.0.05 中的 AT32_ISP_Console.exe)
命令行版本要使用.bat批处理文件命令进行控制,在文件目录下DFU_download.bat等三个*download.bat文件就量分别使用usb_dfu、uart、I2C来下载程序用的。可以放在工程文件夹下使用,使用时先按住BOOT键上电启动进行Bootloader下载模式,然后双击DFU_download.bat即可,之后移植的AT32工程将使用到ISP。
注意,要保证DFU_download.bat中的程序下载文件地址与工程生成的.bin文件一致。ISP使用的命令参考:
6、AT32官方资料\Tool_工具\Artery_ISP_Console_Win_V3.0.05\Document
5.2.2 Keil文件类型
来科普一些概念,Keil 即是一家公司名称,也是一款软件名称。Keil 有几个出名的软件(IDE),包括 MDK、 C51。而µVision(或uVision)是一种开发环境,µVision5是其第5个版本。相关具体信息参考:
Keil科普教程 | Keil C51 和 MDK 的区别 - strongerHuang的文章 - 知乎
Keil科普教程 | Keil C51 和 MDK 的区别 - 知乎
(1)工程文件
(这类文件不能删除) *.uvprojx:µVision5工程文件
(2)工程选项配置文件
(这类文件不能删除) *.uvoptx:µVision5工程选项配置文件
(3)项目界面布局文件
(这类文件可以删除) *.uvguix[.user-name]:µVision5项目界面布局文件。删除之后,重新打开工程,界面布局会恢复到默认布局。如Demo.uvguix.Administrator。
其中[.user-name]为计算机的用户名,若使用联想电脑,默认为.lenovo,如文件:template.uvguix.lenovo
(4)删除过程文件.bat
.bat为winbows批处理文件,是一种调用DOS命令的可执行文件,可以看作一个小软件,一般双击运行来删除编译过程产生的文件。
(5)EventRecorderStub.scvd
EventRecorderStub:事件记录器存根(可能记录了一些用户操作之类的信息)
(6)JLinkSettings.ini
是对Jlink进行一些配置的文件。
INI文件格式是某些平台或软件上的配置文件的非正式标准,以节(section)和键(key)构成,常用于微软Windows操作系统中。这种配置文件的文件扩展名多为INI
5.2.3 options for target(目标选项) 与 Manage Project(管理项目)
Keil使用经年,仍然搞不懂“魔术棒”?来看看下面的整理
|
这玩意,官方名称options for target(目标选项),其中有许多关于工程编译调试的重要选项,可以说工程编译报错时的种种问题都可以在其中找到答案。而其中繁杂的选项学过Makefile的同学可能有点眼熟(了解Makefile,参考 2、参考资料\【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf 中的3.3 Makefile 基础)。
(1)Device
选择芯片类型:ARM等蓝色标号代表厂家,选中芯片,右侧方框会显示芯片主频、内核等关键信息
(2)Target
ROM是一种只读存储器,在老版单片机中使用。Flash则是一种可读可写的存储介质。两者都看成 一种电脑硬盘就可以,这里对ROM的设置实际就是设置Flash。设置Flash起始地址与大小(size),以及是否从该地址开始启动程序(startup)。
查看6、AT32官方资料\AT32F435 参考手册(寄存器).pdf 中的2.2、2.3节,发现Flash、RAM的起始地址与图中相同,大小4024K、384K分别乘1024并变化为16进制的字节数3F 0000、6 0000。与图中默认的有些出入,实测修改后没有问题。这里注意Start地址与手册中一样,因为芯片设计是从该ROM地址启动程序的。
(3)Output
(4)Listing
(5)User
(6)C/C++
注意勾选上C99 Mode选项,选项选上后变量可以定义在函数任意位置。这往往成为一个坑,拿找到的例程编译总是报错时可以看看这里。
C99 Mode具体参考:
https://blog.csdn.net/qq_20017379/article/details/122761158
(7)Asm
(8)Linker
(9)Debug
(10)Utilities
官方资料中有mdk5工程
参考……
去除无用部分,简化后工程在……
注意 jlink选择、前宏定义
5.3 AT32VMT7在Keil下的启动流程
5.3.1 AT32存储器资源
在介绍AT32硬件启动流程前,要先来熟悉一下这个芯片的存储器资源,也就是在程序里常能看到的0X2000 0000等类似的地址数值。“6、AT32官方资料\AT32F435 参考手册(寄存器).pdf”文档中的“2 存储器资源”章节详细说明了相关内容。
AT32F435/437存储器资源主要结构如下图:
这里要涉及到了一些计算机学科方面的概念,包括“映射”、“物理存储器”、“虚拟存储器”,作者本人大学专业机械,如有不符还望指正。
以“淘宝”上买一株腊梅花来进行比喻,你在淘宝上看到的这株腊梅她不是真的腊梅,真的腊梅可能是距你手中“淘宝”手里之外的北京。那么“虚拟存储器”代表的便是你在“淘宝”上看到的腊梅,而“物理存储器”则指代身在北京正在生长的腊梅。而所谓映射就可以比喻为“淘宝”。
虽然“淘宝”上的腊梅不能触碰到,但是在“淘宝”上你却可以远程操纵真实的腊梅。比如让快递把她送到某处。你也可以在“淘宝”上了解她的详细信息。映射也具有相同的作用。对虚拟存储器的操作会对应施加在物理存储器上。
然后来介绍存储资源地址从低到高对应的主要部分:
1. 前存储空间 0x0000_0000~0x1FFF_FFFF(512MB)
手册没有命名这一段地址空间,这里先称其为“前存储空间”,该存储空间中主要包含如下区域:
(1.1)CODE区 0x0000_0000~0x07F F_FFFF(128MB)
这是一段与芯片启动息息相关的地址区域,在芯片上电启动或重启时,会根据BOOT0、BOOT1引脚的高低电平将其他物理存储器的地址映射到此处。
具体方式为:
当{BOOT1,BOOT0}=00/10 时,将 0x0800_0000处的Flash映射到0x0000_0000处。
当{BOOT1,BOOT0}=01 时,将 0x2000_0000处的SRAM映射到0x0000_0000处。
当{BOOT1,BOOT0}=11 时,将 0x2000_0000处的SRAM映射到0x0000_0000处。
(1.2) Flash 0x0800_0000~0x0F FF_FFFF(128MB),这个芯片只用到~0x083E FFFF(4032KB,即3M)
为物理Flash所在的位置,Flash即快闪存储器,简称闪存,是一种存储器种类,可以看作芯片的硬盘。
(1.3) SRAM1 映射区 0x1000_0000~0x1000_FFFF(64KB)
为虚拟的SRAM,SRAM全称静态随机存取存储器(Static Random-Access Memory,SRAM)是随机存取存储器的一种,可以看作芯片的内存。
是将0x2000_0000处的物理SRAM1映射到这里的,访问这部分区域,实际就是访问0x2000_0000处的SRAM1
(1.4)Bootloader flash 0x1FFF_0000~0x1FFF_BFFF(48KB)
为物理Flash,存储着AT官方编写的Bootloader程序,这部分Flash可读不可写。
(1.5)User system data ? 0x1FFF_C000~0x1FFF_CFFF(4KB)
2. SRAM 0x2000_0000~0x200F_FFFF(1024KB)
为物理SRAM,分为SRAM1、SRAM2
SRAM1 ~0x2000_FFFF(64KB)
SRAM2 这个芯片只用到了0x2001_0000~0x2005_FFFF(320KB)
3. 外设空间 0x4000 0000~0xDFFF FFFF(2GB)
C语言工程中对寄存器的操作实际上就是对一段存储区域进行读写,与对硬盘、内存的操作方式并无本质区别。
APB1总线 ~0x4000 FFFF(64KB)
APB2总线 0x4001 0000~0x4001 FFFF(64KB)
AHB1 0x4002 3000~0xA000 2FFF(1GB)
AHB总线矩阵 0x4002 0000~0xC000 0000(2GB-1MB)
5.3.2 硬件启动流程
“6、AT32官方资料\AT32F435 参考手册(寄存器).pdf”文档中的“1.1.6 复位流程”章节详细说明了相关内容。
(1)CODE区映射
在 AT32F435/437 中,当上电启动或复位时,可以将主闪存存储器(位于0x0800_0000的Flash)、启动程序存储器(位于0x1FFF_0000的Bootloader flash)或片上 SRAM(位于0x2000_0000的SRAM) 这三块存储器重映射到0x0000_0000~0x07FF_FFFF 的 CODE 区,由 BOOT1 和 BOOT0 管脚来设定 将哪块存储器映射到CODE区。
具体为:
当{BOOT1,BOOT0}=00/10 时,将 0x0800_0000处的Flash映射到0x0000_0000处。
当{BOOT1,BOOT0}=01 时,将 0x2000_0000处的SRAM映射到0x0000_0000处。
当{BOOT1,BOOT0}=11 时,将 0x2000_0000处的SRAM映射到0x0000_0000处。
(2)主栈指针(MSP)与 程序计数器(PC)初始值读取
当映射完成后,处理器会从 CODE 区中读出前两个字。即:
从地址 0x0000_0000 处取出主栈指针(MSP)的初始值。
从地址 0x0000_0004 处取出程序计数器(PC)的初始值,处理器要求PC的初始值的LSB(最低有效位,即二进制下最低位的值) 必须是 1
具体流程如图:
图 5-1 MSP及PC初始化的一个范例
![]() |
假设{BOOT1,BOOT0},即将 0x0800_0000处的Flash映射到0x0000_0000处,访问0x0000_0000 + k即是访问地址0x0800_0000 + k
32单片机常常是以32位即4个字节为单位来进行存储器操作的,这里同时假设0x0800_0000~0x0800_0003这四个字节存储的值是0x2000_8000,0x0800_0004~0x0800_0007四个字节的值是0x0000_0101
那么当映射完成后:
1. 处理器就会从地址 0x0000_0000 处取出主栈指针(MSP)的初始值0x2000_8000。因为此时对0x0000_0000取值实际就是取0x0800_0000处的数据。
关于主栈指针(MSP),作者的理解MSP就是指向栈地高地址处的指针,而“栈”是一段芯片中的有特殊用途的内存。什么特殊作用呢,作用是当进行函数嵌套调用、中断嵌套调用时保存现场与当前程序位置。
一个子函数执行完成后会使用MSP找到要返回的上一级函数的程序位置,并恢复调用这个子函数前的一些内核寄存器组的值(注意不是外设的寄存器)。
栈所使用内存的大小是有限的(一般在汇编程序中设置大小与位置),所以不能无限的向下调用子函数,不然就寄了。
2. 之后处理器从地址 0x0000_0004 处取出程序计数器(PC)的初始值0x0000_0100。这里注意,赋给程序计数器(PC)的值是不带最低有效位LSB的。
关于程序计数器(PC),这里可以简单的理解为程序计数器(PC)所对应地址处的值,就是接下来要运行的程序(即一个32位的二进制指令)。但是实际上程序计数器(PC)对应的指令会在2个指令周期后才被执行,这就涉及ARM架构取码、译码、执行指令流水线设计了。
这里由于0x0800_0000处的Flash映射到了CODE区,所以(PC)实际指向了地址0x0800_0100处的程序。
3. 之后依次执行编写好的程序
上述操作完成后就是正常的执行程序了。其实 主栈指针(MSP)与 程序计数器(PC)所取的初始值也属于我们编写好的程序的一部分,只不过其通常在汇编.s文件中定义。
5.3.2 软件启动流程
谈完成了一枚芯片在物理层面如何启动,接下来看看实际在Keil中编写的程序是如何运行的。
首先,要明确一点,芯片软件层面最底层的程序是二进制指令,二进制指令中的第个位都由事先定义好的协议来表明不同的操作,而这种底层的指令操作类型是有限的,32单片机一般也就几十种。协议所规定的指令集合在一起,就称为一种“指令集”,AT32所使用的Cotex-M4内核使用的就是Thumb指令集。据了解,这个指令集最初是16位的,后来发展出了其他32位的指令,32单片机中的“32”指的就是指令集的位数,也是处理器一次性处理数据的位数。
但我们当然不会一个个0101地去写程序,而是使用指令集助记符,即“汇编语言”。汇编算是嵌入式中使用的最底层的编程语言了,其本质其实就是将二进制的指令用字符来进行代替,所以常表现为流水依次执行的结构。由于汇编与二进制指令是一一对应的关系,所以不同的指令集对应的汇编语言也不同(一般命令作用、数量都不同)。随着发展,汇编语言也会加入一些“伪操作”,相当于是一个汇编指令集合而成的函数,用来完成常用的功能。
综上所述,软件由汇编文件开始运行,即src\main\startup\startup_at32f435_437.s文件。ARMCC编译器是Keil5软件带有的ARM编译器,ARMCC编译器的伪指令是用于告诉ARMCC编译器如何进行汇编的指令。下表是Keil中常用的伪指令,其他ARM汇编语法参考“2、参考资料\【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf”中的“第七章 ARM 汇编基础”
伪指令助记符 | 格式 | 功能说明 |
AREA | AREA 段名 属性1,属性2… | 定义一个代码段或者数据段 |
ENTRY | ENTRY | 汇编程序的入口点 |
END | END | 汇编程序的程序的结尾 |
EQU | 常量名 EQU 常量数值 | 定义一个常量,相当于c语言中的 define |
EXPORT | EXPORT 标号 | 声明一个全局的标号,可以在其他文件使用声明的标号 |
IMPORT | IMPORT 标号 | 通知编译器要使用一个在其他文件中定义的标号,相当于c语言中的 extern |
DCD | DCD 表达式 | 用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化,表达式可以是程序标号或数字表达式 |
PROC | PROC | 表示一个汇编子程序的开始。相当于C语言中函数开始。 |
ENDP | ENDP | 表示一个汇编子程序的结束。相当于C语言中函数结束。 |
ALIGN | 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即 |
(1)STACK与HEAP段的定义
14~32行,这一部分定义了两个代码段,似乎这部分段的属性初设置成了不初始化,所以并未被编译到输出文件的开头。。作者至今没搞懂代码段相关的内容,以及Keil汇编执行的流程,因为根据作者了解汇编程序也是有入口的(类似C中的main函数)而不是简单的依次往下。
(2)定义中断向量表
__Vectors DCD __initial_sp
DCD Reset_Handler
DCD NMI_Handler
DCD HardFault_Handler
DCD MemManage_Handler
……
这里假设为一般地向芯片Flash的0x0800_0000处下载程序
41~173定义中断向量表,实际上就是使用DCD伪指令依次从存储器的开头(即0x0800_0000)依次将每个字(一个字=四个字节)赋值,赋的值为每个中断所对应中断服务函数所有的存储器所在的地址。其中__initial_sp较为特殊,即之前所说的主栈指针(MSP)的初始值,似乎被“提前”定义好了,实测为200006f8,指向一个SRAM地址。由于栈向下增长,在栈中存储的数据会依次占用地址0x2000 06f8、0x2000 06f4、0x2000 06f0……。
其他的中断部分则依次存储对应中断函数所在的地址,Reset_Handler、NMI_Handler……。比如Reset_Handler对应的中断函数位于0x080002ac地址处的Flash,那么0x08000004地址处的值就是0x080002ac,也是程序计数器(PC)的初值。
(3)启动时调用的第一个函数
根据上一节“硬件件启动流程”可知,程序计数器(PC)初始值所指向地址处的代码是所有软件执行的入口。如果是从主Flash启动,那么程序计数器(PC)初始值就是Flash 0x08000004地址上存储的数据,根所汇编文件可知这个数据就是Reset_Handler函数所在程序的地址,即Reset_Handler是芯片启动时调用的第一个函数。
Reset_Handler是一个汇编函数,定义在startup_at32f435_437.s文件的180~189行。
; Reset handler
Reset_Handler PROC ;表示一个汇编子程序的开始。相当于C语言中函数开始。
EXPORT Reset_Handler [WEAK];声明一个全局的标号,可以在其他文件使用声明的标号
IMPORT __main ;通知编译器要使用一个在其他文件中定义的标号,相当于c语言中的 extern
IMPORT SystemInit ;执行SystemInit函数并返回
LDR R0, =SystemInit
BLX R0
LDR R0, =__main ;执行main函数,不返回? “X”表示切换指令集
BX R0
ENDP ;表示一个汇编子程序的结束。相当于C语言中函数结束。
可以看出Reset_Handler函数中主要是声明与调用了SystemInit与__main函数,其中调用后者时就“一去不复返”了。
这两个函数终于是C语言函数了,SystemInit是AT官方提供的一个系统初始化函数,定义在“src\main\startup\system_at32f435_437.c”的70~112行。主要功能时初始化时钟与设置中断向量表位置。
/**
* @brief setup the microcontroller system
* initialize the flash interface.
* @note this function should be used only after reset.
* @param none
* @retval none
*/
void SystemInit (void)
{
// initialiseMemorySections();
#if defined (__FPU_USED) && (__FPU_USED == 1U)
SCB->CPACR |= ((3U << 10U * 2U) | /* set cp10 full access */
(3U << 11U * 2U) ); /* set cp11 full access */
#endif
/* reset the crm clock configuration to the default reset state(for debug purpose) */
/* set hicken bit */
CRM->ctrl_bit.hicken = TRUE; /* 使能 内部高速振荡器时钟 HICK */
/* wait hick stable */
while(CRM->ctrl_bit.hickstbl != SET); /* 等待 内部调整时钟 稳定 */
/* hick used as system clock */
CRM->cfg_bit.sclksel = CRM_SCLK_HICK; /* 系统时钟(SCLK)时钟源选择,内部调整时钟 */
/* wait sclk switch status */
while(CRM->cfg_bit.sclksts != CRM_SCLK_HICK); /* 等待 系统时钟(SCLK)设置完成 */
/* reset cfg register, include sclk switch, ahbdiv, apb1div, apb2div, adcdiv, clkout bits */
CRM->cfg = 0; /* 复位时钟配置寄存器 */
/* reset hexten, hextbyps, cfden and pllen bits */
CRM->ctrl &= ~(0x010D0000U); /* PLL使能、时钟失效检测使能、HEXT 旁路使能、HEXT 使能 */
/* reset pllms pllns pllfr pllrcs bits */
CRM->pllcfg = 0x00033002U; /* 设置PLL时钟配置寄存器, HICK为时钟源,输出频率:=8M/2*384/8=192M */
/* reset clkout[3], usbbufs, hickdiv, clkoutdiv */
CRM->misc1 = 0; /* 设置额外寄存器 */
/* disable all interrupts enable and clear pending bits */
CRM->clkint = 0x009F0000U; /* 禁用所有中断,并清除所有挂起的位 */
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* vector table relocation in internal sram. */
#else /* 设置中断向量表位置为FLASH_BASE | VECT_TAB_OFFSET,即0x08000000 */
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* vector table relocation in internal flash. */
#endif
}
__main函数则是C标准库中定义的一个函数,该函数最终会调用工程中的main函数,也就是我们所熟悉的C编程部分了。
注意SystemInit (void)函数与之后BF源码初始化中的 init(void) -》systemInit();所调用的systemInit() 不是一个函数(开头字母大小写不同)。
BF Linux环境源码的汇编文件的入口函数也是Reset_Handler,但其内容与Keil中有所出入。如下:
Reset_Handler:
/* custom init */
ldr sp, =_estack /* set stack pointer 设置栈指针*/
bl persistentObjectInit /*保护数据初始化*/
bl checkForBootLoaderRequest /*进行一些关于BootLoader支持的操作*/
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
可以看出其调用了src/main/drivers/persistent.c中的persistentObjectInit 函数、
src/main/drivers/system_at32f43x.c中的checkForBootLoaderRequest函数
以及LoopCopyDataInit
前两个都属于BF源码,而LoopCopyDataInit属于汇编函数,经过一系列子函数调用,
-》LoopCopyDataInit (77)
-》LoopFillZerobss (84)
-》LoopFillZerofastram_bss (103)
最终在LoopFillZerofastram_bss下调用C语言的main函数
Linux环境与Keil环境下汇编文件的不同在之后的BF移植中要注意。
5.3.3 反汇编验证
在Keil“魔术棒”设置界面的User可以设置扩展应用,如图勾选“Run#2”,并在其后输入 fromelf --text -a -c --output ./Listings/template.dis ./obj/template.axf 即可编译完成后在Listings/文件夹中生成反汇编文件template.dis
反汇编文件是根据输出文件反向转换而来的汇编文件,相当于将C语言、汇编伪指令都解码成了最底层的汇编指令,并给出其所在存储器的具体地址。
打开反汇编文件(可以使用“笔记本”),在开关有如下指令:
$d.realdata
RESET
__Vectors
存储地址 数据值 ? 对应汇编指令 指令参数
0x08000000: 200006f8 ... DCD 536872696
0x08000004: 080002ad .... DCD 134218413
……
可以看到,在主Flash的开头0x08000000处储存着的两个值,就是芯片启动时主栈指针(MSP)与 程序计数器(PC)的初始值。0x08000004处数据值为080002ad,也就是要去执行080002ac(舍去LSB)地址处的程序。
向下找到080002ac地址处,发现全是汇编文件中定义的Reset_Handler函数。然后就是依次调用C函数与进入到C工程中了。
Reset_Handler
0x080002ac: 4809 .H LDR r0,[pc,#36] ; [0x80002d4] = 0x8000671
0x080002ae: 4780 .G BLX r0
0x080002b0: 4809 .H LDR r0,[pc,#36] ; [0x80002d8] = 0x800020d
0x080002b2: 4700 .G BX r0