接下来,我将给单片机跑个分。选择MCU时,我们一方面当然是要满足自身项目的要求,另一方面更偏向于选择性价比更高的MCU。
基于单片机的性能跑分就应运而生了,常见的性能测试就包括CoreMark和Dhrystone。
Dhrystone是由Reinhold P. Weicker在1984年提出来的一个基准测试程序,其主要目的是测试处理器的整数运算和逻辑运算的性能。Dhrystone首先用Ada语言发布,后来Rick Richardson为Unix开发了用C语言编写的Version 1.1,这个版本也成功的推动了Dhrystone的广泛应用。Dhrystone标准的测试方法很简单,就是单位时间内跑了多少次Dhrystone程序,其指标单位为DMIPS/MHz。MIPS是Million Instructions Per Second的缩写,每秒处理的百万级的机器语言指令数。DMIPS中的D是Dhrystone的缩写,它表示了在Dhrystone标准的测试方法下的MIPS。
但其缺陷在于它易受编译器影响。在Dhrystone中有大量的字符串复制语句,用来测量字符串复制的性能。然而Dhrystone中字符串的长度不变,并且均开始于自然对齐的边界,这两点便与真实的程序不同。因此一个优化性能好的编译器能够在去掉循环的情形下通过一连串字的移动替代对字符串的复制,这将会快很多。同时Dhrystone代码量过小,在现代CPU中,它能够被放进指令缓存中,所以它并不能严格的测量取指性能。虽然Dhrystone的测试可以作为参考,但更容易受到其他因素的影响下。
而CoreMark能接近实际地反应出工作能力。它是由嵌入式微处理器基准测试联盟(EEMBC)开发,为了取代过时的Dhrystone标准。ARM官方也是建议使用CoreMark而不是Dhrystone来进行基准测试。
软件使用C语言编写,是一个免费使用,易于移植的基准测试程序。目前CoreMark已经成为测量与比较各类处理器性能的业界标准基准测试。CoreMark得分越高,意味着性能更高。下图为CoreMark与Dhrystone对比
来源于网络
CoreMark的模拟工作负载主要包含几种常用的算法:
矩阵操作core_matrix.c:模拟常用的运算;
链表操作core_list_join.c:模拟指针的各种用法;
状态机操作core_state.c:模拟程序分支运行操作;
循环冗余校验core_util.c:嵌入式系统常见的功能。
接下来,我们开始移植CoreMark。
第一步,先去官网在相应位置下载源代码,网站为www.eembc.org
第二步,新建一个工程,添加好必要组件,再新建一个子文件夹CoreMark,将core_list_join.c、core_main.c、core_matrix.c、core_state.c、core_util.c、coremark.h放入其中,再新建一个子文件夹CoreMark_Test,将simple文件夹中的core_portme.c和core_portme.h放入其中。同时打开工程,将上述的c文件添加到工程,记得不要忘了添加路径哦。
第三步,因为在core_main.c文件里已经有main()函数,所以我们要将原来工程中的main函数屏蔽或删除,我这里选择屏蔽,选择main.c,右键选择第一个,将下图include in Target Build勾选去掉,如下图所示,
最重要的移植工作是适配core_portme.c,,其他C文件不动。
首先,添加
------------------------------------------------------------------
#define SysTick_Counter_Disable ((uint32_t)0xFFFFFFFE)
#define SysTick_Counter_Enable ((uint32_t)0x00000001)
#define SysTick_Counter_Clear ((uint32_t)0x00000000)
__IO uint32_t Ticks;
#define ITERATIONS 6666;
------------------------------------------------------------------
前三行是系统滴答定时器SysTick的配置参数,全局变量Ticks ITERATIONS这个视情况而定,如果出现ERROR! Must execute for at least 10 secs for a valid result!,那么需要将此数值变大使程序运行时间至少在10秒以上。
同时,将以下代码屏蔽
------------------------------------------------------------------
#define NSECS_PER_SEC CLOCKS_PER_SEC
#define CORETIMETYPE clock_t
#define GETMYTIME(_t) (*_t = clock())
#define MYTIMEDIFF(fin, ini) ((fin) - (ini))
#define TIMER_RES_DIVIDER 1
#define SAMPLE_TIME_IMPLEMENTATION 1
#define EE_TICKS_PER_SEC (NSECS_PER_SEC / TIMER_RES_DIVIDER)
static CORETIMETYPE start_time_val, stop_time_val;
------------------------------------------------------------------
添加
#define EE_TICKS_PER_SEC 1000.0
将原有这三个函数修改为以下内容
------------------------------------------------------------------
void start_time(void)
{
Ticks=0;
SysTick_Config(SystemCoreClock / 1000);//1ms中断
}
void stop_time(void)
{
/* Stop the Timer and get the encoding time */
SysTick->CTRL &=SysTick_Counter_Disable;
/* Clear the SysTick Counter */
SysTick->VAL = SysTick_Counter_Clear;
}
CORE_TICKS get_time(void)
{
CORE_TICKS elapsed=(CORE_TICKS) Ticks;//(MYTIMEDIFF(stop_time_val, start_time_val));
return elapsed;
}
------------------------------------------------------------------
同时由于core_main.c中的main函数执行时先调用core_portme.c中的portable_init函数,因此需要将原来main函数中的初始化函数放到core_portme.c同时在portable_init函数里调用,如下图所示
第四步,修改core_portme.h和coremark.h.首先要适配ee_printf打印函数,因为我们的板子已经实现了printf函数,所以保持coremark.h下面代码块不变
------------------------------------------------------------------
#if HAS_PRINTF
#define ee_printf printf
#endif
------------------------------------------------------------------
这样程序调用时会将ee_printf替换成printf实现打印功能
如果板子没有printf函数,得自己实现打印函数并进行相应替换.
同时,将core_portme.h中的下列函数改成你对应的编译器版本和优化等级
------------------------------------------------------------------
#ifndef COMPILER_VERSION
#ifdef __GNUC__
#define COMPILER_VERSION "GCC"__VERSION__
#else
#define COMPILER_VERSION "ARM Compiler 5.06 update 7 (build 960)"//changed
#endif
#endif
#ifndef COMPILER_FLAGS
#define COMPILER_FLAGS "-g -O3 -Otime"//changed /* "Please put compiler flags here (e.g. -o3)" */
#endif
#ifndef MEM_LOCATION
#define MEM_LOCATION "STACK"
#endif
------------------------------------------------------------------
最后,因为我们是用系统滴答定时器来计时的,也就是start_time、stop_time、get_time所需要定时基准,所以要在at32a403a_int.c文件里面添加以下SysTick_Handler代码,并添加声明:extern uint32_t Ticks;
------------------------------------------------------------------
void SysTick_Handler(void)
{
Ticks++;
}
------------------------------------------------------------------
最后一步,将Optimization,也就是优化等级改成Level 3,同时勾选MDK 5的一个优化选项Optimize for time,当勾选时,CoreMark跑分会变高,原因大概是在keil中,在不选择"Optimize for time"编译选项时,局部float变量占用8个字节(编译器默认自动扩展成double类型),一旦你使用"Optimize for time"编译选项,局部float变量只会占用4个字节.也就是优化了很多不必要的累赘变量定义,能大大优化编译速度。
这里还需要注意一点,如果出现以下报错,可以尝试将Target中的“Use MicroLIB”勾选上,大概率解决报错。
编译,下载好后,如果都正常的话,用串口助手观察会出现以下信息
最后CoreMark分数为469分,这里尝试一下超频跑分,这里仅作示范,实际要稳定运行不建议超频,会存在隐患。超频是通过将crm_pll_config函数中的CRM_PLL_MULT_50改成CRM_PLL_MULT_64,就是将200MHz改成256MHz主频,最后的CoreMark为601分,与主频的提升幅度一致。