FFT频率箱和PIC32

6
我正在尝试使用可用于PIC32MZ2064DAB176的FFT库来推导音频信号的频率。
我正在使用MPLAB Harmony进行配置。
为了测试,我正在使用两个正弦波,分别是1002 Hz和750 Hz。这是通过在线音调生成器工具完成的。我在一个浏览器窗口中有1002 Hz,在另一个浏览器窗口中有750 Hz。从音频输出插孔输出的信号经过直流偏置后馈送到微控制器ADC。
将信号进行1.6 V的直流偏置后,发送到12位ADC。我期望的最大电压是3 V P-P,因此我认为1.6 V的直流偏置就足够了。
信号以48 kHz的采样率进行采样,因为我需要读取高达20 kHz的频率。
FFT是1024点FFT。
我能够在频率箱的第0个索引中获得直流值。
用于从箱中获取频率值的公式是 Frequency = index * Sampling Frequency / Number of FFT Points
但是,对于任何输入频率的值,我总是在第1个和第2个频率箱中获得高幅度。据我理解,对于1002 Hz,幅度应该在频率箱的第21个索引处较高,并且对于750 Hz信号,幅度应该在约16个索引处较高。
我附上我的代码、ADC Harmony配置截图、结果截图和信号输入截图。
在代码中,用于频率箱的数组是“singleSidedFFT”
非常感谢您能够帮助我推导出正确的频率值。
    /* FFT */
#define N 1024// Also change the log2N variable below!!
#define SAMPLE_FREQ 48000
#define PI 3.14

// Section: Global Data Definitions
APP_DATA appData;

/* ADC */
long count = 0;

/* FFT */
int16c  fftCoefs[N];
int16c *fftc;
int log2N = 10; 
extern const int16c twiddleFactors[];
long int freqVector[N];
int16c sampleBuffer[N]; //initialize buffer to collect samples
long int singleSidedFFT[N];


void APP_Tasks ( void )
{
    /* Check the application's current state. */
    switch ( appData.state )
    {
        /* Application's initial state. */
        case APP_STATE_INIT:
        {
            bool appInitialized = true;

            if (appInitialized)
            {
                int i;
                fftc = &fftCoefs; /* Stores the twiddle factors */

                // zero the freqVector and singleSidedFFT
                for (i=0; i<N; i++)
                {
                    freqVector = 0;
                    singleSidedFFT = 0;
                    sampleBuffer.re = 0;
                }

                // generate frequency vector this is the x-axis of your single sided fft
                for (i=0; i<N; i++)
                {
                    freqVector = i*(SAMPLE_FREQ/2)/((N/2) - 1);
                }

                /* Calculate the twiddle factors */
                DSP_TransformFFT16_setup(fftc, log2N);
                appData.state = APP_STATE_SERVICE_TASKS;

            }
            break;
        }

        case APP_STATE_SERVICE_TASKS:
        {
            /* Trigger a conversion */
            ADCCON3bits.GSWTRG = 1;

            /* Wait the conversions to complete */
            while (ADCDSTAT1bits.ARDY2 == 0);

            if (count < N)
            {
                sampleBuffer[count].re = ADCDATA2; /* fetch the result */
                sampleBuffer[count].im = 0;
                count++;
            }
            else
            {
                appData.state = APP_STATE_COMPUTE_FREQ;
                count = 0;
            }

            break;
        }

        case APP_STATE_COMPUTE_FREQ:
        {
            APP_ComputeFreq();
            appData.state = APP_STATE_SERVICE_TASKS;
            break;
        }
    }
}


void APP_ComputeFreq(void)
{
    int i;
    int16c dout[N]; //holds computed FFT 
    int16c scratch[N];

    // load complex input data into din
    DSP_TransformFFT16(dout, sampleBuffer, fftc, scratch, log2N);

    // compute single sided fft
    for(i = 0; i < N/2; i++)
    {
        singleSidedFFT = sqrt((dout.re*dout.re) + (dout.im*dout.im));
    }

    LATAbits.LATA6 = ~LATAbits.LATA6;
}

我也尝试编写独立的FFT函数,结果是一样的。以下是代码:

void APP_ComputeFreq_2(void)
{
    int16_t k, t;
    for (k = 0; k < N; k++) 
    { 
        // For each output element
        int16_t sumreal = 0;
        int16_t sumimag = 0;

        for (t = 0; t < N; t++) 
        { 
            // For each input element
            double angle = 2 * M_PI * t * k / N;
            sumreal += sampleBuffer[t].re * cos(angle) + sampleBuffer[t].im * sin(angle);
            sumimag += -sampleBuffer[t].re * sin(angle) + sampleBuffer[t].im * cos(angle);
        }
        singleSidedFFT[k] = sqrt((sumreal * sumreal) + (sumimag * sumimag));
    }
}

MPLAB Harmony ADC 配置

ADC 频率分布图

输入信号

FFT 结果 2

谢谢。


3
如果您有一个较大的直流(DC)值,而且没有应用窗函数,那么除了在第0个频率点上会得到高幅度之外,由于谱泄漏,还会从DC一直延伸到一些低频频率点,形成一个“裙子”。如果您绘制幅度频谱图,就能更好地看清发生了什么。 - Paul R
哦,我喜欢这种问题。但是,由于具体情况,你可能会在https://electronics.stackexchange.com/上找到更好的答案。 - Andrejs Cainikovs
你肯定已经查看了所有的垃圾桶以查看整个光谱了吧?显然你正在查看错误的那些。 - Clifford
你好,感谢反馈!如果12.8kHz的频率在bin 22中,则采样率约为596ksps。这可以通过100MHz时钟来解释,但它不能是8MHz的FRC时钟。因此,请检查寄存器ADCCON3的ADCSEL<1:0>位是否为11以获取FRC?默认值00映射到PBCLK3。此外,CONCLKDIV<5:0>(ADCCON3),ADCDIV<6:0>(ADC2TIME)和计数SAMC<9:0>(ADC2TIME)中的缩放因子可以根据数据表第436页上的方程29-1重新计算采样因子。 - francis
2
已解决。混淆是由于PIC数据手册引起的。在PIC32MZDA系列uC数据手册中,ADCSEL<1:0>中FRC的设置为0x11。然而,在uc的ADC特定数据手册(DS60001344B)中,FRC的设置为0x01。将值设置为0x11时,采样频率为625 kHz。将值设置为0x01时,采样率符合要求,即48 kHz。奇怪的是,MPLAB Harmony配置器正在使用家族数据手册中提到的寄存器值。现在我能够获得频率的索引了。非常感谢所有反馈和建议 :-) - Ted
显示剩余10条评论
1个回答

2
泰德揭示了微控制器PIC32MZ Graphics (DA)系列的规格说明书与12位逐次逼近寄存器(SAR)模数转换器(ADC)版本B的规格说明书之间的不一致之处。
在两种情况下,驱动ADC采样率的时钟源都由寄存器ADCCON3的位ADCSEL<1:0>控制。
规格说明书在第452页给出了以下时钟源:
11 = FRC
10 = REFCLK3
01 = System Clock (Tcy)
00 = PBCLK3

相反, 第14页版本B的ADC规格 是:
11 = System Clock (TCY)
10 = REFCLK3
01 = FRC Oscillator output
00 = Peripheral bus clock (PBCLK)

规范的D版本同时指出:

有关ADC时钟源选择,请参阅特定设备数据表中的“12位高速逐次逼近寄存器(SAR)”章节。

MPLAB Harmony ADC配置器符合此规定。然而,采用版本B的时钟设置解决了采样问题,这表明家族数据表不正确。

采样率也可能受到以下因素的影响:

  • ADCCON3`的CONCLKDIV<5:0>:控制时钟分频器
  • ADCxTIME的ADCDIV<6:0>:附加分频器,用于定义各个ADC的时钟源。或者共享ADC的ADCCON2`的ADCDIV<6:0>
  • ADCxTIME<9:0>ADCCON2<25:16>:时钟周期数。
由于采样率远高于预期(625 kHz对48kHz),帧的长度(1024个样本=0.0016秒)与输入信号的周期(约1kHz)相当。因此,大部分幅度存储在DFT的前几个bin中,并且应用窗口无法解决问题。
一旦校正了采样率,DFT就会显示出与输入信号频率对应的峰值。通过应用窗口并估计峰值的平均频率来精确识别这些频率。(即相对于功率密度的平均频率)

1
感谢Francis详细介绍了这个问题。 - Ted

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接