/***************************************************************************//**
* @file
* @brief Pulse Density Modulation (PDM) peripheral API
*******************************************************************************
* # License
* Copyright 2018 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* SPDX-License-Identifier: Zlib
*
* The licensor of this software is Silicon Laboratories Inc.
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
******************************************************************************/
#ifndef EM_PDM_H
#define EM_PDM_H
#include "em_device.h"
#include
#include
#if defined(PDM_PRESENT) && (PDM_COUNT == 1)
#ifdef __cplusplus
extern "C" {
#endif
/* *INDENT-OFF* */
/***************************************************************************//**
* @addtogroup pdm PDM - Pulse Density Modulation
* @brief Pulse Density Modulation (PDM) peripheral API
*
* @details
* PDM API functions provide full support for the PDM peripheral.
* The PDM peripheral accepts PDM bitstreams and produces PCM encoded output.
*
* The following is an example PDM usage when interfacing to two PDM microphones:
*
* Configure clocks and GPIO pins:
* @code
PDM_Init_TypeDef pdmInit = PDM_INIT_DEFAULT;
CMU_DPLLInit_TypeDef pllInit = CMU_DPLL_LFXO_TO_40MHZ;
CMU_OscillatorEnable(cmuOsc_LFXO, true, true);
// Lock PLL to 1,411,209 Hz to achieve 44,100 kHz PCM sampling rate
// when using 32x PDM oversampling
pllInit.frequency = 1411209;
pllInit.m = 14;
pllInit.n = 645;
CMU_DPLLLock(&pllInit);
// Setup all GPIO's.
GPIO_PinModeSet(MIC_CLK_PORT, MIC_CLK_PIN, gpioModePushPull, 0);
GPIO_PinModeSet(MIC_DATA_PORT, MIC_DATA_PIN, gpioModeInput, 0);
// Set fast slew rate on PDM mic CLK and DATA pins
GPIO_SlewrateSet(MIC_CLK_PORT, 7U, 7U);
// Enable PDM peripheral clock.
CMU_ClockEnable(cmuClock_PDM, true);
// Select PDM reference clock source and enable it.
CMU_ClockSelectSet(cmuClock_PDMREF, cmuSelect_HFRCO);
CMU_ClockEnable(cmuClock_PDMREF, true);
// Route PDM signals to correct GPIO's.
PDM->ROUTELOC0 = (PDM->ROUTELOC0 & ~_PDM_ROUTELOC0_DAT0LOC_MASK)
| (MIC_DATA_PDM_LOC << _PDM_ROUTELOC0_DAT0LOC_SHIFT);
PDM->ROUTELOC1 = MIC_CLK_PDM_LOC << _PDM_ROUTELOC1_CLKLOC_SHIFT;
PDM->ROUTEPEN |= PDM_ROUTEPEN_CLKPEN | PDM_ROUTEPEN_DAT0PEN;@endcode
*
* Initialize and start PDM, then read PCM samples from FIFO:
*
* @code
PDM_Init_TypeDef init = PDM_INIT_DEFAULT;
PDM_Init(PDM, &init);
while (true) {
*pBuffer++ = PDM_Rx(PDM);
}@endcode
*
* @{
******************************************************************************/
/* *INDENT-ON* */
/*******************************************************************************
******************************** ENUMS ************************************
******************************************************************************/
#if defined(PDM_CFG0_NUMCH_THREE)
/** Configure CH3 CLK Polarity. */
typedef enum {
pdmCh3ClkPolarityRisingEdge = _PDM_CFG0_CH3CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
pdmCh3ClkPolarityFallingEdge = _PDM_CFG0_CH3CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
} PDM_Ch3ClkPolarity_Typedef;
/** Configure CH2 CLK Polarity. */
typedef enum {
pdmCh2ClkPolarityRisingEdge = _PDM_CFG0_CH2CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
pdmCh2ClkPolarityFallingEdge = _PDM_CFG0_CH2CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
} PDM_Ch2ClkPolarity_Typedef;
#endif
/** Configure CH1 CLK Polarity. */
typedef enum {
pdmCh1ClkPolarityRisingEdge = _PDM_CFG0_CH1CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
pdmCh1ClkPolarityFallingEdge = _PDM_CFG0_CH1CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
} PDM_Ch1ClkPolarity_Typedef;
/** Configure CH0 CLK Polarity. */
typedef enum {
pdmCh0ClkPolarityRisingEdge = _PDM_CFG0_CH0CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
pdmCh0ClkPolarityFallingEdge = _PDM_CFG0_CH0CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
} PDM_Ch0ClkPolarity_Typedef;
/** Configure FIFO Data valid level water-mark. */
typedef enum {
pdmFifoValidWatermarkOne = _PDM_CFG0_FIFODVL_ONE, /**< At least one word. */
pdmFifoValidWatermarkTwo = _PDM_CFG0_FIFODVL_TWO, /**< Two words. */
pdmFifoValidWatermarkThree = _PDM_CFG0_FIFODVL_THREE, /**< Three words. */
pdmFifoValidWatermarkFour = _PDM_CFG0_FIFODVL_FOUR /**< Four words. */
} PDM_FifoValidWatermark_Typedef;
/** Configure PDM filter data output format. */
typedef enum {
pdmDataFormatRight16 = _PDM_CFG0_DATAFORMAT_RIGHT16, /**< Right aligned 16-bit, left bits are sign extended. */
pdmDataFormatDouble16 = _PDM_CFG0_DATAFORMAT_DOUBLE16, /**< Pack two 16-bit samples into one 32-bit word. */
pdmDataFormatRight24 = _PDM_CFG0_DATAFORMAT_RIGHT24, /**< Right aligned 24bit, left bits are sign extended. */
pdmDataFormatFull32bit = _PDM_CFG0_DATAFORMAT_FULL32BIT, /**< 32 bit data. */
pdmDataFormatLeft16 = _PDM_CFG0_DATAFORMAT_LEFT16, /**< Left aligned 16-bit, right bits are zeros. */
pdmDataFormatLeft24 = _PDM_CFG0_DATAFORMAT_LEFT24, /**< Left aligned 24-bit, right bits are zeros. */
pdmDataFormatRaw32bit = _PDM_CFG0_DATAFORMAT_RAW32BIT /**< RAW 32 bit data from integrator. */
} PDM_DataFormat_TypeDef;
/** Configure number of PDM channels. */
typedef enum {
pdmNumberOfChannelsOne = _PDM_CFG0_NUMCH_ONE, /**< Only one Channel. */
pdmNumberOfChannelsTwo = _PDM_CFG0_NUMCH_TWO, /**< Two Channels. */
#if defined(PDM_CFG0_NUMCH_THREE)
pdmNumberOfChannelsThree = _PDM_CFG0_NUMCH_THREE, /**< Three Channels. */
pdmNumberOfChannelsFour = _PDM_CFG0_NUMCH_FOUR /**< Four Channels. */
#endif
} PDM_NumberOfChannels_TypeDef;
/** Configure order of the PDM filter. */
typedef enum {
pdmFilterOrderSecond = _PDM_CFG0_FORDER_SECOND, /**< Second order filter. */
pdmFilterOrderThird = _PDM_CFG0_FORDER_THIRD, /**< Third order filter. */
pdmFilterOrderFourth = _PDM_CFG0_FORDER_FOURTH, /**< Fourth order filter. */
pdmFilterOrderFifth = _PDM_CFG0_FORDER_FIFTH /**< Fifth order filter. */
} PDM_FilterOrder_TypeDef;
/*******************************************************************************
******************************* STRUCTS ***********************************
******************************************************************************/
/** PDM initialization structure. */
typedef struct {
bool start; /**< Start PDM filter after initialization. */
#if defined(PDM_CTRL_OUTCLKEN)
bool outClkEn; /**< Enable PDM clock. */
#endif
uint32_t dsr; /**< PDM down sampling rate. */
uint32_t gain; /**< PDM gain. */
#if defined(PDM_CFG0_NUMCH_THREE)
PDM_Ch3ClkPolarity_Typedef ch3ClkPolarity; /**< Ch 3 clock polarity. */
PDM_Ch2ClkPolarity_Typedef ch2ClkPolarity; /**< Ch 2 clock polarity. */
#endif
PDM_Ch1ClkPolarity_Typedef ch1ClkPolarity; /**< Ch 1 clock polarity. */
PDM_Ch0ClkPolarity_Typedef ch0ClkPolarity; /**< Ch 0 clock polarity. */
#if defined(PDM_CFG0_NUMCH_THREE)
bool enableCh2Ch3Stereo; /**< Enable stereo mode for channel pair CH2 and CH3. */
#endif
bool enableCh0Ch1Stereo; /**< Enable stereo mode for channel pair CH0 and CH1. */
PDM_FifoValidWatermark_Typedef fifoValidWatermark; /**< FIFO Data valid level water-mark. */
PDM_DataFormat_TypeDef dataFormat; /**< PDM filter data output format. */
PDM_NumberOfChannels_TypeDef numChannels; /**< Number of PDM channels. */
PDM_FilterOrder_TypeDef filterOrder; /**< PDM filter order. */
uint32_t prescaler; /**< PDM clock prescaler, resulting PDM clock is input clock / (prescaler + 1). */
} PDM_Init_TypeDef;
/*******************************************************************************
************************** STRUCT INITIALIZERS ****************************
******************************************************************************/
/** Default configuration for PDM.
* Stereo Ch0/1, 16bit samples, 44,100 Hz sampling rate,
* 32 times oversampling (requires 1,411,209 Hz PDM clock).
*/
#if defined(PDM_CFG0_NUMCH_THREE)
#define PDM_INIT_DEFAULT \
{ \
true, /* Start PDM filter after initialization. */ \
true, /* Enable PDM clock. */ \
32U, /* PDM down sampling rate. */ \
5U, /* PDM gain. */ \
pdmCh3ClkPolarityRisingEdge, /* N/A. */ \
pdmCh2ClkPolarityRisingEdge, /* N/A. */ \
pdmCh1ClkPolarityFallingEdge, /* Input data clocked on falling clock edge. */ \
pdmCh0ClkPolarityRisingEdge, /* Input data clocked on rising clock edge. */ \
false, /* N/A. */ \
true, /* Enable stereo mode for channel pair CH0 and CH1. */ \
pdmFifoValidWatermarkOne, /* At least one word water-mark level. */ \
pdmDataFormatDouble16, /* Two 16-bit samples per FIFO entry. */ \
pdmNumberOfChannelsTwo, /* Two Channels. */ \
pdmFilterOrderFifth, /* Fifth order filter. */ \
0U /* No clock prescaling. */ \
}
#else
#define PDM_INIT_DEFAULT \
{ \
true, /* Start PDM filter after initialization. */ \
32U, /* PDM down sampling rate. */ \
5U, /* PDM gain. */ \
pdmCh1ClkPolarityFallingEdge, /* Input data clocked on falling clock edge. */ \
pdmCh0ClkPolarityRisingEdge, /* Input data clocked on rising clock edge. */ \
true, /* Enable stereo mode for channel pair CH0 and CH1. */ \
pdmFifoValidWatermarkOne, /* At least one word water-mark level. */ \
pdmDataFormatDouble16, /* Two 16-bit samples per FIFO entry. */ \
pdmNumberOfChannelsTwo, /* Two Channels. */ \
pdmFilterOrderFifth, /* Fifth order filter. */ \
0U /* No clock prescaling. */ \
}
#endif
/*******************************************************************************
***************************** PROTOTYPES **********************************
******************************************************************************/
void PDM_DeInit(PDM_TypeDef *pdm);
void PDM_Init(PDM_TypeDef *pdm, const PDM_Init_TypeDef *init);
void PDM_Reset(PDM_TypeDef *pdm);
/***************************************************************************//**
* @brief
* Clear the PDM filter.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
******************************************************************************/
__STATIC_INLINE void PDM_Clear(PDM_TypeDef *pdm)
{
while (pdm->SYNCBUSY != 0U) {
// Wait for any pending CMD synchronization
}
pdm->CMD = PDM_CMD_CLEAR;
}
/***************************************************************************//**
* @brief
* Flush the PDM sample FIFO.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
******************************************************************************/
__STATIC_INLINE void PDM_FifoFlush(PDM_TypeDef *pdm)
{
while (pdm->SYNCBUSY != 0U) {
// Wait for any pending CMD synchronization
}
pdm->CMD = PDM_CMD_FIFOFL;
}
/***************************************************************************//**
* @brief
* Clear one or more pending PDM interrupts.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @param[in] flags
* Pending PDM interrupt sources to clear. Use one or more valid
* interrupt flags for the PDM module. The flags are PDM_IFC_DV,
* PDM_IFC_DVL, PDM_IFC_OF and PDM_IFC_UF.
******************************************************************************/
__STATIC_INLINE void PDM_IntClear(PDM_TypeDef *pdm, uint32_t flags)
{
#if defined(PDM_HAS_SET_CLEAR)
pdm->IF_CLR = flags;
#else
pdm->IFC = flags;
#endif
}
/***************************************************************************//**
* @brief
* Disable one or more PDM interrupts.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @param[in] flags
* PDM interrupt sources to disable. Use one or more valid
* interrupt flags for the PDM module. The flags are PDM_IEN_DV,
* PDM_IEN_DVL, PDM_IEN_OF and PDM_IEN_UF.
******************************************************************************/
__STATIC_INLINE void PDM_IntDisable(PDM_TypeDef *pdm, uint32_t flags)
{
pdm->IEN &= ~flags;
}
/***************************************************************************//**
* @brief
* Enable one or more PDM interrupts.
*
* @note
* Depending on the use, a pending interrupt may already be set prior to
* enabling the interrupt. To ignore a pending interrupt, consider using
* PDM_IntClear() prior to enabling the interrupt.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @param[in] flags
* PDM interrupt sources to enable. Use one or more valid
* interrupt flags for the PDM module. The flags are PDM_IEN_DV,
* PDM_IEN_DVL, PDM_IEN_OF and PDM_IEN_UF.
******************************************************************************/
__STATIC_INLINE void PDM_IntEnable(PDM_TypeDef *pdm, uint32_t flags)
{
pdm->IEN |= flags;
}
/***************************************************************************//**
* @brief
* Get pending PDM interrupt flags.
*
* @note
* Event bits are not cleared by the use of this function.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @return
* PDM interrupt sources pending. Returns one or more valid
* interrupt flags for PDM module. The flags are PDM_IF_DV,
* PDM_IF_DVL, PDM_IF_OF and PDM_IF_UF.
******************************************************************************/
__STATIC_INLINE uint32_t PDM_IntGet(PDM_TypeDef *pdm)
{
return pdm->IF;
}
/***************************************************************************//**
* @brief
* Get enabled and pending PDM interrupt flags.
* Useful for handling more interrupt sources in the same interrupt handler.
*
* @note
* Interrupt flags are not cleared by the use of this function.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @return
* Pending and enabled PDM interrupt sources
* Return value is the bitwise AND of
* - the enabled interrupt sources in PDM_IEN and
* - the pending interrupt flags PDM_IF
******************************************************************************/
__STATIC_INLINE uint32_t PDM_IntGetEnabled(PDM_TypeDef *pdm)
{
uint32_t ien;
ien = pdm->IEN;
return pdm->IF & ien;
}
/***************************************************************************//**
* @brief
* Set one or more pending PDM interrupts.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @param[in] flags
* PDM interrupt sources to set to pending. Use one or more valid
* interrupt flags for the PDM module. The flags are PDM_IFS_DV,
* PDM_IFS_DVL, PDM_IFS_OF and PDM_IFS_UF.
******************************************************************************/
__STATIC_INLINE void PDM_IntSet(PDM_TypeDef *pdm, uint32_t flags)
{
#if defined(PDM_HAS_SET_CLEAR)
pdm->IF_SET = flags;
#else
pdm->IFS = flags;
#endif
}
/***************************************************************************//**
* @brief
* Read one entry from the PDM FIFO.
*
* @note
* This function will wait until a sample is available in the FIFO.
* Depending on PDM configuration, a FIFO entry can consist of one or two
* samples.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @return
* The entry read from the FIFO.
******************************************************************************/
__STATIC_INLINE uint32_t PDM_Rx(PDM_TypeDef *pdm)
{
while ((pdm->STATUS & PDM_STATUS_EMPTY) == PDM_STATUS_EMPTY) {
// Wait for data in FIFO
}
return pdm->RXDATA;
}
/***************************************************************************//**
* @brief
* Start the PDM operation (start the PDM filter).
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
******************************************************************************/
__STATIC_INLINE void PDM_Start(PDM_TypeDef *pdm)
{
while (pdm->SYNCBUSY != 0U) {
// Wait for any pending CMD synchronization
}
pdm->CMD = PDM_CMD_START;
}
/***************************************************************************//**
* @brief
* Get the PDM STATUS register.
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
*
* @return
* STATUS register value.
******************************************************************************/
__STATIC_INLINE uint32_t PDM_StatusGet(PDM_TypeDef *pdm)
{
return pdm->STATUS;
}
/***************************************************************************//**
* @brief
* Stop the PDM operation (stop the PDM filter).
*
* @param[in] pdm
* A pointer to the PDM peripheral register block.
******************************************************************************/
__STATIC_INLINE void PDM_Stop(PDM_TypeDef *pdm)
{
while (pdm->SYNCBUSY != 0U) {
// Wait for any pending CMD synchronization
}
pdm->CMD = PDM_CMD_STOP;
}
/** @} (end addtogroup pdm) */
#ifdef __cplusplus
}
#endif
#endif // defined(PDM_PRESENT) && (PDM_COUNT == 1)
#endif // EM_PDM_H