Il y avait des problèmes dans la libraire...

Dependencies:   ST_FREQUENCY_DIVIDER ST_I2S USBDEVICE

Fork of X_NUCLEO_CCA02M1 by ST

BSP/XNucleoCCA02M1.cpp

Committer:
Davidroid
Date:
2017-05-02
Revision:
14:377677cca2e9
Parent:
11:b2f7f79026e4
Child:
15:17bdadc6aa9c

File content as of revision 14:377677cca2e9:

/**
 ******************************************************************************
 * @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))))
        error("\r\nConfiguration error: Currently only mono@16KHz, stero@16KHz or mono@32KHz are supported.\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
}