Files
Meck/lib/PDM_nrf52/src/PDM.cpp
T

262 lines
6.5 KiB
C++

/*
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 <hal/nrf_pdm.h>
#include <Adafruit_TinyUSB.h> // 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);