探索ARM CPU架构的美妙以及C语言编译器的奥秘

笔者接触嵌入式领域软件开发以来,几乎用的都是 arm cortex m 内核系列的微控制器。感谢c语言编译器的存在,让我不用接触汇编即可进行开发,但是彷佛也错过了一些风景,没有领域到编译器之美和cpu之美,所以决定周末无聊的休息时间通过寻找资料、动手实验、得出结论的方法来探索 arm cpu 架构的美妙,以及c语言编译器的奥秘。(因为我个人实在是不赞同学校中微机原理类课程的教学方法)。
arm探索之旅 01 | 带你认识arm cortex-m阵营
arm探索之旅 02 | arm cortex-m 用什么指令集?
一、浮点数的存储
浮点数按照 ieee 754 标准存储在计算机中,arm浮点环境是遵循「ieee 754-1985」标准实现的。
ieee 754 标准规定浮点数的存储格式有三个域
sign:符号位,0表示正数、1表示负数;
exponent:二进制小数的指数值编码;
fraction:二进制小数的有效值编码;
具体的编码规则过多,本文重点不在此,不再展开,感兴趣可以阅读我之前的文章:浮点数在计算机中的存储 —— ieee 754标准[1](可点击阅读原文查看)。
二、浮点支持软件库fplib
1. fplib介绍
arm cortex-m处理器中计算浮点数的方式有软件和硬件两种。
对于不带 fpu 的处理器,arm提供了一个「浮点支持软件库」用于计算浮点数:fplib。
fplib提供的 api 以__aeabi开头,比如:
__aeabi_fadd:计算两个float型浮点数(float占4个字节,32位)
__aeabi_dadd:计算两个double型浮点数(double占8个字节,64位)
__aeabi_f2d:float型转为double型
__aeabi_d2f:double型转为float型
除此之外,fplib库还提供取余、开方等非常多的浮点数操作函数,如有兴趣可以查阅文末我列出的参考文档[2]。
2. 测试代码与优化等级
编写如下测试代码:
float a = 5.625; float b = 5.625; float res_add, res_sub, res_mul, res_div; res_add = a + b; res_sub = a - b; res_mul = a * b; res_div = a / b; printf(“res_add = %f ”, res_add); printf(“res_sub = %f ”, res_sub); printf(“res_mul = %f ”, res_mul); printf(“res_div = %f ”, res_div);

使用这段测试代码,「编译器优化等级推荐设置为-o0」,否则聪明的编译器会直接将结果计算出来编译到程序中,我们就没法研究了。

3. armcc测试结果
这节我们验证是否arm使用 fplib 库来计算浮点数,在设置中关闭fpu:
使用mdk编译之后,进入调试模式查看反汇编结果。
在反汇编中可以看到,变量a是float类型,所以编译器分配了一个寄存器用于存储值:
查看0x080031c4处的值,小端存储模式下(低位在低地址),变量a的值是0x40b40000,存储方式符合ieee 754标准。
再来看看浮点数运算操作的反汇编结果,果然调用fplib库提供的函数完成浮点数的操作:这里还有一个有趣的小细节,在反汇编中可以看到「使用 %f 占位符打印浮点数时,printf是按照double型传参的」:
4. arm-none-eabi-gcc测试结果
使用stm32cubemx生成makeifle工程,修改makeifle中的等级为-o0,设置为软件浮点计算:另外还需要注意,默认gcc编译时不支持printf打印浮点数,需要在 makefile 中手动加入以下链接选项:
ldflags += -u _printf_float
编译完成之后进行反汇编(注意文件名):
arm-none-eabi-objdump -s -d build/usart1-fpu-test.elf 》 build/usart1-fpu-test.dis
同样,在反汇编文件中即可找到浮点计算代码:
三、使用 arm fpu 加速浮点计算
1. arm fpu的魅力
fpu(floating point unit,浮点单元)是arm内核中的硬件外设,用于硬件计算浮点数,要想使用fpu计算浮点数,需要程序和编译器配合。
在程序中使能/开启fpu硬件外设,「使 fpu 硬件可以正常工作」;
在编译器中设置使用fpu,编译器会将所有浮点计算的代码都编译为「使用fpu操作指令完成」。
目前cortex-m4、cortex-m7、cortex-m33、cortex-m35p、cortex-m55处理器中都具备fpu硬件。
在上一节中我们使用fplib软件库来计算浮点数,但是fplib终归还是软件方式,每个计算函数的实现都是通过很多的指令去完成计算,并且最终的程序中还会把函数链接进可执行程序,导致程序体积变大。
「arm fpu的魅力在于,浮点计算可以通过简单的fpu操作指令去完成,相比之下,不仅计算快,也不会增大程序体积。」
2. 如何使能fpu硬件
arm cortex - m4内核中将 fpu 作为协处理器设计的,所以通过设置协处理器访问控制(cpacr,co-processor access control register)来控制是否使能fpu。
复位之后cp11=0、cp10=0,默认禁止访问fpu,因为这是cortex-m内核的外设,寄存器定义cmsis-core中,所以可以直接通过下面这行代码设置cp11=1、cp10=1来允许访问fpu:
scb-》cpacr = 0x00f00000; // enable the floating point unit for full access
无论是stm32 hal库还是标准库,在systeminit()函数中已经存在使能代码,通过__fpu_present和__fpu_used来控制:
/* fpu settings ------------------------------------------------------------*/ #if (__fpu_present == 1) && (__fpu_used == 1) scb-》cpacr |= ((3ul 《《 10*2)|(3ul 《《 11*2)); /* set cp10 and cp11 full access */ #endif
并且,在头文件 stm32l431xx.h 中已经使能__fpu_present宏定义:__fpu_present宏定义是一直使能的,那么如何来控制fpu的使能呢?
别忘了还有一个宏定义__fpu_used,这是留给编译器来控制的!
3. armcc编译器如何开启fpu
mdk编译器开启fpu的方法非常简单,如图:在mdk中使能fpu,一方面编译器会设置宏定义__fpu_used == 1,不放心的话可以在任意位置添加下面的预处理代码,分别在使用/不使用的情况编译一下,查看编译器输出结果:
#if __fpu_used == 1 #error “ok!” #endif
另一方面,编译器在编译的时候,会将所有的浮点运算都编译为使用fpu操作指令去完成
4. gcc编译器如何开启fpu
在makefile中加入以下gcc编译设置项:
# fpu fpu = -mfpu=fpv4-sp-d16 # float-abi float-abi = -mfloat-abi=hard
abi是应用程序二进制接口(application binary interface),-mfloat-abi用来指定使用哪种方式:
soft:使用cpu寄存器组+软件库(fplib)完成浮点操作;
softfp:使用cpu寄存组+fpu硬件+软件库完成浮点操作;
hard:使用fpu寄存器组+fpu硬件+软件库完成浮点操作;
mfpu选项用来指定fpu架构,具体值可以阅读我在文末给出的参考文档,本文所使用的值fpv4-sp-d16,意味着仅仅使能armv7 fpv4-sp-d16 单精度浮点单元扩展。
同样,对之前的测试代码编译,查看反汇编结果,可以看到使用了浮点操作全部使用了fpu相关指令。
四、使用julia测试fpu加速性能
1. 测试准备
需要准备一份裸机工程,具有屏幕打点显示功能和串口打印功能。
参考:stm32cubemx_17 | 使用硬件spi驱动tft-lcd(st7789)。
2. 移植julia分形测试代码
julia测试是通过计算几帧julia分形的数据来测试单精度浮点运算的性能,测试代码参考正点原子,如下:
/* private user code ---------------------------------------------------------*/ /* user code begin 0 */ #define iteration 128 //迭代次数 #define real_constant 0.285f //实部常量 #define img_constant 0.01f //虚部常量 //颜色表 uint16_t 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, }; //初始化颜色表 //clut:颜色表指针 void initclut(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; } } //产生 julia 分形图形 //size_x,size_y:屏幕 x,y 方向的尺寸 //offset_x,offset_y:屏幕 x,y 方向的偏移 //zoom:缩放因子 void generatejulia_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_draw_color_point(x, y, color_map[i]); } } } /* user code end 0 */
在main函数中创建一些需要的变量:
/* user code begin 1 */ uint8_t zoom_index = 0; uint32_t start_time = 0, end_time = 0; /* user code end 1 */
调用初始化函数:
/* user code begin 2 */ printf(“julia test by mculover666 ”); lcd_init(); //初始化颜色表 initclut(color_map); /* user code end 2 */
调用测试函数:
/* infinite loop */ /* user code begin while */ while (1) { /* user code end while */ /* user code begin 3 */ start_time = hal_gettick(); generatejulia_fpu(240, 240, 120, 120, zoom_ratio[zoom_index]); end_time = hal_gettick(); printf(“diff time is %d ms ”, end_time - start_time); zoom_index++; if (zoom_index 》 sizeof(zoom_ratio)) { zoom_index = 0; } } /* user code end 3 */
3. 测试结果
使用-o2优化等级,在不开 fpu 的情况下,「显示一帧平均需要11s左右」:程序大小情况:
使用-o2优化等级,在开启 fpu 的情况下,「显示一帧平均需要4s左右」:程序大小情况:
最后放上好看的julia分形图:
原文标题:揭秘arm fpu 加速浮点计算
文章出处:【微信公众号:strongerhuang】欢迎添加关注!文章转载请注明出处。


网线中分线的作用
让智能立式广告机才重新唤起你的购物欲望
总投资4.9亿元,西安拓尔微电子产业基地项目2024年建成投用
JPEG2000编码器IP核设计的具体算法与结构分析
农药残留快速检测仪的生产厂家哪些更好?
探索ARM CPU架构的美妙以及C语言编译器的奥秘
为什么程序员找份好工作很难
iQOO手机的拆解:看看iQOO手机到底如何强悍
西门子两种新的光学近接开关用于激光传感器
苹果承认iPhone12存绿屏问题,你的也存在这样的问题吗?
生物传感:用“光”测血糖背后的巨大市场
电池会热会鼓包的原因是什么
龙芯、鲲鹏、海光等国产CPU厂商,联手打开中国芯片行业崛起之路
新技术引领智慧城市发展 品质及成本是关键
Mentor Graphics Verification Academy 新增 SystemVerilog 课程和模式库以扩展工程师的专业知识和资源
14位分辨率的模块和16位分辨率的模块的区别
低温漂低功耗的带隙基准源技术设计
苹果中国台湾供应商8月营收296亿美元 同比下滑12.3%
深圳供电局研发出可以远程为电缆“把脉”的10千伏智能电缆测控系统
取消漫游费后,移动通信服务资费对农村用户不公平