Library to handle the X-NUCLEO-CCA02M1 MEMS Microphones Expansion Board.

Dependencies:   ST_I2S ST_FREQUENCY_DIVIDER USBDEVICE

Dependents:   HelloWorld_CCA02M1 HelloWorld_CCA02M1_mbedOS HelloWorld_CCA02M1 Karaoke_CCA01M1_CCA02M1_mbedOS

Fork of X_NUCLEO_CCA02M1 by ST Expansion SW Team

MEMS Microphones Library

Library to handle the X-NUCLEO-CCA02M1 MEMS Microphones Expansion Board. A single board allows to record a standard 2-channel stereo signal as an array of PCM samples (16 bit/sample); in principle, it could make use of six additional MEMS microphones to realize a 8-channel audio system.


Microphones configuration

Currently the configurations supported are the following:

  • Stereo@48KHz
  • Stereo@44.1KHz (CD audio quality)
  • Stereo@32KHz
  • Stereo@16KHz
  • Stereo@8KHz
  • Mono@48KHz
  • Mono@44.1KHz
  • Mono@32KHz
  • Mono@16KHz
  • Mono@8KHz

Mono configurations need a Jumper connecting PB_5 and PB_13 on the Morpho connector to properly work.


Platform compatibility

  • This board can be currently used with the Nucleo F4 Family only, please see the ST_I2S library compatibility for further information.
  • The library is compatible both with mbed OS 5.x and mbed classic 2.x (to work with mbed classic, the main application has to import the "events" library, which is not included into the "mbed" library).


I2S Peripheral Usage

By default this board makes use of the I2S peripheral available on Nucleo boards.


Acquiring through the USB

In order to acquire the recorded PCM audio channel with an audio SW on a PC, please connect the expansion board to a USB port of the PC, and the Nucleo board to a USB power supply.

BSP/XNucleoCCA02M1.cpp

Committer:
Davidroid
Date:
2017-05-04
Revision:
19:1a061e306cc9
Parent:
15:17bdadc6aa9c
Child:
20:9952bef19da1

File content as of revision 19:1a061e306cc9:

/**
 ******************************************************************************
 * @file    XNucleoCCA02M1.cpp
 * @author  AST / Software Platforms and Cloud
 * @version V1.0
 * @date    October 17th, 2016
 * @brief   Implementation file for the X_NUCLEO_CCA02M1 expansion board.
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2015 STMicroelectronics</center></h2>
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *   3. Neither the name of STMicroelectronics nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
 */


/* Includes ------------------------------------------------------------------*/

/* ACTION 1 ------------------------------------------------------------------*
 * Include here platform specific header files.                               *
 *----------------------------------------------------------------------------*/
#include "mbed.h"
/* ACTION 2 ------------------------------------------------------------------*
 * Include here expansion board specific header files.                        *
 *----------------------------------------------------------------------------*/
#include "XNucleoCCA02M1.h"


/* Variables -----------------------------------------------------------------*/

/* Indexes of the PCM buffer. */
uint32_t XNucleoCCA02M1::_PCM_buffer_read_index = 0;
uint32_t XNucleoCCA02M1::_PCM_buffer_write_index = 0;

/* Number of expansion boards. */
uint8_t XNucleoCCA02M1::_number_of_boards = 0;

/* Accessing shared areas: the PCM buffer. */
SingletonPtr<PlatformMutex> XNucleoCCA02M1::_mutex;


/* Methods -------------------------------------------------------------------*/

/**
* @brief  Initializing the X_NUCLEO_CCA02M1 board.
* @param  init Pointer to device specific initalization structure.
* @retval "0" in case of success, an error code otherwise.
* @note   Currently only two channels at 16KHz are supported.
*/
status_t XNucleoCCA02M1::init(void *init)
{
    /* Storing data. */
    if (init != NULL)
    {
        _frequency = (*((XNucleoCCA02M1_init_t *) init)).frequency;
        _channels = (*((XNucleoCCA02M1_init_t *) init)).channels;
	}

#ifdef USE_OPEN_PDM2PCM_LIBRARY
    /* Checking input parameters. */
    if (!(((_frequency == 16000) && (_channels == 1)) ||
          ((_frequency == 16000) && (_channels == 2)) ||
          ((_frequency == 32000) && (_channels == 1)) ||
          ((_frequency == 32000) && (_channels == 2)) ||
          ((_frequency == 44100) && (_channels == 1)) ||
          ((_frequency == 44100) && (_channels == 2)) ||
          ((_frequency == 48000) && (_channels == 1)) ||
          ((_frequency == 48000) && (_channels == 2))))
        error("\r\nError: please set one of the following configurations: mono/stereo 16/32/44.1/48 KHz.\n\r");
#endif

	/* Setting configuration. */
    switch (_frequency)
    {
        case I2S_AUDIOFREQ_8K:
            _decimation_factor = 128;
            break;

        case I2S_AUDIOFREQ_16K:
        case I2S_AUDIOFREQ_32K:
        case I2S_AUDIOFREQ_48K:
        default:
            _decimation_factor = 64;
            break;
    }

    /* Buffer sizes in 16-bits samples. */
    _PCM_samples_one_ms = ((_frequency * _channels) / 1000);
    _PDM_samples_one_ms = ((_PCM_samples_one_ms * _decimation_factor) / 16);
    _PDM_samples_two_ms = (_PDM_samples_one_ms << 1);

    /* Allocating input and output buffers. */
    _PDM_buffer_two_ms = (uint16_t *) calloc(_PDM_samples_two_ms, sizeof(uint16_t));
    _PCM_buffer_n_ms = (int16_t *) calloc(_PCM_samples_one_ms * PCM_BUFFER_SIZE_ms, sizeof(uint16_t));

    /* Allocating support buffers. */
    _PDM_buffer_one_ms = (uint16_t *) calloc(_PDM_samples_one_ms, sizeof(uint16_t));

    /*
     * Starting the I2S frequency divider.
     * Note: put a jumper to connect PB_5 and PB_13 on the MORPHO connector when
     *       running a mono configuration.
     */
    if (_channels >= 2)
    {
	    FrequencyDivider *divider = new FrequencyDivider();
	    divider->start();
    }

    /* Initializing the PDM to PCM conversion library. */
    if ((_pdm2pcm = new PDM2PCMAudio(_frequency, _channels)) == NULL)
        return COMPONENT_ERROR;

    /* Setting I2S parameters. */
    dev_i2s.mode(MASTER_RX, true);
    dev_i2s.audio_frequency(_frequency == I2S_AUDIOFREQ_8K ? 4 * _frequency : 2 * _frequency);
    dev_i2s.protocol(MSB);
    dev_i2s.format(_channels == 1 ? 16 : 32, _channels == 1 ? 16 : 32, 1);

    return COMPONENT_OK;
}

/**
 * @brief  Enabling transmission via USB.
 * @param  None.
 * @retval "0" in case of success, an error code otherwise.
 */
status_t XNucleoCCA02M1::enable_usb(void)
{
    /* Initializing the USBAudio object. */
    if ((_usb_audio = new USBAudio(32000, 2, _frequency, _channels)) == NULL)
        return COMPONENT_ERROR;

    /* Allocating support buffers. */
    _USB_PCM_buffer_one_ms = (int16_t *) calloc(_PCM_samples_one_ms + _channels, sizeof(uint16_t));

    _usb_enabled = true;

    return COMPONENT_OK;
}

/**
 * @brief  Disabling transmission via USB.
 * @param  None.
 * @retval "0" in case of success, an error code otherwise.
 */
status_t XNucleoCCA02M1::disable_usb(void)
{
    /* Freeing memory for the USBAudio object and support buffers. */
    delete _usb_audio;
    free(_USB_PCM_buffer_one_ms);

    _usb_enabled = false;

    return COMPONENT_OK;
}

/*
 * @brief  Start recording audio.
 * @param  None.
 * @retval "0" in case of success, an error code otherwise.
 */
status_t XNucleoCCA02M1::record(void)
{
    /* Reading microphones via I2S. */
    int res = dev_i2s.transfer(
        (void *) NULL, 0,
        (void *) _PDM_buffer_two_ms, _PDM_samples_two_ms * BYTES_PER_SAMPLE,
        event_callback_t(this, &XNucleoCCA02M1::i2s_callback),
        I2S_EVENT_ALL
    );
    if (res != 0)
        return COMPONENT_ERROR;

    /* Attaching a callback to send data through the USB at a standard frequency
       of 1KHz. */
    if (_usb_enabled)
        _usb_audio->attachTx(this, &XNucleoCCA02M1::usb_handler);

    return COMPONENT_OK;
}

/**
 * @brief  Attach a user-defined callback that will be executed whenever PCM
 *         data are ready, i.e. once each millisecond.
 *         The provided PCM buffer will be filled by the microphones.
 * @param  fptr Callback to attach.
 * @retval None.
 */
void XNucleoCCA02M1::attach(void (*fptr) (int16_t *PCM_buffer, uint16_t PCM_buffer_bytes))
{
    /* Allocating support buffers. */
    if ((_USER_PCM_buffer_one_ms = (int16_t *) calloc(_PCM_samples_one_ms, sizeof(uint16_t))) == NULL)
        error("Instantiation of support buffers failed.\r\n");

    /* Attaching the callback. */
    _callback.attach(fptr);
    _callback_attached = true;
}

/**
 * @brief  Attach a user-defined non-static callback that will be executed
 *         whenever PCM data are ready, i.e. once each millisecond.
 *         The provided PCM buffer will be filled by the microphones.
 * @param  tptr Pointer to an object.
 * @param  mptr Pointer to an object's callback.
 * @retval None.
 */
template<typename T>
void XNucleoCCA02M1::attach(T *tptr, void (T::*mptr) (int16_t *PCM_buffer, uint16_t PCM_buffer_bytes))
{
    /* Allocating support buffers. */
    if ((_USER_PCM_buffer_one_ms = (int16_t *) calloc(_PCM_samples_one_ms, sizeof(uint16_t))) == NULL)
        error("Instantiation of support buffers failed.\r\n");

    /* Attaching the callback. */
    _callback.attach(tptr, mptr);
    _callback_attached = true;
}

/**
 * @brief  I2S callback which is executed whenever PCM data are ready, i.e. once
 *         each millisecond.
 * @param  narg Narg flag.
 * @retval None.
 */
void XNucleoCCA02M1::i2s_callback(int narg)
{
    /* Checking for errors. */
    if (!(narg & (I2S_EVENT_RX_COMPLETE | I2S_EVENT_RX_HALF_COMPLETE)))
        error("Unexpected transmission event.\r\n");

    /* PDM to PCM Conversion. */
    if (narg & (I2S_EVENT_RX_COMPLETE | I2S_EVENT_RX_HALF_COMPLETE))
    {
#ifdef X_NUCLEO_CCA02M1_DEBUG
        _i2s_signal = 1;
#endif

	    uint32_t PDM_index = (narg & I2S_EVENT_RX_HALF_COMPLETE ? 0 : _PDM_samples_one_ms);
	    switch (_channels)
	    {
	        case 1:
	        	/* Scrambling PDM audio data. */
	            _pdm2pcm->scramble(_PDM_buffer_one_ms, &_PDM_buffer_two_ms[PDM_index], _PDM_samples_one_ms);
	            break;

	        case 2:
	        	/* Demuxing PDM audio data. */
	            _pdm2pcm->demux(_PDM_buffer_one_ms, &_PDM_buffer_two_ms[PDM_index], _PDM_samples_one_ms);
	            break;
	    }

        /* Acquiring resources. */
		_mutex->lock();

		/* Converting PDM to PCM audio data. */
	    _pdm2pcm->convert(&_PCM_buffer_n_ms[_PCM_buffer_write_index], _PDM_buffer_one_ms, _volume, _decimation_factor);

        /* Copying PCM data to the user buffer. */
        if (_callback_attached)
            memcpy(_USER_PCM_buffer_one_ms, &_PCM_buffer_n_ms[_PCM_buffer_write_index], _PCM_samples_one_ms * BYTES_PER_SAMPLE);

        /* Updating write index. */
		_PCM_buffer_write_index += _PCM_samples_one_ms;
		_PCM_buffer_write_index %= (PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms);

        /* Releasing resources. */
        _mutex->unlock();

        /* Executing user-defined callback. */
        static bool first_time = true;
        if (_callback_attached && first_time)
        {
            _callback.call(_USER_PCM_buffer_one_ms, _PCM_samples_one_ms * BYTES_PER_SAMPLE);
            first_time = !first_time;
        }

#ifdef X_NUCLEO_CCA02M1_DEBUG
        _i2s_signal = 0;
#endif
	}

	/* Start sending data through the USB whenever the PCM buffer has been
	   filled up to the medium threshold. */
    if (_usb_enabled)
    {
        static bool done = false;
        static uint32_t calls = 0;
        if (!done)
    	    if (calls++ >= PCM_BUFFER_TH_MED_ms)
    	    {
    			usb_handler();
    			done = true;
    	    }
    }
}

/**
 * @brief  Sending PCM data via USB.
 * @param  None.
 * @retval None.
 */
void XNucleoCCA02M1::usb_handler(void)
{
#ifdef X_NUCLEO_CCA02M1_DEBUG
	_usb_signal = 1;
#endif

    /* Acquiring resources. */
	_mutex->lock();

	/* Computing the delta-data to send through the USB depending on the
	   dis-alignment between the read and write index of the PCM buffer,
	   which may happen whenever the I2S and the USB do not works at the
	   same frequency (i.e. 1KHz). */
	int32_t PCM_diff = _PCM_buffer_write_index - _PCM_buffer_read_index;
	PCM_diff = (PCM_diff >= 0 ? PCM_diff : PCM_diff + PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms);
	int32_t PCM_buffer_high_index = PCM_BUFFER_TH_HIG_ms * _PCM_samples_one_ms;
	int32_t PCM_buffer_low_index = PCM_BUFFER_TH_LOW_ms * _PCM_samples_one_ms;
    USBAudio::AudioSampleCorrectType PCM_delta_samples = USBAudio::NoCorrection;

    if (PCM_diff >= PCM_buffer_high_index) {
        PCM_delta_samples = USBAudio::AddOneSample;
#ifdef X_NUCLEO_CCA02M1_DEBUG
        _buffer_overrun = 1;
        _buffer_underrun = 0;
#endif
    }
    else if (PCM_diff <= PCM_buffer_low_index) {
        PCM_delta_samples = USBAudio::RemoveOneSample;
#ifdef X_NUCLEO_CCA02M1_DEBUG
        _buffer_overrun = 0;
        _buffer_underrun = 1;
    } else {
        _buffer_overrun = _buffer_underrun = 0;
#endif
    }

    /* Writing data through the USB. */
    for (uint32_t i = 0, j = _PCM_buffer_read_index; i < _PCM_samples_one_ms + ((uint32_t) PCM_delta_samples) * _channels; j %= (PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms))
        _USB_PCM_buffer_one_ms[i++] = _PCM_buffer_n_ms[j++];
    _usb_audio->writeSync((uint8_t *) _USB_PCM_buffer_one_ms, PCM_delta_samples);

    /* Updating read index. */
	_PCM_buffer_read_index += _PCM_samples_one_ms + ((uint32_t) PCM_delta_samples) * _channels;
	_PCM_buffer_read_index %= (PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms);

    /* Releasing resources. */
	_mutex->unlock();

#ifdef X_NUCLEO_CCA02M1_DEBUG
    _usb_signal = 0;
#endif
}