赞
踩
最近,由于项目需要,学习了一下F4的FPU浮点数运算单元的使用方法和DSP库函数的建立使用,在此简单总结记录。
项目背景分析:
目标与流程梳理:
下面基于STM32F446RET6,使用标准库函数(版本:V1.8.0),在Keil MDK 5.26环境下,完成上述需求。
FPU 即浮点运算单元(Float Point Unit)。浮点运算,对于定点 CPU(没有 FPU 的 CPU)来说必须要按照 IEEE-754 标准的算法来完成运算,是相当耗费时间的。而对于有 FPU 的 CPU来说,浮点运算则只是几条指令的事情,速度相当快。
STM32F4 属于 Cortex M4F 架构,带有 32 位单精度硬件 FPU,支持浮点指令集,相对于Cortex M0 和 Cortex M3 等,高出数十倍甚至上百倍的运算性能。
打开system_stm32f4xx.c文件,在SystemInit函数中,已规定了FPU的启用条件,即486行所示:
前者是要检查MCU是否拥有FPU,后者使能FPU,二者需同时满足。
打开stm32f4xx.h头文件,如下图第190行所示,文件已经在编译时将__FPU_PRESENT置为1,意思是该MCU存在FPU,那么下面只需关注如何将__FPU_USED置1即可。
使能FPU的方法有很多,对于Keil MDK环境,最方便的方法是在Code Generation中将Floating Point Hardware配置为Single Precision,如下图所示:
经过这个设置,编译器会自动加入标识符__FPU_USED为 1。 这样,遇到浮点运算就会使用硬件FPU相关指令,执行浮点运算,从而大大减少计算时间。
在调试窗口,可以看到对于进行浮点运算的指令,反汇编代码中存在xxx.F32的指令,说明FPU已开启,如下图所示。
需要注意,当运算中有浮点的数字时,数字后面要加上一个f,例如:
a = (float)Para*9.1E-02f;
若不加f,Keil会提示warning: #1035-D: single-precision operand implicitly converted to double-precision,意思是单精度运算隐式转换成了双精度运算了,运算速度将无法显著提升。
对于复杂运算,例如RMS计算,如果编程时还是使用math.h头文件,效率提升很有限,因为math.h头文件是针对所有ARM处理器的,其运算函数都是基于定点CPU和标准算法(IEEE-754),没有使用FPU。因此,要充分发挥M4F的浮点功能,需要使用固件库自带的arm_math.h,即搭建DSP运行环境。
STM32F4的DSP库源码和测试实例在ST提供的标准库:stm32f4_dsp_stdperiph_lib.zip里提供,官方下载链接:
http://www.st.com/web/en/catalog/tools/FM147/CL1794/SC961/SS1743/PF257901
这里使用V1.8.0版本的标准函数库,下载完成后打开DSP_Lib源码包的Source文件夹,里面存放了所有DSP库的源码, 如上图所示。Examples文件夹是相对应的一些测试实例。以下是Source源码文件夹下面的子文件夹包含的DSP库的功能:
BasicMathFunctions
基本数学函数:提供浮点数的各种基本运算函数,如加减乘等运算。
CommonTables
arm_common_tables.c文件提供位翻转或相关参数表。
ComplexMathFunctions
复杂数学功能,如向量处理,求模运算的。
ControllerFunctions
控制功能函数。包括正弦余弦,PID电机控制,矢量Clarke变换,矢量Clarke逆变换等。
FastMathFunctions
快速数学功能函数。提供了一种快速的近似正弦,余弦和平方根等相比CMSIS计算库要快的数学函数。
FilteringFunctions
滤波函数功能,主要为FIR和LMS等滤波函数。
MatrixFunctions
矩阵处理函数。包括矩阵加法 、矩阵初始化 、矩阵反、矩阵乘法、矩阵规模、矩阵减法、矩阵转置等函数。
StatisticsFunctions
统计功能函数。 如求平均值、最大值 、最小值、计算均方根RMS、计算方差/标准差等。
SupportFunctions
支持功能函数,如数据拷贝,Q格式和浮点格式相互转换,Q任意格式相互转换。
TransformFunctions
变换功能。包括复数FFT(CFFT)/复数FFT逆运算(CIFFT)、实数FFT(RFFT)/实数
FFT逆运算(RIFFT)、和DCT(离散余弦变换)和配套的初始化函数。
所有这些DSP库代码合在一起是比较多的,因此,ST提供了.lib格式的文件,方便使用。这些.lib文件就是由 Source文件夹下的源码编译生成的,如果想看某个函数的源码,可在Source文件夹下面查找。
我们需要根据所用MCU内核类型以及端模式来选择符合要求的 .lib文件,而STM32F4属于CortexM4F内核,小端模式,应选择: arm_cortexM4lf_math.lib(浮点Cortex M4,小端模式)。
在工程目录下新建文件夹DSP,放置arm_cortexM4lf_math.lib和相关头文件,过程如图所示:
Include文件夹直接拷贝STM32F4xx标准外设库_V1.8.0\Libraries\CMSIS\Include中的所有文件。
打开工程,添加分组,将arm_cortexM4lf_math.lib添加到工程里面,如图:
添加好.lib文件后,要添加头文件包含路径,将第一步拷贝的Include文件夹和DSP文件夹,加入头文件包含路径,如图:
最后,为了使用DSP库的所有功能,还需要添加几个全局宏定义:
ARM_MATH_CM4
__CC_ARM
ARM_MATH_MATRIX_CHECK
ARM_MATH_ROUNDING
对于STM32F446RET6,需要添加的所有宏为:
USE_STDPERIPH_DRIVER,STM32F446xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING
注意两个宏之间用,隔开,如下图所示:
至此,DSP库运行环境搭建完成。
对于电压信号来说,RMS是其方均根值,即有效值。与平均值的概念不同,RMS直接反映信号的能量特征,对于连续的模拟信号而言,其定义计算式如下:
而对于经过ADC采样后的数字信号而言,其计算式可表示为:
Result = sqrt(((pSrc[0] * pSrc[0] + pSrc[1] * pSrc[1] + ... + pSrc[blockSize-1] * pSrc[blockSize-1]) / blockSize));
其中,Result为RMS计算结果,pSrc[]为采样后的数字量,blockSize为处理的数据量。
打开StatisticsFunctions文件夹中的arm_rms_f32.c文件,可见DSP库对于RMS计算的函数定义,如下图所示:
因此,要使用arm_rms_f32函数,需要向其传递三个参数:
程序编写的整体思路是:
另外,若要实现实时信号RMS计算处理,则要每存一个数据,计算一次RMS值,即不断更新数组中的元素,这一需求可通过操作数组指针完成,当指针从数组首地址移动到尾地址时,将指针归位到首地址,思路上类似于数据结构中的循环队列。
为提高程序运行效率,数组元素的赋值采用指针操作,这样往往比下标的赋值方式更快。
根据上述思路,编写程序如下:
PUMP.c
#include "PUMP.h"
#include "ads1256.h"
#include <stdio.h>
float DataInput[DATA_LENGTH] = {0}; //传感器电压值存储数组、RMS计算输入数据存储数组初始化
float *InputPOINT = DataInput; //输入数据数组指针初始化
float DataOutput; //计算后的RMS值
float *OutputPOINT = &DataOutput; //计算结果变量指针
float *ifPoint = &DataInput[DATA_LENGTH - 1] + 1; //判断指针,定义于输入数组的最后一个元素+1的地址处
int blockSize = 0; //每次RMS计算的数据量
int NPA_DData; //差压传感器ADC数字值
float NPA_VData; //差压传感器ADC电压值
void Flow_RMS(void)
{
NPA_DData = Ads1256ReadData(ADS1256_MUXP_AIN1|ADS1256_MUXN_AINCOM); //ADC采集数据
NPA_VData = (float)NPA_DData/8388607.0f*5.0f; //转换为电压值
*InputPOINT = NPA_VData; //把数据存入数组,指针指向数组中的某个元素地址
InputPOINT ++; //地址+1,以存放下一个数据
if(blockSize != DATA_LENGTH) //若DataInput数组中的元素数未满,则使RMS计算的数据量+1,与当前数据量保持一致;反之则维持blockSize = DATA_LENGTH不变
blockSize ++;
if(InputPOINT == ifPoint) //若输入数组的指针移动到数组的最后一个元素+1的地址处,则使指针重新归位
InputPOINT = DataInput;
arm_rms_f32(DataInput, blockSize, OutputPOINT); //对采集的数据进行RMS计算
printf("%f, %f\r\n", DataOutput, NPA_VData); //打印RMS计算结果和实时信号值
}
PUMP.h
#ifndef __PUMP_H
#define __PUMP_H
#include <stm32f4xx.h>
#include "arm_math.h"
#define DATA_LENGTH 250 //数组长度
void Flow_RMS(void);
#endif
TIM3中断服务函数,4ms-250Hz
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM3中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIM3的中断待处理位
Flow_RMS();
}
}
使用微型气泵为差压传感器提供气压差源,配置不同占空比控制气泵抽气速率。确定条件下,使用VOFA+上位机观察输出,结果如下:
可见,虽然输入为一系列不规则波形(与气泵引动的气流有关),但经RMS计算,其均方根值基本稳定。由此可见,该方法非常适合于对不规则信号的直流化的应用中,如噪声计算等,此时应考虑硬件RMS计算方案和软件哪种更合适。
后续程序可根据实际应用,加以完善。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。