390 lines
12 KiB
C
390 lines
12 KiB
C
//*****************************************************************************
|
|
//
|
|
//! @file pdm_fft.c
|
|
//!
|
|
//! @brief An example to show basic PDM operation.
|
|
//!
|
|
//! Purpose: This example enables the PDM interface to record audio signals from an
|
|
//! external microphone. The required pin connections are:
|
|
//!
|
|
//! Printing takes place over the ITM at 1M Baud.
|
|
//!
|
|
//! GPIO 10 - PDM DATA
|
|
//! GPIO 11 - PDM CLK
|
|
//
|
|
//*****************************************************************************
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Copyright (c) 2019, Ambiq Micro
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its
|
|
// contributors may be used to endorse or promote products derived from this
|
|
// software without specific prior written permission.
|
|
//
|
|
// Third party software included in this distribution is subject to the
|
|
// additional license terms as defined in the /docs/licenses directory.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// This is part of revision v2.2.0-7-g63f7c2ba1 of the AmbiqSuite Development Package.
|
|
//
|
|
//*****************************************************************************
|
|
|
|
#define ARM_MATH_CM4
|
|
#include <arm_math.h>
|
|
|
|
#include "am_mcu_apollo.h"
|
|
#include "am_bsp.h"
|
|
#include "am_util.h"
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Example parameters.
|
|
//
|
|
//*****************************************************************************
|
|
#define PDM_FFT_SIZE 4096
|
|
#define PDM_FFT_BYTES (PDM_FFT_SIZE * 2)
|
|
#define PRINT_PDM_DATA 0
|
|
#define PRINT_FFT_DATA 0
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Global variables.
|
|
//
|
|
//*****************************************************************************
|
|
volatile bool g_bPDMDataReady = false;
|
|
uint32_t g_ui32PDMDataBuffer[PDM_FFT_SIZE];
|
|
float g_fPDMTimeDomain[PDM_FFT_SIZE * 2];
|
|
float g_fPDMFrequencyDomain[PDM_FFT_SIZE * 2];
|
|
float g_fPDMMagnitudes[PDM_FFT_SIZE * 2];
|
|
uint32_t g_ui32SampleFreq;
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// PDM configuration information.
|
|
//
|
|
//*****************************************************************************
|
|
void *PDMHandle;
|
|
|
|
am_hal_pdm_config_t g_sPdmConfig =
|
|
{
|
|
.eClkDivider = AM_HAL_PDM_MCLKDIV_1,
|
|
.eLeftGain = AM_HAL_PDM_GAIN_0DB,
|
|
.eRightGain = AM_HAL_PDM_GAIN_0DB,
|
|
.ui32DecimationRate = 64,
|
|
.bHighPassEnable = 0,
|
|
.ui32HighPassCutoff = 0xB,
|
|
.ePDMClkSpeed = AM_HAL_PDM_CLK_6MHZ,
|
|
.bInvertI2SBCLK = 0,
|
|
.ePDMClkSource = AM_HAL_PDM_INTERNAL_CLK,
|
|
.bPDMSampleDelay = 0,
|
|
.bDataPacking = 1,
|
|
.ePCMChannels = AM_BSP_PDM_CHANNEL,
|
|
.ui32GainChangeDelay = 1,
|
|
.bI2SEnable = 0,
|
|
.bSoftMute = 0,
|
|
.bLRSwap = 0,
|
|
};
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// PDM initialization.
|
|
//
|
|
//*****************************************************************************
|
|
void
|
|
pdm_init(void)
|
|
{
|
|
//
|
|
// Initialize, power-up, and configure the PDM.
|
|
//
|
|
am_hal_pdm_initialize(0, &PDMHandle);
|
|
am_hal_pdm_power_control(PDMHandle, AM_HAL_PDM_POWER_ON, false);
|
|
am_hal_pdm_configure(PDMHandle, &g_sPdmConfig);
|
|
am_hal_pdm_enable(PDMHandle);
|
|
|
|
//
|
|
// Configure the necessary pins.
|
|
//
|
|
am_hal_gpio_pinconfig(AM_BSP_PDM_DATA_PIN, g_AM_BSP_PDM_DATA);
|
|
am_hal_gpio_pinconfig(AM_BSP_PDM_CLOCK_PIN, g_AM_BSP_PDM_CLOCK);
|
|
|
|
//
|
|
// Configure and enable PDM interrupts (set up to trigger on DMA
|
|
// completion).
|
|
//
|
|
am_hal_pdm_interrupt_enable(PDMHandle, (AM_HAL_PDM_INT_DERR
|
|
| AM_HAL_PDM_INT_DCMP
|
|
| AM_HAL_PDM_INT_UNDFL
|
|
| AM_HAL_PDM_INT_OVF));
|
|
|
|
NVIC_EnableIRQ(PDM_IRQn);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Print PDM configuration data.
|
|
//
|
|
//*****************************************************************************
|
|
void
|
|
pdm_config_print(void)
|
|
{
|
|
uint32_t ui32PDMClk;
|
|
uint32_t ui32MClkDiv;
|
|
float fFrequencyUnits;
|
|
|
|
//
|
|
// Read the config structure to figure out what our internal clock is set
|
|
// to.
|
|
//
|
|
switch (g_sPdmConfig.eClkDivider)
|
|
{
|
|
case AM_HAL_PDM_MCLKDIV_4: ui32MClkDiv = 4; break;
|
|
case AM_HAL_PDM_MCLKDIV_3: ui32MClkDiv = 3; break;
|
|
case AM_HAL_PDM_MCLKDIV_2: ui32MClkDiv = 2; break;
|
|
case AM_HAL_PDM_MCLKDIV_1: ui32MClkDiv = 1; break;
|
|
|
|
default:
|
|
ui32MClkDiv = 0;
|
|
}
|
|
|
|
switch (g_sPdmConfig.ePDMClkSpeed)
|
|
{
|
|
case AM_HAL_PDM_CLK_12MHZ: ui32PDMClk = 12000000; break;
|
|
case AM_HAL_PDM_CLK_6MHZ: ui32PDMClk = 6000000; break;
|
|
case AM_HAL_PDM_CLK_3MHZ: ui32PDMClk = 3000000; break;
|
|
case AM_HAL_PDM_CLK_1_5MHZ: ui32PDMClk = 1500000; break;
|
|
case AM_HAL_PDM_CLK_750KHZ: ui32PDMClk = 750000; break;
|
|
case AM_HAL_PDM_CLK_375KHZ: ui32PDMClk = 375000; break;
|
|
case AM_HAL_PDM_CLK_187KHZ: ui32PDMClk = 187000; break;
|
|
|
|
default:
|
|
ui32PDMClk = 0;
|
|
}
|
|
|
|
//
|
|
// Record the effective sample frequency. We'll need it later to print the
|
|
// loudest frequency from the sample.
|
|
//
|
|
g_ui32SampleFreq = (ui32PDMClk /
|
|
(ui32MClkDiv * 2 * g_sPdmConfig.ui32DecimationRate));
|
|
|
|
fFrequencyUnits = (float) g_ui32SampleFreq / (float) PDM_FFT_SIZE;
|
|
|
|
am_util_stdio_printf("Settings:\n");
|
|
am_util_stdio_printf("PDM Clock (Hz): %12d\n", ui32PDMClk);
|
|
am_util_stdio_printf("Decimation Rate: %12d\n", g_sPdmConfig.ui32DecimationRate);
|
|
am_util_stdio_printf("Effective Sample Freq.: %12d\n", g_ui32SampleFreq);
|
|
am_util_stdio_printf("FFT Length: %12d\n\n", PDM_FFT_SIZE);
|
|
am_util_stdio_printf("FFT Resolution: %15.3f Hz\n", fFrequencyUnits);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Start a transaction to get some number of bytes from the PDM interface.
|
|
//
|
|
//*****************************************************************************
|
|
void
|
|
pdm_data_get(void)
|
|
{
|
|
//
|
|
// Configure DMA and target address.
|
|
//
|
|
am_hal_pdm_transfer_t sTransfer;
|
|
sTransfer.ui32TargetAddr = (uint32_t ) g_ui32PDMDataBuffer;
|
|
sTransfer.ui32TotalCount = PDM_FFT_BYTES;
|
|
|
|
//
|
|
// Start the data transfer.
|
|
//
|
|
am_hal_pdm_enable(PDMHandle);
|
|
am_util_delay_ms(100);
|
|
am_hal_pdm_fifo_flush(PDMHandle);
|
|
am_hal_pdm_dma_start(PDMHandle, &sTransfer);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// PDM interrupt handler.
|
|
//
|
|
//*****************************************************************************
|
|
void
|
|
am_pdm0_isr(void)
|
|
{
|
|
uint32_t ui32Status;
|
|
|
|
//
|
|
// Read the interrupt status.
|
|
//
|
|
am_hal_pdm_interrupt_status_get(PDMHandle, &ui32Status, true);
|
|
am_hal_pdm_interrupt_clear(PDMHandle, ui32Status);
|
|
|
|
//
|
|
// Once our DMA transaction completes, we will disable the PDM and send a
|
|
// flag back down to the main routine. Disabling the PDM is only necessary
|
|
// because this example only implemented a single buffer for storing FFT
|
|
// data. More complex programs could use a system of multiple buffers to
|
|
// allow the CPU to run the FFT in one buffer while the DMA pulls PCM data
|
|
// into another buffer.
|
|
//
|
|
if (ui32Status & AM_HAL_PDM_INT_DCMP)
|
|
{
|
|
am_hal_pdm_disable(PDMHandle);
|
|
g_bPDMDataReady = true;
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Analyze and print frequency data.
|
|
//
|
|
//*****************************************************************************
|
|
void
|
|
pcm_fft_print(void)
|
|
{
|
|
float fMaxValue;
|
|
uint32_t ui32MaxIndex;
|
|
int16_t *pi16PDMData = (int16_t *) g_ui32PDMDataBuffer;
|
|
uint32_t ui32LoudestFrequency;
|
|
|
|
//
|
|
// Convert the PDM samples to floats, and arrange them in the format
|
|
// required by the FFT function.
|
|
//
|
|
for (uint32_t i = 0; i < PDM_FFT_SIZE; i++)
|
|
{
|
|
if (PRINT_PDM_DATA)
|
|
{
|
|
am_util_stdio_printf("%d\n", pi16PDMData[i]);
|
|
}
|
|
|
|
g_fPDMTimeDomain[2 * i] = pi16PDMData[i] / 1.0;
|
|
g_fPDMTimeDomain[2 * i + 1] = 0.0;
|
|
}
|
|
|
|
if (PRINT_PDM_DATA)
|
|
{
|
|
am_util_stdio_printf("END\n");
|
|
}
|
|
|
|
//
|
|
// Perform the FFT.
|
|
//
|
|
arm_cfft_radix4_instance_f32 S;
|
|
arm_cfft_radix4_init_f32(&S, PDM_FFT_SIZE, 0, 1);
|
|
arm_cfft_radix4_f32(&S, g_fPDMTimeDomain);
|
|
arm_cmplx_mag_f32(g_fPDMTimeDomain, g_fPDMMagnitudes, PDM_FFT_SIZE);
|
|
|
|
if (PRINT_FFT_DATA)
|
|
{
|
|
for (uint32_t i = 0; i < PDM_FFT_SIZE / 2; i++)
|
|
{
|
|
am_util_stdio_printf("%f\n", g_fPDMMagnitudes[i]);
|
|
}
|
|
|
|
am_util_stdio_printf("END\n");
|
|
}
|
|
|
|
//
|
|
// Find the frequency bin with the largest magnitude.
|
|
//
|
|
arm_max_f32(g_fPDMMagnitudes, PDM_FFT_SIZE / 2, &fMaxValue, &ui32MaxIndex);
|
|
|
|
ui32LoudestFrequency = (g_ui32SampleFreq * ui32MaxIndex) / PDM_FFT_SIZE;
|
|
|
|
if (PRINT_FFT_DATA)
|
|
{
|
|
am_util_stdio_printf("Loudest frequency bin: %d\n", ui32MaxIndex);
|
|
}
|
|
|
|
am_util_stdio_printf("Loudest frequency: %d \n", ui32LoudestFrequency);
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//
|
|
// Main
|
|
//
|
|
//*****************************************************************************
|
|
int
|
|
main(void)
|
|
{
|
|
//
|
|
// Perform the standard initialzation for clocks, cache settings, and
|
|
// board-level low-power operation.
|
|
//
|
|
am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);
|
|
am_hal_cachectrl_config(&am_hal_cachectrl_defaults);
|
|
am_hal_cachectrl_enable();
|
|
//am_bsp_low_power_init();
|
|
|
|
//
|
|
// Initialize the printf interface for UART output
|
|
//
|
|
am_bsp_uart_printf_enable();
|
|
|
|
//
|
|
// Print the banner.
|
|
//
|
|
am_util_stdio_terminal_clear();
|
|
am_util_stdio_printf("PDM FFT example.\n\n");
|
|
|
|
//
|
|
// Turn on the PDM, set it up for our chosen recording settings, and start
|
|
// the first DMA transaction.
|
|
//
|
|
pdm_init();
|
|
pdm_config_print();
|
|
am_hal_pdm_fifo_flush(PDMHandle);
|
|
pdm_data_get();
|
|
|
|
//
|
|
// Loop forever while sleeping.
|
|
//
|
|
while (1)
|
|
{
|
|
am_hal_interrupt_master_disable();
|
|
|
|
if (g_bPDMDataReady)
|
|
{
|
|
g_bPDMDataReady = false;
|
|
|
|
pcm_fft_print();
|
|
|
|
while (PRINT_PDM_DATA || PRINT_FFT_DATA);
|
|
|
|
//
|
|
// Start converting the next set of PCM samples.
|
|
//
|
|
pdm_data_get();
|
|
}
|
|
|
|
//
|
|
// Go to Deep Sleep.
|
|
//
|
|
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
|
|
|
|
am_hal_interrupt_master_enable();
|
|
}
|
|
}
|