1 /***************************************************************************//**
2 * @file
3 * @brief Pulse Density Modulation (PDM) peripheral API
4 *******************************************************************************
5 * # License
6 * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
7 *******************************************************************************
8 *
9 * SPDX-License-Identifier: Zlib
10 *
11 * The licensor of this software is Silicon Laboratories Inc.
12 *
13 * This software is provided 'as-is', without any express or implied
14 * warranty. In no event will the authors be held liable for any damages
15 * arising from the use of this software.
16 *
17 * Permission is granted to anyone to use this software for any purpose,
18 * including commercial applications, and to alter it and redistribute it
19 * freely, subject to the following restrictions:
20 *
21 * 1. The origin of this software must not be misrepresented; you must not
22 * claim that you wrote the original software. If you use this software
23 * in a product, an acknowledgment in the product documentation would be
24 * appreciated but is not required.
25 * 2. Altered source versions must be plainly marked as such, and must not be
26 * misrepresented as being the original software.
27 * 3. This notice may not be removed or altered from any source distribution.
28 *
29 ******************************************************************************/
30
31 #ifndef EM_PDM_H
32 #define EM_PDM_H
33
34 #include "em_device.h"
35
36 #include <stdint.h>
37 #include <stdbool.h>
38
39 #if defined(PDM_PRESENT) && (PDM_COUNT == 1)
40
41 #ifdef __cplusplus
42 extern "C" {
43 #endif
44
45 /* *INDENT-OFF* */
46 /***************************************************************************//**
47 * @addtogroup pdm PDM - Pulse Density Modulation
48 * @brief Pulse Density Modulation (PDM) peripheral API
49 *
50 * @details
51 * PDM API functions provide full support for the PDM peripheral.
52 * The PDM peripheral accepts PDM bitstreams and produces PCM encoded output.
53 *
54 * <b> The following is an example PDM usage when interfacing to two PDM microphones: </b>
55 *
56 * Configure clocks and GPIO pins:
57 * @code
58 PDM_Init_TypeDef pdmInit = PDM_INIT_DEFAULT;
59 CMU_DPLLInit_TypeDef pllInit = CMU_DPLL_LFXO_TO_40MHZ;
60
61 CMU_OscillatorEnable(cmuOsc_LFXO, true, true);
62 // Lock PLL to 1,411,209 Hz to achieve 44,100 kHz PCM sampling rate
63 // when using 32x PDM oversampling
64 pllInit.frequency = 1411209;
65 pllInit.m = 14;
66 pllInit.n = 645;
67 CMU_DPLLLock(&pllInit);
68
69 // Setup all GPIO's.
70 GPIO_PinModeSet(MIC_CLK_PORT, MIC_CLK_PIN, gpioModePushPull, 0);
71 GPIO_PinModeSet(MIC_DATA_PORT, MIC_DATA_PIN, gpioModeInput, 0);
72
73 // Set fast slew rate on PDM mic CLK and DATA pins
74 GPIO_SlewrateSet(MIC_CLK_PORT, 7U, 7U);
75
76 // Enable PDM peripheral clock.
77 CMU_ClockEnable(cmuClock_PDM, true);
78 // Select PDM reference clock source and enable it.
79 CMU_ClockSelectSet(cmuClock_PDMREF, cmuSelect_HFRCO);
80 CMU_ClockEnable(cmuClock_PDMREF, true);
81
82 // Route PDM signals to correct GPIO's.
83 PDM->ROUTELOC0 = (PDM->ROUTELOC0 & ~_PDM_ROUTELOC0_DAT0LOC_MASK)
84 | (MIC_DATA_PDM_LOC << _PDM_ROUTELOC0_DAT0LOC_SHIFT);
85 PDM->ROUTELOC1 = MIC_CLK_PDM_LOC << _PDM_ROUTELOC1_CLKLOC_SHIFT;
86 PDM->ROUTEPEN |= PDM_ROUTEPEN_CLKPEN | PDM_ROUTEPEN_DAT0PEN;@endcode
87 *
88 * Initialize and start PDM, then read PCM samples from FIFO:
89 *
90 * @code
91 PDM_Init_TypeDef init = PDM_INIT_DEFAULT;
92 PDM_Init(PDM, &init);
93
94 while (true) {
95 *pBuffer++ = PDM_Rx(PDM);
96 }@endcode
97 *
98 * @{
99 ******************************************************************************/
100 /* *INDENT-ON* */
101
102 /*******************************************************************************
103 ******************************** ENUMS ************************************
104 ******************************************************************************/
105
106 #if defined(PDM_CFG0_NUMCH_THREE)
107 /** Configure CH3 CLK Polarity. */
108 typedef enum {
109 pdmCh3ClkPolarityRisingEdge = _PDM_CFG0_CH3CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
110 pdmCh3ClkPolarityFallingEdge = _PDM_CFG0_CH3CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
111 } PDM_Ch3ClkPolarity_Typedef;
112
113 /** Configure CH2 CLK Polarity. */
114 typedef enum {
115 pdmCh2ClkPolarityRisingEdge = _PDM_CFG0_CH2CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
116 pdmCh2ClkPolarityFallingEdge = _PDM_CFG0_CH2CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
117 } PDM_Ch2ClkPolarity_Typedef;
118 #endif
119
120 /** Configure CH1 CLK Polarity. */
121 typedef enum {
122 pdmCh1ClkPolarityRisingEdge = _PDM_CFG0_CH1CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
123 pdmCh1ClkPolarityFallingEdge = _PDM_CFG0_CH1CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
124 } PDM_Ch1ClkPolarity_Typedef;
125
126 /** Configure CH0 CLK Polarity. */
127 typedef enum {
128 pdmCh0ClkPolarityRisingEdge = _PDM_CFG0_CH0CLKPOL_NORMAL, /**< Input data clocked on rising clock edge. */
129 pdmCh0ClkPolarityFallingEdge = _PDM_CFG0_CH0CLKPOL_INVERT /**< Input data clocked on falling clock edge. */
130 } PDM_Ch0ClkPolarity_Typedef;
131
132 /** Configure FIFO Data valid level water-mark. */
133 typedef enum {
134 pdmFifoValidWatermarkOne = _PDM_CFG0_FIFODVL_ONE, /**< At least one word. */
135 pdmFifoValidWatermarkTwo = _PDM_CFG0_FIFODVL_TWO, /**< Two words. */
136 pdmFifoValidWatermarkThree = _PDM_CFG0_FIFODVL_THREE, /**< Three words. */
137 pdmFifoValidWatermarkFour = _PDM_CFG0_FIFODVL_FOUR /**< Four words. */
138 } PDM_FifoValidWatermark_Typedef;
139
140 /** Configure PDM filter data output format. */
141 typedef enum {
142 pdmDataFormatRight16 = _PDM_CFG0_DATAFORMAT_RIGHT16, /**< Right aligned 16-bit, left bits are sign extended. */
143 pdmDataFormatDouble16 = _PDM_CFG0_DATAFORMAT_DOUBLE16, /**< Pack two 16-bit samples into one 32-bit word. */
144 pdmDataFormatRight24 = _PDM_CFG0_DATAFORMAT_RIGHT24, /**< Right aligned 24bit, left bits are sign extended. */
145 pdmDataFormatFull32bit = _PDM_CFG0_DATAFORMAT_FULL32BIT, /**< 32 bit data. */
146 pdmDataFormatLeft16 = _PDM_CFG0_DATAFORMAT_LEFT16, /**< Left aligned 16-bit, right bits are zeros. */
147 pdmDataFormatLeft24 = _PDM_CFG0_DATAFORMAT_LEFT24, /**< Left aligned 24-bit, right bits are zeros. */
148 pdmDataFormatRaw32bit = _PDM_CFG0_DATAFORMAT_RAW32BIT /**< RAW 32 bit data from integrator. */
149 } PDM_DataFormat_TypeDef;
150
151 /** Configure number of PDM channels. */
152 typedef enum {
153 pdmNumberOfChannelsOne = _PDM_CFG0_NUMCH_ONE, /**< Only one Channel. */
154 pdmNumberOfChannelsTwo = _PDM_CFG0_NUMCH_TWO, /**< Two Channels. */
155 #if defined(PDM_CFG0_NUMCH_THREE)
156 pdmNumberOfChannelsThree = _PDM_CFG0_NUMCH_THREE, /**< Three Channels. */
157 pdmNumberOfChannelsFour = _PDM_CFG0_NUMCH_FOUR /**< Four Channels. */
158 #endif
159 } PDM_NumberOfChannels_TypeDef;
160
161 /** Configure order of the PDM filter. */
162 typedef enum {
163 pdmFilterOrderSecond = _PDM_CFG0_FORDER_SECOND, /**< Second order filter. */
164 pdmFilterOrderThird = _PDM_CFG0_FORDER_THIRD, /**< Third order filter. */
165 pdmFilterOrderFourth = _PDM_CFG0_FORDER_FOURTH, /**< Fourth order filter. */
166 pdmFilterOrderFifth = _PDM_CFG0_FORDER_FIFTH /**< Fifth order filter. */
167 } PDM_FilterOrder_TypeDef;
168
169 /*******************************************************************************
170 ******************************* STRUCTS ***********************************
171 ******************************************************************************/
172
173 /** PDM initialization structure. */
174 typedef struct {
175 bool start; /**< Start PDM filter after initialization. */
176 #if defined(PDM_CTRL_OUTCLKEN)
177 bool outClkEn; /**< Enable PDM clock. */
178 #endif
179 uint32_t dsr; /**< PDM down sampling rate. */
180 uint32_t gain; /**< PDM gain. */
181 #if defined(PDM_CFG0_NUMCH_THREE)
182 PDM_Ch3ClkPolarity_Typedef ch3ClkPolarity; /**< Ch 3 clock polarity. */
183 PDM_Ch2ClkPolarity_Typedef ch2ClkPolarity; /**< Ch 2 clock polarity. */
184 #endif
185 PDM_Ch1ClkPolarity_Typedef ch1ClkPolarity; /**< Ch 1 clock polarity. */
186 PDM_Ch0ClkPolarity_Typedef ch0ClkPolarity; /**< Ch 0 clock polarity. */
187 #if defined(PDM_CFG0_NUMCH_THREE)
188 bool enableCh2Ch3Stereo; /**< Enable stereo mode for channel pair CH2 and CH3. */
189 #endif
190 bool enableCh0Ch1Stereo; /**< Enable stereo mode for channel pair CH0 and CH1. */
191 PDM_FifoValidWatermark_Typedef fifoValidWatermark; /**< FIFO Data valid level water-mark. */
192 PDM_DataFormat_TypeDef dataFormat; /**< PDM filter data output format. */
193 PDM_NumberOfChannels_TypeDef numChannels; /**< Number of PDM channels. */
194 PDM_FilterOrder_TypeDef filterOrder; /**< PDM filter order. */
195 uint32_t prescaler; /**< PDM clock prescaler, resulting PDM clock is input clock / (prescaler + 1). */
196 } PDM_Init_TypeDef;
197
198 /*******************************************************************************
199 ************************** STRUCT INITIALIZERS ****************************
200 ******************************************************************************/
201
202 /** Default configuration for PDM.
203 * Stereo Ch0/1, 16bit samples, 44,100 Hz sampling rate,
204 * 32 times oversampling (requires 1,411,209 Hz PDM clock).
205 */
206 #if defined(PDM_CFG0_NUMCH_THREE)
207 #define PDM_INIT_DEFAULT \
208 { \
209 true, /* Start PDM filter after initialization. */ \
210 true, /* Enable PDM clock. */ \
211 32U, /* PDM down sampling rate. */ \
212 5U, /* PDM gain. */ \
213 pdmCh3ClkPolarityRisingEdge, /* N/A. */ \
214 pdmCh2ClkPolarityRisingEdge, /* N/A. */ \
215 pdmCh1ClkPolarityFallingEdge, /* Input data clocked on falling clock edge. */ \
216 pdmCh0ClkPolarityRisingEdge, /* Input data clocked on rising clock edge. */ \
217 false, /* N/A. */ \
218 true, /* Enable stereo mode for channel pair CH0 and CH1. */ \
219 pdmFifoValidWatermarkOne, /* At least one word water-mark level. */ \
220 pdmDataFormatDouble16, /* Two 16-bit samples per FIFO entry. */ \
221 pdmNumberOfChannelsTwo, /* Two Channels. */ \
222 pdmFilterOrderFifth, /* Fifth order filter. */ \
223 0U /* No clock prescaling. */ \
224 }
225 #else
226 #define PDM_INIT_DEFAULT \
227 { \
228 true, /* Start PDM filter after initialization. */ \
229 32U, /* PDM down sampling rate. */ \
230 5U, /* PDM gain. */ \
231 pdmCh1ClkPolarityFallingEdge, /* Input data clocked on falling clock edge. */ \
232 pdmCh0ClkPolarityRisingEdge, /* Input data clocked on rising clock edge. */ \
233 true, /* Enable stereo mode for channel pair CH0 and CH1. */ \
234 pdmFifoValidWatermarkOne, /* At least one word water-mark level. */ \
235 pdmDataFormatDouble16, /* Two 16-bit samples per FIFO entry. */ \
236 pdmNumberOfChannelsTwo, /* Two Channels. */ \
237 pdmFilterOrderFifth, /* Fifth order filter. */ \
238 0U /* No clock prescaling. */ \
239 }
240 #endif
241
242 /*******************************************************************************
243 ***************************** PROTOTYPES **********************************
244 ******************************************************************************/
245
246 void PDM_DeInit(PDM_TypeDef *pdm);
247 void PDM_Init(PDM_TypeDef *pdm, const PDM_Init_TypeDef *init);
248 void PDM_Reset(PDM_TypeDef *pdm);
249
250 /***************************************************************************//**
251 * @brief
252 * Clear the PDM filter.
253 *
254 * @param[in] pdm
255 * A pointer to the PDM peripheral register block.
256 ******************************************************************************/
PDM_Clear(PDM_TypeDef * pdm)257 __STATIC_INLINE void PDM_Clear(PDM_TypeDef *pdm)
258 {
259 while (pdm->SYNCBUSY != 0U) {
260 // Wait for any pending CMD synchronization
261 }
262 pdm->CMD = PDM_CMD_CLEAR;
263 }
264
265 /***************************************************************************//**
266 * @brief
267 * Flush the PDM sample FIFO.
268 *
269 * @param[in] pdm
270 * A pointer to the PDM peripheral register block.
271 ******************************************************************************/
PDM_FifoFlush(PDM_TypeDef * pdm)272 __STATIC_INLINE void PDM_FifoFlush(PDM_TypeDef *pdm)
273 {
274 while (pdm->SYNCBUSY != 0U) {
275 // Wait for any pending CMD synchronization
276 }
277 pdm->CMD = PDM_CMD_FIFOFL;
278 }
279
280 /***************************************************************************//**
281 * @brief
282 * Clear one or more pending PDM interrupts.
283 *
284 * @param[in] pdm
285 * A pointer to the PDM peripheral register block.
286 *
287 * @param[in] flags
288 * Pending PDM interrupt sources to clear. Use one or more valid
289 * interrupt flags for the PDM module. The flags are PDM_IFC_DV,
290 * PDM_IFC_DVL, PDM_IFC_OF and PDM_IFC_UF.
291 ******************************************************************************/
PDM_IntClear(PDM_TypeDef * pdm,uint32_t flags)292 __STATIC_INLINE void PDM_IntClear(PDM_TypeDef *pdm, uint32_t flags)
293 {
294 #if defined(PDM_HAS_SET_CLEAR)
295 pdm->IF_CLR = flags;
296 #else
297 pdm->IFC = flags;
298 #endif
299 }
300
301 /***************************************************************************//**
302 * @brief
303 * Disable one or more PDM interrupts.
304 *
305 * @param[in] pdm
306 * A pointer to the PDM peripheral register block.
307 *
308 * @param[in] flags
309 * PDM interrupt sources to disable. Use one or more valid
310 * interrupt flags for the PDM module. The flags are PDM_IEN_DV,
311 * PDM_IEN_DVL, PDM_IEN_OF and PDM_IEN_UF.
312 ******************************************************************************/
PDM_IntDisable(PDM_TypeDef * pdm,uint32_t flags)313 __STATIC_INLINE void PDM_IntDisable(PDM_TypeDef *pdm, uint32_t flags)
314 {
315 pdm->IEN &= ~flags;
316 }
317
318 /***************************************************************************//**
319 * @brief
320 * Enable one or more PDM interrupts.
321 *
322 * @note
323 * Depending on the use, a pending interrupt may already be set prior to
324 * enabling the interrupt. To ignore a pending interrupt, consider using
325 * PDM_IntClear() prior to enabling the interrupt.
326 *
327 * @param[in] pdm
328 * A pointer to the PDM peripheral register block.
329 *
330 * @param[in] flags
331 * PDM interrupt sources to enable. Use one or more valid
332 * interrupt flags for the PDM module. The flags are PDM_IEN_DV,
333 * PDM_IEN_DVL, PDM_IEN_OF and PDM_IEN_UF.
334 ******************************************************************************/
PDM_IntEnable(PDM_TypeDef * pdm,uint32_t flags)335 __STATIC_INLINE void PDM_IntEnable(PDM_TypeDef *pdm, uint32_t flags)
336 {
337 pdm->IEN |= flags;
338 }
339
340 /***************************************************************************//**
341 * @brief
342 * Get pending PDM interrupt flags.
343 *
344 * @note
345 * Event bits are not cleared by the use of this function.
346 *
347 * @param[in] pdm
348 * A pointer to the PDM peripheral register block.
349 *
350 * @return
351 * PDM interrupt sources pending. Returns one or more valid
352 * interrupt flags for PDM module. The flags are PDM_IF_DV,
353 * PDM_IF_DVL, PDM_IF_OF and PDM_IF_UF.
354 ******************************************************************************/
PDM_IntGet(PDM_TypeDef * pdm)355 __STATIC_INLINE uint32_t PDM_IntGet(PDM_TypeDef *pdm)
356 {
357 return pdm->IF;
358 }
359
360 /***************************************************************************//**
361 * @brief
362 * Get enabled and pending PDM interrupt flags.
363 * Useful for handling more interrupt sources in the same interrupt handler.
364 *
365 * @note
366 * Interrupt flags are not cleared by the use of this function.
367 *
368 * @param[in] pdm
369 * A pointer to the PDM peripheral register block.
370 *
371 * @return
372 * Pending and enabled PDM interrupt sources
373 * Return value is the bitwise AND of
374 * - the enabled interrupt sources in PDM_IEN and
375 * - the pending interrupt flags PDM_IF
376 ******************************************************************************/
PDM_IntGetEnabled(PDM_TypeDef * pdm)377 __STATIC_INLINE uint32_t PDM_IntGetEnabled(PDM_TypeDef *pdm)
378 {
379 uint32_t ien;
380
381 ien = pdm->IEN;
382 return pdm->IF & ien;
383 }
384
385 /***************************************************************************//**
386 * @brief
387 * Set one or more pending PDM interrupts.
388 *
389 * @param[in] pdm
390 * A pointer to the PDM peripheral register block.
391 *
392 * @param[in] flags
393 * PDM interrupt sources to set to pending. Use one or more valid
394 * interrupt flags for the PDM module. The flags are PDM_IFS_DV,
395 * PDM_IFS_DVL, PDM_IFS_OF and PDM_IFS_UF.
396 ******************************************************************************/
PDM_IntSet(PDM_TypeDef * pdm,uint32_t flags)397 __STATIC_INLINE void PDM_IntSet(PDM_TypeDef *pdm, uint32_t flags)
398 {
399 #if defined(PDM_HAS_SET_CLEAR)
400 pdm->IF_SET = flags;
401 #else
402 pdm->IFS = flags;
403 #endif
404 }
405
406 /***************************************************************************//**
407 * @brief
408 * Read one entry from the PDM FIFO.
409 *
410 * @note
411 * This function will wait until a sample is available in the FIFO.
412 * Depending on PDM configuration, a FIFO entry can consist of one or two
413 * samples.
414 *
415 * @param[in] pdm
416 * A pointer to the PDM peripheral register block.
417 *
418 * @return
419 * The entry read from the FIFO.
420 ******************************************************************************/
PDM_Rx(PDM_TypeDef * pdm)421 __STATIC_INLINE uint32_t PDM_Rx(PDM_TypeDef *pdm)
422 {
423 while ((pdm->STATUS & PDM_STATUS_EMPTY) == PDM_STATUS_EMPTY) {
424 // Wait for data in FIFO
425 }
426 return pdm->RXDATA;
427 }
428
429 /***************************************************************************//**
430 * @brief
431 * Start the PDM operation (start the PDM filter).
432 *
433 * @param[in] pdm
434 * A pointer to the PDM peripheral register block.
435 ******************************************************************************/
PDM_Start(PDM_TypeDef * pdm)436 __STATIC_INLINE void PDM_Start(PDM_TypeDef *pdm)
437 {
438 while (pdm->SYNCBUSY != 0U) {
439 // Wait for any pending CMD synchronization
440 }
441 pdm->CMD = PDM_CMD_START;
442 }
443
444 /***************************************************************************//**
445 * @brief
446 * Get the PDM STATUS register.
447 *
448 * @param[in] pdm
449 * A pointer to the PDM peripheral register block.
450 *
451 * @return
452 * STATUS register value.
453 ******************************************************************************/
PDM_StatusGet(PDM_TypeDef * pdm)454 __STATIC_INLINE uint32_t PDM_StatusGet(PDM_TypeDef *pdm)
455 {
456 return pdm->STATUS;
457 }
458
459 /***************************************************************************//**
460 * @brief
461 * Stop the PDM operation (stop the PDM filter).
462 *
463 * @param[in] pdm
464 * A pointer to the PDM peripheral register block.
465 ******************************************************************************/
PDM_Stop(PDM_TypeDef * pdm)466 __STATIC_INLINE void PDM_Stop(PDM_TypeDef *pdm)
467 {
468 while (pdm->SYNCBUSY != 0U) {
469 // Wait for any pending CMD synchronization
470 }
471 pdm->CMD = PDM_CMD_STOP;
472 }
473
474 /** @} (end addtogroup pdm) */
475
476 #ifdef __cplusplus
477 }
478 #endif
479
480 #endif // defined(PDM_PRESENT) && (PDM_COUNT == 1)
481 #endif // EM_PDM_H
482