OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 60|回复: 0

《STM32H7R7开发指南 V1.1 》第六十三章 FPU测试(Julia分形)实验

[复制链接]

1352

主题

1368

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5772
金钱
5772
注册时间
2019-5-8
在线时间
1575 小时
发表于 11 小时前 | 显示全部楼层 |阅读模式
第六十三章 FPU测试(Julia分形)实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

本章我们将学习如何开启STM32H7R7的硬件FPU,并对比使用硬件FPU和不使用硬件FPU的速度差别,以体现硬件FPU的优势。
本章分为如下几个小节:
63.1 FPU&Julia分形简介
63.2 硬件设计
63.3 程序设计
63.4 下载验证


63.1 FPU&Julia分形简介
本节将分别介绍STM32H7R7的FPU和Julia分形。

63.1.1 FPU简介
FPU即浮点运算单元(Float Point Unit)。浮点运算,对于定点CPU(没有FPU的CPU)来说必须要按照IEEE-754标准的算法来完成运算,是相当耗费时间的。而对于有FPU的CPU来说,浮点运算则只是几条指令的事情,速度相当快。
STM32H7R7属于Cortex M7架构,带有32位双精度硬件FPU,支持浮点指令集,相对于Cortex M0和Cortex M3等,高出数十倍甚至上百倍的运算性能。
STM32H7R7硬件上要开启FPU是很简单的,通过一个叫:协处理器控制寄存器(CPACR)的寄存器设置即可开启STM32H7R7的硬件FPU,该寄存器各位描述如图63.1.1.1所示:


第六十三章 FPU测试518.png
图 63.1.1.1 协处理器控制寄存器(CPACR)各位描述

这里我们就是要设置CP11和CP10这4个位,复位后,这4个位的值都为0,此时禁止访问协处理器(禁止了硬件FPU),我们将这4个位都设置为1,即可完全访问协处理器(开启硬件FPU),此时便可以使用STM32H7R7内置的硬件FPU了。CPACR寄存器这4个位的设置,我们在system_stm32h7rsxx_c文件里面开启,代码如下:
  1. void SystemInit(void)
  2. {
  3.   /* Configure the Vector Table location ------------------------------*/
  4.   SCB->VTOR = INTVECT_START;

  5.   /* FPU settings -----------------------------------------------------*/
  6. #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  7.   SCB->CPACR |= ((3UL << 20U)|(3UL << 22U));  
  8. /* set CP10 and CP11 Full Access */
  9. #endif
  10. }
复制代码
此部分代码是系统初始化函数的部分内容,功能就是设置CPACR寄存器的20~23位为1,以开启STM32H7R7的硬件FPU功能。从程序可以看出,只要我们定义了全局宏定义标识符__FPU_PRESENT以及__FPU_USED为1,那么就可以开启硬件FPU。其中宏定义标识符__FPU_PRESENT用来确定处理器是否带FPU功能,标识符__FPU_USED用来确定是否开启FPU功能。
实际上,因为H7R7是带FPU功能的,所以在我们的stm32h7r7xx.h头文件里面,我们默认是定义了__FPU_PRESENT为1。大家可以打开文件搜索即可找到下面一行代码:

  1. #define __FPU_PRESENT             1       /* FPU present */
复制代码
但是,仅仅只是说明处理器有FPU功能是不够的,我们还需要开启FPU功能。开启FPU有两种方法,第一种是直接在头文件stm32h7r7xx.h中定义宏定义标识符__FPU_USED的值为1。也可以直接在MDK编译器上面设置,我们在MDK5编译器里面,点击 第六十三章 FPU测试1530.png 按钮,然后在Target选项卡里面,设置Not Used为Use Double Precision,如图63.1.1.2所示:

第六十三章 FPU测试1595.png
图 63.1.1.2 编译器开启硬件FPU选型

经过这个设置,编译器会自动加入标识符__FPU_USED为1。这样遇到浮点运算就会使用硬件FPU相关指令,执行浮点运算,从而大大减少计算时间。
最后,总结下STM32H7R7硬件FPU使用的要点:
1,设置CPACR寄存器bit20~23为1,使能硬件FPU(参考SystemInit函数开头部分)。
2,MDK编译器Target选项卡中Floating Point Hardware选项设置为:Use Double Precision。
经过这两步设置,我们的编写的浮点运算代码,即可使用STM32H7R7的硬件FPU了,可以大大加快浮点运算速度。
这里需要注意:当不使用硬件FPU的时候,需要在分散加载文件将ffltui.o、fepilogue.o、fdiv.o、fadd.o、fmul.o、ffixui.o等文件放在内部ROM,否则会产生错误。


63.1.2 Julia分形简介
Julia分形即Julia集,它最早由法国数学家Gaston Julia发现,因此命名为Julia(朱利亚)集。Julia集合的生成算法非常简单:对于复平面的每个点,我们计算一个定义序列的发散速度。该序列的 Julia 集计算公式为:
zn+1 = zn2 + c
针对复平面的每个 x + i.y 点,我们用 c = cx + i.cy 计算该序列:
xn+1 + i.yn+1 = xn2 - yn2 + 2.i.xn.yn + cx + i.cy
xn+1 = xn2 - yn2 + cx 且 yn+1 = 2.xn.yn + cy
一旦计算出的复值超出给定圆的范围(数值大小大于圆半径),序列便会发散,达到此限值时完成的迭代次数与该点相关。随后将该值转换为颜色,以图形方式显示复平面上各个点的分散速度。
经过给定的迭代次数后,若产生的复值保持在圆范围内,则计算过程停止,并且序列也不发散,本例程生成Julia分形图片的代码如下:

  1. #define     ITERATION           128                  /* 迭代次数 */
  2. #define     REAL_CONSTANT       0.285f              /* 实部常量 */
  3. #define     IMG_CONSTANT        0.01f               /* 虚部常量 */

  4. /**
  5. * @brief        产生Julia分形图形
  6. * [url=home.php?mod=space&uid=271674]@param[/url]       size_x           : 屏幕x方向的尺寸
  7. * @param       size_y           : 屏幕y方向的尺寸
  8. * @param       offset_x        : 屏幕x方向的偏移
  9. * @param       offset_y        : 屏幕y方向的偏移
  10. * @param       zoom             : 缩放因子
  11. * @retval      无
  12. */
  13. void julia_generate_fpu(uint16_t size_x, uint16_t size_y, uint16_t offset_x,
  14.                         uint16_t offset_y, uint16_t zoom)
  15. {
  16.     uint8_t i;
  17.     uint16_t x, y;
  18.     float tmp1, tmp2;
  19.     float num_real, num_img;
  20.     float radius;

  21.     for (y = 0; y < size_y; y++)
  22.     {
  23.         for (x = 0; x < size_x; x++)
  24.         {
  25.             num_real = y - offset_y;
  26.             num_real = num_real / zoom;
  27.             num_img = x - offset_x;
  28.             num_img = num_img / zoom;
  29.             i = 0;
  30.             radius = 0;

  31.             while ((i < ITERATION - 1) && (radius < 4))
  32.             {
  33.                 tmp1 = num_real * num_real;
  34.                 tmp2 = num_img * num_img;
  35.                 num_img = 2 * num_real * num_img + IMG_CONSTANT;
  36.                 num_real = tmp1 - tmp2 + REAL_CONSTANT;
  37.                 radius = tmp1 + tmp2;
  38.                 i++;
  39.             }
  40.             if (lcdltdc.pwidth != 0)
  41.             {
  42.                 g_lcdbuf[lcddev.width - x - 1] = g_color_map[i];
  43.                 /* 保存颜色值到lcdbuf */
  44.             }
  45.             else
  46.             {
  47.                 LCD->LCD_RAM = g_color_map[i];             /* 绘制到屏幕 */
  48.             }
  49.         }
  50.         if (lcdltdc.pwidth != 0)
  51.         {
  52.             ltdc_color_fill(0, y, lcddev.width - 1, y, g_lcdbuf);
  53.             /* DM2D填充 */
  54.         }
  55.     }
  56. }
复制代码
这种算法非常有效地展示了 FPU 的优势:无需修改代码,只需在编译阶段激活或禁止 FPU(在MDK Code Generation的Float Point Hardware选项里面设置:Double Precision /Not Used),即可测试使用硬件FPU和不使用硬件FPU的差距。
注意,是该函数将颜色数据填充到LCD的时候,根据MCU屏还是RGB屏,做了不同的处理:MCU屏可以直接写LCD_RAM,将颜色显示到LCD上面;而RGB屏,则需要先缓存到lcdbuf,然后通过DMA2D一次性填充,以提高速度。


63.2 硬件设计

1. 例程功能
1、实验开机后,根据迭代次数生成颜色表(RGB565),然后计算Julia分形,并显示到LCD上面。同时,程序开启了定时器6,用于统计一帧所要的时间(ms),在一帧Julia分形图片显示完成后,程序会显示运行时间、当前是否使用FPU和缩放因子(zoom)等信息,方便观察对比。KEY0/KEY1用于调节缩放因子,KEY_UP用于设置自动缩放,还是手动缩放。
2、LED0闪烁,提示程序运行。

2. 硬件资源
1)LED灯
       LED0 :LED0 – PD14
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)独立按键 :KEY0 – PE9、KEY1 – PE8、WK_UP – PC13
5)FPU(浮点计算单元)
6)定时器6


63.3 程序设计

63.3.1 程序解析
本实验例程,分成两个工程:
1,实验52_1 FPU测试(Julia分形)实验_开启硬件FPU
2,实验52_2 FPU测试(Julia分形)实验_关闭硬件FPU
这两个工程的代码一模一样,只是前者使用硬件FPU计算Julia分形集(MDK设置Double Precision),后者使用IEEE-754标准计算Julia分形集(MDK设置Not Used)。由于两个工程代码一模一样,我们这里仅介绍其中一个:实验52_1 FPU测试(Julia分形)实验_开启硬件FPU。

1. TIMER驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。由于要统计帧时间,所以我们需要用到TIMER驱动代码的btim.c和btim.h文件。
btim.h文件不需要做任何修改,只要在btim.c文件中,修改定时器超时中断回调函数即可,具体修改后的代码如下:

  1. /**
  2. * @brief   HAL库基本定时器超时中断回调函数
  3. * @param   无
  4. * @retval  无
  5. */
  6. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  7. {
  8.     if (htim->Instance == BTIM_TIMX_INT)
  9.     {
  10.         LED1_TOGGLE();
  11.     }
  12. }
复制代码
在定时器超时中断回调函数中,我们只是需要让LED1的状态翻转就行。

2. main.c代码
下面是main.c的程序,具体如下:

  1. /* FPU模式提示 */
  2. #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
  3. #define FPU_MODE        "FPU ON"
  4. #else
  5. #define FPU_MODE        "FPU OFF"
  6. #endif

  7. #define ITERATION       128     /* 迭代次数 */
  8. #define REAL_CONSTANT   0.285f  /* 实部常量 */
  9. #define IMG_CONSTANT    0.01f   /* 虚部常量 */

  10. /* 颜色表 */
  11. uint16_t g_color_map[ITERATION];

  12. /* 缩放因子列表 */
  13. static const uint16_t zoom_ratio[] =
  14. {
  15.     120, 110, 100, 150, 200, 275, 350, 450,
  16.     600, 800, 1000, 1200, 1500, 2000, 1500,
  17.     1200, 1000, 800, 600, 450, 350, 275, 200,
  18.     150, 100, 110,
  19. };

  20. uint8_t g_timeout;

  21. extern TIM_HandleTypeDef g_timx_handle;

  22. uint16_t g_lcdbuf[800];

  23. /**
  24. * @brief   初始化颜色表
  25. * @param   clut: 颜色表指针
  26. * @retval  无
  27. */
  28. static void julia_clut_init(uint16_t *clut)
  29. {
  30.     uint32_t i;
  31.     uint16_t red;
  32.     uint16_t green;
  33.     uint16_t blue;
  34.    
  35.     for (i=0; i<ITERATION; i++)
  36.     {
  37.         /* 产生RGB颜色值 */
  38.         red = (i * 8 * 256 / ITERATION) % 256;
  39.         green = (i * 6 * 256 / ITERATION) % 256;
  40.         blue = (i * 4 * 256 / ITERATION) % 256;
  41.         
  42.         /* 将RGB888转换为RGB565 */
  43.         red = red >> 3;
  44.         red = red << 11;
  45.         green = green >> 2;
  46.         green = green << 5;
  47.         blue = blue >> 3;
  48.         clut[i] = red + green + blue;
  49.     }
  50. }

  51. /**
  52. * @brief   产生Julia分形图形
  53. * @param   size_x: 屏幕X方向的尺寸
  54. * @param   size_y: 屏幕Y方向的尺寸
  55. * @param   offset_x: 屏幕X方向的偏移
  56. * @param   offset_y: 屏幕Y方向的偏移
  57. * @param   zoom: 缩放因子
  58. * @retval  无
  59. */
  60. static void julia_generate_fpu(uint16_t size_x, uint16_t size_y, uint16_t offset_x, uint16_t offset_y, uint16_t zoom)
  61. {
  62.     uint8_t i;
  63.     uint16_t x;
  64.     uint16_t y;
  65.     float tmp1;
  66.     float tmp2;
  67.     float num_real;
  68.     float num_img;
  69.     float radius;
  70.    
  71.     for (y=0; y<size_y; y++)
  72.     {
  73.         for (x=0; x<size_x; x++)
  74.         {
  75.             num_real = y - offset_y;
  76.             num_real = num_real / zoom;
  77.             num_img = x - offset_x;
  78.             num_img = num_img / zoom;
  79.             i = 0;
  80.             radius = 0;
  81.             
  82.             while ((i < ITERATION - 1) && (radius < 4))
  83.             {
  84.                 tmp1 = num_real * num_real;
  85.                 tmp2 = num_img * num_img;
  86.                 num_img = 2 * num_real * num_img + IMG_CONSTANT;
  87.                 num_real = tmp1 - tmp2 + REAL_CONSTANT;
  88.                 radius = tmp1 + tmp2;
  89.                 i++;
  90.             }
  91.             
  92.             if (lcdltdc.pwidth != 0)
  93.             {
  94.                 g_rgblcd_buf[lcddev.width - x - 1] = g_color_map[i];
  95.             }
  96.             else
  97.             {
  98.                 LCD->LCD_RAM = g_color_map[i];
  99.             }
  100.         }
  101.         
  102.         if (lcdltdc.pwidth != 0)
  103.         {
  104.             ltdc_color_fill(0, y, lcddev.width - 1, y, g_rgblcd_buf);
  105.         }
  106.     }
  107. }

  108. int main(void)
  109. {
  110.     uint8_t key;
  111.     uint8_t zoom = 0;
  112.     uint8_t autorun = 0;
  113.     float time;
  114.     char buf[50];
  115.    
  116.     sys_mpu_config();                   /* 配置MPU */
  117.     sys_cache_enable();                 /* 使能Cache */
  118.     HAL_Init();                         /* 初始化HAL库 */
  119.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  120.     delay_init(600);                    /* 初始化延时 */
  121.     usart_init(115200);                 /* 初始化串口 */
  122.     led_init();                         /* 初始化LED */
  123.     key_init();                         /* 按键初始化 */
  124.     hyperram_init();                    /* 初始化HyperRAM */
  125.     lcd_init();                         /* 初始化LCD */
  126.     btim_timx_int_init(65535,30000 - 1);/* 10Khz计数频率,最大计时6.5秒超出 */
  127.    
  128.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  129.     lcd_show_string(30, 70, 200, 16, 16, "FPU TEST", RED);
  130.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  131. lcd_show_string(30, 110, 200, 16, 16, "KEY0:+    KEY1:-", RED);     
  132. /* 显示提示信息 */
  133. lcd_show_string(30, 130, 200, 16, 16, "KEY_UP:AUTO/MANUL", RED);   
  134. /* 显示提示信息 */
  135.     delay_ms(1200);
  136.    
  137.     julia_clut_init(g_color_map);       /* 初始化颜色表 */

  138.     while (1)
  139.     {
  140.         key = key_scan(0);

  141.         switch (key)
  142.         {
  143.             case KEY0_PRES:
  144.                 i++;

  145.                 if (i > sizeof(zoom_ratio) / 2 - 1)i = 0; /* 限制范围 */

  146.                 break;

  147.             case KEY1_PRES:
  148.                 if (i)i--;
  149.                 else i = sizeof(zoom_ratio) / 2 - 1;

  150.                 break;

  151.             case WKUP_PRES:
  152.                 autorun = !autorun; /* 自动/手动 */
  153.                 break;
  154.         }

  155.         if (autorun == 1)   /* 自动时,自动设置缩放因子 */
  156.         {
  157.             i++;

  158.             if (i > sizeof(zoom_ratio) / 2 - 1)
  159.             {
  160.                 i = 0;      /* 限制范围 */
  161.             }
  162.         }

  163.         lcd_set_window(0, 0, lcddev.width, lcddev.height);  /* 设置窗口 */
  164.         lcd_write_ram_prepare();
  165.         
  166.         BTIM_TIMX->CNT = 0;  /* 重设TIM3定时器的计数器值 */
  167.         g_timeout = 0;
  168.         
  169.         julia_generate_fpu(lcddev.width, lcddev.height, lcddev.width / 2,
  170. lcddev.height / 2, zoom_ratio[i]);
  171.         
  172.         time = BTIM_TIMX->CNT + (uint32_t)g_timeout * 65536;
  173.         
  174.         sprintf(buf, "%s: zoom:%d  runtime:%0.1fms\r\n", SCORE_FPU_MODE,
  175. zoom_ratio[i], time / 10);
  176.         lcd_show_string(5, lcddev.height - 5 - 12, lcddev.width - 5, 12, 12,     buf, RED);           /* 显示当前运行情况 */
  177.         printf("%s", buf);   /* 输出到串口 */
  178.         LED0_TOGGLE();
  179.     }
  180. }
复制代码
这部分程序,总共3个函数:julia_clut_init、julia_generate_fpu和main函数。
julia_clut_init函数,该函数用于初始化颜色表,该函数根据迭代次数(ITERATION)计算出颜色表,这些颜色值将显示在TFTLCD上。
julia_generate_fpu函数,该函数根据给定的条件计算Julia分形集,当迭代次数大于等于ITERATION或者半径大于等于4时,结束迭代,并在TFTLCD上面显示迭代次数对应的颜色值,从而得到漂亮的Julia分形图。我们可以通过修改REAL_CONSTANT和IMG_CONSTANT这两个常量的值来得到不同的Julia分形图。
main函数,完成我们在63.2节所介绍的实验功能,代码比较简单。这里我们用到一个缩放因子表:zoom_ratio,里面存储了一些不同的缩放因子,方便演示效果。
再次提醒大家:本例程两个代码(实验52_1和实验52_2)程序是完全一模一样的,他们的区别就是MDKOptions for Target ‘Target1’Target选项卡Floating Point Hardware的设置不一样,当设置Single Precision时,使用硬件FPU;当设置Not Used时,不使用硬件FPU。分别下载这两个代码,通过屏幕显示的runtime时间,即可看出速度上的区别。


63.4 下载验证
将实验52_1的程序下载到开发板后,可以看到LCD首先显示一些实验相关的信息,然后开始显示Julia分形图,并显示相关参数,如图63.4.1所示:

第六十三章 FPU测试11364.png
图63.4.1 Julia分形显示效果

实验52_1是开启了硬件FPU的,所以显示Julia分形图片速度比较快。除了LCD屏幕,还可以通过串口调试助手观察相关信息,如图63.4.2所示:

第六十三章 FPU测试11460.png
图63.4.2 Julia分形显示效果(开启硬件FPU)

下面是关闭硬件FPU的运行情况,如图63.4.3所示:

第六十三章 FPU测试11519.png
图63.4.3 Julia分形显示效果(关闭硬件FPU)

对比图63.4.2和图63.4.3知道,关闭硬件FPU会比开启硬件FPU,同等情况下慢18倍左右,这充分体现了STM32H7R7硬件FPU的优势。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-6-16 21:12

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表