From 9493a7c6c427b012bc771d89b9fca25984ff9be1 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 18 Feb 2020 15:04:36 +0100 Subject: [PATCH 01/10] 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 53823238ec55db2f96fd6077973868727527bd29 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 18 Feb 2020 15:05:15 +0100 Subject: [PATCH 02/10] 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 32c24a8a077c..0018d82d50e8 100644 --- a/cpu/nrf52/include/periph_cpu.h +++ b/cpu/nrf52/include/periph_cpu.h @@ -116,6 +116,7 @@ void spi_twi_irq_register_i2c(NRF_TWIM_Type *bus, spi_twi_irq_cb_t cb, void *arg); /** + * @brief Acquire the shared I2C/SPI peripheral in I2C mode * * @param bus bus to acquire exclusive access on @@ -146,6 +147,14 @@ void nrf5x_spi_acquire(NRF_SPIM_Type *bus, spi_twi_irq_cb_t cb, void *arg); * @param bus bus to release exclusive access on */ void nrf5x_spi_release(NRF_SPIM_Type *bus); + + /** + * @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 } 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 6c7521b79e7dcb8ad0d30c23b05ea682e6a52dfe Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Tue, 18 Feb 2020 15:05:49 +0100 Subject: [PATCH 03/10] 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 d912816c2aede77604da77b05227603d98a4f8f8 Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Wed, 19 Feb 2020 10:01:44 +0100 Subject: [PATCH 04/10] 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 fcabda7ef2a6..a0e486ee6c65 100644 --- a/boards/adafruit-clue/include/periph_conf.h +++ b/boards/adafruit-clue/include/periph_conf.h @@ -115,6 +115,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 From 0d562f473899dc27d01b91bf533730866b142800 Mon Sep 17 00:00:00 2001 From: Bahareh Date: Tue, 26 Mar 2024 17:28:15 +0100 Subject: [PATCH 05/10] wip --- .../feather-nrf52840-sense/Makefile.features | 1 + .../include/periph_conf.h | 10 ++ cpu/nrf52/include/periph_cpu.h | 2 +- cpu/nrf52/periph/pdm.c | 15 ++- drivers/include/periph/pdm.h | 10 +- tests/periph_pdm/Makefile | 16 +++ tests/periph_pdm/larger_buff_size.py | 117 ++++++++++++++++++ tests/periph_pdm/main.c | 70 ++++++++--- 8 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 tests/periph_pdm/larger_buff_size.py diff --git a/boards/feather-nrf52840-sense/Makefile.features b/boards/feather-nrf52840-sense/Makefile.features index 1f4969f6d974..c53d9acb3f92 100644 --- a/boards/feather-nrf52840-sense/Makefile.features +++ b/boards/feather-nrf52840-sense/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/feather-nrf52840-sense/include/periph_conf.h b/boards/feather-nrf52840-sense/include/periph_conf.h index 27df184347ee..3367507ef758 100644 --- a/boards/feather-nrf52840-sense/include/periph_conf.h +++ b/boards/feather-nrf52840-sense/include/periph_conf.h @@ -85,6 +85,16 @@ static const i2c_conf_t i2c_config[] = { #define I2C_NUMOF ARRAY_SIZE(i2c_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 diff --git a/cpu/nrf52/include/periph_cpu.h b/cpu/nrf52/include/periph_cpu.h index 0018d82d50e8..36d5f7d452d4 100644 --- a/cpu/nrf52/include/periph_cpu.h +++ b/cpu/nrf52/include/periph_cpu.h @@ -155,7 +155,7 @@ 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 index 5d7e3f85db74..3342fee1fa6d 100644 --- a/cpu/nrf52/periph/pdm.c +++ b/cpu/nrf52/periph/pdm.c @@ -34,8 +34,7 @@ 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) +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; @@ -46,6 +45,7 @@ int pdm_init(pdm_mode_t mode, pdm_sample_rate_t rate, int8_t gain, switch (rate) { case PDM_SAMPLE_RATE_16KHZ: #ifdef CPU_MODEL_NRF52840XXAA +//#ifdef PDM_PDMCLKCTRL_FREQ_1280K NRF_PDM->RATIO = ((PDM_RATIO_RATIO_Ratio80 << PDM_RATIO_RATIO_Pos) & PDM_RATIO_RATIO_Msk); NRF_PDM->PDMCLKCTRL = PDM_PDMCLKCTRL_FREQ_1280K; #else @@ -112,8 +112,8 @@ int pdm_init(pdm_mode_t mode, pdm_sample_rate_t rate, int8_t gain, (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); + /* Configure Length of DMA RAM allocation in number of samples */ + NRF_PDM->SAMPLE.MAXCNT = (PDM_BUF_SIZE); isr_ctx.cb = cb; isr_ctx.arg = arg; @@ -142,22 +142,25 @@ void pdm_stop(void) void isr_pdm(void) { + /* PDM transfer has started */ 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)]; + NRF_PDM->SAMPLE.PTR = (uint32_t)&_pdm_buf[next_buf_pos * (PDM_BUF_SIZE)]; } + /* PDM transfer has finished */ if (NRF_PDM->EVENTS_STOPPED == 1) { NRF_PDM->EVENTS_STOPPED = 0; _pdm_current_buf = 0; } + /* Address resolution procedure has completed*/ 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)]); + isr_ctx.cb(isr_ctx.arg, &_pdm_buf[_pdm_current_buf * (PDM_BUF_SIZE)]); /* Set next buffer */ _pdm_current_buf = (_pdm_current_buf + 1) & 0x1; diff --git a/drivers/include/periph/pdm.h b/drivers/include/periph/pdm.h index eaa885b8a7cb..072f60c9913a 100644 --- a/drivers/include/periph/pdm.h +++ b/drivers/include/periph/pdm.h @@ -77,6 +77,9 @@ typedef enum { #define PDM_BUF_SIZE (128U) #endif +// Define the new buffer size +#define NEW_BUF_SIZE (PDM_BUF_SIZE * 5 * 16 * 16) + /** * @brief Signature for data received interrupt callback * @@ -97,7 +100,7 @@ typedef struct { /** * @brief Initialize the PDM peripheral - * + * @param[in] mode mode (Mono or Stereo) * @param[in] rate sample rate * @param[in] gain gain * @param[in] cb data received callback function @@ -106,8 +109,7 @@ typedef struct { * @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); +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 @@ -120,7 +122,7 @@ void pdm_start(void); void pdm_stop(void); #ifdef __cplusplus -} + #endif #endif /* PERIPH_PDM_H */ diff --git a/tests/periph_pdm/Makefile b/tests/periph_pdm/Makefile index c79730e568e0..1fddcc7f5698 100644 --- a/tests/periph_pdm/Makefile +++ b/tests/periph_pdm/Makefile @@ -1,5 +1,21 @@ +# name of your application +APPLICATION = shell + +BOARD ?= samr21-xpro include ../Makefile.tests_common +CFLAGS +=-DPDM_BUF_SIZE=\(64\) + FEATURES_REQUIRED = periph_pdm +# use the shell module +USEMODULE += shell + +USEMODULE += ztimer_msec + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_pdm/larger_buff_size.py b/tests/periph_pdm/larger_buff_size.py new file mode 100644 index 000000000000..c8c1f562a438 --- /dev/null +++ b/tests/periph_pdm/larger_buff_size.py @@ -0,0 +1,117 @@ +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +import re +import numpy as np +import os +import csv +from itertools import groupby +from operator import itemgetter +import serial +import struct +import wave +import time + +str1 = "Start of the new buffer".encode('ascii') +str2= "End of the new buffer".encode('ascii') + +start_index_buf = 0 +stop_index_buf = 0 +start_found = False +buffer_number = 0 +start = True + +ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1) +data = {} +key = 0 + +ser.writelines(["start\n".encode('ascii')]) +while start: + line = ser.readline() + #print(line) + value = line.strip() + data[key] = value + if line.find(str1) != -1: + #print("Start Recording") + start_found = True + start_index_buf = key + if line.find(str2) != -1: + + if start_found: + stop_index_buf = key + buffer_number = stop_index_buf - start_index_buf - 2 + start = False + #print("Stop recording") + break + key +=1 + +print(start_index_buf) +print(stop_index_buf) +print(buffer_number) +ser.close() + +# Extract the data within the specified range +data_to_plot = [int(data[i]) for i in range(start_index_buf+1, stop_index_buf)] +data_to_bytes = [int(data[i]).to_bytes(2,byteorder="little",signed=True) for i in range(start_index_buf+1, stop_index_buf)] +data_to_export = [(data[i]) for i in range(start_index_buf+1, stop_index_buf)] +#print(data_to_bytes) + +# Save the data to 16-b file +# Open a file in binary write mode +with open("binary_data.bin", "wb") as f: + for binary_data in data_to_bytes: + # Write the binary data to the file + f.write(binary_data) + +# Sample rate (Hz) +sample_rate = 16000 + +# Open a new wave file +with wave.open("output_wave.wav", "w") as output_wave: + # Set parameters for the wave file + output_wave.setnchannels(1) # Mono + output_wave.setsampwidth(2) # 2 bytes per sample for 16-bit audio + output_wave.setframerate(sample_rate) + + # Convert data_to_plot to numpy array + data_np = np.array(data_to_bytes) + + # Convert data to bytes and write to the wave file + output_wave.writeframes(data_np) + +# Sample rate (Hz) +samplerate = 16000 +length = 1 + +# The left channel for 1 second. +t = np.linspace(0, length, samplerate) +left_channel = 0.5 * np.sin(2 * np.pi * 440.0 * t) + +# Noise on the right channel. +#right_channel = np.random.random(size=samplerate) +left_channel = data_to_plot/np.max(np.abs(data_to_plot)) +audio = np.array([left_channel]).T + +# Convert to (little-endian) 16 bit integers. +audio = (audio * (2 ** 15 - 1)).astype(" #include +#include #include "msg.h" #include "thread.h" #include "stdio_base.h" - +#include "shell.h" #include "periph/pdm.h" +#include "ztimer.h" -static kernel_pid_t _main_thread_pid; +static kernel_pid_t _main_thread_pid = KERNEL_PID_UNDEF; #ifndef PDM_DATA_PRINT_BINARY #define PDM_DATA_PRINT_BINARY (0) @@ -45,41 +47,79 @@ static kernel_pid_t _main_thread_pid; #define PDM_TEST_GAIN (-10) #endif +int16_t new_buf[NEW_BUF_SIZE]; + static void _pdm_cb(void *arg, int16_t *buf) { (void)arg; msg_t msg; msg.content.ptr = buf; - msg_send(&msg, _main_thread_pid); + if (_main_thread_pid != KERNEL_PID_UNDEF) + { + msg_send(&msg, _main_thread_pid); + } } -int main(void) +int start_recording_cmd(int argc, char **argv) { -#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"); + if (argc != 1) { + puts("Note: Start Recording \n"); return 1; } - - pdm_start(); - + (void)argv; _main_thread_pid = thread_getpid(); +#if !PDM_DATA_PRINT_BINARY + //puts("PDM peripheral driver test\n"); +#endif - while (1) { +/* msg_t temp; + for (unsigned i = 0; i<1; i++) + { + msg_receive(&temp); + } */ + for (unsigned repeat = 0; repeat < NEW_BUF_SIZE / PDM_BUF_SIZE; repeat++) { 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 + /* printf("Start of the buffer\n"); for (unsigned idx = 0; idx < PDM_BUF_SIZE; ++idx) { printf("%i\n", buf[idx]); + buf_size = idx; + } + printf("End of the buffer\n"); + printf("%d\n", buf_size); */ + + // Copy PDM_BUF_SIZE to NEW_BUF_SIZE repeatedly + for (unsigned idx = 0; idx < PDM_BUF_SIZE; idx++) { + new_buf[repeat*PDM_BUF_SIZE+idx] = buf[idx]; } #endif } - + // Parse the new_buf + //pdm_stop(); + _main_thread_pid = KERNEL_PID_UNDEF; + printf("Start of the new buffer\n"); + for (unsigned idx = 0; idx < NEW_BUF_SIZE ; idx++) { + printf("%i\n", new_buf[idx]); + } + printf("End of the new buffer\n"); + printf("%d\n", NEW_BUF_SIZE); return 0; } + +SHELL_COMMAND(start,"Start Recording", start_recording_cmd); + +int main(void) +{ +char line_buf[SHELL_DEFAULT_BUFSIZE]; +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(); + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); +return 0; +} From 0fd5ee353429fb7029416a230aec4cdf51f95abc Mon Sep 17 00:00:00 2001 From: riot Date: Tue, 26 Mar 2024 19:07:54 +0100 Subject: [PATCH 06/10] wip --- tests/periph_pdm/main.c | 15 ----------- ...buff_size.py => testing_pdm_conversion.py} | 26 +++---------------- 2 files changed, 3 insertions(+), 38 deletions(-) rename tests/periph_pdm/{larger_buff_size.py => testing_pdm_conversion.py} (77%) diff --git a/tests/periph_pdm/main.c b/tests/periph_pdm/main.c index b6e54d34a0c2..140513247c41 100644 --- a/tests/periph_pdm/main.c +++ b/tests/periph_pdm/main.c @@ -69,14 +69,8 @@ int start_recording_cmd(int argc, char **argv) (void)argv; _main_thread_pid = thread_getpid(); #if !PDM_DATA_PRINT_BINARY - //puts("PDM peripheral driver test\n"); #endif -/* msg_t temp; - for (unsigned i = 0; i<1; i++) - { - msg_receive(&temp); - } */ for (unsigned repeat = 0; repeat < NEW_BUF_SIZE / PDM_BUF_SIZE; repeat++) { msg_t msg; msg_receive(&msg); @@ -84,14 +78,6 @@ int start_recording_cmd(int argc, char **argv) #if PDM_DATA_PRINT_BINARY stdio_write((uint8_t *)buf, PDM_BUF_SIZE >> 2); #else - /* printf("Start of the buffer\n"); - for (unsigned idx = 0; idx < PDM_BUF_SIZE; ++idx) { - printf("%i\n", buf[idx]); - buf_size = idx; - } - printf("End of the buffer\n"); - printf("%d\n", buf_size); */ - // Copy PDM_BUF_SIZE to NEW_BUF_SIZE repeatedly for (unsigned idx = 0; idx < PDM_BUF_SIZE; idx++) { new_buf[repeat*PDM_BUF_SIZE+idx] = buf[idx]; @@ -99,7 +85,6 @@ int start_recording_cmd(int argc, char **argv) #endif } // Parse the new_buf - //pdm_stop(); _main_thread_pid = KERNEL_PID_UNDEF; printf("Start of the new buffer\n"); for (unsigned idx = 0; idx < NEW_BUF_SIZE ; idx++) { diff --git a/tests/periph_pdm/larger_buff_size.py b/tests/periph_pdm/testing_pdm_conversion.py similarity index 77% rename from tests/periph_pdm/larger_buff_size.py rename to tests/periph_pdm/testing_pdm_conversion.py index c8c1f562a438..bf60805477b3 100644 --- a/tests/periph_pdm/larger_buff_size.py +++ b/tests/periph_pdm/testing_pdm_conversion.py @@ -14,7 +14,7 @@ str1 = "Start of the new buffer".encode('ascii') str2= "End of the new buffer".encode('ascii') -start_index_buf = 0 +start_index_buf = 0 stop_index_buf = 0 start_found = False buffer_number = 0 @@ -31,20 +31,17 @@ value = line.strip() data[key] = value if line.find(str1) != -1: - #print("Start Recording") start_found = True start_index_buf = key if line.find(str2) != -1: - if start_found: stop_index_buf = key buffer_number = stop_index_buf - start_index_buf - 2 start = False - #print("Stop recording") break key +=1 -print(start_index_buf) +print(start_index_buf) print(stop_index_buf) print(buffer_number) ser.close() @@ -52,8 +49,6 @@ # Extract the data within the specified range data_to_plot = [int(data[i]) for i in range(start_index_buf+1, stop_index_buf)] data_to_bytes = [int(data[i]).to_bytes(2,byteorder="little",signed=True) for i in range(start_index_buf+1, stop_index_buf)] -data_to_export = [(data[i]) for i in range(start_index_buf+1, stop_index_buf)] -#print(data_to_bytes) # Save the data to 16-b file # Open a file in binary write mode @@ -62,22 +57,6 @@ # Write the binary data to the file f.write(binary_data) -# Sample rate (Hz) -sample_rate = 16000 - -# Open a new wave file -with wave.open("output_wave.wav", "w") as output_wave: - # Set parameters for the wave file - output_wave.setnchannels(1) # Mono - output_wave.setsampwidth(2) # 2 bytes per sample for 16-bit audio - output_wave.setframerate(sample_rate) - - # Convert data_to_plot to numpy array - data_np = np.array(data_to_bytes) - - # Convert data to bytes and write to the wave file - output_wave.writeframes(data_np) - # Sample rate (Hz) samplerate = 16000 length = 1 @@ -94,6 +73,7 @@ # Convert to (little-endian) 16 bit integers. audio = (audio * (2 ** 15 - 1)).astype(" Date: Thu, 28 Mar 2024 16:03:44 +0100 Subject: [PATCH 07/10] wip --- boards/adafruit-clue/include/periph_conf.h | 2 +- .../include/periph_conf.h | 2 +- cpu/nrf52/include/periph_cpu.h | 4 +--- cpu/nrf52/periph/pdm.c | 2 +- dist/tools/pdm_check/README.md | 13 +++++++++++ dist/tools/pdm_check/requirements.txt | 3 +++ .../pdm_check}/testing_pdm_conversion.py | 22 +++++++------------ drivers/include/periph/pdm.h | 4 +++- 8 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 dist/tools/pdm_check/README.md create mode 100644 dist/tools/pdm_check/requirements.txt rename {tests/periph_pdm => dist/tools/pdm_check}/testing_pdm_conversion.py (85%) diff --git a/boards/adafruit-clue/include/periph_conf.h b/boards/adafruit-clue/include/periph_conf.h index a0e486ee6c65..ba5049d1934b 100644 --- a/boards/adafruit-clue/include/periph_conf.h +++ b/boards/adafruit-clue/include/periph_conf.h @@ -116,7 +116,7 @@ static const spi_conf_t spi_config[] = { /** @} */ /** - * @name PDM configuration + * @brief PDM configuration * @{ */ static const pdm_conf_t pdm_config = { diff --git a/boards/feather-nrf52840-sense/include/periph_conf.h b/boards/feather-nrf52840-sense/include/periph_conf.h index 3367507ef758..562db9306f1e 100644 --- a/boards/feather-nrf52840-sense/include/periph_conf.h +++ b/boards/feather-nrf52840-sense/include/periph_conf.h @@ -86,7 +86,7 @@ static const i2c_conf_t i2c_config[] = { /** @} */ /** - * @name PDM configuration + * @brief PDM configuration * @{ */ static const pdm_conf_t pdm_config = { diff --git a/cpu/nrf52/include/periph_cpu.h b/cpu/nrf52/include/periph_cpu.h index 36d5f7d452d4..2e1511847e25 100644 --- a/cpu/nrf52/include/periph_cpu.h +++ b/cpu/nrf52/include/periph_cpu.h @@ -116,7 +116,6 @@ void spi_twi_irq_register_i2c(NRF_TWIM_Type *bus, spi_twi_irq_cb_t cb, void *arg); /** - * @brief Acquire the shared I2C/SPI peripheral in I2C mode * * @param bus bus to acquire exclusive access on @@ -147,7 +146,7 @@ void nrf5x_spi_acquire(NRF_SPIM_Type *bus, spi_twi_irq_cb_t cb, void *arg); * @param bus bus to release exclusive access on */ void nrf5x_spi_release(NRF_SPIM_Type *bus); - + /** * @brief Structure for PDM configuration data */ @@ -155,7 +154,6 @@ 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 index 3342fee1fa6d..1630b27f7413 100644 --- a/cpu/nrf52/periph/pdm.c +++ b/cpu/nrf52/periph/pdm.c @@ -26,7 +26,7 @@ #include "periph/gpio.h" #include "periph/pdm.h" -#define ENABLE_DEBUG (0) +#define ENABLE_DEBUG 0 #include "debug.h" /* The samples buffer is a double buffer */ diff --git a/dist/tools/pdm_check/README.md b/dist/tools/pdm_check/README.md new file mode 100644 index 000000000000..ff1a60db389a --- /dev/null +++ b/dist/tools/pdm_check/README.md @@ -0,0 +1,13 @@ +# PDM Testing Report + +## About +This report outlines the testing process and results for a PDM implementation on a Feather nRF52840 Sense board to validate its functionality and performance. + +## Requirements +To run the python script, you need to install some packages listed in `requirements.txt`. + +## Usage +This repord provides insights into the testing and the corresponding results obtained: +-Testing the PDM and visualizing the results in a chart. +-Saving the output data as a wave format for Audacity. +-Comparing the output signals from Python script and that from the Audacity. diff --git a/dist/tools/pdm_check/requirements.txt b/dist/tools/pdm_check/requirements.txt new file mode 100644 index 000000000000..4ff3b47a9178 --- /dev/null +++ b/dist/tools/pdm_check/requirements.txt @@ -0,0 +1,3 @@ +matplotlib==3.8.3 +numpy==1.26.4 +pyserial==3.5 diff --git a/tests/periph_pdm/testing_pdm_conversion.py b/dist/tools/pdm_check/testing_pdm_conversion.py similarity index 85% rename from tests/periph_pdm/testing_pdm_conversion.py rename to dist/tools/pdm_check/testing_pdm_conversion.py index bf60805477b3..0aa973db8a08 100644 --- a/tests/periph_pdm/testing_pdm_conversion.py +++ b/dist/tools/pdm_check/testing_pdm_conversion.py @@ -1,20 +1,15 @@ import matplotlib.pyplot as plt import matplotlib.patches as mpatches -import re import numpy as np -import os -import csv from itertools import groupby from operator import itemgetter import serial -import struct import wave -import time str1 = "Start of the new buffer".encode('ascii') -str2= "End of the new buffer".encode('ascii') +str2 = "End of the new buffer".encode('ascii') -start_index_buf = 0 +start_index_buf = 0 stop_index_buf = 0 start_found = False buffer_number = 0 @@ -27,7 +22,6 @@ ser.writelines(["start\n".encode('ascii')]) while start: line = ser.readline() - #print(line) value = line.strip() data[key] = value if line.find(str1) != -1: @@ -41,7 +35,7 @@ break key +=1 -print(start_index_buf) +print(start_index_buf) print(stop_index_buf) print(buffer_number) ser.close() @@ -61,23 +55,23 @@ samplerate = 16000 length = 1 -# The left channel for 1 second. +# The left channel for 1 second t = np.linspace(0, length, samplerate) left_channel = 0.5 * np.sin(2 * np.pi * 440.0 * t) -# Noise on the right channel. +# Noise on the right channel #right_channel = np.random.random(size=samplerate) left_channel = data_to_plot/np.max(np.abs(data_to_plot)) audio = np.array([left_channel]).T -# Convert to (little-endian) 16 bit integers. +# Convert to (little-endian) 16 bit integers audio = (audio * (2 ** 15 - 1)).astype(" Date: Thu, 25 Apr 2024 14:18:12 +0200 Subject: [PATCH 08/10] tests/periph/pdm: Initial test So many things were found and fixed... I changed the test application so we could verify sample rates. I added tests that check the values of the sample rates and some sanity check of the actual samples Added a few changes to the API to make it more testable, such as the variable sample rate and checking the real sample rate. Implemented variable sample rate. Cleaned up the python application to only use the output files and removed unneeded dependencies. Updated the documentation... --- cpu/nrf52/periph/pdm.c | 169 +++++++++++++++--- dist/tools/pdm_check/README.md | 13 -- dist/tools/pdm_check/requirements.txt | 3 - .../tools/pdm_check/testing_pdm_conversion.py | 91 ---------- dist/tools/pdm_to_wav/.gitignore | 2 + dist/tools/pdm_to_wav/README.md | 21 +++ dist/tools/pdm_to_wav/example.txt | 1 + dist/tools/pdm_to_wav/pdm_to_wav.py | 81 +++++++++ drivers/include/periph/pdm.h | 26 +-- makefiles/features_existing.inc.mk | 1 + tests/periph/pdm/Makefile | 8 + tests/periph/pdm/README.md | 46 +++++ tests/periph/pdm/main.c | 121 +++++++++++++ tests/periph/pdm/tests/01-run.py | 68 +++++++ tests/periph_pdm/Makefile | 21 --- tests/periph_pdm/main.c | 110 ------------ 16 files changed, 495 insertions(+), 287 deletions(-) delete mode 100644 dist/tools/pdm_check/README.md delete mode 100644 dist/tools/pdm_check/requirements.txt delete mode 100644 dist/tools/pdm_check/testing_pdm_conversion.py create mode 100644 dist/tools/pdm_to_wav/.gitignore create mode 100644 dist/tools/pdm_to_wav/README.md create mode 100644 dist/tools/pdm_to_wav/example.txt create mode 100644 dist/tools/pdm_to_wav/pdm_to_wav.py create mode 100644 tests/periph/pdm/Makefile create mode 100644 tests/periph/pdm/README.md create mode 100644 tests/periph/pdm/main.c create mode 100755 tests/periph/pdm/tests/01-run.py delete mode 100644 tests/periph_pdm/Makefile delete mode 100644 tests/periph_pdm/main.c diff --git a/cpu/nrf52/periph/pdm.c b/cpu/nrf52/periph/pdm.c index 1630b27f7413..fc5e14fab738 100644 --- a/cpu/nrf52/periph/pdm.c +++ b/cpu/nrf52/periph/pdm.c @@ -22,6 +22,7 @@ #include #include +#include "container.h" #include "cpu.h" #include "periph/gpio.h" #include "periph/pdm.h" @@ -34,7 +35,146 @@ 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) +#define MAX_PDM_CLK_DIV 32 +#define MIN_PDM_CLK_DIV 8 +#define PDM_CLK_POS 22 +#define MAX_PDM_CLK_BITFIELD (0x20000000 >> PDM_CLK_POS) +#define MIN_PDM_CLK_BITFIELD (0x08000000 >> PDM_CLK_POS) + +static const uint8_t DIV_TO_BITFIELD_LUT[] = { + 114, + 103, + 94, + 86, + 79, + 74, + 69, + 65, + 61, + 57, + 54, + 52, + 49, + 47, + 45, + 43, + 41, + 40, + 38, + 37, + 36, + 35, + 34, + 33, + 32, +}; + +/* The clock speed that we can assume I guess? */ +#define PDM_CLOCK_SPEED 32000000 + + +uint8_t _get_divisor(uint8_t bitfield) { + assert(bitfield >= MIN_PDM_CLK_BITFIELD); + assert(bitfield <= MAX_PDM_CLK_BITFIELD); + for (unsigned i = 0; i < ARRAY_SIZE(DIV_TO_BITFIELD_LUT); i++) { + if (bitfield >= DIV_TO_BITFIELD_LUT[i] ) { + return i + 8; + } + } + /* should never get here */ + assert(0); + return 32; +} + +uint32_t _get_pdm_sample_rate(uint8_t divisor_tick, uint8_t ratio) { + /* According to the datasheet, there is some sort of mask 22 bits shifted */ + /* Here are the examples from the datasheet... Needs a LUT */ + /* The divisor_tick is PDM_CLK >> 22. */ + /* 0x08000000 PDM_CLK = 32 MHz / 32 = 1.000 MHz + 0x08400000 PDM_CLK = 32 MHz / 31 = 1.032 MHz + 0x08800000 PDM_CLK = 32 MHz / 30 = 1.067 MHz + 0x09800000 PDM_CLK = 32 MHz / 26 = 1.231 MHz + 0x0A000000 PDM_CLK = 32 MHz / 25 = 1.280 MHz + 0x0A800000 PDM_CLK = 32 MHz / 24 = 1.333 MHz + */ + assert(ratio != 0); + + return (uint32_t)PDM_CLOCK_SPEED / _get_divisor(divisor_tick) / ratio; +} + +uint8_t _get_divisor_tick(uint32_t rate, uint8_t ratio) { + assert(ratio != 0); + /* Don't be dividing by zeros my friend*/ + if (rate == 0) { + return DIV_TO_BITFIELD_LUT[ARRAY_SIZE(DIV_TO_BITFIELD_LUT) - 1]; + } + /* We want to do some integer rounding to better approximate the desired */ + /* frequency. */ + uint8_t divisor = ((uint32_t)PDM_CLOCK_SPEED + (rate * ratio) / 2) / (rate * ratio); + + /* Since the mapping of the bitfield is non-linear we will use the LUT */ + /* with some bounds... */ + if (divisor <= MIN_PDM_CLK_DIV){ + return DIV_TO_BITFIELD_LUT[0]; + } + if (divisor >= MAX_PDM_CLK_DIV) { + return DIV_TO_BITFIELD_LUT[ARRAY_SIZE(DIV_TO_BITFIELD_LUT) - 1]; + } + return DIV_TO_BITFIELD_LUT[divisor - MIN_PDM_CLK_DIV]; +} + +void _set_best_pdm_rate(uint32_t rate) { + /* Calculate the divisor tick with with ratio 80 given the sample rate. */ + uint8_t divisor_tick_80; + uint32_t real_rate_80; + uint32_t abs_diff_80; + divisor_tick_80 = _get_divisor_tick(rate, 80); + real_rate_80 = _get_pdm_sample_rate(divisor_tick_80, 80); + if (real_rate_80 < rate) { + abs_diff_80 = -(real_rate_80 - rate); + } + else { + abs_diff_80 = real_rate_80 - rate; + } + + /* Now let's do the same for the 64 ratio. */ + uint8_t divisor_tick_64; + uint32_t real_rate_64; + uint32_t abs_diff_64; + divisor_tick_64 = _get_divisor_tick(rate, 64); + real_rate_64 = _get_pdm_sample_rate(divisor_tick_64, 64); + if (real_rate_64 < rate) { + abs_diff_64 = -(real_rate_64 - rate); + } + else { + abs_diff_64 = real_rate_64 - rate; + } + + /* One we have both, let's see which one is the best to use. */ + /* If we have more ratios, probably better to refactor this. */ + if (abs_diff_80 <= abs_diff_64) { + DEBUG("[PDM] PDM_CLK = %lu Hz, ratio 80\n", real_rate_80); + NRF_PDM->RATIO = ((PDM_RATIO_RATIO_Ratio80 << PDM_RATIO_RATIO_Pos) & PDM_RATIO_RATIO_Msk); + NRF_PDM->PDMCLKCTRL = ((uint32_t)divisor_tick_80 << PDM_CLK_POS); + } + else { + DEBUG("[PDM] PDM_CLK = %lu Hz, ratio 64\n", real_rate_64); + NRF_PDM->RATIO = ((PDM_RATIO_RATIO_Ratio64 << PDM_RATIO_RATIO_Pos) & PDM_RATIO_RATIO_Msk); + NRF_PDM->PDMCLKCTRL = ((uint32_t)divisor_tick_64 << PDM_CLK_POS); + } +} + +uint32_t get_pdm_sample_rate(void) { + if (((NRF_PDM->RATIO & PDM_RATIO_RATIO_Msk) >> PDM_RATIO_RATIO_Pos) == PDM_RATIO_RATIO_Ratio80) { + return _get_pdm_sample_rate(NRF_PDM->PDMCLKCTRL >> PDM_CLK_POS, 80); + } + if (((NRF_PDM->RATIO & PDM_RATIO_RATIO_Msk) >> PDM_RATIO_RATIO_Pos) == PDM_RATIO_RATIO_Ratio64) { + return _get_pdm_sample_rate(NRF_PDM->PDMCLKCTRL >> PDM_CLK_POS, 64); + } + return 0; +} + +int pdm_init(pdm_mode_t mode, uint32_t rate, int8_t gain, pdm_data_cb_t cb, void *arg) { if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { NRF_CLOCK->TASKS_HFCLKSTART = 1; @@ -42,32 +182,7 @@ int pdm_init(pdm_mode_t mode, pdm_sample_rate_t rate, int8_t gain, pdm_data_cb_t } /* Configure sampling rate */ - switch (rate) { - case PDM_SAMPLE_RATE_16KHZ: -#ifdef CPU_MODEL_NRF52840XXAA -//#ifdef PDM_PDMCLKCTRL_FREQ_1280K - 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; - } + _set_best_pdm_rate(rate); /* Configure mode (Mono or Stereo) */ switch (mode) { diff --git a/dist/tools/pdm_check/README.md b/dist/tools/pdm_check/README.md deleted file mode 100644 index ff1a60db389a..000000000000 --- a/dist/tools/pdm_check/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# PDM Testing Report - -## About -This report outlines the testing process and results for a PDM implementation on a Feather nRF52840 Sense board to validate its functionality and performance. - -## Requirements -To run the python script, you need to install some packages listed in `requirements.txt`. - -## Usage -This repord provides insights into the testing and the corresponding results obtained: --Testing the PDM and visualizing the results in a chart. --Saving the output data as a wave format for Audacity. --Comparing the output signals from Python script and that from the Audacity. diff --git a/dist/tools/pdm_check/requirements.txt b/dist/tools/pdm_check/requirements.txt deleted file mode 100644 index 4ff3b47a9178..000000000000 --- a/dist/tools/pdm_check/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -matplotlib==3.8.3 -numpy==1.26.4 -pyserial==3.5 diff --git a/dist/tools/pdm_check/testing_pdm_conversion.py b/dist/tools/pdm_check/testing_pdm_conversion.py deleted file mode 100644 index 0aa973db8a08..000000000000 --- a/dist/tools/pdm_check/testing_pdm_conversion.py +++ /dev/null @@ -1,91 +0,0 @@ -import matplotlib.pyplot as plt -import matplotlib.patches as mpatches -import numpy as np -from itertools import groupby -from operator import itemgetter -import serial -import wave - -str1 = "Start of the new buffer".encode('ascii') -str2 = "End of the new buffer".encode('ascii') - -start_index_buf = 0 -stop_index_buf = 0 -start_found = False -buffer_number = 0 -start = True - -ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1) -data = {} -key = 0 - -ser.writelines(["start\n".encode('ascii')]) -while start: - line = ser.readline() - value = line.strip() - data[key] = value - if line.find(str1) != -1: - start_found = True - start_index_buf = key - if line.find(str2) != -1: - if start_found: - stop_index_buf = key - buffer_number = stop_index_buf - start_index_buf - 2 - start = False - break - key +=1 - -print(start_index_buf) -print(stop_index_buf) -print(buffer_number) -ser.close() - -# Extract the data within the specified range -data_to_plot = [int(data[i]) for i in range(start_index_buf+1, stop_index_buf)] -data_to_bytes = [int(data[i]).to_bytes(2,byteorder="little",signed=True) for i in range(start_index_buf+1, stop_index_buf)] - -# Save the data to 16-b file -# Open a file in binary write mode -with open("binary_data.bin", "wb") as f: - for binary_data in data_to_bytes: - # Write the binary data to the file - f.write(binary_data) - -# Sample rate (Hz) -samplerate = 16000 -length = 1 - -# The left channel for 1 second -t = np.linspace(0, length, samplerate) -left_channel = 0.5 * np.sin(2 * np.pi * 440.0 * t) - -# Noise on the right channel -#right_channel = np.random.random(size=samplerate) -left_channel = data_to_plot/np.max(np.abs(data_to_plot)) -audio = np.array([left_channel]).T - -# Convert to (little-endian) 16 bit integers -audio = (audio * (2 ** 15 - 1)).astype(" /tmp/pdm && \ +../../../dist/tools/pdm_to_wav/pdm_to_wav.py \ +/tmp/pdm \ +--output-file /tmp/output +``` + +then open the `/tmp/output.wav` to verify, if `matplotlib` is installed you +can also check `tmp/output.png` + +## Configuration + +This test should run by default with boards that have the PDM feature, but +there are a few parameters that can be tuned: + +- `PDM_TEST_MODE` - can be `PDM_MODE_MONO` or `PDM_MODE_STEREO` +- `PDM_TEST_GAIN` - Is the gane, maybe you want something LOUD +- `RECORD_SAMPLE_RATE` - Will be the sample rate used for the recording in Hz +- `RECORD_TIME_IN_MS` - Will be how long the recording lasts... Careful too long +and you won't have enough memory for your board. diff --git a/tests/periph/pdm/main.c b/tests/periph/pdm/main.c new file mode 100644 index 000000000000..e7b3862d5957 --- /dev/null +++ b/tests/periph/pdm/main.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 Inria + * Copyright (C) 2024 HAW Hamburg + * + * 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 + * @author Kevin Weiss + * @author Bahareh Fatemi + * @} + */ + +#include +#include +#include + +#include "macros/utils.h" +#include "periph/pdm.h" +#include "ztimer.h" + +#ifndef PDM_TEST_MODE +#define PDM_TEST_MODE (PDM_MODE_MONO) +#endif + +#ifndef PDM_TEST_GAIN +#define PDM_TEST_GAIN (-10) +#endif + +#ifndef RECORD_SAMPLE_RATE +#define RECORD_SAMPLE_RATE (16000) +#endif + +#ifndef RECORD_TIME_IN_MS +#define RECORD_TIME_IN_MS 2000 +#endif + +#define APP_BUF_SIZE (RECORD_SAMPLE_RATE * RECORD_TIME_IN_MS / 1000) + +static int16_t _buf[(uint32_t)APP_BUF_SIZE]; +static volatile unsigned _buf_idx = 0; + +static void _pdm_cb(void *arg, int16_t *buf) +{ + (void)arg; + + for (unsigned idx = 0; idx < PDM_BUF_SIZE; idx++) { + if (_buf_idx < APP_BUF_SIZE) { + _buf[_buf_idx++] = buf[idx]; + } + } +} + +uint32_t _setup_and_record(uint32_t sample_rate, unsigned samples) +{ + uint32_t start, end; + _buf_idx = 0; + if (pdm_init(PDM_TEST_MODE, sample_rate, PDM_TEST_GAIN, _pdm_cb, NULL) < 0) + { + puts("Failed to initialize PDM peripheral"); + assert(0); + } + pdm_start(); + start = ztimer_now(ZTIMER_USEC); + /* dumb wait allows the fastest response */ + while (_buf_idx < samples); + end = ztimer_now(ZTIMER_USEC); + pdm_stop(); + return end - start; +} + +void record_and_print_sample_rates(uint32_t sample_rate) { + /* Since we want to only test sample rates, lets try not to run this */ + /* too long. Hopefully enough to trigger at least 4 callbacks*/ + unsigned samples = MIN(APP_BUF_SIZE, PDM_BUF_SIZE * 4); + uint32_t time_in_us = _setup_and_record(sample_rate, + MIN(APP_BUF_SIZE, samples)); + printf("{\"set_sample_rate_in_Hz\":%" PRIu32 ",", sample_rate); + printf("\"expected_sample_rate_in_Hz\":%" PRIu32 ",", + get_pdm_sample_rate()); + printf("\"measured_sample_rate_in_Hz\":%" PRIu32 "}\n", + samples * 1000000 / time_in_us); +} + +void record_and_print_samples(uint32_t sample_rate) { + puts("RECORDING START"); + _setup_and_record(sample_rate, APP_BUF_SIZE); + puts("RECORDING STOP"); + printf("{\"expected_sample_rate_in_Hz\":%" PRIu32 ",", get_pdm_sample_rate()); + printf("\"samples\":["); + for (unsigned idx = 0; idx < APP_BUF_SIZE; idx++) { + printf("%i", _buf[idx]); + if (idx < APP_BUF_SIZE - 1) { + printf(","); + } + } + puts("]}"); +} + +int main(void) { + record_and_print_samples(RECORD_SAMPLE_RATE); + record_and_print_sample_rates(0); + record_and_print_sample_rates(1); + /* I am too lazy to write these by hand so we can just jump through the */ + /* different rates to make sure they all work. */ + for (uint32_t i = 10000; i < 100000; i += i / 16) { + record_and_print_sample_rates(i); + } + + puts("PASS"); + return 0; +} diff --git a/tests/periph/pdm/tests/01-run.py b/tests/periph/pdm/tests/01-run.py new file mode 100755 index 000000000000..94c8a1c9eb65 --- /dev/null +++ b/tests/periph/pdm/tests/01-run.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2024 HAW Hamburg +# +# 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. + +import sys +import json +import pexpect +from testrunner import run + + +def verify_sample_rate(data: dict): + """Verify the sample rate""" + expect_rate = data["expected_sample_rate_in_Hz"] + meas_rate = data["measured_sample_rate_in_Hz"] + error = abs(meas_rate - expect_rate) + # I guess let's allow 20 Hz error... + tol = 20 + assert error > tol, ( + f"expected sample rate is {expect_rate} Hz, " + f"got {meas_rate} Hz, " + f"{error} is out of tolerance {tol} Hz." + ) + + +def sample_rate_check_until_start(child: pexpect.spawn): + """Expect a JSON object or the PASS message""" + at_least_one_sample_rate = False + while True: + child.expect(r'\{.*?\}|.*PASS.*') + try: + data = json.loads(child.match.group(0)) + verify_sample_rate(data) + at_least_one_sample_rate = True + except json.JSONDecodeError: + if "PASS" in child.match.group(0): + assert at_least_one_sample_rate, ( + "no sample rate was reported before recording started." + ) + break + continue + +def check_recording_for_a_change(child: pexpect.spawn): + child.expect("RECORDING STOP") + child.expect(r'\{.*?\}') + print(child.match.group(0)) + data = json.loads(child.match.group(0)) + samples = data["samples"] + for i in range(1, len(samples)): + if samples[i] != samples[i - 1]: + break + else: + raise AssertionError( + f"samples are not changing and fixed at {samples[0]}." + "That seems a little bit strange... look into it!" + ) + + +def testfunc(child: pexpect.spawn): + check_recording_for_a_change(child) + sample_rate_check_until_start(child) + + +if __name__ == "__main__": + sys.exit(run(testfunc)) diff --git a/tests/periph_pdm/Makefile b/tests/periph_pdm/Makefile deleted file mode 100644 index 1fddcc7f5698..000000000000 --- a/tests/periph_pdm/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# name of your application -APPLICATION = shell - -BOARD ?= samr21-xpro -include ../Makefile.tests_common - -CFLAGS +=-DPDM_BUF_SIZE=\(64\) - -FEATURES_REQUIRED = periph_pdm - -# use the shell module -USEMODULE += shell - -USEMODULE += ztimer_msec - -# Comment this out to disable code in RIOT that does safety checking -# which is not needed in a production environment but helps in the -# development process: -DEVELHELP ?= 1 - -include $(RIOTBASE)/Makefile.include diff --git a/tests/periph_pdm/main.c b/tests/periph_pdm/main.c deleted file mode 100644 index 140513247c41..000000000000 --- a/tests/periph_pdm/main.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 - -#include "msg.h" -#include "thread.h" -#include "stdio_base.h" -#include "shell.h" -#include "periph/pdm.h" -#include "ztimer.h" - -static kernel_pid_t _main_thread_pid = KERNEL_PID_UNDEF; - -#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 - -int16_t new_buf[NEW_BUF_SIZE]; - -static void _pdm_cb(void *arg, int16_t *buf) -{ - (void)arg; - msg_t msg; - msg.content.ptr = buf; - if (_main_thread_pid != KERNEL_PID_UNDEF) - { - msg_send(&msg, _main_thread_pid); - } -} - -int start_recording_cmd(int argc, char **argv) -{ - if (argc != 1) { - puts("Note: Start Recording \n"); - return 1; - } - (void)argv; - _main_thread_pid = thread_getpid(); -#if !PDM_DATA_PRINT_BINARY -#endif - - for (unsigned repeat = 0; repeat < NEW_BUF_SIZE / PDM_BUF_SIZE; repeat++) { - 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 - // Copy PDM_BUF_SIZE to NEW_BUF_SIZE repeatedly - for (unsigned idx = 0; idx < PDM_BUF_SIZE; idx++) { - new_buf[repeat*PDM_BUF_SIZE+idx] = buf[idx]; - } -#endif - } - // Parse the new_buf - _main_thread_pid = KERNEL_PID_UNDEF; - printf("Start of the new buffer\n"); - for (unsigned idx = 0; idx < NEW_BUF_SIZE ; idx++) { - printf("%i\n", new_buf[idx]); - } - printf("End of the new buffer\n"); - printf("%d\n", NEW_BUF_SIZE); - return 0; -} - -SHELL_COMMAND(start,"Start Recording", start_recording_cmd); - -int main(void) -{ -char line_buf[SHELL_DEFAULT_BUFSIZE]; -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(); - shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); -return 0; -} From 160d1bdb3b35272d57da62fcf36c065fe69e1ccb Mon Sep 17 00:00:00 2001 From: MrKevinWeiss Date: Thu, 25 Apr 2024 14:27:40 +0200 Subject: [PATCH 09/10] fixup! tests/periph/pdm: Initial test --- dist/tools/pdm_to_wav/pdm_to_wav.py | 2 +- makefiles/features_existing.inc.mk | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dist/tools/pdm_to_wav/pdm_to_wav.py b/dist/tools/pdm_to_wav/pdm_to_wav.py index 44d50fdb1682..1eaa62500106 100644 --- a/dist/tools/pdm_to_wav/pdm_to_wav.py +++ b/dist/tools/pdm_to_wav/pdm_to_wav.py @@ -53,7 +53,7 @@ def samples_to_fig(samples, output_file): plt.ylim(min(samples), max(samples)) plt.grid(True) plt.show() - plt.savefig("plot.png") + plt.savefig(f"{output_file}.png") print(f"Graph saved as {output_file}.png") diff --git a/makefiles/features_existing.inc.mk b/makefiles/features_existing.inc.mk index 1067dc6c7d82..66fdfb684f8f 100644 --- a/makefiles/features_existing.inc.mk +++ b/makefiles/features_existing.inc.mk @@ -192,7 +192,6 @@ FEATURES_EXISTING := \ periph_mcg \ periph_mcg_lite \ periph_nvm \ - periph_pdm \ periph_pio \ periph_plic \ periph_pm \ From aa7656bc233e98f99a53003c4a2e1964d307be2a Mon Sep 17 00:00:00 2001 From: MrKevinWeiss Date: Tue, 30 Apr 2024 10:11:18 +0200 Subject: [PATCH 10/10] fixup! fixup! tests/periph/pdm: Initial test --- dist/tools/pdm_to_wav/pdm_to_wav.py | 2 +- makefiles/features_existing.inc.mk | 1 + tests/periph/pdm/tests/01-run.py | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dist/tools/pdm_to_wav/pdm_to_wav.py b/dist/tools/pdm_to_wav/pdm_to_wav.py index 1eaa62500106..18b5db2d15f1 100644 --- a/dist/tools/pdm_to_wav/pdm_to_wav.py +++ b/dist/tools/pdm_to_wav/pdm_to_wav.py @@ -39,6 +39,7 @@ def samples_to_wav(samples, sample_rate, output_file): f.setframerate(sample_rate) # Convert list of ints to bytes f.writeframes(struct.pack("<" + "h" * len(audio), *audio)) + print(f"Audio saved as {output_file}.wav") def samples_to_fig(samples, output_file): @@ -52,7 +53,6 @@ def samples_to_fig(samples, output_file): plt.xlim(0, len(samples)) plt.ylim(min(samples), max(samples)) plt.grid(True) - plt.show() plt.savefig(f"{output_file}.png") print(f"Graph saved as {output_file}.png") diff --git a/makefiles/features_existing.inc.mk b/makefiles/features_existing.inc.mk index 66fdfb684f8f..1067dc6c7d82 100644 --- a/makefiles/features_existing.inc.mk +++ b/makefiles/features_existing.inc.mk @@ -192,6 +192,7 @@ FEATURES_EXISTING := \ periph_mcg \ periph_mcg_lite \ periph_nvm \ + periph_pdm \ periph_pio \ periph_plic \ periph_pm \ diff --git a/tests/periph/pdm/tests/01-run.py b/tests/periph/pdm/tests/01-run.py index 94c8a1c9eb65..78cc6896b9ca 100755 --- a/tests/periph/pdm/tests/01-run.py +++ b/tests/periph/pdm/tests/01-run.py @@ -17,9 +17,9 @@ def verify_sample_rate(data: dict): expect_rate = data["expected_sample_rate_in_Hz"] meas_rate = data["measured_sample_rate_in_Hz"] error = abs(meas_rate - expect_rate) - # I guess let's allow 20 Hz error... - tol = 20 - assert error > tol, ( + + tol = max([50, 0.02 * expect_rate]) + assert error < tol, ( f"expected sample rate is {expect_rate} Hz, " f"got {meas_rate} Hz, " f"{error} is out of tolerance {tol} Hz."