你们好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给你们介绍的是恩智浦i.MX RTxxx系列MCU的性能。html
在前面的文章 i.MXRTxxx微控制器概览 里,痞子衡给你们简介过恩智浦半导体在2018年推出的全新跨界微控制器i.MX RTxxx系列,该系列第一款芯片i.MXRT600搭载一颗Cortex-M33控制内核和一颗Tensilica HiFi4 DSP处理内核,该芯片可在超低功耗边缘处理应用中实现高效本地音频预处理、沉浸式3D音频播放和支持语音的体验。今天痞子衡先为你们实测一下其Cortex-M33控制内核的性能,性能测试程序采用经典的Dhrystone算法。git
关于Dhrystone标准的基本知识,痞子衡以前专门写过一篇文章 微处理器CPU性能测试基准(Dhrystone) ,本篇就是基于了解Dhrystone基本知识以后的一次实践。来,让咱们开始吧。github
要开始实测i.MXRT600的Dhrystone,首先你得有一块开发板,恩智浦官网上有i.MXRT600配套的评估板,以下图所示,痞子衡今天用的就是这块板子,板载主芯片型号为iMXRT685EVKA。算法
ARM Cortex-M微控制器的集成开发环境有不少,其中IAR EWARM凭借优良的特性备受广大工程师青睐,今天痞子衡就选用IAR做为软件环境,具体版本为IAR EWARM v8.32.2。app
在开始移植Dhrystone程序到i.MXRT685上以前,咱们须要先有一个i.MXRT685的基本hello world的例程,固然咱们能够对着数据手册本身从头写一个,可是这里痞子衡推荐使用官方软件开发包。
注册并登陆恩智浦官网,来到 MCUXpresso SDK Builder 页面,在"Select Development Board"里选择EVK-IMXRT685后点击Build MCUXpresso SDK后跳转到下一个页面,在"Developer Environment Settings"里选择IAR并点击Download SDK后即可获得SDK_2.5.0_EVK-MIMXRT685.zip,下面是痞子衡下载的开发包具体版本信息:less
使用USB线链接电脑与板子的J5 USB口,此时在设备管理器应该能够看到USB虚拟的串口(EVK板载LPC-LINK2调试器内含USB转串口功能,若是看不到串口,请自行安装LPC-LINK2驱动)。
打开前一步下载的开发包里的\SDK_2.5.0_EVK-MIMXRT685\boards\evkimxrt685\demo_apps\hello_world\iar\hello_world.eww工程,确认工程option里linker文件选择的是MIMXRT685Sxxxx_ram.icf,而后使用板载调试器直接将工程下载进主芯片的RAM运行。
若是工程运行正常,你在串口调试助手(115200,8N1)里应该能看到"hello world."打印输出。ide
以hello_world工程为基础,将从Roy Longbottom的网站下载到的classic_benchmarks.tar.gz包解压,将\classic_benchmarks\source_code\dhrystone2\路径下的以下全部源文件(.c或.h)所有拷贝到hello world工程目录下函数
\classic_benchmarks\source_code\dhrystone2 \dhry.h --关于兼容性的原型定义 \dhry_1.c --主程序入口 \dhry_2.c --算法子程序
将上面全部Dhrystone源文件所有添加进hello_world工程并将工程改名为dhrystone,而后再将工程中原主函数入口文件hello_world.c改名为dhrystone.c,此时基本Dhrystone工程就完成了。但注意此时工程没法编译,由于Dhrystone源文件还须要进一步修改。性能
咱们下载的Dhrystone源码本用做在PC上运行的,因此源码里面有一些仅适用于PC上运行的代码,好比计时部分、文件I/O部分,须要将这些代码所有删除以适合在嵌入式平台运行。
关于计时部分,须要删除dhry_1.c文件里的#include < time.h> 语句,而且删除dhry.h文件里跟TIME宏相关的以下代码:测试
#ifndef TIME #define TIMES #endif /* Use times(2) time function unless */ /* explicitly defined otherwise */ #ifdef TIMES #include <sys/types.h> #include <sys/times.h> /* for "times" */ #endif
关于文件I/O部分,须要删除dhry_1.c文件里的#include < stdio.h> 语句,以及以下涉及文件I/O(主要是关于Dhry.txt的操做)的代码:
#include <stdio.h> // 需删除 void main (int argc, char *argv[]) // 需更改成void main(void) { // ... // 如下代码需删除 /* Initializations */ if (argc > 1) { switch (argv[1][0]) { case 'N': nopause = 0; break; case 'n': nopause = 0; break; } } if ((Ap = fopen("Dhry.txt","a+")) == NULL) { printf(" Can not open Dhry.txt\n\n"); printf(" Press Enter\n\n"); int g = getchar(); exit(1); } // ... { // ... /************************************************************************ * Add results to output file Dhry.txt * ************************************************************************/ fprintf (Ap, " #####################################################\n\n"); fprintf (Ap, " Dhrystone Benchmark 2.1 %s via C/C++ %s\n", options, timeday); // ... fclose(Ap); } }
上一节里已经将dhry_1.c里面的main函数形参int argc, char *argv[]改为了void,因为dhrystone.c(原hello_world.c)里已有main函数,且该main函数中含有板级初始化代码,因此咱们须要将dhry_1.c里的main函数改名(好比可改名为dhrystone()),使dhrystone源码做为子程序来调用,这样便于往不一样MCU平台移植,最后直接dhrystone.c里的main函数中增长dhrystone()的调用便可。
// dhry_1.c文件中 void main (void) // 需更改成void dhrystone(void) { ... } // dhrystone.c文件中 int main(void) { /* Init board hardware. */ BOARD_InitPins(); BOARD_BootClockRUN(); BOARD_InitDebugConsole(); // 增长的dhrystone函数调用 dhrystone(); while (1) { } }
原dhry_1.c文件里的关于计时部分的代码应该作一些微调整,time.h头文件的包含应该去除,这是Windows系统里的头文件,start_time(), end_time()是基于time.h里clock_gettime()函数而封装的API,secs是用于记录时间差的变量,这些API和全局变量均可以保留,但须要用MCU里的计时器从新实现。
#include <time.h> // 需删除 void dhrystone (void) { // ... do { start_time(); // ... end_time(); User_Time = secs; } while (count >0); }
关于计时器,第一个想到的天然是Cortex-M内核里的SysTick,不过考虑到Dhrystone程序是要跑在300MHz的主频下,而SysTick计时器只有24bit,也就是说即便SysTick->LOAD设最大的reload值0xFFFFFF,也将每隔0.05592s(0x1000000/300MHz)产生一次SysTick中断,而Dhrystone程序至少要跑2s以上,在Dhrystone运行的2s内会产生35次(2/0.05592)SysTick中断,这无疑会下降Dhrystone得分,因此SysTick直接被pass。(备注:SysTick->CTRL[2]用于选择SysTick的时钟源,默认1'b0为Core Clock,1'b1为外部clock,暂未研究这里的外部clock在i.MXRT685上是否能用)。
翻看i.MXRT685的参考手册,其支持的计时器种类不少,有CTIMER、SCT、MRT、UTICK、WWDT,就选择比较经常使用的32bit计时器CTIMER吧。
以前下载的软件包里也有CTIMER的例程\SDK_2.5.0_EVK-MIMXRT685\boards\evkimxrt685\driver_examples\ctimer,打开simple_match_interrupt.c文件将其中CTIMER初始化相关代码放入新定义的timer_ctimer_init()函数中并在start_time()中调用timer_ctimer_init()一次以完成CTIMER初始化。
此外还须要添加定义#define CLOCKS_PER_SEC (16000000),由于CTIMER选择的时钟源是16MHz。
#define CLOCKS_PER_SEC (16000000) volatile uint32_t s_timerHighCounter = 0; void ctimer_match0_callback(uint32_t flags) { s_timerHighCounter++; } void timer_ctimer_init(void) { ctimer_config_t config; /* Use 16 MHz clock for the Ctimer2 */ CLOCK_AttachClk(kSFRO_to_CTIMER2); CTIMER_GetDefaultConfig(&config); CTIMER_Init(CTIMER, &config); /* Configuration 0 */ matchConfig0.enableCounterReset = true; matchConfig0.enableCounterStop = false; matchConfig0.matchValue = (uint32_t)~0; matchConfig0.outControl = kCTIMER_Output_Toggle; matchConfig0.outPinInitState = false; matchConfig0.enableInterrupt = true; CTIMER_RegisterCallBack(CTIMER, &ctimer_callback_table[0], kCTIMER_SingleCallback); CTIMER_SetupMatch(CTIMER, CTIMER_MAT0_OUT, &matchConfig0); CTIMER_StartTimer(CTIMER); } void start_time(void) { timer_ctimer_init(); s_timerHighCounter = 0; } void end_time(void) { uint64_t retVal; uint32_t high; uint32_t low; do { high = s_timerHighCounter; low = CTIMER_GetTimerCountValue(CTIMER); } while (high != s_timerHighCounter); retVal = ((uint64_t)high << 32U) + low; CTIMER_StopTimer(CTIMER); secs = retVal / (CLOCKS_PER_SEC * 1.0); }
串口打印功能的改动比较简单,直接把原dhry_1.c文件里的printf()所有替换成PRINTF()便可,PRINTF函数在原hello world工程里已经实现了。
痞子衡在Dhrystone标准的基本知识介绍里说过,Dhrystone几乎没有参数配置,惟一须要注意的就是REG,Cortex-M33平台支持register关键字,因此咱们在IAR工程option里宏定义框内加上 REG=register。
此外咱们还须要在宏定义框内设置额外两个宏 NUMBER_OF_RUNS、CORE_FREQ_MHz,前者表明跑dhrystone核心算法程序的总次数,后者是当前MCU实际运行主频。最后还须要设置一下IAR的优化选项,以下图所示:
到这里Dhrystone的移植工做就彻底结束了,此时Dhrystone工程也应该能正常编译了。为获得最高的Dhrystone得分,最后须要再肯定两件事:1、Core Clock是否肯定配置为300MHz;2、工程代码段/数据段是否放在了SRAM。
打开clock_config.c文件,查看BOARD_BootClockRUN()函数,ARM Core/AHB时钟仅保守地配了250MHz,让咱们修改Pfd0的分频系数,从19修改成16,直接超频到300MHz。
void BOARD_BootClockRUN(void) { // ... CLOCK_InitSysPll(&g_configSysPll); /* Configure system PLL to 528Mhz. */ /* Valid PFD values are decimal 12-35. */ // 将分频系数修改16 // CLOCK_InitSysPfd(kCLOCK_Pfd0, 19); /* Enable main PLL clock 500MHz. */ CLOCK_InitSysPfd(kCLOCK_Pfd0, 16); /* Enable main PLL clock 594MHz. */ // ... /* Let CPU run on SYS PLL PFD0 with divider 2. */ CLOCK_SetClkDiv(kCLOCK_DivSysCpuAhbClk, 2); CLOCK_AttachClk(kMAIN_PLL_to_MAIN_CLK); // ... }
再打开MIMXRT685Sxxxx_ram.icf文件,查看text/data段确实放在了SRAM里(0x0 - 0x47FFFF):
define symbol m_interrupts_start = 0x00080000; define symbol m_interrupts_end = 0x00080143; define symbol m_text_start = 0x00080144; define symbol m_text_end = 0x001BFFFF; define symbol m_data_start = 0x001C0000; define symbol m_data_end = 0x002FFFFF;
还等什么?将Dhrystone工程赶忙下载进芯片并打开串口调试助手看Dhrystone得分啊。痞子衡实测的Dhrystone得分为411 DMIPS,1.37 DMIPS/MHz,这跟ARM官方公布的CM33内核Dhrystone得分1.5 DMIPS/MHz有点差距。
对于Dhrystone得分没上1.5 DMIPS/MHz,痞子衡固然不服,鉴于以前在 RT1052上测试CoreMark性能 的经验,咱们知道低版本的IAR在速度优化上表现要更好,因此咱们继续用IAR 7.80.4再测一遍,这一次获得了440 DMIPS,1.466 DMIPS/MHz,这才差很少接近ARM官方指标。
想偷懒的朋友直接移步痞子衡的github https://github.com/JayHeng/Cortex-M-Apps 去下载移植好的工程,工程在\Cortex-M-Apps\apps\dhrystone_imxrt685\bsp\下面。
至此,恩智浦i.MX RTxxx系列MCU的Dhrystone性能与实测痞子衡便介绍完毕了,掌声在哪里~~~