zoukankan      html  css  js  c++  java
  • 【STM32F407的DSP教程】第30章 STM32F407复数浮点FFT(支持单精度和双精度)

    完整版教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547

    第30章       STM32F407复数浮点FFT(支持单精度和双精度)

    本章主要讲解复数浮点FTT,支持单精度和双精度。

    30.1 初学者重要提示

    30.2 复数浮点FFT 说明

    30.3 单精度函数arm_cfft_f32的使用(含幅频和相频)

    30.4 双精度函数arm_cfft_f64的使用(含幅频和相频)

    30.5 实验例程说明(MDK)

    30.6 实验例程说明(IAR)

    30.7 总结

    30.1 初学者重要提示

    1.   新版DSP库浮点FFT推荐使用混合基函数arm_cfft_f32,而基2函数arm_cfft_radix2_f32和基4函数arm_cfft_radix4_f32将废弃。ARM说明如下:
    Earlier releases of the library provided separate radix-2 and radix-4 algorithms that operated on floating-point data.  These functions are still provided but are deprecated.  The older functions are slower and less general than the new functions.
    DSP库的早期发行版提供了单独的radix-2和radix-4对浮点数据进行运算的算法。 这些功能仍然提供,但已弃用。 相比新版函数,老版的功能较慢且通用性较低

    30.2 复数浮点FFT说明

    30.2.1 功能描述

    当前复数FFT函数支持三种数据类型,分别是浮点,定点Q31和Q15。这些FFT函数有一个共同的特点,就是用于输入信号的缓冲,在转化结束后用来存储输出结果。这样做的好处是节省了RAM空间,不需要为输入和输出结果分别设置缓存。由于是复数FFT,所以输入和输出缓存要存储实部和虚部。存储顺序如下:{real[0], imag[0], real[1], imag[1],………………} ,在使用中切记不要搞错。

    30.2.2 浮点FFT

    浮点复数FFT使用了一个混合基数算法,通过多个基8与单个基2或基4算法实现。根据需要,该算法支持的长度[16,32,64,...,4096]和每个长度使用不同的旋转因子表。

    浮点复数FFT使用了标准的FFT定义,FFT正变换的输出结果会被放大fftLen倍数,计算FFT逆变换的时候会缩小到1/fftLen。这样就与教科书中的定义一致了。

    定义好的旋转因子和位反转表已经在头文件arm_const_structs.h中定义好了,调用浮点FFT函数arm_cfft_f32时,包含相应的头文件即可。比如:

    arm_cfft_f32(arm_cfft_sR_f32_len64, pSrc, 1, 1)

    上式就是计算一个64点的FFT逆变换包括位反转。数据结构arm_cfft_sR_f32_len64可以认为是常数,计算的过程中是不能修改的。同样是这种数据结构还能用于混合基的FFT正变换和逆变换。

    早期发布的浮点复数FFT函数版本包含基2和基4两种方法实现的,但是不推荐大家再使用。现在全部用arm_cfft_f32代替了。

    30.3 单精度函数arm_cfft_f32的使用(含幅频和相频)

    30.3.1 函数说明

    函数原型:

    void arm_cfft_f32(
      const arm_cfft_instance_f32 * S,
            float32_t * p1,
            uint8_t ifftFlag,
            uint8_t bitReverseFlag)

    函数描述:

    这个函数用于单精度浮点复数FFT。

    函数参数:

    1、  第1个参数是封装好的浮点FFT例化,支持的参数如下:

    •   arm_cfft_sR_f32_len16,16点FFT
    •   arm_cfft_sR_f32_len32,32点FFT
    •   arm_cfft_sR_f32_len64,64点FFT
    •   arm_cfft_sR_f32_len128,128点FFT
    •   arm_cfft_sR_f32_len256,256点FFT
    •   arm_cfft_sR_f32_len512,512点FFT
    •   arm_cfft_sR_f32_len1024,1024点FFT
    •   arm_cfft_sR_f32_len2048,2048点FFT
    •   arm_cfft_sR_f32_len4096,4096点FFT

    2、  第2个参数是复数地址,存储顺序是实部,虚部,实部,虚部,依次类推。

    3、  第3个参数用于设置正变换和逆变换,ifftFlag=0表示正变换,ifftFlag=1表示逆变换。

    4、  第4个参数用于设置输出位反转,bitReverseFlag=1表示使能,bitReverseFlag=0表示禁止。

    30.3.2 使用举例并和Matlab比较

    下面通过在开发板上运行这个函数并计算幅频相应,然后再与Matlab计算的结果做对比。

    /*
    *********************************************************************************************************
    *    函 数 名: arm_cfft_f32_app
    *    功能说明: 调用函数arm_cfft_f32计算幅频和相频
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void arm_cfft_f32_app(void)
    {
        uint16_t i;
        
        ifftFlag = 0; 
        doBitReverse = 1; 
        
        /* 按照实部,虚部,实部,虚部..... 的顺序存储数据 */
        for(i=0; i<TEST_LENGTH_SAMPLES; i++)
        {
            /* 波形是由直流分量,50Hz正弦波组成,波形采样率1024,初始相位60° */
            testInput_f32[i*2] = 1 + cos(2*3.1415926f*50*i/1024 + 3.1415926f/3);
            testInput_f32[i*2+1] = 0;
        }
        
        /* CFFT变换 */ 
        arm_cfft_f32(&arm_cfft_sR_f32_len1024, testInput_f32, ifftFlag, doBitReverse);
    
        /* 求解模值  */ 
        arm_cmplx_mag_f32(testInput_f32, testOutput_f32, TEST_LENGTH_SAMPLES);
        
    
        printf("=========================================
    ");    
        
        /* 求相频 */
        PowerPhaseRadians_f32(testInput_f32, Phase_f32, TEST_LENGTH_SAMPLES, 0.5f);
        
        /* 串口打印求解的模值 */
        for(i=0; i<TEST_LENGTH_SAMPLES; i++)
        {
            printf("%f, %f
    ", testOutput_f32[i], Phase_f32[i]);
        }    
    }

    运行函数arm_cfft_f32_app可以通过串口打印出计算的模值和相角,下面我们就通过Matlab计算的模值和相角跟arm_cfft_f32计算的做对比。

    对比前需要先将串口打印出的数据加载到Matlab中,并给这个数组起名sampledata,加载方法在前面的教程的第13章13.6小结已经讲解,这里不做赘述了。Matlab中运行的代码如下::

    Fs = 1024;               % 采样率
    N  = 1024;               % 采样点数
    n  = 0:N-1;              % 采样序列
    t  = 0:1/Fs:1-1/Fs;      % 时间序列
    f = n * Fs / N;          %真实的频率
    
    %波形是由直流分量,50Hz正弦波正弦波组成
    x = 1 + cos(2*pi*50*t + pi/3)   ;  
    y = fft(x, N);               %对原始信号做FFT变换
    Mag = abs(y);
    
    subplot(2,2,1);
    plot(f, Mag); 
    title('Matlab计算幅频响应');
    xlabel('频率');
    ylabel('赋值');
    
    subplot(2,2,2);
    realvalue = real(y);
    imagvalue = imag(y);
    plot(f, atan2(imagvalue, realvalue)*180/pi.*(Mag>=200)); 
    title('Matlab计算相频响应');
    xlabel('频率');
    ylabel('相角');
    
    subplot(2,2,3);
    plot(f, sampledata1);  %绘制STM32计算的幅频相应
    title('STM32计算幅频响应');
    xlabel('频率');
    ylabel('赋值');
    
    subplot(2,2,4);
    plot(f, sampledata2);   %绘制STM32计算的相频相应
    title('STM32计算相频响应');
    xlabel('频率');
    ylabel('相角');

    运行Matlab后的输出结果如下:

     

    从上面的对比结果中可以看出,Matlab和函数arm_cfft_f32计算的结果基本是一直的。幅频响应求出的幅值和相频响应中的求出的初始相角都是没问题的。

    30.4 双精度函数arm_cfft_f64的使用(含幅频和相频)

    30.4.1 函数说明

    函数原型:

    void arm_cfft_f64(
      const arm_cfft_instance_f64 * S,
            float64_t * p1,
            uint8_t ifftFlag,
            uint8_t bitReverseFlag)

    函数描述:

    这个函数用于双精度浮点复数FFT。

    函数参数:

    1、  第1个参数是封装好的浮点FFT例化,支持的参数如下:

    •   arm_cfft_sR_f64_len16,16点FFT
    •   arm_cfft_sR_f64_len32,32点FFT
    •   arm_cfft_sR_f64_len64,64点FFT
    •   arm_cfft_sR_f64_len128,128点FFT
    •   arm_cfft_sR_f64_len256,256点FFT
    •   arm_cfft_sR_f64_len512,512点FFT
    •   arm_cfft_sR_f64_len1024,1024点FFT
    •   arm_cfft_sR_f64_len2048,2048点FFT
    •   arm_cfft_sR_f64_len4096,4096点FFT

    2、 第2个参数是复数地址,存储顺序是实部,虚部,实部,虚部,依次类推。

    3、  第3个参数用于设置正变换和逆变换,ifftFlag=0表示正变换,ifftFlag=1表示逆变换。

    4、  第4个参数用于设置输出位反转,bitReverseFlag=1表示使能,bitReverseFlag=0表示禁止。

    30.4.2 使用举例并和Matlab比较

    下面通过在开发板上运行这个函数并计算幅频相应,然后再与Matlab计算的结果做对比。

    /*
    *********************************************************************************************************
    *    函 数 名: arm_cfft_f64_app
    *    功能说明: 调用函数arm_cfft_f64计算幅频和相频
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void arm_cfft_f64_app(void)
    {
        uint16_t i;
        float64_t lX,lY;
        
        ifftFlag = 0; 
        doBitReverse = 1; 
        
        /* 按照实部,虚部,实部,虚部..... 的顺序存储数据 */
        for(i=0; i<TEST_LENGTH_SAMPLES; i++)
        {
            /* 波形是由直流分量,50Hz正弦波组成,波形采样率1024,初始相位60° */
            testInput_f64[i*2] = 1 + cos(2*3.1415926*50*i/1024 + 3.1415926/3);
            testInput_f64[i*2+1] = 0;
        }
        
        /* CFFT变换 */ 
        arm_cfft_f64(&arm_cfft_sR_f64_len1024, testInput_f64, ifftFlag, doBitReverse);
    
        /* 求解模值  */ 
        for (i =0; i < TEST_LENGTH_SAMPLES; i++)
        {
             lX = testInput_f64[2*i];            /* 实部*/
            lY = testInput_f64[2*i+1];          /* 虚部 */  
            testOutput_f64[i] = sqrt(lX*lX+ lY*lY);   /* 求模 */
        }
        
        printf("=========================================
    ");    
        
        /* 求相频 */
        PowerPhaseRadians_f64(testInput_f64, Phase_f64, TEST_LENGTH_SAMPLES, 0.5);
        
        
        /* 串口打印求解的模值 */
        for(i=0; i<TEST_LENGTH_SAMPLES; i++)
        {
            printf("%.11f, %.11f
    ", testOutput_f64[i], Phase_f64[i]);
        }    
        
    }

    运行函数arm_cfft_f64_app可以通过串口打印出计算的模值和相角,下面我们就通过Matlab计算的模值和相角跟arm_cfft_f64计算的做对比。

    对比前需要先将串口打印出的数据加载到Matlab中,并给这个数组起名sampledata,加载方法在前面的教程的第13章13.6小结已经讲解,这里不做赘述了。Matlab中运行的代码如下::

    Fs = 1024;               % 采样率
    N  = 1024;               % 采样点数
    n  = 0:N-1;              % 采样序列
    t  = 0:1/Fs:1-1/Fs;      % 时间序列
    f = n * Fs / N;          %真实的频率
    
    %波形是由直流分量,50Hz正弦波正弦波组成
    x = 1 + cos(2*pi*50*t + pi/3)   ;  
    y = fft(x, N);               %对原始信号做FFT变换
    Mag = abs(y);
    
    subplot(2,2,1);
    plot(f, Mag); 
    title('Matlab计算幅频响应');
    xlabel('频率');
    ylabel('赋值');
    
    subplot(2,2,2);
    realvalue = real(y);
    imagvalue = imag(y);
    plot(f, atan2(imagvalue, realvalue)*180/pi.*(Mag>=200)); 
    title('Matlab计算相频响应');
    xlabel('频率');
    ylabel('相角');
    
    subplot(2,2,3);
    plot(f, sampledata1);  %绘制STM32计算的幅频相应
    title('STM32计算幅频响应');
    xlabel('频率');
    ylabel('赋值');
    
    subplot(2,2,4);
    plot(f, sampledata2);   %绘制STM32计算的相频相应
    title('STM32计算相频响应');
    xlabel('频率');
    ylabel('相角');

    运行Matlab后的输出结果如下:

     

    从上面的对比结果中可以看出,Matlab和函数arm_cfft_f64计算的结果基本是一直的。幅频响应求出的幅值和相频响应中的求出的初始相角都是没问题的。

    30.5 实验例程说明(MDK)

    配套例子:

    V5-220_复数浮点FTT(支持单精度和双精度)

    实验目的:

    1. 学习复数浮点FFT,支持单精度浮点和双精度浮点

    实验内容:

    1. 启动一个自动重装软件定时器,每100ms翻转一次LED2。
    2. 按下按键K1,串口打印1024点复数单精度FFT的幅频响应和相频响应。
    3. 按下按键K2,串口打印1024点复数双精度FFT的幅频响应和相频响应。

    使用AC6注意事项

    特别注意附件章节C的问题

    上电后串口打印的信息:

    波特率 115200,数据位 8,奇偶校验位无,停止位 1。

     

    RTT方式打印信息:

     

    程序设计:

      系统栈大小分配:

     

      硬件外设初始化

    硬件外设的初始化是在 bsp.c 文件实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        /* 
           STM32F407 HAL 库初始化,此时系统用的还是F407自带的16MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到168MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V5开发板用户手册第8章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
        
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitTimer();      /* 初始化滴答定时器 */
        bsp_InitUart();    /* 初始化串口 */
        bsp_InitExtIO();   /* 初始化扩展IO */
        bsp_InitLed();        /* 初始化LED */        
    }

      主功能:

    主程序实现如下操作:

    •   启动一个自动重装软件定时器,每100ms翻转一次LED2。
    •   按下按键K1,串口打印1024点复数单精度FFT的幅频响应和相频响应。
    •   按下按键K2,串口打印1024点复数双精度FFT的幅频响应和相频响应。
    /*
    *********************************************************************************************************
    *    函 数 名: main
    *    功能说明: c程序入口
    *    形    参: 无
    *    返 回 值: 错误代码(无需处理)
    *********************************************************************************************************
    */
    int main(void)
    {
        uint8_t ucKeyCode;        /* 按键代码 */
        
    
        bsp_Init();        /* 硬件初始化 */
        PrintfLogo();    /* 打印例程信息到串口1 */
    
        PrintfHelp();    /* 打印操作提示信息 */
        
    
        bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    
        /* 进入主程序循环体 */
        while (1)
        {
            bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
            
    
            if (bsp_CheckTimer(0))    /* 判断定时器超时时间 */
            {
                /* 每隔100ms 进来一次 */
                bsp_LedToggle(4);    /* 翻转LED2的状态 */   
            }
            
            ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
            if (ucKeyCode != KEY_NONE)
            {
                switch (ucKeyCode)
                {
                    case KEY_DOWN_K1:            /* K1键按下 */
                        arm_cfft_f32_app();
                        break;
                    
                    case KEY_DOWN_K2:            /* K2键按下 */
                        arm_cfft_f64_app();
                        break;
                    
                        
                    default:
                        /* 其它的键值不处理 */
                        break;
                }
            }
    
        }
    }

    30.6 实验例程说明(IAR)

    配套例子:

    V5-220_复数浮点FTT(支持单精度和双精度)

    实验目的:

    1. 学习复数浮点FFT,支持单精度浮点和双精度浮点

    实验内容:

    1. 启动一个自动重装软件定时器,每100ms翻转一次LED2。
    2. 按下按键K1,串口打印1024点复数单精度FFT的幅频响应和相频响应。
    3. 按下按键K2,串口打印1024点复数双精度FFT的幅频响应和相频响应。

    上电后串口打印的信息:

    波特率 115200,数据位 8,奇偶校验位无,停止位 1。

     

    RTT方式打印信息:

     

    程序设计:

      系统栈大小分配:

     

      硬件外设初始化

    硬件外设的初始化是在 bsp.c 文件实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        /* 
           STM32F407 HAL 库初始化,此时系统用的还是F407自带的16MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到168MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V5开发板用户手册第8章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
        
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitTimer();      /* 初始化滴答定时器 */
        bsp_InitUart();    /* 初始化串口 */
        bsp_InitExtIO();   /* 初始化扩展IO */
        bsp_InitLed();        /* 初始化LED */        
    }

      主功能:

    主程序实现如下操作:

    •   启动一个自动重装软件定时器,每100ms翻转一次LED2。
    •  按下按键K1,串口打印1024点复数单精度FFT的幅频响应和相频响应。
    •   按下按键K2,串口打印1024点复数双精度FFT的幅频响应和相频响应。
    /*
    *********************************************************************************************************
    *    函 数 名: main
    *    功能说明: c程序入口
    *    形    参: 无
    *    返 回 值: 错误代码(无需处理)
    *********************************************************************************************************
    */
    int main(void)
    {
        uint8_t ucKeyCode;        /* 按键代码 */
        
    
        bsp_Init();        /* 硬件初始化 */
        PrintfLogo();    /* 打印例程信息到串口1 */
    
        PrintfHelp();    /* 打印操作提示信息 */
        
    
        bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    
        /* 进入主程序循环体 */
        while (1)
        {
            bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
            
    
            if (bsp_CheckTimer(0))    /* 判断定时器超时时间 */
            {
                /* 每隔100ms 进来一次 */
                bsp_LedToggle(4);    /* 翻转LED2的状态 */   
            }
            
            ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
            if (ucKeyCode != KEY_NONE)
            {
                switch (ucKeyCode)
                {
                    case KEY_DOWN_K1:            /* K1键按下 */
                        arm_cfft_f32_app();
                        break;
                    
                    case KEY_DOWN_K2:            /* K2键按下 */
                        arm_cfft_f64_app();
                        break;
                    
                        
                    default:
                        /* 其它的键值不处理 */
                        break;
                }
            }
    
        }
    }

    30.7 总结

    本章节设计到FFT实现,有兴趣的可以深入了解源码的实现。

    微信公众号:armfly_com 安富莱论坛:www.armbbs.cn 安富莱淘宝:https://armfly.taobao.com
  • 相关阅读:
    ADF中遍历VO中的行数据(Iterator)
    程序中实现两个DataTable的Left Join效果(修改了,网上第二个DataTable为空,所处的异常)
    ArcGIS api for javascript——鼠标悬停时显示信息窗口
    ArcGIS api for javascript——查询,然后单击显示信息窗口
    ArcGIS api for javascript——查询,立刻打开信息窗口
    ArcGIS api for javascript——显示多个查询结果
    ArcGIS api for javascript——用图表显示查询结果
    ArcGIS api for javascript——查询没有地图的数据
    ArcGIS api for javascript——用第二个服务的范围设置地图范围
    ArcGIS api for javascript——显示地图属性
  • 原文地址:https://www.cnblogs.com/armfly/p/14875670.html
Copyright © 2011-2022 走看看