×

Raspberry Pi Pico上的ADC采样和FFT

消耗积分:2 | 格式:zip | 大小:0.00 MB | 2023-06-16

分享资料个

描述

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

如果您正在阅读这篇文章,那么您很可能已经有了一个想要从中收集数据的传感器。就我而言,我有一个麦克风连接到我的 Pico 的 A0 输入。如果您只是来这里学习,您可以让模拟输入悬空而不连接任何东西。

你可以在 GitHub 上找到完整的程序

1. 背景

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

DMA 模块可以配置为一旦准备好就从 ADC 中提取样本。以最快的速度,您可以以高达 0.5 MHz 的频率进行采样!

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

2. ADC 采样代码

pYYBAGN16dOADFXSAAk-YIIZhsU106.jpg
我的麦克风测试台
 

 

如果您还没有这样做,我强烈建议您在 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 倍,即每秒产生 50、000 个样本。您猜对了,将 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;isum;i++)>+=cap_buf[i];}
float avg = (float)sum/NSAMP;
for (int i=0;ifloat;i++)>)cap_buf[i]-avg;}

上面的这一部分用来自 ADC 的样本填充 cap_buf 数组,然后为傅里叶变换库对其进行预处理。对于许多应用程序,在应用傅里叶变换之前从数据序列中减去平均值是有利的。没有这个,任何 DC 电平(高于零的信号偏移)都会导致接近零的输出频率区间具有巨大的幅度。我使用的库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 进行 bin 处理。由于Nyquist 采样定理,任何大于 1/2 采样率的频率都会混叠在一起,因此这些 bin 对我们没有用处。这是信号处理中的一个基本结果,如果您不熟悉,值得进一步研究!

在音频的情况下,人耳通常可以听到高达 20 kHz 左右的频率。我使用 960 的 CLOCK_DIV 值,产生 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,但我想 CMake 在 Windows 上也有类似的过程。

  • 要编译我的代码,首先在 GitHub 上克隆我的存储库
  • 导航到 adc_fft 目录
  • 创建一个名为“build”的目录
  • 在里面导航,然后输入“cmake../”
  • 输入“make”,如果你正确安装了 Pico 构建系统,一切都应该编译
  • 将您的 Pico 置于引导加载程序模式,然后将 adc_fft.uf2 文件拖放到出现的驱动器中

应该就是这样!您可以通过 USB 监控程序的输出。它将输出从 A0 采样的数据中的最大频率分量,并且 LED 应快速闪烁。

在我的例子中,我将麦克风连接到模拟引脚,并通过从扬声器输入麦克风音调来验证我的代码是否正确。如果您有任何问题,请告诉我!


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !