yang377156216 发表于 2021-4-19 23:32

【HC32F460开发板测评】NO.3 用OLED显示benchmark指标

<p><span style="font-size:20px;">【前言】</span></p>

<p>上篇主要针对&nbsp;HC32F460 该颗芯片的开发流程和一些试用过的通用外设做了简单记录,此篇按照计划,会评测该M4内核芯片的benchmark跑分情况&nbsp;。由于之前已经在GD32上面移植过了常用的coremark跑分,也清楚了该跑分小程序主要受哪些因素影响,所以这里换一个比较古老的方法去跑分,那就是&nbsp;<strong>Dhrystone。&nbsp;</strong></p>

<p>Dhrystone 是 1984 年由 Reinhold P. Weicker 提出的通用处理器 (CPU) 性能测试标准,最早用 ADA 实现,随后 Rick Richardson 把它翻译为 C 语言,并很快成为了业界标准。不过 Dhdrystone 只测试整型运算,并不包含浮点运算,因此无法用它来判断 FPU 的性能。最新的源码可以在这里:<a href="https://www.netlib.org/benchmark/dhry-c" target="_blank">https://www.netlib.org/benchmark/dhry-c</a> 找到,虽说是最新,其实也有几十年历史了。</p>

<p>HC32F460 据介绍说是 ARMv7架构32bit&nbsp;&nbsp;M4 cpu,集成FPU,MPU,支持SIMD指令的DSP,及CoreSight标准调试单元。最高工作主频168Mhz,FLASH加速单元是先0-wait程序执行。达到210DMIPS或者485Coremarks的运算性能。这个DMIPS的含义表示:&nbsp;Dhrystone标准的测试方法很简单,就是单位时间内跑了多少次Dhrystone程序,其指标单位为DMIPS/MHz。MIPS是Million Instructions Per Second的缩写,每秒处理的百万级的机器语言指令数。DMIPS中的D是Dhrystone的缩写,它表示了在Dhrystone标准的测试方法下的MIPS。</p>

<p><strong>参考资料<br />
Dhrystone 软件包:</strong></p>

<p><strong>https://github.com/wuhanstudio/dhrystone</strong></p>

<p><strong>Dhrystone 测试结果汇总:</strong></p>

<p><strong>http://performance.netlib.org/performance/html/dhrystone.data.col0.html</strong></p>

<p><strong>Dhrystone 历史源码:</strong></p>

<p><strong>https://github.com/Keith-S-Thompson/dhrystone</strong><br />
<span style="font-size:20px;">【<strong>Dhrystone移植&nbsp;</strong>】</span></p>

<p>Dhrystone是测量处理器运算能力的最常见<a data-lemmaid="1200074" href="https://baike.baidu.com/item/%E5%9F%BA%E5%87%86%E7%A8%8B%E5%BA%8F/1200074" target="_blank">基准程序</a>之一,常用于处理器的整型运算性能的测量。程序是用C语言编写的,因此C编译器的编译效率对测试结果也有很大影响。</p>

<p>Dhrystone的计量单位为每秒计算多少次Dhrystone,后来把在VAX-11/780机器上的测试结果1757 Dhrystones/s定义为1 Dhrystone MIPS(百万条指令每秒)。</p>

<p>但其也有许多不足,Dhrystone不仅不适合于作为嵌入式系统的测试向量,甚至在其大多数场合下都不适合进行应用。Dhrystone还有许多漏洞,例如:易被非法利用、人为痕迹明显、代码长度太短、缺乏验证及标准的运行规则等。</p>

<p>核心程序包下载后,在\\classic_benchmarks\\source_code\\dhrystone2\\下可找到源代码。详细文件目录如下:</p>

<ol>
        <li><code>\\classic_benchmarks\\source_code\\dhrystone2</code></li>
        <li><code>\\dhry.h --关于兼容性的原型定义</code></li>
        <li><code>\\dhry_1.c --主程序入口</code></li>
        <li><code>\\dhry_2.c --算法子程序</code></li>
</ol>

<p>移植到ARM Cortex-M平台下裸系统运行,一般只需要简单修改dhry.h和dhry_1.c文件即可,Dhrystone本身并没有太多移植工作,其源码本是用作在PC上运行的,而在嵌入式系统里运行仅需要把一些文件I/O的相关代码删除即可,此外就是<strong>计时函数和打印函数的重实现</strong>。Dhrystone源码几乎没有提供配置选项,唯一一个能算得上的配置就是关于REG的宏定义,即你所选用的IDE和嵌入式平台是否支持regiser关键字。在dhry_1.c中的main入口函数如下:</p>

<pre>
<code class="language-cpp">void main (int argc, char *argv[])
{
          One_Fifty   Int_1_Loc;
    REG   One_Fifty   Int_2_Loc;
          One_Fifty   Int_3_Loc;
    REG   char      Ch_Index;
          Enumeration Enum_Loc;
          Str_30      Str_1_Loc;
          Str_30      Str_2_Loc;
    REG   int         Run_Index;
    REG   int         Number_Of_Runs;
          int         endit, count = 10;
    // ...
    // 定义和初始化关键buffer
    Next_Ptr_Glob = (Rec_Pointer) malloc (sizeof (Rec_Type));
    Ptr_Glob = (Rec_Pointer) malloc (sizeof (Rec_Type));
    Ptr_Glob-&gt;Ptr_Comp                  = Next_Ptr_Glob;
    // ...
    // 设置循环跑Dhrystone核心算法程序次数
    Number_Of_Runs = 5000;
    do
    {
      Number_Of_Runs = Number_Of_Runs * 2;
      count = count - 1;
      // 开始循环跑Dhrystone核心算法程序且记录累计消耗时间
      start_time();
      for (Run_Index = 1; Run_Index &lt;= Number_Of_Runs; ++Run_Index)
      {
            Proc_5();
            Proc_4();
            // ...
      }
      end_time();
      User_Time = secs;
      printf (\"%12.0f runs %6.2f seconds \\n\",(double) Number_Of_Runs, User_Time);
      if (User_Time &gt; 2)
      {
            count = 0;
      }
      else
      {
            if (User_Time &lt; 0.05)
            {
                Number_Of_Runs = Number_Of_Runs * 5;
            }
      }
    }
    while (count &gt;0);
    // ...
    // 最终信息的打印
    if (User_Time &lt; Too_Small_Time)
    {
      printf (\"Measured time too small to obtain meaningful results\\n\");
      printf (\"Please increase number of runs\\n\");
      printf (\"\\n\");
    }
    else
    {
      Microseconds = User_Time * Mic_secs_Per_Second / (double) Number_Of_Runs;
      Dhrystones_Per_Second = (double) Number_Of_Runs / User_Time;
      Vax_Mips = Dhrystones_Per_Second / 1757.0;
      printf (\"Microseconds for one run through Dhrystone: \");
      printf (\"%12.2lf \\n\", Microseconds);
      printf (\"Dhrystones per Second:                      \");
      printf (\"%10.0lf \\n\", Dhrystones_Per_Second);
      printf (\"VAXMIPS rating =                        \");
      printf (\"%12.2lf \\n\",Vax_Mips);
      printf (\"\\n\");
    }
    // ...
}</code></pre>

<p>以上的计时器可以是由 SysTick来驱动,也可以是做成同步触发的两个timer来共同完成时间计数功能 。printf打印函数就是把串口3的重定向做好即可,所有移植完成后即可打印出实际的DMIPS数据,这里卖个关子,将实测的打印数据先不贴出来,因为我想将其显示在 0.91寸的OLED屏幕上,由于出厂时候我的屏幕就不显示,所以一直怀疑是屏幕坏了,得实际测试评估。</p>

<p><span style="font-size:20px;">【<strong>OLED驱动及显示</strong>】</span></p>

<h4>板载<a href="http://jmd12864.cn.b2b168.com/" title="深圳市金马鼎电子有限公司">深圳市金马鼎电子有限公司</a>的0.91寸OLED,白光,分辨率为128*32,内部有SSD1306控制器,采用I2C进行控制,全屏点亮时功耗0.08W 。</h4>

<p>主要的宏定义如下:</p>

<pre>
<code class="language-cpp">#define OLED_CMD      0   //写命令
#define OLED_DATA       1   //写数据
#define OLED_MODE       0
#define IIC_NO_ACK      1
#define IIC_ACK             0

#define SIZE                16
#define Max_Column128
#define Max_Row         32
#define X_WIDTH         128
#define Y_WIDTH         32

#define IIC_SCL_PORT    PortD
#define IIC_SCL_PINS    Pin00

#define IIC_SDA_PORT    PortD
#define IIC_SDA_PINS    Pin01


#define OLED_SCL_Set()PORT_SetBits(IIC_SCL_PORT,IIC_SCL_PINS)
#define OLED_SCL_Clr()PORT_ResetBits(IIC_SCL_PORT, IIC_SCL_PINS)

#define OLED_SDA_Set()PORT_SetBits(IIC_SDA_PORT, IIC_SDA_PINS)
#define OLED_SDA_Clr()PORT_ResetBits(IIC_SDA_PORT, IIC_SDA_PINS)
#define OLED_READ_SDA() PORT_GetBit(IIC_SDA_PORT, IIC_SDA_PINS)</code></pre>

<p>主要的接口函数如下:</p>

<pre>
<code class="language-cpp">void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr);
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len, unsigned char size2);
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *p);
void OLED_Set_Pos(unsigned char x, unsigned char y);
void OLED_ShowCHinese(unsigned char x, unsigned char y, unsigned char no);
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[]);
void OLED_Scroll(void);</code></pre>

<p>一些重要的实现函数如下:</p>

<pre>
<code class="language-cpp">
/**
* @函数名       Write_IIC_Command
* @功能       OLED写入命令
* @参数       IIC_Command:命令
* @返回值       无
*/
void Write_IIC_Command(unsigned char IIC_Command)
{
    IIC_Start();
    Write_IIC_Byte(0x78);//写入从机地址,SD0 = 0
    Write_IIC_Byte(0x00);//写入命令
    Write_IIC_Byte(IIC_Command);//数据
    IIC_Stop();//发送停止信号
}


/**
* @函数名       Write_IIC_Data
* @功能       OLED写入数据
* @参数       IIC_Data:数据
* @返回值       无
*/
void Write_IIC_Data(unsigned char IIC_Data)
{
    IIC_Start();
    Write_IIC_Byte(0x78);    //写入从机地址,SD0 = 0
    Write_IIC_Byte(0x40);    //写入数据
    Write_IIC_Byte(IIC_Data);//数据
    IIC_Stop();      //发送停止信号
}


/**
* @函数名       OLED_WR_Byte
* @功能       OLED写入数据/命令
* @参数       dat:数据;cmd:0-命令、1-数据
* @返回值       无
*/
void OLED_WR_Byte(unsigned char dat, unsigned char cmd)
{
    if (cmd)
    {
       Write_IIC_Data(dat); //写入数据
    }
    else
    {
       Write_IIC_Command(dat); //写入命令 Write_IIC_Command(0x78,0x00,&amp;dat,1,0x40000ul) ;
    }
}
</code></pre>

<pre>
<code class="language-cpp">/**
* @函数名       IIC_Init
* @功能       配置IIC
* @参数       无
* @返回值       无
*/
void IIC_GPIO_Init(void)
{
   stc_i2c_init_t stcI2cInit;
   stc_clk_freq_t stcClkFreq;
   en_result_t enRet;

    stc_port_init_t stcPortInit;

    /* configuration structure initialization */
    MEM_ZERO_STRUCT(stcPortInit);

    stcPortInit.enPinMode = Pin_Mode_Out;
    stcPortInit.enExInt = Disable;
    stcPortInit.enPullUp = Enable;
    stcPortInit.enPinOType = Pin_OType_Od ;//Pin_OType_Cmos ;//Pin_OType_Od ;
    /* LED0 Port/Pin initialization */
    PORT_Init(PortD, Pin00, &amp;stcPortInit);

    /* LED1 Port/Pin initialization */
    PORT_Init(PortD, Pin01, &amp;stcPortInit);


    OLED_SCL_Set(); //拉高SCL
    OLED_SDA_Set(); //拉高SDA
}</code></pre>

<pre>
<code class="language-cpp">//OLED的初始化
void OLED_Init(void)
{      
    IIC_GPIO_Init();                //初始化模拟IIC的IO口
    delay(2000);      //延迟,由于单片机上电初始化比OLED快,所以必须加上延迟,等待OLED上电初始化完成

    OLED_WR_Byte(0xAE, OLED_CMD); //关闭显示
    OLED_WR_Byte(0x2e, OLED_CMD); //关闭滚动

    OLED_WR_Byte(0x00, OLED_CMD); //设置低列地址
    OLED_WR_Byte(0x10, OLED_CMD); //设置高列地址
    OLED_WR_Byte(0x40, OLED_CMD); //设置起始行地址
    OLED_WR_Byte(0xB0, OLED_CMD); //设置页地址

    OLED_WR_Byte(0x81, OLED_CMD); // 对比度设置,可设置亮度
    OLED_WR_Byte(0xFF, OLED_CMD); //265

    OLED_WR_Byte(0xA1, OLED_CMD); //设置段(SEG)的起始映射地址;column的127地址是SEG0的地址
    OLED_WR_Byte(0xA6, OLED_CMD); //正常显示;0xa7逆显示

    OLED_WR_Byte(0xA8, OLED_CMD); //设置驱动路数
    OLED_WR_Byte(0x1F, OLED_CMD); //1/32 duty

    OLED_WR_Byte(0xC8, OLED_CMD); //重映射模式,COM~COM0扫描

    OLED_WR_Byte(0xD3, OLED_CMD); //设置显示偏移
    OLED_WR_Byte(0x00, OLED_CMD); //无偏移

    OLED_WR_Byte(0xD5, OLED_CMD); //设置震荡器分频(默认)大概370KHz
    OLED_WR_Byte(0x80, OLED_CMD);

    OLED_WR_Byte(0xD9, OLED_CMD); //设置 Pre-Charge Period(默认)
    OLED_WR_Byte(0x1F, OLED_CMD);

    OLED_WR_Byte(0xDA, OLED_CMD); //设置 com pin configuartion(默认)
    OLED_WR_Byte(0x00, OLED_CMD);

    OLED_WR_Byte(0xDB, OLED_CMD); //设置 Vcomh,可调节亮度(默认)
    OLED_WR_Byte(0x30, OLED_CMD);

    OLED_WR_Byte(0x8D, OLED_CMD); //设置OLED电荷泵
    OLED_WR_Byte(0x14, OLED_CMD); //开显示

    OLED_WR_Byte(0xA4, OLED_CMD); // Disable Entire Display On (0xa4/0xa5)
    OLED_WR_Byte(0xA6, OLED_CMD); // Disable Inverse Display On (0xa6/a7)

    OLED_WR_Byte(0xAF, OLED_CMD); //开启OLED面板显示
    OLED_Clear();      //清屏
    OLED_Set_Pos(0, 0);    //画点
}</code></pre>

<p>在取模时用到软件工具 PCtoLCD,最终显示之前跑分数据如下:</p>

<p></p>

<p><span style="font-size:20px;">【<b>后记</b>】</span></p>

<p>作为一项基准程序Dhrystone具有以下缺陷:</p>

<ul>
        <li>它的代码与具有代表性的实际程序代码并不相同。</li>
        <li>它易受编译器影响。举例来说,在Dhrystone中有大量的字符串复制语句,用来测量字符串复制的性能。然而Dhrystone中字符串的长度不变,并且均开始于自然对齐的边界,这两点便与真实的程序不同。因此一个优化性能好的编译器能够在去掉循环的情形下通过一连串字的移动替代对字符串的复制,这将会块很多,可能会高达30%。</li>
        <li>Dhrystone代码量过小,在现代CPU中,它能够被放进指令缓存中,所以它并不能严格的测量取指性能。</li>
</ul>

<p>此次测试还未解决的问题是: 硬件I2C 模式驱动OLED未正常点亮,还未找到代码哪里问题,一起打包在附件中,还请大家分析指点。<img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/congra.gif" width="48" /></p>

<p><br />
</p>

w494143467 发表于 2021-4-20 15:25

<p>厉害了,看来是大佬级别的!</p>

梦溪开物 发表于 2021-4-28 15:50

<p>请问你是怎么知道是<a href="http://jmd12864.cn.b2b168.com/">深圳市金马鼎电子有限公司</a>的OLED呀</p>

梦溪开物 发表于 2021-4-28 17:01

<p>想请教一下,下载你的程序报错,不知道是怎么回事</p>

<p></p>

tagetage 发表于 2021-4-30 11:16

<p>很用心的作品,顶一下楼主。</p>

yang377156216 发表于 2021-4-30 13:01

梦溪开物 发表于 2021-4-28 17:01
想请教一下,下载你的程序报错,不知道是怎么回事

<p>我那个工程应该放到 官方例程SDK包的 example 目录下的,替换掉之前的 template 包,我为了省事儿就没带上驱动库。</p>

yang377156216 发表于 2021-4-30 13:02

梦溪开物 发表于 2021-4-28 15:50
请问你是怎么知道是深圳市金马鼎电子有限公司的OLED呀

<p>万能的淘宝以及百度呀</p>

littleshrimp 发表于 2021-5-21 14:08

<p>小赵的电话号码被你曝光了</p>
页: [1]
查看完整版本: 【HC32F460开发板测评】NO.3 用OLED显示benchmark指标