你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【原创】【安富莱——DSP教程】第32章 实数FFT的实现

[复制链接]
baiyongbin2009 发布时间:2015-4-17 10:26
特别说明:完整45期数字信号处理教程,原创高性能示波器代码全开源地址:链接
第32章 实数FFT的实现

    本章主要讲解实数的浮点和定点Q31,Q15的实现。关于这部分的知识点和函数的计算结果上,官方的文档有一些小错误,在章节中会跟大家详细讲述,还有一个要注意的问题,调用实数FFT函数一定要使用CMSIS-DSP V1.4.4及其以上版本,以前的版本有bug。
    本章节使用的复数FFT函数来自ARM官方库的TransformFunctions部分
    32.1 复数FFT
    32.2 复数FFT-基2算法
    32.3 复数FFT-基4算法
    32.4 总结


32.1 实数FFT
32.1.1 描述
    CMSIS DSP库里面包含一个专门用于计算实数序列的FFT库,很多情况下,用户只需要计算实数序列即可。计算同样点数FFT的实数序列要比计算同样点数的虚数序列有速度上的优势。
    快速的rfft算法是基于混合基cfft算法实现的。
    一个N点的实数序列FFT正变换采用下面的步骤实现:
32.1.png
    由上面的框图可以看出,实数序列的FFT是先计算N/2个实数的CFFT,然后再重塑数据进行处理从而获得半个FFT频谱即可(利用了FFT变换后频谱的对称性)。
    一个N点的实数序列FFT逆变换采用下面的步骤实现:
32.2.png
    实数FFT支持浮点,Q31和Q15三种数据类型。

32.2 实数FFT
32.2.1 arm_rfft_fast_f32
函数定义如下:
    void arm_rfft_fast_f32(
      arm_rfft_fast_instance_f32 * S,
      float32_t * p, float32_t * pOut,
      uint8_t ifftFlag)
参数定义:
     [in]  *S          points to an arm_rfft_fast_instance_f32 structure.
     [in]  *p          points to the input buffer.
     [in]  *pOut      points to the output buffer.
     [in]  ifftFlag     RFFT if flag is 0, RIFFT if flag is 1
注意事项:
结构arm_rfft_fast_instance_f32的定义如下(在文件arm_math.h文件):
      typedef struct
      {
          arm_cfft_instance_f32 Sint;      /**< Internal CFFT structure. */
          uint16_t fftLenRFFT;            /**< length of the real sequence */
      float32_t * pTwiddleRFFT;           /**< Twiddle factors real stage  */
      } arm_rfft_fast_instance_f32 ;

    下面通过在开发板上运行函数arm_rfft_fast_f32和arm_cfft_f32计算幅频响应,然后将相应的频率响应结果在Matlab上面绘制出来。
  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: arm_rfft_fast_f32_app
  4. *        功能说明: 调用函数arm_rfft_fast_f32计算1024点实数序列的幅频响应并跟使用函数arm_cfft_f32计算结果做对比
  5. *        形    参:无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void arm_rfft_fast_f32_app(void)
  10. {
  11. uint16_t i;
  12. arm_rfft_fast_instance_f32 S;
  13. /* 实数序列FFT长度 */
  14. fftSize = 1024;
  15. /* 正变换 */
  16.     ifftFlag = 0;
  17. /* 初始化结构体S中的参数 */
  18.          arm_rfft_fast_init_f32(&S, fftSize);
  19. /* 按照实部,虚部,实部,虚部..... 的顺序存储数据 */
  20. for(i=0; i<1024; i++)
  21. {
  22. /* 50Hz正弦波,采样率1KHz */
  23. testInput_f32_10khz[i] = 1.2f*arm_sin_f32(2*3.1415926f*50*i/1000)+1;
  24. }
  25. /* 1024点实序列快速FFT */
  26. arm_rfft_fast_f32(&S, testInput_f32_10khz, testOutput_f32_10khz, ifftFlag);
  27. /* 为了方便跟函数arm_cfft_f32计算的结果做对比,这里求解了1024组模值,实际函数arm_rfft_fast_f32
  28.    只求解出了512组  
  29. */
  30.          arm_cmplx_mag_f32(testOutput_f32_10khz, testOutput, fftSize);

  31. /* 串口打印求解的模值 */
  32. for(i=0; i<fftSize; i++)
  33. {
  34. printf("%f\r\n", testOutput[i]);
  35. }

  36. printf("****************************分割线***************************************\r\n");

  37. for(i=0; i<1024; i++)
  38. {
  39. /* 虚部全部置零 */
  40. testInput_f32_10khz[i*2+1] = 0;
  41. /* 50Hz正弦波,采样率1KHz ,作为实部 */
  42. testInput_f32_10khz[i*2] = 1.2f*arm_sin_f32(2*3.1415926f*50*i/1000)+1;
  43. }
  44. arm_cfft_f32(&arm_cfft_sR_f32_len1024, testInput_f32_10khz, ifftFlag, doBitReverse);
  45. /* 求解模值  */
  46.          arm_cmplx_mag_f32(testInput_f32_10khz, testOutput, fftSize);

  47. /* 串口打印求解的模值 */
  48. for(i=0; i<fftSize; i++)
  49. {
  50. printf("%f\r\n", testOutput[i]);
  51. }
  52. }
复制代码
运行如上函数可以通过串口打印出函数arm_rfft_fast_f32和arm_cfft_f32计算的幅频模值,下面通过Matlab绘制波形来对比这两种模值。
    对比前需要先将串口打印出的两组数据加载到Matlab中,arm_rfft_fast_f32的计算结果起名signal,arm_cfft_f32的计算结果起名sampledata,加载方法在前面的教程中已经讲解过,这里不做赘述了。Matlab中运行的代码如下:
Fs = 1000;              % 采样率
N  = 1024;            % 采样点数


n  = 0:N-1;            % 采样序列
f = n * Fs / N;          %真实的频率


subplot(3,1,1);
plot(f,  signal);        %绘制RFFT结果
title('实数FFT');
xlabel('时间');
ylabel('幅值');


subplot(3,1,2);
plot(f,  sampledata);   %CFFT结果
title('复数FFT');
xlabel('时间');
ylabel('幅值');


Matlab运行结果如下:
32.3.png
    从上面的前512点对比中,我们可以看出两者的计算结果是相符的。这里有一点要特别注意,官方文档中对于函数arm_rfft_fast_f32输出结果的实部,虚部排列顺序说明是错误的。函数arm_rfft_fast_f32的输出结果仍然是实部,虚部,实部,虚部….. 依次排列下去。
    函数arm_rfft_fast_f32在计算直流分量(也就是频率为0的值)的虚部上是有错误的。关于这点大家可以将实际的实部和虚部输出结果打印出来做对比,但差别很小,基本可以忽略。

32.2.2 arm_rfft_q15
函数定义如下:
    void arm_rfft_q15(
          const arm_rfft_instance_q15 * S,
          q15_t * pSrc,
          q15_t * pDst)
参数定义:
    [in]  *S     points to an instance of the Q15 RFFT/RIFFT structure.   
    [in]  *pSrc  points to the input buffer.   
    [out] *pDst  points to the output buffer.   
    return       none.  
注意事项:
结构arm_rfft_instance_q15的定义如下(在文件arm_math.h文件):
      typedef struct
      {
      uint32_t fftLenReal;   
      uint8_t ifftFlagR;   
          uint8_t bitReverseFlagR;                 
          uint32_t twidCoefRModifier;   
          q15_t *pTwiddleAReal;                    
          q15_t *pTwiddleBReal;                     
          const arm_cfft_instance_q15 *pCfft;      
      } arm_rfft_instance_q15;

    下面通过在开发板上运行函数arm_rfft_q15和arm_cfft_f32计算幅频响应,然后将相应的频率响应结果在Matlab上面绘制出来。
  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: arm_rfft_q15_app
  4. *        功能说明: 调用函数arm_rfft_q15计算1024点实数序列的幅频响应并跟使用函数arm_cfft_f32计算的结果做对比。
  5. *        形    参:无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void arm_rfft_q15_app(void)
  10. {
  11. uint16_t i,j;
  12. arm_rfft_instance_q15 S;
  13. /* 实数序列FFT长度 */
  14. fftSize = 1024;
  15. /* 正变换 */
  16.     ifftFlag = 0;
  17. /* 码位倒序 */
  18.     doBitReverse = 1;
  19. /* 初始化结构体S */
  20.          arm_rfft_init_q15(&S, fftSize, ifftFlag, doBitReverse);
  21. /* 按照实部,虚部,实部,虚部..... 的顺序存储数据 */
  22. for(i=0; i<1024; i++)
  23. {
  24. /* 51.2Hz正弦波,采样率1024Hz。
  25.    arm_sin_q15输入参数的范围[0, 32768), 这里每20次为一个完整的正弦波,
  26.    32768 / 20 = 1638.4
  27. */
  28. j = i % 20;
  29.           testInput_q15_50hz[i] = arm_sin_q15(1638*j);
  30. }
  31. /* 1024点实序列快速FFT */
  32. arm_rfft_q15(&S, testInput_q15_50hz, testOutput_q15_50hz);
  33. /* 由于输出结果的格式是Q5,所以这里将定点数转换为浮点数 */
  34. for(i = 0; i < fftSize; i++)
  35. {
  36. testOutput_f32_10khz[i] = (float32_t)testOutput_q15_50hz[i]/32;
  37. }
  38. /* 为了方便对比,这里求解了1024组复数,实际上面的变化只有512组  
  39.    实际函数arm_rfft_q15只求解出了512组  */
  40.          arm_cmplx_mag_f32(testOutput_f32_10khz, testOutput, fftSize);

  41. /* 串口打印求解的模值 */
  42. for(i=0; i<fftSize; i++)
  43. {
  44. printf("%f\r\n", testOutput[i]);
  45. }
  46. printf("****************************分割线***************************************\r\n");
  47. for(i=0; i<1024; i++)
  48. {
  49. /* 51.2Hz正弦波,采样率1024Hz。
  50.    arm_sin_q15输入参数的范围[0, 32768), 这里每20次为一个完整的正弦波,
  51.    32768 / 20 = 1638.4
  52. */
  53. j = i % 20;
  54. testInput_f32_10khz[i*2] = (float32_t) arm_sin_q15(1638*j)/32768;
  55. /* 虚部全部置零 */
  56. testInput_f32_10khz[i*2+1] = 0;
  57. }
  58. arm_cfft_f32(&arm_cfft_sR_f32_len1024, testInput_f32_10khz, ifftFlag, doBitReverse);
  59. /* 求解模值  */
  60.          arm_cmplx_mag_f32(testInput_f32_10khz, testOutput, fftSize);
  61. /* 串口打印求解的模值 */
  62. for(i=0; i<fftSize; i++)
  63. {
  64. printf("%f\r\n", testOutput[i]);
  65. }
  66. }
复制代码
运行如上函数可以通过串口打印出函数arm_rfft_q15和arm_cfft_f32计算的幅频模值,下面通过Matlab绘制波形来对比这两种模值。
    对比前需要先将串口打印出的两组数据加载到Matlab中,arm_rfft_q15的计算结果起名signal,arm_cfft_f32的计算结果起名sampledata,加载方法在前面的教程中已经讲解过,这里不做赘述了。Matlab中运行的代码如下:
Fs = 1000;             % 采样率
N  = 1024;            % 采样点数


n  = 0:N-1;            % 采样序列
f = n * Fs / N;          %真实的频率


subplot(3,1,1);
plot(f,  signal);        %绘制RFFT结果
title('实数FFT');
xlabel('时间');
ylabel('幅值');


subplot(3,1,2);
plot(f,  sampledata);   %CFFT结果
title('复数FFT');
xlabel('时间');
ylabel('幅值');


Matlab运行结果如下:
32.4.png
    从上面的前512点对比中,我们可以看出两者的计算结果是相符的。这里有一点要特别注意,官方文档中对于函数arm_rfft_q31输出结果的实部,虚部排列顺序说明是错误的。函数arm_rfft_q31的输出结果仍然是实部,虚部,实部,虚部….. 依次排列下去。

32.2.3 arm_rfft_q31
函数定义如下:
    void arm_rfft_q31(
          const arm_rfft_instance_q31 * S,
          q31_t * pSrc,
          q31_t * pDst)
参数定义:
    [in]  *S     points to an instance of the Q31 RFFT/RIFFT structure.   
    [in]  *pSrc  points to the input buffer.   
    [out] *pDst  points to the output buffer.   
    return       none.  
注意事项:
结构arm_rfft_instance_q31的定义如下(在文件arm_math.h文件):
      typedef struct
      {
          uint32_t fftLenReal;                     
          uint8_t ifftFlagR;                          
          uint8_t bitReverseFlagR;                  
          uint32_t twidCoefRModifier;                 
           q31_t *pTwiddleAReal;                       
          q31_t *pTwiddleBReal;                       
          const arm_cfft_instance_q31 *pCfft;      
      } arm_rfft_instance_q31;

    下面通过在开发板上运行函数arm_rfft_q31和arm_cfft_f32计算幅频响应,然后将相应的频率响应结果在Matlab上面绘制出来。
  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: arm_rfft_q31_app
  4. *        功能说明: 调用函数arm_rfft_q31计算1024点实数序列的幅频响应并跟使用函数arm_cfft_f32计算的结果做对比。
  5. *        形    参:无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void arm_rfft_q31_app(void)
  10. {
  11. uint16_t i,j;
  12. arm_rfft_instance_q31 S;
  13. /* 实数序列FFT长度 */
  14. fftSize = 1024;
  15. /* 正变换 */
  16.     ifftFlag = 0;
  17. /* 码位倒序 */
  18.     doBitReverse = 1;
  19. /* 初始化结构体S */
  20.          arm_rfft_init_q31(&S, fftSize, ifftFlag, doBitReverse);
  21. /* 按照实部,虚部,实部,虚部..... 的顺序存储数据 */
  22. for(i=0; i<1024; i++)
  23. {
  24. /* 51.2Hz正弦波,采样率1024Hz。
  25.    arm_sin_q31输入参数的范围0-2^31, 这里每20次为一个完整的正弦波,
  26.    2^31 / 20 = 107374182.4
  27. */
  28. j = i % 20;
  29.           testInput_q31_50hz[i] = arm_sin_q31(107374182*j);
  30. }
  31. /* 1024点实序列快速FFT */
  32. arm_rfft_q31(&S, testInput_q31_50hz, testOutput_q31_50hz);
  33. /* 由于输出结果的格式是Q21,所以这里将定点数转换为浮点数 */
  34. for(i = 0; i < fftSize; i++)
  35. {
  36. /* 输出的数据是11.21格式,2^21 = 4194304*/
  37. testOutput_f32_10khz[i] = (float32_t)testOutput_q31_50hz[i]/2097152;
  38. }
  39. /* 为了方便对比,这里求解了1024组复数,实际上面的变化只有512组  
  40.    实际函数arm_rfft_q31只求解出了512组  */
  41.          arm_cmplx_mag_f32(testOutput_f32_10khz, testOutput, fftSize);
  42. /* 串口打印求解的模值 */
  43. for(i=0; i<fftSize; i++)
  44. {
  45. printf("%f\r\n", testOutput[i]);
  46. }
  47. printf("****************************分割线***************************************\r\n");
  48. for(i=0; i<1024; i++)
  49. {
  50. /* 51.2Hz正弦波,采样率1024Hz。
  51.    arm_sin_q31输入参数的范围0-2^31, 这里每20次为一个完整的正弦波,
  52.    2^31 / 20 = 107374182.4
  53. */
  54. j = i % 20;
  55.           testInput_f32_10khz[i*2] = (float32_t)arm_sin_q31(107374182*j)/2147483648;
  56. /* 虚部全部置零 */
  57. testInput_f32_10khz[i*2+1] = 0;
  58. }
  59. arm_cfft_f32(&arm_cfft_sR_f32_len1024, testInput_f32_10khz, ifftFlag, doBitReverse);
  60. /* 求解模值  */
  61.          arm_cmplx_mag_f32(testInput_f32_10khz, testOutput, fftSize);
  62. /* 串口打印求解的模值 */
  63. for(i=0; i<fftSize; i++)
  64. {
  65. printf("%f\r\n", testOutput[i]);
  66. }
  67. }
复制代码
运行如上函数可以通过串口打印出函数arm_rfft_q15和arm_cfft_f32计算的幅频模值,下面通过Matlab绘制波形来对比这两种模值。
    对比前需要先将串口打印出的两组数据加载到Matlab中,arm_rfft_q15的计算结果起名signal,arm_cfft_f32的计算结果起名sampledata,加载方法在前面的教程中已经讲解过,这里不做赘述了。Matlab中运行的代码如下:
Fs = 1000;             % 采样率
N  = 1024;            % 采样点数


n  = 0:N-1;            % 采样序列
f = n * Fs / N;          %真实的频率


subplot(3,1,1);
plot(f,  signal);        %绘制RFFT结果
title('实数FFT');
xlabel('时间');
ylabel('幅值');


subplot(3,1,2);
plot(f,  sampledata);   %CFFT结果
title('复数FFT');
xlabel('时间');
ylabel('幅值');


Matlab运行结果如下:
32.5.png
    从上面的前512点对比中,我们可以看出两者的计算结果是相符的。这里有一点要特别注意,官方文档中对于函数arm_rfft_q31输出结果的实部,虚部排列顺序说明是错误的。函数arm_rfft_q31的输出结果仍然是实部,虚部,实部,虚部….. 依次排列下去。

32.3 总结
    使用实数FFT计算的时候要特别的注意本章节提到的几个错误点。

收藏 2 评论12 发布时间:2015-4-17 10:26

举报

12个回答
a003 回答时间:2019-7-23 17:45:54
楼主你好,请问怎么看出来的信号的频率以及幅值的啊?
stary666 回答时间:2015-4-17 10:30:27
沙发,楼主对算法很有研究,赞一个
baiyongbin2009 回答时间:2015-4-17 11:38:23
stary666 发表于 2015-4-17 10:30
沙发,楼主对算法很有研究,赞一个

谢谢!希望继续支持啊~~
wamcncn 回答时间:2015-4-17 13:55:04
算法在程序里很重要
小蚂蚁快溜跑 回答时间:2015-4-17 21:10:28
支持。。。。。。。
aderson 回答时间:2015-4-17 21:32:55
好高端,看不懂,
拼命三郎 回答时间:2015-4-17 22:27:23
xxxxxxxxxx.jpg
拼命三郎 回答时间:2015-4-17 22:27:45
dd.jpg
拼命三郎 回答时间:2015-4-17 22:38:37
stm32.jpg
jack-2054789 回答时间:2018-7-30 15:30:20
谢谢分享!!
lewe 回答时间:2019-8-19 09:49:09
请教一下:我这样计算出来的值是不是对的?为什么每个频率上都有值啊?
fft2.png
程序是这样子:
用ADC采集值填入fft输入buff
void ADC_proc(void)
{
    uint16_t ai,cnt;
       
    if(adc_conv_done)
    {
       adc_conv_done = 0;


            for(ai=0;ai<NPT;ai++)
                {
                      lbufin[ai*2] = (float)(adc_buf[ai*2]-2048);
                          lbufin[ai*2+1] = (float)0;
                }
          FFT_proc();      
         HAL_ADC_Start_DMA(&hadc,(uint32_t*)adc_buf,sizeof(adc_buf)/2);
    }
}



FFT处理



float  lbufin[NPT*2];                                                           /* Complex input vector */
float  lbufout[NPT];                                                          /* Complex output vector */
float  lbufmag;                                                                  /* Magnitude vector */
uint16_t fftSize = 64;


uint8_t ifftFlag = 0;
uint8_t doBitReverse = 1;
uint16_t audio_mag;
extern  uint8_t  audio_intf_flag;
//uint32_t refIndex = 213,
uint32_t testIndex = 0;
__IO uint8_t  new_mag_flag;


void FFT_proc()
{                      
    arm_cfft_f32(&arm_cfft_sR_f32_len64, lbufin, ifftFlag, doBitReverse);
        arm_cmplx_mag_f32(lbufin,lbufout,fftSize);
    arm_max_f32(lbufout, NPT, &lbufmag, &testIndex);
}  

adc.png
fft1.png
whyil 回答时间:2019-8-20 16:17:55

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版