GD32移植CoreMark实现性能测评

简介

本文将演示CoreMark移植到GD32/STM32上进行性能测评。文章末尾给出了GD32F130C8T6、GD32F303CCT6、STM32F103ZET6、Air32F103CBT6这几款单片机的测试对比结果。

工程搭建

访问CoreMark的Github主页,下载如下图所示的源文件。在工程中新建一个子目录CoreMark,并放入其中。在Keil开发环境中,需要对CoreMark源文件(.c)加入到分组中,并指定CoreMark的头文件的包含路径,这些步骤不再赘述。

GD32移植CoreMark实现性能测评_第1张图片

移植CoreMark

main函数

CoreMark代码框架定义了main函数,因此我们要屏蔽掉自己写的main函数。在这个mian函数中,首先调用了portable_init()函数,这个函数就是我们移植的时候,实现系统时钟以及串口初始化的地方。

int main(int argc, char *argv[])
{

    //省略
    /* first call any initializations needed */
    portable_init(&(results[0].port), &argc, argv);
    //省略
}

栈深度的调整

CoreMark默认使用栈内存实现数据的存储和计算,它会在main函数中定义一个2KB大小的数组。而在单片机的xxx_startup.s汇编启动文件中,对栈的容量限制为1KB,因此必须扩大栈容量,否则会进入HardFault。笔者因为这个问题调试了很久。建议定义为4KB,如下所示。

;  Stack Configuration
;    Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; 

;Stack_Size      EQU     0x00000400    
Stack_Size      EQU     0x00001000    

对串口的适配

CoreMark使用串口将测试信息输出,这样我们就可以在电脑的串口调试助手上看到评分。具体来说,CoreMark使用ee_printf()函数来输出数据。那么我们就要配置串口,并实现ee_printf()函数。

在portable_init()函数中我们实现对串口的配置,并实现ee_printf()函数,如下:

//#include "xxxxx"      //MCU指定的头文件
#include 
#include 
#include 

//USART0配置函数
void USART0_config(void)
{
	rcu_periph_clock_enable(RCU_GPIOA);
	rcu_periph_clock_enable(RCU_USART0);

	//PA9 USART0_Tx 
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
    //PA10 USART0_Rx 
    gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
	
	usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_enable(USART0);
}

//实现ee_printf
int ee_printf(const char *fmt, ...) 
{
	char buf[256];
	va_list args;
	
	va_start(args, fmt);
	int len = vsnprintf((char*)buf,256,fmt,args);  //需要C99支持
	va_end(args);

	if(len > 0)
	{
		for(int i=0;iportable_id = 1;
}

由于我们直接实现了ee_printf()函数而不是依赖C标准库中的printf,因此需要在core_portme.h中对将HAS_PRINTF 宏修改为0,并同时声明ee_printf()函数。

#ifndef HAS_PRINTF
#define HAS_PRINTF 0  
int ee_printf(const char *fmt, ...) ;
#endif

对SysTick的适配

CoreMark需要使用一个定时器进行计时,它的主要操作流程为:开启定时器,运行性能测试代码片段,停止定时器,计算时间差值,用这个差值来衡量CPU的性能,差值越小说明CPU的计算速度越快。由于SysTick是对于CM3/CM4内核的单片机来说是标配,且配置过程简单,所以本文使用SysTick作为CoreMark的时基。

对SysTick的配置在core_portme.c文件中进行。在这个文件中,CoreMark定义了2个全局变量start_time_val和stop_time_val。每次迭代开始的时候,CoreMark使用start_time()函数来初始化并启动SysTick,并使用start_time_val保存启动SysTick时的初始tick数,由于tick初始值为0,所以start_time_val初始化为0即可。

我们需要自己在这个文件中定义一个全局变量Tick,来记录从定时器启动以来经过的tick数。在本文中,笔者在start_time()函数使用CMSIS定义的SysTick_Config()函数来配置SysTick每1ms中断一次,并在中断函数中递增全局变量Tick。

每次迭代完成后,CoreMark使用stop_time()函数来停止SysTick,并将Tick数存储在全局变量start_time_val中。然后,CoreMark使用get_time()函数来计算得到从定时器启动到结束经过的tick数,然后通过time_in_secs()函数将tick数转换为秒数。为了能让CoreMark正确计算出秒数,我们必须根据自己的定时器配置正确定义EE_TICKS_PER_SEC  的值,这个宏代表了安装我们当前的定时器配置,1秒会产生多少个tick。本文SysTick配置为1ms中断一次,因此定义为1000。CoreMark预定义的get_time()函数和time_in_secs()函数可以不用修改。

//注释原有的EE_TICKS_PER_SEC 的定义,重新定义为1000,也就是每秒钟SysTick中断1000次,产生1000个tick计数
//#define EE_TICKS_PER_SEC           (NSECS_PER_SEC / TIMER_RES_DIVIDER)
#define EE_TICKS_PER_SEC  1000      //每隔1ms中断一次,也就是1秒有1000个tick


volatile uint32_t Tick ; //定义SysTick的中断次数计数器,每中断一次,就在中断函数中加1

//SysTick的中断函数
void SysTick_Handler(void)
{
	Tick++;
}


//启动定时器并将start_time_val设置为当前的定时器Tick数
void start_time(void)
{
	//GETMYTIME(&start_time_val);
	Tick = 0;   //每次初始化时让tick数从0开始
	start_time_val = Tick;  //当前tick数保存到全局变量start_time_val 
	SysTick_Config(SystemCoreClock/1000); //配置并启动SysTick,这里配置Systick每隔1ms中断一次

}

//停止定时器并将stop_time_val设置为当前的定时器Tick数
void stop_time(void)
{
	//GETMYTIME(&stop_time_val);
	SysTick->CTRL = 0; //停止SysTick
	stop_time_val = Tick; //当前tick数保存到全局变量stop_time_val 
}

//==========================【以下代码不用修改】==============================
//通过stop_time_val和start_time_val的差值得到CPU跑评测代码消耗的时间,单位是tick数。
CORE_TICKS get_time(void)
{
    CORE_TICKS elapsed =
			(CORE_TICKS) (MYTIMEDIFF(stop_time_val, start_time_val));
	return elapsed;
}

//将get_time()函数返回的tick数转换为秒单位时间。
//需要根据定时器的tick周期正确定义EE_TICKS_PER_SEC的值。
secs_ret time_in_secs(CORE_TICKS ticks)
{
    secs_ret retval = ((secs_ret)ticks) / (secs_ret)EE_TICKS_PER_SEC;
    return retval;
}

如果你想超频

像STM32,GD32这种都是在Reset_Handler函数中调用SystemInit() 函数 执行的系统时钟初始化,也就是在复位完成后系统时钟就配置好了。如果你想超频,可以在portable_init()函数中对系统时钟进行二次配置,实现超频。

其他配置

在core_portme.h中对ITERATIONS进行定义,它定义了测试代码迭代运行的次数,这个值的大小不会影响评分结果,但是必须要让单片机运行至少10秒,值太大会让评测时间变长。这里给个参考值,一般72MHz的主频可以使用2000,120MHz可以使用3500。

//#######################################
#define ITERATIONS 3500    /*定义迭代次数*/
//#######################################

在core_portme.h中对MAIN_HAS_NOARGC进行定义,定义为1时,coremark使用的main函数的参数列表为空;定义为0时coremark使用的main函数的参数列表为main(int argc, char *argv[])。由于单片机的main函数一般都是不带参数的,因此这里修改为1。

#ifndef MAIN_HAS_NOARGC
#define MAIN_HAS_NOARGC 1  
#endif

在core_portme.h中对编译器版本和编译器参数进行定义。这2个宏不会影响跑分结果,只是coremark在打印评测结果的时候会输出定义的值。如果你只想看单片机性能,这2个宏可以不用在意。

#define COMPILER_VERSION "Armcc v5.06"

#define COMPILER_FLAGS \
   "-O3 -Otime"

实验结果

输出样例

GD32移植CoreMark实现性能测评_第2张图片

实验环境

  • CoreMark 1.0 , run on 2KB Stack Memory
  • Keil MDK V536 with ArmCC V5.06 Update 7
MCU 内核 CPU主频 编译器和编译参数 每秒迭代次数* 功耗
GD32F303CCT6 M4 120M Armcc v5.06 -O3 -Otime 279.307318 3.3V@17mA
GD32F303CCT6 M4 72M Armcc v5.06 -O3 -Otime 167.560322 3.3V@11mA
GD32F130C8T6 M3 72M Armcc v5.06 -O3 -Otime 162.737713 3.3V@23mA
GD32F130C8T6 M3 48M Armcc v5.06 -O3 -Otime 108.469954 3.3V@17mA
STM32F103ZET6 M3 72M Armcc v5.06 -O3 -Otime 134.188162 
Air32F103CBT6 M3 72M Armcc v5.06 -O3 -Otime 184.162063  3.3V@18mA
Air32F103CBT6 M3 120M Armcc v5.06 -O3 -Otime 306.983883  3.3V@23mA

注*:数值越大代表CPU运算能力越强

你可能感兴趣的:(GD32F130开发笔记,单片机,嵌入式硬件)