/* PDM.cpp - library to interface with nRF52840 PDM peripheral Part of Arduino - http://www.arduino.cc/ Copyright (c) 2019 Arduino SA This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "PDM.h" #include #include // for Serial #define DEFAULT_PDM_GAIN 20 #define PDM_IRQ_PRIORITY 7 #define NRF_PDM_FREQ_1280K (nrf_pdm_freq_t)(0x0A000000UL) ///< PDM_CLK= 1.280 MHz (32 MHz / 25) => Fs= 20000 Hz #define NRF_PDM_FREQ_2000K (nrf_pdm_freq_t)(0x10000000UL) ///< PDM_CLK= 2.000 MHz (32 MHz / 16) => Fs= 31250 Hz #define NRF_PDM_FREQ_2667K (nrf_pdm_freq_t)(0x15000000UL) ///< PDM_CLK= 2.667 MHz (32 MHz / 12) => Fs= 41667 Hz #define NRF_PDM_FREQ_3200K (nrf_pdm_freq_t)(0x19000000UL) ///< PDM_CLK= 3.200 MHz (32 MHz / 10) => Fs= 50000 Hz #define NRF_PDM_FREQ_4000K (nrf_pdm_freq_t)(0x20000000UL) ///< PDM_CLK= 4.000 MHz (32 MHz / 8) => Fs= 62500 Hz PDMClass::PDMClass(int dinPin, int clkPin, int pwrPin) : _dinPin(dinPin), _clkPin(clkPin), _pwrPin(pwrPin), _onReceive(NULL) { } PDMClass::~PDMClass() { } void PDMClass::setPins(int dataPin, int clkPin, int pwrPin) { _dinPin = dataPin; _clkPin = clkPin; _pwrPin = pwrPin; } int PDMClass::begin(int channels, long sampleRate) { _channels = channels; // Enable high frequency oscillator if not already enabled uint8_t sd_en = 0; sd_softdevice_is_enabled(&sd_en); if (sd_en) { uint32_t is_running; sd_clock_hfclk_is_running(&is_running); if (!is_running) { sd_clock_hfclk_request(); while(!is_running) { yield(); sd_clock_hfclk_is_running(&is_running); } } } else { if (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { NRF_CLOCK->TASKS_HFCLKSTART = 1; while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) yield(); } } // configure the sample rate and channels switch (sampleRate) { case 16000: #ifndef NRF52832_XXAA NRF_PDM->RATIO = ((PDM_RATIO_RATIO_Ratio80 << PDM_RATIO_RATIO_Pos) & PDM_RATIO_RATIO_Msk); #endif nrf_pdm_clock_set(NRF_PDM, NRF_PDM_FREQ_1280K); break; case 41667: nrf_pdm_clock_set(NRF_PDM, NRF_PDM_FREQ_2667K); break; default: return 0; // unsupported } switch (channels) { case 2: nrf_pdm_mode_set(NRF_PDM, NRF_PDM_MODE_STEREO, NRF_PDM_EDGE_LEFTFALLING); break; case 1: nrf_pdm_mode_set(NRF_PDM, NRF_PDM_MODE_MONO, NRF_PDM_EDGE_LEFTFALLING); break; default: return 0; // unsupported } setGain(DEFAULT_PDM_GAIN); // configure the I/O and mux pinMode(_clkPin, OUTPUT); digitalWrite(_clkPin, LOW); pinMode(_dinPin, INPUT); nrf_pdm_psel_connect(NRF_PDM, digitalPinToPinName(_clkPin), digitalPinToPinName(_dinPin)); // clear events and enable PDM interrupts nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_STARTED); nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_END); nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_STOPPED); nrf_pdm_int_enable(NRF_PDM, NRF_PDM_INT_STARTED | NRF_PDM_INT_STOPPED); if (_pwrPin > -1) { // power the mic on pinMode(_pwrPin, OUTPUT); digitalWrite(_pwrPin, HIGH); } // clear the buffer _doubleBuffer.reset(); // set the PDM IRQ priority and enable NVIC_SetPriority(PDM_IRQn, PDM_IRQ_PRIORITY); NVIC_ClearPendingIRQ(PDM_IRQn); NVIC_EnableIRQ(PDM_IRQn); // set the buffer for transfer // nrf_pdm_buffer_set((uint32_t*)_doubleBuffer.data(), _doubleBuffer.availableForWrite() / (sizeof(int16_t) * _channels)); // _doubleBuffer.swap(); // enable and trigger start task nrf_pdm_enable(NRF_PDM); nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_STARTED); nrf_pdm_task_trigger(NRF_PDM, NRF_PDM_TASK_START); return 1; } void PDMClass::end() { // disable PDM and IRQ nrf_pdm_disable(NRF_PDM); NVIC_DisableIRQ(PDM_IRQn); if (_pwrPin > -1) { // power the mic off digitalWrite(_pwrPin, LOW); pinMode(_pwrPin, INPUT); } // Don't disable high frequency oscillator since it could be in use by RADIO // unconfigure the I/O and un-mux nrf_pdm_psel_disconnect(NRF_PDM); pinMode(_clkPin, INPUT); } int PDMClass::available() { NVIC_DisableIRQ(PDM_IRQn); size_t avail = _doubleBuffer.available(); NVIC_EnableIRQ(PDM_IRQn); return avail; } int PDMClass::read(void* buffer, size_t size) { NVIC_DisableIRQ(PDM_IRQn); int read = _doubleBuffer.read(buffer, size); NVIC_EnableIRQ(PDM_IRQn); return read; } void PDMClass::onReceive(void(*function)(void)) { _onReceive = function; } void PDMClass::setGain(int gain) { nrf_pdm_gain_set(NRF_PDM, gain, gain); } void PDMClass::setBufferSize(int bufferSize) { _doubleBuffer.setSize(bufferSize); } void PDMClass::IrqHandler() { if (nrf_pdm_event_check(NRF_PDM, NRF_PDM_EVENT_STARTED)) { nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_STARTED); if (_doubleBuffer.available() == 0) { // switch to the next buffer nrf_pdm_buffer_set(NRF_PDM, (uint32_t*)_doubleBuffer.data(), _doubleBuffer.availableForWrite() / (sizeof(int16_t) * _channels)); // make the current one available for reading _doubleBuffer.swap(_doubleBuffer.availableForWrite()); // call receive callback if provided if (_onReceive) { _onReceive(); } } else { // buffer overflow, stop nrf_pdm_disable(NRF_PDM); } } else if (nrf_pdm_event_check(NRF_PDM, NRF_PDM_EVENT_STOPPED)) { nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_STOPPED); } else if (nrf_pdm_event_check(NRF_PDM, NRF_PDM_EVENT_END)) { nrf_pdm_event_clear(NRF_PDM, NRF_PDM_EVENT_END); } } extern "C" { void PDM_IRQHandler(void) { PDM.IrqHandler(); } } #ifndef PIN_PDM_DIN #define PIN_PDM_DIN -1 #endif #ifndef PIN_PDM_CLK #define PIN_PDM_CLK -1 #endif #ifndef PIN_PDM_PWR #define PIN_PDM_PWR -1 #endif PDMClass PDM(PIN_PDM_DIN, PIN_PDM_CLK, PIN_PDM_PWR);