专栏首页裸机思维【教程】如何用GCC“零汇编”白嫖MDK

【教程】如何用GCC“零汇编”白嫖MDK

【说在前面的话】


其实我很久之前就想写这篇文章了,但彼时总觉得这是一个伪命题:

  • 既然已经用了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 00008000
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: 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 <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#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 ----------------------------------
  <h> Flash Configuration
    <o0> Flash Base Address <0x0-0xFFFFFFFF:8>
    <o1> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8>
  </h>
  -----------------------------------------------------------------------------*/
__ROM_BASE = 0x08000000;
__ROM_SIZE = 0x00040000;

同理,SRAM的起始地址和大小可以通过__RAM_BASE__RAM_SIZE来设置,这里就不再赘述:

/*--------------------- Embedded RAM Configuration ----------------------------
  <h> RAM Configuration
    <o0> RAM Base Address    <0x0-0xFFFFFFFF:8>
    <o1> RAM Size (in Bytes) <0x0-0xFFFFFFFF:8>
  </h>
 -----------------------------------------------------------------------------*/
__RAM_BASE = 0x20000000;
__RAM_SIZE = 0x00020000;

最后,关于StackHeap大小的设置可以借助__STACK_SIZE__HEAP_SIZE来设置:

/*--------------------- Stack / Heap Configuration ----------------------------
  <h> Stack / Heap Configuration
    <o0> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
    <o1> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
  </h>
  -----------------------------------------------------------------------------*/
__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)(&__INITIAL_SP),       /*     Initial Stack Pointer */
  Reset_Handler,                            /*     Reset Handler */
  NMI_Handler,                              /* -14 NMI Handler */
  HardFault_Handler,                        /* -13 Hard Fault Handler */
  MemManage_Handler,                        /* -12 MPU Fault Handler */
  BusFault_Handler,                         /* -11 Bus Fault Handler */
  UsageFault_Handler,                       /* -10 Usage Fault Handler */
  0,                                        /*     Reserved */
  0,                                        /*     Reserved */
  0,                                        /*     Reserved */
  0,                                        /*     Reserved */
  SVC_Handler,                              /*  -5 SVC Handler */
  DebugMon_Handler,                         /*  -4 Debug Monitor Handler */
  0,                                        /*     Reserved */
  PendSV_Handler,                           /*  -2 PendSV Handler */
  SysTick_Handler,                          /*  -1 SysTick Handler */

  /* Interrupts */
  [WWDG_IRQn+16]= WWDG_IRQn_Handler,        /*   0 WWDG_IRQn */
  ...
  [SPDIF_RX_IRQn+16]= SPDIF_RX_IRQn_Handler,/*   97 SPDIF_RX_IRQn */

};

接下来,我们需要更新 startup_ARMCM7.c system_ARMCM7.c中的头文件,同样以STM32F746为例,将原本的ARMCM7相关的包含注释掉,加入#include "stm32f746xx.h"

#if 0
#if defined (ARMCM7)
  #include "ARMCM7.h"
#elif defined (ARMCM7_SP)
  #include "ARMCM7_SP.h"
#elif defined (ARMCM7_DP)
  #include "ARMCM7_DP.h"
#else
  #error device not specified!
#endif
#endif

#include "stm32f746xx.h"

有时候,客户芯片的头文件会缺少一些必要的定义,比如函数指针VECTOR_TABLE_Type,则直接在这两个.c文件中补充即可:

  /**
  \brief Exception / Interrupt Handler Function Prototype
*/
typedef void(*VECTOR_TABLE_Type)(void);

对于STM32芯片的用户来说,其实官方的CMSIS Pack已经为arm gcc提供了对应的启动文件,我们可以在RTE中将其打开:

随后在工程管理器中就可以在Device选项卡下看到它们:

遗憾的是,这里的启动文件使用的是汇编,如果你不喜欢它们,则仍然可以使用本文介绍的方法。

再次重申下,本文使用STM32作为例子仅仅是因为我手上有这块板子,并不是说对于STM32来说,使用GCC一定要用我说的方法而不使用官方提供好的gcc启动文件。本文讲解的目标时提供最大的通用性,因此会涉及到很多具体的细节。


值得注意的是:有时候,某些芯片会提供面向Arm Compiler 5或者Arm Compiler 6system_xxxx.c,其实我们完全可以拷贝出来直接替换掉这里的 system_ARMCM7.c。在STM32F746的例子中,我们看中了厂家提供的system_stm32f7xx.c——因为其中包含了必要的芯片初始化代码(时钟、外设等等),因此,我们将其单独拷贝到工程目录下:

加入工程管理器中参与编译,并将原本的system_ARMCM7.c从编译中剔除:

最终呈现的效果如下:

在我们着急开始编译以验证效果之前,这里有一个细节需要分情况讨论:

  • 目标芯片原本就与针对MDK的 CMSIS-Pack支持 对于这种情况,我们需要在“Options for Target”的Device选项卡中选择对应芯片,这样MDK会自动将目标芯片头文件的路径加入编译器的头文件搜索列表中
  • 目标芯片没有针对MDK的CMSIS-Pack,而只提供了目标芯片的头文件(包含了寄存器定义等等) 此时,我们需要将目标芯片的头文件拷贝到工程目录下,并收工将对应路径添加到编译器的头文件搜索列表中。这里因为我们假设你直接将头文件保存在了工程目录下,因此这里的搜索路径就是"工程所在当前目录"——直接用"."就可以了:

完成了上述步骤,基本上就完成了对新的目标芯片的最基础支持。

【如何设置开启编译优化】


MDK在“Option for Target”的"CC"选项卡中提供了简化的优化选项支持:

看似满足要求,其实远远不够——哪怕你选择了"Level 2 (Size)"优化,可能最终代码的尺寸依然大的吓人。要解决这一问题,需要在 "Misc Controlls" 文本框中追加以下的编译选项:

-ffunction-sections -fdata-sections

由于linker进行尺寸优化的基本单位是section,而section是变量和代码的容器。默认情况下,每个c源文件中所有函数生成的代码都会放在一个叫做“.text”的容器中;而所有静态分配的变量也会被类似的放在名为.data或者.bss的section中——这样的缺点是,整个section中只有一个函数或者变量被用到了,整个section中的内容都会被判定为是需要保留的。更糟糕的是,这种判定是具有“传染”性的,这意味着哪怕某一个section中存在没用到函数,只要该section被判定为要保留,则这些没有用到的函数所调用的函数,其所在section也会被传染。

要解决这一问题的方式很简单,直接给每一个函数或者静态变量都分配一个独立的section即可。

关于这方面的详细讨论,请参考文章《真刀真枪模块化(3.5)——骚操作?不!这才是正统》,这里就不再赘述。


此外,还有一些更高阶的优化选项并未提供在Optimisation列表中,例如,最高的性能优化"-Ofast",以及更聪明的链接优化“Link Time Optimisation”,详细的使用效果请参考gcc的官方文档,这里就不再赘述。要想使用它们:

  1. 可以将 Optimisation列表设置为<Default>;
  2. Misc Controls文本框中添加对应的选项"-Ofast"已开启最高性能优化;
  3. Misc Controls文本框中添加对应的选项"-flto"已开启Link Time Optimisation

注意,对应的编译选项也要在 "Linker"的Misc Controls中添加:

【如何在编译成功后打印尺寸信息】


MDK使用Arm Compiler 5或者Arm Compiler 6进行编译,成功后会在Build Output窗口中打印一些尺寸信息,比如:

MDK使用GCC进行编译时,默认情况下就没有这么方便了。为了达到同样的效果,我们可以在"Options for Target"的“User” 选项卡中增加 After Build/Rebuild命令行:

arm-none-eabi-size.exe ./Objects/gcc_template.elf

注意,如果你修改了输出文件的名称,千万要记得同步更新这里的命令行呀!

最终实现了类似的效果:

【如何优雅的测量系统的性能】


熟悉我公众号的朋友一定注意到我有一个开源项目 perf_counter,可以帮助用户在不额外占用SysTick的情况下提供一系列服务,包括但不限于:

  • 为裸机或者RTOS提供Cycle级别的性能测量;
  • 评估代码片段的CPU占用;
  • 算法精细优化时用于测量和观察优化的效果;
  • 测量中断的响应时间;
  • 测量中断的发生间隔(查找最短时间间隔);
  • 评估GUI的帧率或者刷新率;
  • 与SystemCoreClock计算后,获得一个系统时间戳(Timestamp);
  • 当做Realtime Clock的基准;
  • 作为随机数种子
  • ……

其详细使用方法在文章《如何“优雅”的测量系统性能》中有详细介绍,这里就不再赘述。在Github上的最新版本中,优化了gcc的部署体验——也能像Arm Compiler 5以及Arm Compiler 6那样简单拖放lib即可完成部署

具体步骤如下:

1、通过下面连接获取最新版本的 perf_counter

https://github.com/GorgonMeducer/perf_counter/releases/tag/v1.5.0

2、解压缩后拷贝到gcc工程所在目录下,改名为perf_counter

3、添加perf_counter.h所在路径到编译器头文件搜索路径中:

4、修改startup_ARMCM7.c文件,将原本的:

void SysTick_Handler (void) __attribute__ ((weak, alias("Default_Handler")));

修改为

void SysTick_Handler(void) __attribute__ ((weak));

注意,千万不要在startup_ARMCM7.c中为 SysTick_Handler提供默认的weak实现函数!切记,切记!

5、更新"Options for Target"的"Linker"配置,在Misc Controls文本框中追加:

 -Wl,--wrap=SysTick_Handler
"./perf_counter/lib/libperf_counter_gcc.a"

6、在要用到 perf_counter 服务的地方添加对头文件的包含:

#include "perf_counter.h"
...

7、编译工程,如果报告如下的错误:

libperf_counter_gcc.a(systick_wrapper_gcc.o): in function `__ensure_systick_wrapper':
(.text+0x18): undefined reference to `SysTick_Handler'

则说明我们的工程中并没有实现SysTick_Handler,此时,应该在任意c文件中增加一个SysTick_Handler,并在 main() 函数中对 perf_counter.c进行初始化:

#include "perf_counter.h"

void SysTick_Handler(void)
{
    
}

int main(void)
{
    init_cycle_counter(false);
    
    while(1) {
    }
    
    return 0;
}

编译后一切正常:

详细的使用方法,还请参考《如何“优雅”的测量系统性能》。

【说在后面的话】


MDK中使用GCC具有很多实际意义,比如:

  • 编译不受License限制
  • 可以进行调试(需要License)
  • 可以借助RTE实现各类CMSIS Pack的快速部署(比如很多操作系统:cmsis-rtos2等等)
  • 不必编写makefile
  • 很多大厂芯片(比如STM32)其实都在CMSIS-Pack的软件包里提供了arm gcc的支持(提供了arm gcc下的启动文件和 *.ld 文件),直接通过RTE就可以加入对应的文件

不管你是否喜欢MDK,总的来说是多了一种选择把。


本文分享自微信公众号 - 裸机思维(bare-metal),作者:GorgonMeducer 傻孩子

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-07-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • RTX5的汽车级,工业级,医疗和铁路安全认证已经通过,证书已颁发

    1、当前RTX5的教程已经在制作中,使用CMSIS-RTOS V2封装层,含FreeRTOS,配套V7,V6和V5板子​。

    armfly
  • 懒人玩Arm-2D究竟有几种姿势

    如果你是第一次接触Arm-2D,可以单击这篇文章《为什么说Arm-2D是小资源单片机的GUI人权卡!》来了解大概、或是观看公开课(https://aijishu...

    GorgonMeducer 傻孩子
  • 白嫖和付费

    几天前看到个 1 分钱学 Redis 的课程,近乎白嫖的价格,买完发现所谓的课程毫无质量可言,拉的群组是另一门 Java 开发课程的推广群。

    TTTEED
  • 基于STM32的ThreadX GUIX的工程模板发布,1452个源码文件,请准备好一台性能强劲的电脑,否则后果自负^_^

    说明: 1、初次使用GUIX,涉及到的一些细节,后面教程中为大家分享。 2、开发板主板芯片: (1)V5是STM32F407IGT6 (2)V6是STM...

    armfly
  • 基于STM32的ThreadX GUIX的工程模板发布,1452个源码文件

    说明: 1、初次使用GUIX,涉及到的一些细节,后面教程中为大家分享。 2、开发板主板芯片: (1)V5是STM32F407IGT6 (2)V6是STM...

    armfly
  • 什么?Arm放弃了自家的汇编语法?改投GNU了?

    那么多对于我们初学者来说要学习哪种风格呢?答案是肯定的,学习GNU风格的汇编代码,因为做Linux驱动开发必须掌握的linux内核、uboot,而这两个软件就是...

    GorgonMeducer 傻孩子
  • 【STM32F429】第5章 ThreadX操作系统移植(MDK AC6)

    论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514

    armfly
  • 【STM32F429】第4章 ThreadX操作系统移植(MDK AC5)

    论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514

    armfly
  • 基于STM32H7,F407,F429的ThreadX内核程序模板,含GCC,MDK和IAR三个版本(2020-06-08)

    V5是STM32F407IGT6,V6是STM32F429BIT6,V7是STM32H743XIH6 模板下载:

    armfly
  • 【STM32F429】第2章 初学ThreadX GUIX的准备工作及其快速上手

    最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429

    armfly
  • 【STM32H7】第2章 初学ThreadX GUIX的准备工作及其快速上手

    最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429

    armfly
  • 【STM32H7】第4章 ThreadX操作系统移植(MDK AC5)

    论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514

    armfly
  • 【安富莱】STM32H7用户手册发布,重在BSP驱动包设计方法,HAL库的框架学习

    1、本教程重在BSP驱动包设计方法和HAL库的框架学习,并将HAL库里面的各种弯弯绕捋顺,从而方便我们的程序设计。

    armfly
  • 【独家】我就要用MDK来开发树莓Pico,怎么地吧!

    树莓派 Pico是一个小巧、“迅速”且多功能的开发板,基于独家定制的RP2040芯片打造,是在英国的树莓派团队设计的全新微控制器。

    GorgonMeducer 傻孩子
  • 惊爆内幕:老MDK也可以使用新编译器

    如果说“喜新厌旧”是人类的天性,那么嵌入式程序员一定是特例——他们尤其不喜欢更换自己用惯了的工具——拿IDE来说吧,相当一部分人仍然抱着老版本的MDK迟迟不愿意...

    GorgonMeducer 傻孩子
  • TencentOS tiny 内核移植参考指南(Keil版)

    TencentOS tiny目前主要支持ARM Cortex M核芯片的移植,比如STM32 基于Cortex M核全系列、NXP 基于Cortex M核全系列...

    Supowang
  • 【STM32F429】第7章 ThreadX操作系统移植(GCC)

    论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514

    armfly
  • 【STM32F429】第6章 ThreadX操作系统移植(IAR)

    论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514

    armfly
  • 一万个进程的鬼故事 --- 多线程系列(三)

    大家好,我是天天被白嫖却越被白嫖越爽、秃顶法师老赵养猪的御用兽医、东北大膘客的灵魂伴侣、早就进化且早就开始享受的高等文明、欧阳狂霸的第二面孔---谢顶道人老李。

    老李秀

扫码关注云+社区

领取腾讯云代金券