当前位置:首页 > 嵌入式 > 技术让梦想更伟大
[导读]【说在前面的话】其实我很久之前就想写这篇文章了,但彼时总觉得这是一个伪命题:既然已经用了MDK,编译出来的代码,无论是体积还是性能都甩下armgcc好几条街,谁还会想用gcc来进行Cortex-M开发呢?对那些只能使用armgcc、或者对gcc情有独钟的小伙伴来说,无论是配合ec...



【说在前面的话】

其实我很久之前就想写这篇文章了,但彼时总觉得这是一个伪命题:
  • 既然已经用了MDK,编译出来的代码,无论是体积还是性能都甩下arm gcc好几条街,谁还会想用gcc来进行Cortex-M开发呢?

  • 对那些只能使用arm gcc、或者对gcc情有独钟的小伙伴来说,无论是配合eclipsevscodeEmbedded Studio还是其它什么开发环境,哪个不比MDK香呢?


然而,既然你点开了这篇文章,无论是否真的有这样的需求,至少说明你对这样的搭配还是“颇有些好奇”的。我就不去担心背后的真正原因了,就让我们速速切入正题,进入实操环节吧。


先说结论:
  • MDK原生支持GCC开发,且不受License限制

  • MDK使用GCC开发时“可以做到”不写一句汇编的程度

  • MDK使用GCC开发时可以享受来自Runtime Environment配置机制的福利——也就是你可以轻松的享用来自Pack Installer所引入的各类软件包的支持——这同样也是免费的

  • MDK使用GCC开发时支持调试(所能调试的代码尺寸受到License限制)


我们知道MDK是一个集成开发环境(Integrated Development Environment),它默认原生支持Arm Compiler 5(armcc)Arm Compiler 6(armclang)arm gcc。虽然这三个编译器都是由Arm所维护和提供的,但前两者算是彼此兼容的编译器:

  • 使用共同的 armlink

  • 使用相同的方式来描述地址空间布局(分散加载脚本 scatter script)

  • 从Arm Compiler 6.14开始,armclang甚至开始支持armasm的汇编语法了


实际上可以认为,armccarmclang是一对连体兄弟,身子是armlink,而两个脑袋分别是 armccarmclang。大约是这种感觉,你体会下。



与亲生的两兄弟不同,牛头人arm gccArm公司从GCC开源社区“抱回来的孩子”。它虽然语法上与armclang(clang)基本相同,但却拥有自己独立的编译和连接环节,用来描述地址空间布局的方式也完全不同——采用 linker script(*.ld)来进行。


那么这些差异对我们在MDK中使用gcc进行开发有什么意义呢?我们需要做哪些工作准备工作呢?总的来说,问题集中在以下几个方面:
  1. 编译器的获取和集成

  2. 如何芯片的启动

  3. 如何描述目标软件的地址空间布局

  4. 如何对编译选项进行配置

  5. 如何进行代码的优化


接下来,我们就有针对性的为您解答这些问题。

【如何在将arm gcc集成到MDK环境中】


arm gcc 获取并不困难,可以访问arm的官方页面直接下载:


https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm


下载后一路无脑安装即可,这里就不再赘述。接下来,我们打开MDK,通过菜单 project->New uVision Project... 新建一个工程:



为了方便,工程文件名不妨就叫 gcc_template好了:


单击 "Save" 后,MDK会弹出窗口让我们选择工程的目标芯片,实际上很多芯片公司都为MDK提供了面向gcc的工程模板,因此在这里直接选择实际芯片型号往往就可以省略后面大部分步骤,但考虑到让本教程拥有更强的通用性,这里我们选择目标芯片所使用的处理器


假设,我们要使用的芯片是STM32F746,我们知道它的内核是Cortex-M7,因此这里就选择 Arm->ARM Cortex-M7->ARMCM7_SP(假设是单精度浮点运算单元),单击OK。
对这里选什么芯片比较纠结的小伙伴大可不必,因为后面随时可以回来改,不会存在那种“买定离手”而“无法反悔”的问题。
接下来,MDK会弹出RTE的配置界面。RTE的配置我们将在后面介绍,此时直接单击OK进行跳过即可。


如果一切顺利,你会看到如下的界面:



以上步骤只能算是准备工作,接下来才是将arm gcc集成到MDK中的正题。依次通过菜单 Project -> Manage -> Project Items 打开配置窗体:


在新打开的对话框中选择 "Folders/Extensions" 选项卡,并勾选“Use GCC Compiler (GNU)for ARM projects”(如下图所示):

单击 “...” 按钮,选择arm gcc工具链所在的安装目录。以最新的的arm gcc 2020-q4-major 版本为例,默认情况下它会被安装在 


“C:\Program Files (x86)\GNU Arm Embedded Toolchain”


目录下。我们选中这里的 "10 2020-q4-major" 目录,单击 Select Folder 按钮。


在回到上一级窗口时,我们注意到,此时arm gcc的路径已经被正确配置了:


单击“OK”就完成了 arm gcc 的添加工作。此时,如果打开 Project -> Options for Target 窗口,我们会看到编译器配置界面变成了一个陌生的样子:

如果你看到类似这样的界面,恭喜您,您的MDK已经和arm gcc“喜结连理”了

【实现“无汇编化”的启动】

很多人可能都有错觉——以为使用gcc开发项目一定要用汇编的方式来处理启动文件——过去也许是这样,但是,“大人时代变了”!。




借助 CMSIS的帮助,我们现在也可以优雅的完全使用C语言来实现芯片的启动过程。首先,我们需要获得最新的CMSIS,具体方法可以在这篇文章《CMSIS玩家的“阴间成就”指南》中获得,这里就不在赘述。
无论是通过Pack安装还是github导入,在确保最新的CMSIS被成功的安装到MDK中以后,我们首先需要在工程中通过RTE窗口引入最新的CMSIS支持:在工具栏中,单击下面的按钮:
 
打开 Runtime Environment 配置窗口:


这里,我们展开CMSIS,并勾选 CORE(这里,请确保CORE的版本不低于 5.4.0),单击OK确认配置。
如果你对CMSIS的版本有所疑问,可以单击 “Select Packs” 按钮,确保窗体顶端的 “Use latest versions of all installed Software Packs” 被勾选,如果这样做以后,CMSIS-CORE的版本仍然低于 5.4.0,请务必参考这篇文章CMSIS玩家的“阴间成就”指南来获取最新的CMSIS
单击CMSIS-CORE后面的注释文字:



会打开一个浏览器页面,忽略其中的内容,我们需要的是页面网址中的路径信息:


这里,我们找到了当前CMSIS Pack在本地的路径,利用这一路径信息在浏览器中打开对应文件夹,找到 Device目录:



依次进入目录 “Device\ARM\ARMCM7\Source”:


将上图选中的文件拷贝到我们的工程中来:


MDK工程中,将startup_ARMCM7.csystem_ARMCM7.c加入到工程中参与编译(这里我们新建了一个分组叫做 low_level):



先别着急去编译,注意到这里的小钥匙图标了么?这说明这两个文件自带了“只读属性”。由于我们后面要修改这两个文件,因此必须要通过Windows的文件属性管理将只读属性去除(把下图的勾选去掉后单击OK):

此时再看MDK的工程管理器,小钥匙标志就已经消失了:



接下来,打开 “Option for Target...” 窗体,进入Linker选项卡:


将这里的 "Do not use Standard System Startup Files" 选项去除。


注意,这一步骤非常重要,不可以省略,否则你会看到如下的编译错误:

linking...c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: warning: cannot find entry symbol _start; defaulting to 00008000c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: ./startup_armcm7.o: in function `__cmsis_start':C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `_start'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__copy_table_start__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__copy_table_end__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__zero_table_start__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: C:/Users/gabriel/AppData/Local/Arm/Packs/ARM/CMSIS/5.8.0/CMSIS/Core/Include/cmsis_gcc.h:163: undefined reference to `__zero_table_end__'c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: ./startup_armcm7.o:E:\Temp Project\gcc_template/startup_ARMCM7.c:84: undefined reference to `__StackTop'collect2.exe: error: ld returned 1 exit status".\gcc_template.elf" - 1 Error(s), 0 Warning(s).正如错误提示中指出的那样,CMSIS会在一个叫做 __cmsis_start的函数中,调用 "_start" 函数,而这一函数正是gcc标准启动文件的入口,当你在MDK中选择"Do not use Standard System Startup Files时,linker自然就找不到这个“不存在”的入口函数啦。


接下来,单击如下图所示的按钮:



打开我们刚刚一起拷贝过来的GCC目录,选中其中的连接脚本 gcc_arm.ld后,单击Open:

最后的结果如下图所示,单击OK确认我们的配置:


虽然不是必须,但推荐在Misc controls中添加如下的内容:

--specs=nosys.specs -Wl,--gc-sections-fshort-enums -fshort-wchar即:

接下来,为了初步检验一下我们的成果,在工程中添加一个main.c(实现一个简单的main() 函数):



怀着忐忑的心理,按下编译按钮:




不用怀疑,我们已经成功的实现了“零汇编”gcc工程建立。简单不?你可以把这个工程连同文件夹一起保存好,这就是未来的工程模板了。此外,关于main.c中的代码,需要做一些简单的说明:

#include #include #include #include "cmsis_compiler.h"
int main(void){ while(1) { }
return 0;}
__attribute__((noreturn))void exit(int err_code) { while(1) { __NOP(); }}
  • GCC要求main函数的返回值是 int 类型,而这里的返回值会被作为 exit() 函数的传入参数——一般负数表示出错,0表示平安。

  • 如果不实现一个 exit() 函数,链接器会报错。

  • __attribute__((noreturn)) 就是字面意思,告诉编译器这个这个函数是有去无回的。

  • 为了使用类似 __NOP() 这样的“固有函数(intrinsics)”,我们需要直接或者间接的包含头文件  "cmsis_compiler.h"


此外,如果我们不做任何的设置,MDK会将所有生成的中间文件(比如 .o、.d之类)直接保存到工程文件夹下,产生“垃圾遍布”的感觉:

为了解决这一问题,我们可以在"Options for Target"窗口的Target选项卡中通过“Select Folder for Objects” 来选择一个专门的文件夹放置这些中间文件:

完成基础模板的制作后,接下来我们来一一介绍一些模板在使用过程中所需要处理的细节问题:


【简单的地址空间布局、Stack和Heap的配置】

在去掉 GCC/gcc_arm.ld 文件的只读属性后,我们就可以借助它根据目标芯片的实际情况描述地址空间布局,打开gcc_arm.ld,可以看到如下的内容:



如果你的目标芯片较为简单,比如,FLASH是一片完整的地址区间,则可以通过修改__ROM_BASE的方式来设置目标镜像中FLASH的起始地址,通过修改修改__ROM_SIZE来设置FLASH的实际大小,比如,起始地址为0x0800-0000,大小为256K的Flash对应的修改方式为:

/*---------------------- Flash Configuration ---------------------------------- Flash Configuration Flash Base Address <0x0-0xFFFFFFFF:8> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8> -----------------------------------------------------------------------------*/__ROM_BASE = 0x08000000;__ROM_SIZE = 0x00040000;同理,SRAM的起始地址和大小可以通过__RAM_BASE__RAM_SIZE来设置,这里就不再赘述:

/*--------------------- Embedded RAM Configuration ---------------------------- RAM Configuration RAM Base Address <0x0-0xFFFFFFFF:8> RAM Size (in Bytes) <0x0-0xFFFFFFFF:8> -----------------------------------------------------------------------------*/__RAM_BASE = 0x20000000;__RAM_SIZE = 0x00020000;最后,关于StackHeap大小的设置可以借助__STACK_SIZE__HEAP_SIZE来设置:

/*--------------------- Stack / Heap Configuration ---------------------------- Stack / Heap Configuration Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> -----------------------------------------------------------------------------*/__STACK_SIZE = 0x00000800; /* 2K Byte */__HEAP_SIZE  = 0x00000200;   /* 256 Byte */

【如何配置中断向量表】

不同的芯片拥有不同的中断向量表,而此前我们所建立的gcc工程模板中,startup_ARMCM7.c 里定义的其实是一个默认的中断向量表:


可以看到,这一向量表完全采用的是C语言函数指针数组初始化的形式定义的。它不仅提供了默认的各类系统异常的定义,还以Interruptn_Handler的形式为我们提供了定义的范例。

更新这一文件的步骤并不复杂。实际上一般芯片公司都会提供符合CMSIS规范的芯片头文件,这一头文件中会提供对应的中断向量定义,比如STM32F746就有一个对应的头文件 STM32F746xx.h。将其打开会看到专门的向量表定义:
/** * @brief STM32F7xx Interrupt Number Definition, according to the selected device * in @ref Library_configuration_section */typedef enum{/****** Cortex-M7 Processor Exceptions Numbers ****************************************************************/ NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */ MemoryManagement_IRQn = -12, /*!< 4 Cortex-M7 Memory Management Interrupt */ BusFault_IRQn = -11, /*!< 5 Cortex-M7 Bus Fault Interrupt */ UsageFault_IRQn = -10, /*!< 6 Cortex-M7 Usage Fault Interrupt */ SVCall_IRQn = -5, /*!< 11 Cortex-M7 SV Call Interrupt */ DebugMonitor_IRQn = -4, /*!< 12 Cortex-M7 Debug Monitor Interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M7 Pend SV Interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M7 System Tick Interrupt *//****** STM32 specific Interrupt Numbers **********************************************************************/ WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */  ... SPDIF_RX_IRQn = 97, /*!< SPDIF-RX global Interrupt */} IRQn_Type;这里,WWDG_IRQnSPDIF_RX_IRQn之间的每一项都对应一个外设中断,可以将它们拷贝出来,添加到我们的startup_ARMCM7.c的向量表中,并依样画葫芦,修改成对应的形式:

.../*---------------------------------------------------------------------------- Exception / Interrupt Handler *----------------------------------------------------------------------------*//* Exceptions */void NMI_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void HardFault_Handler (void) __attribute__ ((weak));void MemManage_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void BusFault_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void UsageFault_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void SVC_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void DebugMon_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void PendSV_Handler (void) __attribute__ ((weak, alias("Default_Handler")));void SysTick_Handler (void) __attribute__ ((weak, alias("Default_Handler")));/*void Interrupt0_Handler (void) __attribute__ ((weak, alias("Default_Handler")));...void Interrupt9_Handler (void) __attribute__ ((weak, alias("Default_Handler")));*/void WWDG_IRQn_Handler (void) __attribute__ ((weak, alias("Default_Handler")));...void SPDIF_RX_IRQn_Handler  (void) __attribute__ ((weak, alias("Default_Handler")));
...
extern const VECTOR_TABLE_Type __VECTOR_TABLE[240]; const VECTOR_TABLE_Type __VECTOR_TABLE[240] __VECTOR_TABLE_ATTRIBUTE = { (VECTOR_TABLE_Type)(
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭