【正点原子K210连载】第二十九章 快速傅里叶变换加速器实验《DNK210使用指南-SDK版》

[复制链接]
1115|1
第二十九章 快速傅里叶变换加速器实验

本章将介绍Kendryte K210的FFT模块的使用,通过傅里叶变换实验帮助读者了解FFT相关知识。通过本章的学习,读者将会对Kendryte K210内置的硬件FFT模块有初步认知和应用能力。
本章分为如下几个小节:
29.1 FFT介绍
29.2 硬件设计
29.3 程序设计
29.4 运行验证


29.1 FFT介绍
FFT即快速傅里叶变换,可以将一个时域信号变换到频域。因为有些信号在时域上是很难看出什么特征的,但是如果变换到频域之后,就很容易看出特征了这就是很多信号分析采用FFT变换的原因。另外,FFT可以将一个信号的频谱提取出来,这在频谱分析方面也是经常用的。简而言之,FFT就是将一个信号从时域变换到频域方便我们分析处理。
在实际应用中,一般的处理过程是先对一个信号在时域进行采集,比如我们通过ADC,按照一定大小采样频率F去采集信号,采集N个点,那么通过对这N个点进行FFT运算,就可以得到这个信号的频谱特性。
这里还涉及到一个采样定理的概念:在进行模拟/数字信号的转换过程中,当采样频率F大于信号中最高频率fmax2倍时(F>2*fmax),采样之后的数字信号完整地保留了原始信号中的信息,采样定理又称奈奎斯特定理举个简单的例子:比如我们正常人发声,频率范围一般在8KHz以内,那么我们要通过采样之后的数据来恢复声音,我们的采样频率必须为8KHz2倍以上,也就是必须大于16KHz才行。
模拟信号经过ADC采样之后,就变成了数字信号采样得到的数字信号,就可以做FFT变换了。N个采样点数据经过FFT之后,就可以得到N个点的FFT结果。为了方便进行FFT运算,通常N2的整数次方。
假设采样频率为F对一个信号采样,采样点数为N,那么FFT之后结果就是一个N点的复数每一个点就对应着一个频率点(以基波频率为单位递增),这个点的模值(sqrt(实部2+虚部2))就是该频点频率值下的幅度特性。具体跟原始信号的幅度有什么关系呢?假设原始信号的峰值为A,那么FFT的结果的每个点(除了第一个点直流分量之外)的模值就是AN/2而第一个点就是直流分量,它的模值就是直流分量的N倍。
这里还有个基波频率,也叫频率分辨率的概念,就是如果我们按照F的采样频率去采集一个信号,一共采集N个点,那么基波频率(频率分辨率)就是fk=F/N。这样,第n个点对应信号频率为:F*(n-1)/N;其中n1,当n=1时为直流分量。
关于FFT我们就简单介绍到这,大家可以通过网上进一步了解相关知识。
Kendryte K210内置快速傅里叶变换加速器FFT AcceleraterFFT加速器是用硬件的方式来实现 FFT 的基 2 时分运算,主要功能包括:
1. 支持多种运算长度,即支持64点、128点、256点以及512点运算
2. 支持两种运算模式,即FFT以及IFFT运算
3. 支持可配的输入数据位宽,即支持32位及64位输入
4. 支持可配的输入数据排列方式,即支持虚部、实部交替,纯实部以及实部、虚部分离三种数据排列方式
5. 支持DMA传输
Kendryte K210官方SDK提供了1个操作FFT的函数,这个函数介绍如下:
1, fft_complex_uint16_dma 函数
该函数主要用于FFT运算,该函数原型及参数描述如下所示:
void fft_complex_uint16_dma(dmac_channel_number_t dma_send_channel_num,
                            dmac_channel_number_t dma_receive_channel_num,
                            uint16_t shift,
                            fft_direction_t direction,
                            const uint64_t *input,
                            size_t point_num,
                            uint64_t *output);
/* FFT正变换或逆变换配置参数 */
typedef enum _fft_direction
{
    FFT_DIR_BACKWARD,
    FFT_DIR_FORWARD,
    FFT_DIR_MAX,
} fft_direction_t;
函数共有七个参数,第一个参数和第二个参数分别为DMA发送通道号和DMA接收通道号,第三个参数shift介绍比较复杂,官方文件的描述是FFT 模块 16 位寄存器导致数据溢出 (-32768~32767)FFT 变换有 9 层,shift 决定哪一层需要移位操作 (0x1ff 表示 9 层都做移位操作;0x03 表示第第一层与第二层做移位操作),防止溢出。如果移位了,则变换后的幅值不是正常 FFT 变换的幅值,对应关系可以参考 fft_test 测试demo 程序。包含了求解频率点、相位、幅值的示例”,第四个参数为输入FFT 正变换或是逆变换,第五个参数为输入的数据序列,第六个参数为待运算的数据点数,只能为512/256/128/64,第七个参数为输出运算后的结果。
29.2 硬件设计
29.2.1 例程功能
1.程序对一组复数分别使用硬件FFT和软件FFT进行快速傅里叶正变换和快速傅里叶逆变换的运算,并同时记录运行消耗的时间,最后将数据通过串口打印
29.2.2 硬件资源
1.USB接口
UARTHS_TX – IO5
UARTHS_RX – IO4
29.2.3 原理图
本章实验内容,主要讲解FFT模块的使用,无需关注原理图。
29.3 程序设计
29.3.1 main.c代码
本章例程均来自官方SDKDEMO,本章仅简单介绍下maic.c文件部分函数的功能,代码如下所示:
int main(void)
{
    int32_t i;
    float tempf1[3];
    fft_data_t *output_data;
    fft_data_t *input_data;
    uint16_t bit1_num = get_bit1_num(FFT_FORWARD_SHIFT);
    complex_hard_t data_hard[FFT_N] = {0};
    complex data_soft[FFT_N] = {0};
    for (i = 0; i < FFT_N; i++)
    {
        tempf1[0] = 0.3 * cosf(2 * PI * i / FFT_N + PI / 3) * 256;
        tempf1[1] = 0.1 * cosf(16 * 2 * PI * i / FFT_N - PI / 9) * 256;
        tempf1[2] = 0.5 * cosf((19 * 2 * PI * i / FFT_N) + PI / 6) * 256;
        data_hard.real = (int16_t)(tempf1[0] + tempf1[1] + tempf1[2] + 10);
        data_hard.imag = (int16_t)0;
        data_soft.real = data_hard.real;
        data_soft.imag = data_hard.imag;
    }
    for (int i = 0; i < FFT_N / 2; ++i)
    {
        input_data = (fft_data_t *)&buffer_input;
        input_data->R1 = data_hard[2 * i].real;
        input_data->I1 = data_hard[2 * i].imag;
        input_data->R2 = data_hard[2 * i + 1].real;
        input_data->I2 = data_hard[2 * i + 1].imag;
    }
    cycle[FFT_HARD][FFT_DIR_FORWARD] = read_cycle();
    fft_complex_uint16_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, FFT_FORWARD_SHIFT, FFT_DIR_FORWARD, buffer_input, FFT_N, buffer_output);
    cycle[FFT_HARD][FFT_DIR_FORWARD] = read_cycle() - cycle[FFT_HARD][FFT_DIR_FORWARD];
    cycle[FFT_SOFT][FFT_DIR_FORWARD] = read_cycle();
    fft_soft(data_soft, FFT_N);
    cycle[FFT_SOFT][FFT_DIR_FORWARD] = read_cycle() - cycle[FFT_SOFT][FFT_DIR_FORWARD];
    for (i = 0; i < FFT_N / 2; i++)
    {
        output_data = (fft_data_t*)&buffer_output;
        data_hard[2 * i].imag = output_data->I1 ;
        data_hard[2 * i].real = output_data->R1 ;
        data_hard[2 * i + 1].imag = output_data->I2 ;
        data_hard[2 * i + 1].real = output_data->R2 ;
    }
    for (i = 0; i < FFT_N; i++)
    {
        hard_power = sqrt(data_hard.real * data_hard.real + data_hard.imag * data_hard.imag) * 2;
        soft_power = sqrt(data_soft.real * data_soft.real + data_soft.imag * data_soft.imag) * 2;
    }
    printf("\n[hard fft real][soft fft real][hard fft imag][soft fft imag]\n");
    for (i = 0; i < FFT_N / 2; i++)
        printf("%3d:%7d %7d %7d %7d\n",
                i, data_hard.real, (int32_t)data_soft.real, data_hard.imag, (int32_t)data_soft.imag);
    printf("\nhard power  soft power:\n");
    printf("%3d : %f  %f\n", 0, hard_power[0] / 2 / FFT_N * (1 << bit1_num), soft_power[0] / 2 / FFT_N);
    for (i = 1; i < FFT_N / 2; i++)
        printf("%3d : %f  %f\n", i, hard_power / FFT_N * (1 << bit1_num), soft_power / FFT_N);
    printf("\nhard phase  soft phase:\n");
    for (i = 0; i < FFT_N / 2; i++)
    {
        hard_angel = atan2(data_hard.imag, data_hard.real);
        soft_angel = atan2(data_soft.imag, data_soft.real);
        printf("%3d : %f  %f\n", i, hard_angel * 180 / PI, soft_angel * 180 / PI);
    }
    for (int i = 0; i < FFT_N / 2; ++i)
    {
        input_data = (fft_data_t *)&buffer_input;
        input_data->R1 = data_hard[2 * i].real;
        input_data->I1 = data_hard[2 * i].imag;
        input_data->R2 = data_hard[2 * i + 1].real;
        input_data->I2 = data_hard[2 * i + 1].imag;
    }
    cycle[FFT_HARD][FFT_DIR_BACKWARD] = read_cycle();
    fft_complex_uint16_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, FFT_BACKWARD_SHIFT, FFT_DIR_BACKWARD, buffer_input, FFT_N, buffer_output);
    cycle[FFT_HARD][FFT_DIR_BACKWARD] = read_cycle() - cycle[FFT_HARD][FFT_DIR_BACKWARD];
    cycle[FFT_SOFT][FFT_DIR_BACKWARD] = read_cycle();
    ifft_soft(data_soft, FFT_N);
    cycle[FFT_SOFT][FFT_DIR_BACKWARD] = read_cycle() - cycle[FFT_SOFT][FFT_DIR_BACKWARD];
    for (i = 0; i < FFT_N / 2; i++)
    {
        output_data = (fft_data_t*)&buffer_output;
        data_hard[2 * i].imag = output_data->I1 ;
        data_hard[2 * i].real = output_data->R1 ;
        data_hard[2 * i + 1].imag = output_data->I2 ;
        data_hard[2 * i + 1].real = output_data->R2 ;
    }
    printf("\n[hard ifft real][soft ifft real][hard ifft imag][soft ifft imag]\n");
    for (i = 0; i < FFT_N / 2; i++)
        printf("%3d:%7d  %7d %7d %7d\n",
                i, data_hard.real, (int32_t)data_soft.real, data_hard.imag, (int32_t)data_soft.imag);
    printf("[hard fft test] [%d bytes] forward time = %ld us, backward time = %ld us\n",
            FFT_N,
            cycle[FFT_HARD][FFT_DIR_FORWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000),
            cycle[FFT_HARD][FFT_DIR_BACKWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000));
    printf("[soft fft test] [%d bytes] forward time = %ld us, backward time = %ld us\n",
            FFT_N,
            cycle[FFT_SOFT][FFT_DIR_FORWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000),
            cycle[FFT_SOFT][FFT_DIR_BACKWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000));
    while (1)
        ;
    return 0;
}
可以看到,mian函数首先通过三角函数获得一组复数,然后将获得的复数转化成快速傅里叶变换的数据结构,作为输入的数据(待计算的数据),再分别用Kendryte K210的硬件FFT和软件FFT进行快速傅里叶变换的运算,同时记录各自运行所消耗的时间,然后对输出数据进行取模操作,接着是打印复数的实部和虚部的数据、模和相位等信息 ,接下来是进行快速傅里叶变换的逆运算,也就是将刚刚计算得到的值作为输入数据进行逆变换,和上面一样,分别使用硬件FFT和软件FFT进行快速傅里叶逆变换,同时记录运行所消耗的时间,最后将数据打印出来。
29.4 运行验证
DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,我们打开“串口终端”,可以看到串口中断不断的打印数据,如下图所示:
图29.4.1 “串行终端”窗口打印输出
由此可知,Kendryte K210内置的硬件FFT(快速傅里叶变换)性能非常出色,这赋予了它在信号处理和分析应用中显著的竞争优势。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
yangjiaxu 发表于 2025-9-28 10:09 | 显示全部楼层
现在K210做的产品多吗?感觉都是创客再玩的多一些
您需要登录后才可以回帖 登录 | 注册

本版积分规则

136

主题

137

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部