痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU特性介紹(2)- RT685EVKA效能實測(Dhrystone)
大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是恩智浦i.MX RTxxx系列MCU的效能。
在前面的文章 i.MXRTxxx微控制器概覽 裡,痞子衡給大家簡介過恩智浦半導體在2018年推出的全新跨界微控制器i.MX RTxxx系列,該系列第一款晶片i.MXRT600搭載一顆Cortex-M33控制核心和一顆Tensilica HiFi4 DSP處理核心,該晶片可在超低功耗邊緣處理應用中實現高效本地音訊預處理、沉浸式3D音訊播放和支援語音的體驗。今天痞子衡先為大家實測一下其Cortex-M33控制核心的效能,效能測試程式採用經典的Dhrystone演算法。
關於Dhrystone標準的基本知識,痞子衡之前專門寫過一篇文章 微處理器CPU效能測試基準(Dhrystone) ,本篇就是基於瞭解Dhrystone基本知識之後的一次實踐。來,讓我們開始吧。
一、準備工作
1.1 硬體平臺NXP i.MX RT600 EVK
要開始實測i.MXRT600的Dhrystone,首先你得有一塊開發板,恩智浦官網上有i.MXRT600配套的評估板,如下圖所示,痞子衡今天用的就是這塊板子,板載主晶片型號為iMXRT685EVKA。
1.2 整合開發環境IAR EWARM
ARM Cortex-M微控制器的整合開發環境有很多,其中IAR EWARM憑藉優良的特性備受廣大工程師青睞,今天痞子衡就選用IAR作為軟體環境,具體版本為IAR EWARM v8.32.2。
1.3 官方軟體開發包NXP MCUXpresso SDK
在開始移植Dhrystone程式到i.MXRT685上之前,我們需要先有一個i.MXRT685的基本hello world的例程,當然我們可以對著資料手冊自己從頭寫一個,但是這裡痞子衡推薦使用官方軟體開發包。
二、開始實測
2.1 跑通hello world
使用USB線連線電腦與板子的J5 USB口,此時在裝置管理器應該可以看到USB虛擬的串列埠(EVK板載LPC-LINK2偵錯程式內含USB轉串列埠功能,如果看不到串列埠,請自行安裝LPC-LINK2驅動)。
如果工程執行正常,你在串列埠除錯助手(115200,8N1)裡應該能看到"hello world."列印輸出。
2.2 移植Dhrystone程式
以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原始檔還需要進一步修改。
2.2.1 適配嵌入式平臺
我們下載的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);
}
}
2.2.2 板級初始化
上一節裡已經將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)
{
}
}
2.2.3 計時功能
原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);
}
2.2.4 串列埠列印功能
串列埠列印功能的改動比較簡單,直接把原dhry_1.c檔案裡的printf()全部替換成PRINTF()即可,PRINTF函式在原hello world工程裡已經實現了。
2.3 Dhrystone引數配置
痞子衡在Dhrystone標準的基本知識介紹裡說過,Dhrystone幾乎沒有引數配置,唯一需要注意的就是REG,Cortex-M33平臺支援register關鍵字,所以我們在IAR工程option裡巨集定義框內加上 REG=register。
此外我們還需要在巨集定義框內設定額外兩個巨集 NUMBER_OF_RUNS、CORE_FREQ_MHz,前者代表跑dhrystone核心演算法程式的總次數,後者是當前MCU實際執行主頻。最後還需要設定一下IAR的優化選項,如下圖所示:
2.4 輸出Dhrystone結果
到這裡Dhrystone的移植工作就完全結束了,此時Dhrystone工程也應該能正常編譯了。為得到最高的Dhrystone得分,最後需要再確定兩件事:一、Core Clock是否確定配置為300MHz;二、工程程式碼段/資料段是否放在了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得分為400 DMIPS。
想偷懶的朋友直接移步痞子衡的github https://github.com/JayHeng/Cortex-M-Apps 去下載移植好的工程,工程在\Cortex-M-Apps\apps\dhrystone_imxrt685\bsp\下面。
至此,恩智浦i.MX RTxxx系列MCU的Dhrystone效能與實測痞子衡便介紹完畢了,掌聲在哪裡