首页 > 其他分享 >《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验

时间:2022-10-14 12:32:32浏览次数:56  
标签:硬件 MiniPRO Julia FPU uint16 分形 num

第五十二章 FPU测试(Julia分形)实验​


本章我们将学习如何开启STM32H750的硬件FPU,并对比使用硬件FPU和不使用硬件FPU的速度差别,以体现硬件FPU的优势。​

本章分为如下几个小节:​

52.1 FPU&Julia分形简介 ​

52.2 硬件设计​

52.3 程序设计​

52.4 下载验证​



52.1 FPU&Julia分形简介

本节将分别介绍STM32H750的FPU和Julia分形。​

52.1.1 FPU简介

FPU即浮点运算单元(Float Point Unit)。浮点运算,对于定点CPU(没有FPU的CPU)来说必须要按照IEEE-754标准的算法来完成运算,是相当耗费时间的。而对于有FPU的CPU来说,浮点运算则只是几条指令的事情,速度相当快。​

STM32H750属于Cortex M7架构,带有32位双精度硬件FPU,支持浮点指令集,相对于Cortex M0和Cortex M3等,高出数十倍甚至上百倍的运算性能。​

STM32H750硬件上要开启FPU是很简单的,通过一个叫:协处理器控制寄存器(CPACR)的寄存器设置即可开启STM32H750的硬件FPU,该寄存器各位描述如图52.1.1.1所示:​

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_分形


图2.1.1.1 协处理器控制寄存器(CPACR)各位描述​

这里我们就是要设置CP11和CP10这4个位,复位后,这4个位的值都为0,此时禁止访问协处理器(禁止了硬件FPU),我们将这4个位都设置为1,即可完全访问协处理器(开启硬件FPU),此时便可以使用STM32H750内置的硬件FPU了。CPACR寄存器这4个位的设置,我们在system_stm32h7xx_c文件里面开启,代码如下:​

voidSystemInit (void)​
{​
/* 省略部分代码 */​
/* FPU settings ------------------------------------------------------------*/​
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)​
SCB->CPACR |= ((3UL << (10*2))|(3UL << (11*2)));​
/* set CP10 and CP11 Full Access */​
#endif​
/* 省略部分代码 */​
}

此部分代码是系统初始化函数的部分内容,功能就是设置CPACR寄存器的20~23位为1,以开启STM32H7的硬件FPU功能。从程序可以看出,只要我们定义了全局宏定义标识符__FPU_PRESENT以及__FPU_USED为1,那么就可以开启硬件FPU。其中宏定义标识符__FPU_PRESENT用来确定处理器是否带FPU功能,标识符__FPU_USED用来确定是否开启FPU功能。​

实际上,因为H7是带FPU功能的,所以在我们的stm32H750xx.h头文件里面,我们默认是定义了__FPU_PRESENT为1。大家可以打开文件搜索即可找到下面一行代码:​

#define __FPU_PRESENT 1 /* FPU present */

但是,仅仅只是说明处理器有FPU功能是不够的,我们还需要开启FPU功能。开启FPU有两种方法,第一种是直接在头文件STM32H750xx.h中定义宏定义标识符__FPU_USED的值为1。也可以直接在MDK编译器上面设置,我们在MDK5编译器里面,点击

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_初始化_02

按钮,然后在Target选项卡里面,设置Not Used为Use Double Precision,如图52.1.1.2所示:​

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_初始化_03


图2.1.1.2 编译器开启硬件FPU选型​

经过这个设置,编译器会自动加入标识符__FPU_USED为1。这样遇到浮点运算就会使用硬件FPU相关指令,执行浮点运算,从而大大减少计算时间。​

最后,总结下STM32H7硬件FPU使用的要点:​

  1. 设置CPACR寄存器bit20~23为1,使能硬件FPU(参考SystemInit函数开头部分)。​
  2. MDK编译器Target选项卡中Not Used选项设置为:Use Double Precision。​

经过这两步设置,我们的编写的浮点运算代码,即可使用STM32H7的硬件FPU了,可以大大加快浮点运算速度。 ​

52.1.2分形简介

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 + cxn+1 = 2.xn.yn + cy

一旦计算出的复值超出给定圆的范围(数值大小大于圆半径),序列便会发散,达到此限值时完成的迭代次数与该点相关。随后将该值转换为颜色,以图形方式显示复平面上各个点的分散速度。​

经过给定的迭代次数后,若产生的复值保持在圆范围内,则计算过程停止,并且序列也不发散,本例程生成Julia分形图片的代码如下:​

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

/**​
产生Julia分形图形​
* @param size_x : 屏幕x方向的尺寸​
* @param size_y : 屏幕y方向的尺寸​
* @param offset_x : 屏幕x方向的偏移​
* @param offset_y : 屏幕y方向的偏移​
* @param zoom : 缩放因子​
无​
*/​
void julia_generate_fpu(uint16_t size_x, uint16_t size_y, uint16_t offset_x, ​
uint16_t offset_y, uint16_t zoom)​
{​
uint8_t i;​
uint16_t x, y;​
float tmp1, tmp2;​
float num_real, num_img;​
float radius;​

for (y = 0; y < size_y; y++)​
{​
for (x = 0; x < size_x; x++)​
{​
num_real = y - offset_y;​
num_real = num_real / zoom;​
num_img = x - offset_x;​
num_img = num_img / zoom;​
i = 0;​
radius = 0;​

while ((i < ITERATION - 1) && (radius < 4))​
{​
tmp1 = num_real * num_real;​
tmp2 = num_img * num_img;​
num_img = 2 * num_real * num_img + IMG_CONSTANT;​
num_real = tmp1 - tmp2 + REAL_CONSTANT;​
radius = tmp1 + tmp2;​
i++;​
}​
LCD->LCD_RAM = g_color_map[i]; /* 绘制到屏幕 */​
}​
}​
}

这种算法非常有效地展示了 FPU 的优势:无需修改代码,只需在编译阶段激活或禁止 FPU(在MDK Code Generation的Float Point Hardware选项里面设置:Double Precision /Not Used),即可测试使用硬件FPU和不使用硬件FPU的差距。 ​

注意,是该函数将颜色数据填充到LCD的时候,根据MCU屏还是RGB屏,做了不同的处理:MCU屏可以直接写LCD_RAM,将颜色显示到LCD上面;而RGB屏,则需要先缓存到lcdbuf,然后通过DMA2D一次性填充,以提高速度。本实验不支持RGB屏,所以RGB屏的情况忽略即可。​

52.2 硬件设计

1. 例程功能

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

2、LED0闪烁,提示程序运行。​

2. 硬件资源

1)RGB灯​

RED :LED0 - PB4 ​

2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)​

3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)​

4)独立按键 :KEY0 - PA1、KEY1 - PA15、WK_UP - PA0​

5)FPU(浮点计算单元)​

6)定时器6​

52.3 程序设计

52.3.1 程序流程图

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_初始化_04


图52.3.1.1 FPU测试(Julia分形)实验程序流程图​

52.3.2 程序解析

本实验例程,分成两个工程:​

1,实验40_1 FPU测试(Julia分形)实验_开启硬件FPU​

2,实验40_2测试(Julia分形)实验_关闭硬件FPU​

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

1. TIMER驱动代码

这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。由于要统计帧时间,所以我们需要用到TIMER驱动代码的btim.c和btim.h文件。​

btim.h文件不需要做任何修改,只要在btim.c文件中,修改定时器更新中断回调函数即可,具体修改后的代码如下:​

extern uint8_t g_timeout ; /* 在main.c里面定义 */​

/**​
定时器更新中断回调函数​
定时器句柄指针​
无​
*/​
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)​
{​
if (htim == (&g_timx_handle))​
{​
g_timeout++;​
//LED1_TOGGLE();​
}​
}

在定时器更新中断回调函数中,我们只是需要让g_timeout自加就行,最后把g_timeout变量的值*定时器更新中断的周期就可以知道帧时间了。当然在程序上,我们还要声明g_timeout变量。​

2. main.c代码

下面是main.c的程序,具体如下:​

/* FPU模式提示 */​
#if __FPU_USED==1​
#define SCORE_FPU_MODE "FPU On"​
#else​
#define SCORE_FPU_MODE "FPU Off"​
#endif​

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

/* 颜色表 */​
uint16_t g_color_map[ITERATION];​

/* 缩放因子列表 */​
const uint16_t zoom_ratio[] =​
{​
120, 110, 100, 150, 200, 275, 350, 450,​
600, 800, 1000, 1200, 1500, 2000, 1500,​
1200, 1000, 800, 600, 450, 350, 275, 200,​
150, 100, 110,​
};​

/**​
初始化颜色表​
颜色表指针​
无​
*/​
void julia_clut_init(uint16_t *clut)​
{​
uint32_t i = 0x00;​
uint16_t red = 0, green = 0, blue = 0;​
for (i = 0; i < ITERATION; i++) /* 产生颜色表 */​
{​
/* 产生RGB颜色值 */​
red = (i * 8 * 256 / ITERATION) % 256;​
green = (i * 6 * 256 / ITERATION) % 256;​
blue = (i * 4 * 256 / ITERATION) % 256;​

/* 将RGB888,转换为RGB565 */​
red = red >> 3;​
red = red << 11;​
green = green >> 2;​
green = green << 5;​
blue = blue >> 3;​
clut[i] = red + green + blue;​
}​
}​

/* RGB LCD 缓存*/​
uint16_t g_lcdbuf[800];​

/**​
产生Julia分形图形​
* @param size_x : 屏幕x方向的尺寸​
* @param size_y : 屏幕y方向的尺寸​
* @param offset_x : 屏幕x方向的偏移​
* @param offset_y : 屏幕y方向的偏移​
* @param zoom : 缩放因子​
无​
*/​
void julia_generate_fpu(uint16_t size_x, uint16_t size_y, uint16_t offset_x, uint16_t offset_y, uint16_t zoom)​
{​
uint8_t i;​
uint16_t x, y;​
float tmp1, tmp2;​
float num_real, num_img;​
float radius;​
for (y = 0; y < size_y; y++)​
{​
for (x = 0; x < size_x; x++)​
{​
num_real = y - offset_y;​
num_real = num_real / zoom;​
num_img = x - offset_x;​
num_img = num_img / zoom;​
i = 0;​
radius = 0;​
while ((i < ITERATION - 1) && (radius < 4))​
{​
tmp1 = num_real * num_real;​
tmp2 = num_img * num_img;​
num_img = 2 * num_real * num_img + IMG_CONSTANT;​
num_real = tmp1 - tmp2 + REAL_CONSTANT;​
radius = tmp1 + tmp2;​
i++;​
}​
LCD->LCD_RAM = g_color_map[i]; /* 绘制到屏幕 */​
}​
}​
}​

uint8_t g_timeout;​

int main(void)​
{​
uint8_t key;​
uint8_t i = 0;​
uint8_t autorun = 0;​
float time;​
char buf[50];​

sys_cache_enable(); /* 打开L1-Cache */​
HAL_Init(); /* 初始化HAL库 */​
sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */​
delay_init(480); /* 延时初始化 */​
usart_init(115200); /* 串口初始化为115200 */​
mpu_memory_protection(); /* 保护相关存储区域 */​
led_init(); /* 初始化LED */​
key_init(); /* 初始化按键 */​
lcd_init(); /* 初始化LCD */​
btim_timx_int_init(65535, 24000 - 1); /* 10Khz计数频率,最大计时6.5秒超出 */​
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);​
lcd_show_string(30, 70, 200, 16, 16, "FPU TEST", RED);​
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);​
lcd_show_string(30, 110, 200, 16, 16, "KEY0:+ KEY1:-", RED);/*显示提示信息*/​
lcd_show_string(30, 130, 200, 16, 16, "KEY_UP:AUTO/MANUL", RED); ​
delay_ms(1200);​
julia_clut_init(g_color_map); /* 初始化颜色表 */​
while (1)​
{​
key = key_scan(0);​
switch (key)​
{​
case KEY0_PRES:​
i++;​
if (i > sizeof(zoom_ratio) / 2 - 1)i = 0; /* 限制范围 */​
break;​
case KEY1_PRES:​
if (i)i--;​
else i = sizeof(zoom_ratio) / 2 - 1;​
break;​
case WKUP_PRES:​
autorun = !autorun; /* 自动/手动 */​
break;​
}​
if (autorun == 1) /* 自动时,自动设置缩放因子 */​
{​
i++;​
if (i > sizeof(zoom_ratio) / 2 - 1)​
{​
i = 0; /* 限制范围 */​
}​
}​
lcd_set_window(0, 0, lcddev.width, lcddev.height); /* 设置窗口 */​
lcd_write_ram_prepare();​
BTIM_TIMX_INT->CNT = 0; /* 重设TIM6定时器的计数器值 */​
g_timeout = 0;​
julia_generate_fpu(lcddev.width, lcddev.height, lcddev.width / 2, ​
lcddev.height / 2, zoom_ratio[i]);​
time = BTIM_TIMX_INT->CNT + (uint32_t)g_timeout * 65536;​
sprintf(buf, "%s: zoom:%d runtime:%0.1fms\r\n", SCORE_FPU_MODE, ​
zoom_ratio[i], time / 10);​
lcd_show_string(5, lcddev.height - 5 - 12, lcddev.width - 5,​
12, 12, buf, RED); /* 显示当前运行情况 */​
printf("%s", buf); /* 输出到串口 */​
LED0_TOGGLE();​
}​
}

这部分程序,总共3个函数:julia_clut_init、julia_generate_fpu和main函数。​

julia_clut_init函数,该函数用于初始化颜色表,该函数根据迭代次数(ITERATION)计算出颜色表,这些颜色值将显示在TFTLCD上。 ​

GenerateJulia_fpu函数,该函数根据给定的条件计算Julia分形集,当迭代次数大于等于ITERATION或者半径大于等于4时,结束迭代,并在TFTLCD上面显示迭代次数对应的颜色值,从而得到漂亮的Julia分形图。我们可以通过修改REAL_CONSTANT和IMG_CONSTANT这两个常量的值来得到不同的Julia分形图。​

main函数,完成我们在52.2节所介绍的实验功能,代码比较简单。这里我们用到一个缩放因子表:zoom_ratio,里面存储了一些不同的缩放因子,方便演示效果。​

为了提高速度,我们还可以在MDK里面选择使用-O2优化,优化代码速度,本例程代码就介绍到这里。​

再次提醒大家:本例程两个代码(实验40_1和实验40_12)程序是完全一模一样的,他们的区别就是MDKàOptions for Target ‘Target1’àTarget选项卡àFloating Point Hardware的设置不一样,当设置Double Precision时,使用硬件FPU;当设置Not Used时,不使用硬件FPU。分别下载这两个代码,通过屏幕显示的runtime时间,即可看出速度上的区别。​

52.4 下载验证

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

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_sed_05


图52.4.1 Julia分形显示效果​

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

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_初始化_06


图52.4.2分形显示效果(开启硬件FPU)​

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

《MiniPRO H750开发指南》第五十二章 FPU测试(Julia分形)实验_初始化_07


图52.4.3分形显示效果(关闭硬件FPU)​

对比图52.4.2和图52.4.3知道,关闭硬件FPU会比开启硬件FPU,同等情况下慢16倍左右,这充分体现了STM32H750硬件FPU的优势。​


标签:硬件,MiniPRO,Julia,FPU,uint16,分形,num
From: https://blog.51cto.com/u_15046463/5756241

相关文章