From 05b5169edfb6b6f5d1bbca4c0d3bc2f7671a5b7a Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 18 Feb 2020 15:04:36 +0100 Subject: [PATCH 1/4] drivers/periph: introduce PDM peripheral interface --- drivers/include/periph/pdm.h | 127 +++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 drivers/include/periph/pdm.h diff --git a/drivers/include/periph/pdm.h b/drivers/include/periph/pdm.h new file mode 100644 index 000000000000..eaa885b8a7cb --- /dev/null +++ b/drivers/include/periph/pdm.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_periph_pdm Pulse Density Modulation (PDM) driver + * @ingroup drivers_periph + * @brief Low-level Pulse Density Modulation (PDM) driver + * + * @{ + * @file + * + * @author Alexandre Abadie + * + */ + +#ifndef PERIPH_PDM_H +#define PERIPH_PDM_H + +#include + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default PDM mode values + * @{ + */ +#ifndef HAVE_PDM_MODE_T +typedef enum { + PDM_MODE_MONO = 0, /**< Mono mode */ + PDM_MODE_STEREO, /**< Stereo mode */ +} pdm_mode_t; +#endif +/** @} */ + +/** + * @brief Default PDM sampling rate values + * @{ + */ +#ifndef HAVE_PDM_SAMPLE_RATE_T +typedef enum { + PDM_SAMPLE_RATE_16KHZ = 0, /**< 16kHz */ + PDM_SAMPLE_RATE_20KHZ, /**< 20kHz */ + PDM_SAMPLE_RATE_41KHZ, /**< 41.6kHz */ + PDM_SAMPLE_RATE_50KHZ, /**< 50kHz */ + PDM_SAMPLE_RATE_60KHZ, /**< 60kHz */ +} pdm_sample_rate_t; +#endif +/** @} */ + +/** + * @brief Default PDM min gain values (in dB) + */ +#ifndef PDM_GAIN_MIN +#define PDM_GAIN_MIN (-20) +#endif + +/** + * @brief Default PDM max gain values (in dB) + */ +#ifndef PDM_GAIN_MAX +#define PDM_GAIN_MAX (20) +#endif + +/** + * @brief Default PDM samples frame buffer size + */ +#ifndef PDM_BUF_SIZE +#define PDM_BUF_SIZE (128U) +#endif + +/** + * @brief Signature for data received interrupt callback + * + * @param[in] arg context to the callback (optional) + * @param[in] buf the buffer containing the current samples frame + */ +typedef void(*pdm_data_cb_t)(void *arg, int16_t *buf); + +/** + * @brief Interrupt context for a PDM device + */ +#ifndef HAVE_PDM_ISR_CTX_T +typedef struct { + pdm_data_cb_t cb; /**< data received interrupt callback */ + void *arg; /**< argument to both callback routines */ +} pdm_isr_ctx_t; +#endif + +/** + * @brief Initialize the PDM peripheral + * + * @param[in] rate sample rate + * @param[in] gain gain + * @param[in] cb data received callback function + * @param[in] arg context passed to the callback function + * + * @return 0 on successful initialization + * @return <0 on error + */ +int pdm_init(pdm_mode_t mode, pdm_sample_rate_t rate, int8_t gain, + pdm_data_cb_t cb, void *arg); + +/** + * @brief Start the PDM peripheral + */ +void pdm_start(void); + +/** + * @brief Stop the PDM peripheral + */ +void pdm_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif /* PERIPH_PDM_H */ +/** @} */ From a6b4d35c586f7df975072e9b4b2a617f82581fa8 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 18 Feb 2020 15:05:15 +0100 Subject: [PATCH 2/4] cpu/nrf52: add PDM peripheral driver --- cpu/nrf52/include/periph_cpu.h | 9 ++ cpu/nrf52/periph/pdm.c | 167 +++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 cpu/nrf52/periph/pdm.c diff --git a/cpu/nrf52/include/periph_cpu.h b/cpu/nrf52/include/periph_cpu.h index 6725e16d5d7e..c4a67b39b2cc 100644 --- a/cpu/nrf52/include/periph_cpu.h +++ b/cpu/nrf52/include/periph_cpu.h @@ -257,6 +257,15 @@ void spi_twi_irq_register_spi(NRF_SPIM_Type *bus, */ void spi_twi_irq_register_i2c(NRF_TWIM_Type *bus, spi_twi_irq_cb_t cb, void *arg); + +/** + * @brief Structure for PDM configuration data + */ +typedef struct { + uint8_t din_pin; /**< DIN pin */ + uint8_t clk_pin; /**< CLK pin */ +} pdm_conf_t; + #ifdef __cplusplus } #endif diff --git a/cpu/nrf52/periph/pdm.c b/cpu/nrf52/periph/pdm.c new file mode 100644 index 000000000000..5d7e3f85db74 --- /dev/null +++ b/cpu/nrf52/periph/pdm.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_nrf52 + * @{ + * + * @file + * @brief Implementation of the peripheral PDM interface + * + * @author Alexandre Abadie + * + * @} + */ + +#include +#include +#include + +#include "cpu.h" +#include "periph/gpio.h" +#include "periph/pdm.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* The samples buffer is a double buffer */ +int16_t _pdm_buf[PDM_BUF_SIZE * 2] = { 0 }; +static uint8_t _pdm_current_buf = 0; +static pdm_isr_ctx_t isr_ctx; + +int pdm_init(pdm_mode_t mode, pdm_sample_rate_t rate, int8_t gain, + pdm_data_cb_t cb, void *arg) +{ + if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { + NRF_CLOCK->TASKS_HFCLKSTART = 1; + while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) {} + } + + /* Configure sampling rate */ + switch (rate) { + case PDM_SAMPLE_RATE_16KHZ: +#ifdef CPU_MODEL_NRF52840XXAA + NRF_PDM->RATIO = ((PDM_RATIO_RATIO_Ratio80 << PDM_RATIO_RATIO_Pos) & PDM_RATIO_RATIO_Msk); + NRF_PDM->PDMCLKCTRL = PDM_PDMCLKCTRL_FREQ_1280K; +#else + NRF_PDM->PDMCLKCTRL = PDM_PDMCLKCTRL_FREQ_Default; /* 1.033MHz */ +#endif + break; + case PDM_SAMPLE_RATE_20KHZ: + NRF_PDM->PDMCLKCTRL = 0x0A000000; /* CLK= 1.280 MHz (32 MHz / 25) => rate= 20000 Hz */ + break; + case PDM_SAMPLE_RATE_41KHZ: + NRF_PDM->PDMCLKCTRL = 0x15000000; /* CLK= 2.667 MHz (32 MHz / 12) => rate= 41667 Hz */ + break; + case PDM_SAMPLE_RATE_50KHZ: + NRF_PDM->PDMCLKCTRL = 0x19000000; /* CLK= 3.200 MHz (32 MHz / 10) => rate= 50000 Hz */ + break; + case PDM_SAMPLE_RATE_60KHZ: + NRF_PDM->PDMCLKCTRL = 0x20000000; /* CLK= 4.000 MHz (32 MHz / 8) => rate= 60000 Hz */ + break; + default: + DEBUG("[pdm] init: sample rate not supported\n"); + return -ENOTSUP; + } + + /* Configure mode (Mono or Stereo) */ + switch (mode) { + case PDM_MODE_MONO: + NRF_PDM->MODE = PDM_MODE_OPERATION_Mono; + break; + case PDM_MODE_STEREO: + NRF_PDM->MODE = PDM_MODE_OPERATION_Stereo; + break; + default: + DEBUG("[pdm] init: mode not supported\n"); + return -ENOTSUP; + } + + /* Configure gain */ + if (gain > PDM_GAIN_MAX) { + gain = PDM_GAIN_MAX; + } + + if (gain < PDM_GAIN_MIN) { + gain = PDM_GAIN_MIN; + } + + NRF_PDM->GAINR = (gain << 1) + 40; + NRF_PDM->GAINL = (gain << 1) + 40; + + /* Configure CLK and DIN pins */ + gpio_init(pdm_config.clk_pin, GPIO_OUT); + gpio_clear(pdm_config.clk_pin); + gpio_init(pdm_config.din_pin, GPIO_IN); + + NRF_PDM->PSEL.CLK = pdm_config.clk_pin; + NRF_PDM->PSEL.DIN = pdm_config.din_pin; + + /* clear pending events */ + NRF_PDM->EVENTS_STARTED = 0; + NRF_PDM->EVENTS_STOPPED = 0; + NRF_PDM->EVENTS_END = 0; + + /* Enable end/started/stopped events */ + NRF_PDM->INTENSET = ((PDM_INTEN_END_Enabled << PDM_INTEN_END_Pos) | + (PDM_INTEN_STARTED_Enabled << PDM_INTEN_STARTED_Pos) | + (PDM_INTEN_STOPPED_Enabled << PDM_INTEN_STOPPED_Pos)); + + /* Configure internal RAM buffer size, divide by 2 for stereo mode */ + NRF_PDM->SAMPLE.MAXCNT = (PDM_BUF_SIZE >> mode); + + isr_ctx.cb = cb; + isr_ctx.arg = arg; + + /* enable interrupt */ + NVIC_EnableIRQ(PDM_IRQn); + + /* Enable PDM */ + NRF_PDM->ENABLE = (PDM_ENABLE_ENABLE_Enabled << PDM_ENABLE_ENABLE_Pos); + + return 0; +} + +void pdm_start(void) +{ + NRF_PDM->SAMPLE.PTR = (uint32_t)_pdm_buf; + DEBUG("[PDM] MAXCNT: %lu\n", NRF_PDM->SAMPLE.MAXCNT); + + NRF_PDM->TASKS_START = 1; +} + +void pdm_stop(void) +{ + NRF_PDM->TASKS_STOP = 1; +} + +void isr_pdm(void) +{ + if (NRF_PDM->EVENTS_STARTED == 1) { + NRF_PDM->EVENTS_STARTED = 0; + uint8_t next_buf_pos = (_pdm_current_buf + 1) & 0x1; + NRF_PDM->SAMPLE.PTR = (uint32_t)&_pdm_buf[next_buf_pos * (PDM_BUF_SIZE >> 1)]; + } + + if (NRF_PDM->EVENTS_STOPPED == 1) { + NRF_PDM->EVENTS_STOPPED = 0; + _pdm_current_buf = 0; + } + + if (NRF_PDM->EVENTS_END == 1) { + NRF_PDM->EVENTS_END = 0; + + /* Process received samples frame */ + isr_ctx.cb(isr_ctx.arg, &_pdm_buf[_pdm_current_buf * (PDM_BUF_SIZE >> 1)]); + + /* Set next buffer */ + _pdm_current_buf = (_pdm_current_buf + 1) & 0x1; + } + + cortexm_isr_end(); +} From f6fcadc0c19834a178bb21466e78d69ec8b6f4fd Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 18 Feb 2020 15:05:49 +0100 Subject: [PATCH 3/4] tests/periph_pdm: add test application --- tests/periph_pdm/Makefile | 5 +++ tests/periph_pdm/main.c | 85 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/periph_pdm/Makefile create mode 100644 tests/periph_pdm/main.c diff --git a/tests/periph_pdm/Makefile b/tests/periph_pdm/Makefile new file mode 100644 index 000000000000..c79730e568e0 --- /dev/null +++ b/tests/periph_pdm/Makefile @@ -0,0 +1,5 @@ +include ../Makefile.tests_common + +FEATURES_REQUIRED = periph_pdm + +include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_pdm/main.c b/tests/periph_pdm/main.c new file mode 100644 index 000000000000..27384b45aaba --- /dev/null +++ b/tests/periph_pdm/main.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Low-level PDM driver test + * + * @author Alexandre Abadie + * + * @} + */ + +#include +#include + +#include "msg.h" +#include "thread.h" +#include "stdio_base.h" + +#include "periph/pdm.h" + +static kernel_pid_t _main_thread_pid; + +#ifndef PDM_DATA_PRINT_BINARY +#define PDM_DATA_PRINT_BINARY (0) +#endif + +#ifndef PDM_TEST_MODE +#define PDM_TEST_MODE (PDM_MODE_MONO) +#endif + +#ifndef PDM_TEST_SAMPLE_RATE +#define PDM_TEST_SAMPLE_RATE (PDM_SAMPLE_RATE_16KHZ) +#endif + +#ifndef PDM_TEST_GAIN +#define PDM_TEST_GAIN (-10) +#endif + +static void _pdm_cb(void *arg, int16_t *buf) +{ + (void)arg; + msg_t msg; + msg.content.ptr = buf; + msg_send(&msg, _main_thread_pid); +} + +int main(void) +{ +#if !PDM_DATA_PRINT_BINARY + puts("PDM peripheral driver test\n"); +#endif + + if (pdm_init(PDM_TEST_MODE, PDM_TEST_SAMPLE_RATE, PDM_TEST_GAIN, _pdm_cb, NULL) < 0) { + puts("Failed to initialize PDM peripheral"); + return 1; + } + + pdm_start(); + + _main_thread_pid = thread_getpid(); + + while (1) { + msg_t msg; + msg_receive(&msg); + int16_t *buf = (int16_t *)msg.content.ptr; +#if PDM_DATA_PRINT_BINARY + stdio_write((uint8_t *)buf, PDM_BUF_SIZE >> 2); +#else + for (unsigned idx = 0; idx < PDM_BUF_SIZE; ++idx) { + printf("%i\n", buf[idx]); + } +#endif + } + + return 0; +} From 4fd8bd11961184b4f0da87d49a86ce6e191d41c6 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 19 Feb 2020 10:01:44 +0100 Subject: [PATCH 4/4] boards/adafruit-clue: configure PDM peripheral --- boards/adafruit-clue/Makefile.features | 1 + boards/adafruit-clue/include/periph_conf.h | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/boards/adafruit-clue/Makefile.features b/boards/adafruit-clue/Makefile.features index 1f4969f6d974..c53d9acb3f92 100644 --- a/boards/adafruit-clue/Makefile.features +++ b/boards/adafruit-clue/Makefile.features @@ -2,6 +2,7 @@ CPU_MODEL = nrf52840xxaa # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_i2c +FEATURES_PROVIDED += periph_pdm FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_uart FEATURES_PROVIDED += periph_usbdev diff --git a/boards/adafruit-clue/include/periph_conf.h b/boards/adafruit-clue/include/periph_conf.h index 0cd987c03e6f..c75a45b58213 100644 --- a/boards/adafruit-clue/include/periph_conf.h +++ b/boards/adafruit-clue/include/periph_conf.h @@ -95,6 +95,16 @@ static const spi_conf_t spi_config[] = { #define SPI_NUMOF ARRAY_SIZE(spi_config) /** @} */ +/** + * @name PDM configuration + * @{ + */ +static const pdm_conf_t pdm_config = { + .din_pin = GPIO_PIN(0, 0), + .clk_pin = GPIO_PIN(0, 1), +}; +/** @} */ + #ifdef __cplusplus } #endif