本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

Raspberry Pi Pico上的ADC采样和FFT
发布时间:2021-02-13
分享到:
Raspberry Pi Pico上的ADC采样和FFT
发布时间:2021-02-13
分享到:

了解如何在Raspberry Pi Pico上以高达500 kHz的频率采样并根据捕获的数据计算快速傅立叶变换。

硬件部件:
Raspberry Pi Pico×    1个   

在此项目中,我们将使用一些特殊功能以极快的速度从Raspberry Pi Pico的模数转换器(ADC)捕获数据,然后对数据进行快速傅立叶变换。这是许多项目的常见任务,例如涉及音频处理或广播的项目。

很可能您已经有了一个想要从中收集数据的传感器。就我而言,我有一个麦克风连接到Pico的A0输入。如果您只是来这里学习,则可以使模拟输入悬空而无任何连接。

您可以在GitHub上找到完整程序。

1.背景

Raspberry Pi Pico如此有用的一个主要原因是其众多的硬件功能使处理器从执行常规I / O任务中解放出来。在我们的例子中,我们将使用Pico的直接内存访问(DMA)模块。这是一项硬件功能,可以自动化任务,涉及以极快的速度将大量数据进出内存到IO。

可以将DMA模块配置为在准备好样本后立即将它们从ADC中取出。以最快的速度,您可以在高达0.5 MHz的频率下采样!

收集所有这些数据后,您可能需要对其进行一些处理。一个常见的任务是将您的信息从时域转换到频域以进行进一步处理。就我而言,我有一个麦克风,我想从该麦克风收集音频样本,然后计算样本中包含的最大频率分量。最常用的算法是快速傅立叶变换。

2. ADC采样代码

如果您还没有这样做,我强烈建议您在GitHub上克隆Raspberry Pi的pico-examples库。这是我用来开始的所有采样代码的地方。以下代码的很大一部分来自此存储库中的dma_capture示例。

我将介绍软件的一些关键要素,以解释发生了什么。您可以在“代码”部分找到完整的程序。

// set sample rate
adc_set_clkdiv(CLOCK_DIV);
这条线确定ADC收集样本的速度。“ clkdiv”是指时钟分频,它使您可以分割48 MHz基本时钟,以更低的速率进行采样。目前,一个样本需要96个循环来收集。这样得出的最大采样率是每秒48、000、000个循环/每个样本96个循环=每秒500、000个样本。

为了降低采样速度,可以增加时钟分频。将CLOCK_DIV设置为960将使每个样本的循环数增加10倍,从而每秒产生50000个样本。您猜到了,将CLOCK_DIV设置为9600可以得到每秒5 000个样本。

void sample(uint8_t *capture_buf) {
    adc_fifo_drain();
    adc_run(false);

    dma_channel_configure(dma_chan, &cfg,
        capture_buf, // dst
        &adc_hw->fifo, // src
        NSAMP, // transfer count
        true // start immediately
    );

    gpio_put(LED_PIN, 1);
    adc_run(true);
    dma_channel_wait_for_finish_blocking(dma_chan);
    gpio_put(LED_PIN, 0);
}
该功能实际上是从ADC收集样本。处理器复位ADC,排空缓冲区,然后开始采样。它还将在采样期间打开LED,以便您查看正在发生的情况。

3. FFT代码
// get NSAMP samples at FSAMP
sample(cap_buf);
// fill fourier transform input while subtracting DC component
uint64_t sum = 0;
for (int i=0;i<NSAMP;i++) {sum+=cap_buf[i];}
float avg = (float)sum/NSAMP;
for (int i=0;i<NSAMP;i++) {fft_in[i]=(float)cap_buf[i]-avg;}
上面的这一部分用ADC的采样填充cap_buf数组,然后对其进行预处理以进行傅立叶变换库。对于许多应用程序,在对数据进行傅里叶变换之前,先从数据序列中减去平均值是有利的。否则,任何直流电平(信号偏移会超过零)将导致输出频点接近零而具有巨大的幅度。我使用的库KISS FFT期望信号具有浮点类型,因此我在减去均值的同时也转换了样本。

// compute fast fourier transform
kiss_fftr(cfg , fft_in, fft_out);
// compute power and calculate max freq component
float max_power = 0;
int max_idx = 0;
// any frequency bin over NSAMP/2 is aliased (nyquist sampling theorum)
for (int i = 0; i < NSAMP/2; i++) {
    float power = fft_out[i].r*fft_out[i].r+fft_out[i].i*fft_out[i].i;
    if (power>max_power) {
        max_power=power;
        max_idx = i;
    }
}

float max_freq = freqs[max_idx];
printf("Greatest Frequency Component: %0.1f Hz\n",max_freq);
下一部分将计算FFT,然后计算输出数据中的最大频率分量。FFT的输出是复数值,因此要获得可用的功率值,可以取复结果的大小。

还要注意的是,与其循环遍历FFT的所有NSAMP输出值,我们仅对NSAMP / 2进行装箱。由于奈奎斯特采样定理,任何大于采样率1/2的频率都将被混叠在一起,因此这些bin对我们没有用。这是信号处理的基本结果,如果您不熟悉,则值得进一步研究!

就音频而言,人耳通常可以听到高达20 kHz左右的频率。我使用的CLOCK_DIV值为960,产生的采样率为50 kHz。因此,我可以捕获的最大非混叠频率为25 kHz,这应该绰绰有余!

// BE CAREFUL: anything over about 9000 here will cause things
// to silently break. The code will compile and upload, but due
// to memory issues nothing will work properly
#define NSAMP 1000
需要指出的最后一点代码是NSAMP或收集的样本数。在信号处理中,在较高和较低数量的样本之间存在一个基本的权衡。更多的样本将花费更长的时间来收集和处理,但是会产生更高分辨率的傅里叶变换。更少的样本将导致更短的采样周期和更快的处理,但是您的傅立叶变换将更加精细。

对于Pico,我发现分配过多的内存会导致难以调试的失败。如果您将NSAMP设置得太大,您的Pico将没有足够的内存来分配给保存样本的阵列。该代码仍然可以编译和上传,但是您可能会得到一些奇怪的行为。在我的示例中,将NSAMP保持在9000以下似乎很好。

3.编译和上传
如果尚未下载,请下载Raspberry Pi Pico入门。这是一个坚实的资源,可为您提供设置构建系统,编译C / C ++代码并将其上传到Pico所需的一切。

以下所有说明均适用于macOS / Linux,但我想Windows上的CMake也有类似的过程。

要编译我的代码,请先在GitHub上下载我的存储库。
导航到adc_fft目录
创建一个名为“ build”的目录
在其中导航,然后键入“ cmake ../”
输入“ make”,如果正确安装了Pico构建系统,则所有内容均应编译
将您的Pico放入引导加载程序模式,然后将adc_fft.uf2文件拖放到出现的驱动器中
那应该是全部!您可以通过USB监视程序的输出。它将在从A0采样的数据中输出最大频率分量,并且LED应该快速闪烁。

GitHub快速入口

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论