ST / X_NUCLEO_CCA02M1

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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers XNucleoCCA02M1.cpp Source File

XNucleoCCA02M1.cpp

Go to the documentation of this file.
00001 /**
00002  ******************************************************************************
00003  * @file    XNucleoCCA02M1.cpp
00004  * @author  AST / Software Platforms and Cloud
00005  * @version V1.0
00006  * @date    October 17th, 2016
00007  * @brief   Implementation file for the X_NUCLEO_CCA02M1 expansion board.
00008  ******************************************************************************
00009  * @attention
00010  *
00011  * <h2><center>&copy; COPYRIGHT(c) 2015 STMicroelectronics</center></h2>
00012  *
00013  * Redistribution and use in source and binary forms, with or without modification,
00014  * are permitted provided that the following conditions are met:
00015  *   1. Redistributions of source code must retain the above copyright notice,
00016  *      this list of conditions and the following disclaimer.
00017  *   2. Redistributions in binary form must reproduce the above copyright notice,
00018  *      this list of conditions and the following disclaimer in the documentation
00019  *      and/or other materials provided with the distribution.
00020  *   3. Neither the name of STMicroelectronics nor the names of its contributors
00021  *      may be used to endorse or promote products derived from this software
00022  *      without specific prior written permission.
00023  *
00024  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00025  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00026  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00027  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
00028  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00029  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
00030  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00031  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
00032  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
00033  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00034  *
00035  ******************************************************************************
00036  */
00037 
00038 
00039 /* Includes ------------------------------------------------------------------*/
00040 
00041 /* ACTION 1 ------------------------------------------------------------------*
00042  * Include here platform specific header files.                               *
00043  *----------------------------------------------------------------------------*/
00044 #include "mbed.h"
00045 /* ACTION 2 ------------------------------------------------------------------*
00046  * Include here expansion board specific header files.                        *
00047  *----------------------------------------------------------------------------*/
00048 #include "XNucleoCCA02M1.h"
00049 
00050 
00051 /* Variables -----------------------------------------------------------------*/
00052 
00053 /* Indexes of the PCM buffer. */
00054 uint32_t XNucleoCCA02M1::_PCM_buffer_read_index = 0;
00055 uint32_t XNucleoCCA02M1::_PCM_buffer_write_index = 0;
00056 
00057 /* Number of expansion boards. */
00058 uint8_t XNucleoCCA02M1::_number_of_boards = 0;
00059 
00060 /* Accessing shared areas: the PCM buffer. */
00061 SingletonPtr<PlatformMutex> XNucleoCCA02M1::_mutex;
00062 
00063 
00064 /* Methods -------------------------------------------------------------------*/
00065 
00066 /**
00067 * @brief  Initializing the X_NUCLEO_CCA02M1 board.
00068 * @param  init Pointer to device specific initalization structure.
00069 * @retval "0" in case of success, an error code otherwise.
00070 */
00071 status_t XNucleoCCA02M1::init(void *init)
00072 {
00073     /* Storing data. */
00074     if (init != NULL)
00075     {
00076         _frequency = (*((XNucleoCCA02M1_init_t *) init)).frequency;
00077         _channels = (*((XNucleoCCA02M1_init_t *) init)).channels;
00078     }
00079 
00080 #ifdef USE_OPEN_PDM2PCM_LIBRARY
00081     /* Checking input parameters. */
00082     if (!(((_frequency ==  8000) && (_channels == 1)) ||
00083           ((_frequency ==  8000) && (_channels == 2)) ||
00084           ((_frequency == 16000) && (_channels == 1)) ||
00085           ((_frequency == 16000) && (_channels == 2)) ||
00086           ((_frequency == 32000) && (_channels == 1)) ||
00087           ((_frequency == 32000) && (_channels == 2)) ||
00088           ((_frequency == 44100) && (_channels == 1)) ||
00089           ((_frequency == 44100) && (_channels == 2)) ||
00090           ((_frequency == 48000) && (_channels == 1)) ||
00091           ((_frequency == 48000) && (_channels == 2))))
00092         error("\r\nError: please set one of the following configurations: mono/stereo @ 8/16/32/44.1/48 KHz.\r\n");
00093 #endif
00094 
00095     /*
00096      * Starting the I2S frequency divider.
00097      * Note: put a jumper to connect PB_5 and PB_13 on the MORPHO connector when
00098      *       running a mono configuration.
00099      */
00100     if (_channels >= 2)
00101     {
00102         FrequencyDivider *divider = new FrequencyDivider();
00103         divider->start();
00104     }
00105 
00106     /* Initializing the PDM to PCM conversion library. */
00107     if ((_pdm2pcm = new PDM2PCMAudio(_frequency, _channels)) == NULL)
00108         return COMPONENT_ERROR;
00109 
00110     /* Setting I2S parameters. */
00111     dev_i2s.mode(MASTER_RX, true);
00112     dev_i2s.audio_frequency(_frequency == I2S_AUDIOFREQ_8K ? 4 * _frequency : 2 * _frequency);
00113     dev_i2s.protocol(MSB);
00114     dev_i2s.format(_channels == 1 ? 16 : 32, _channels == 1 ? 16 : 32, 1);
00115 
00116     /* Buffer sizes in 16-bits samples. */
00117     _PCM_samples_one_ms = ((_frequency * _channels) / 1000);
00118     _PDM_samples_one_ms = _pdm2pcm->pcm2pdm_samples(_PCM_samples_one_ms);
00119     _PDM_samples_two_ms = (_PDM_samples_one_ms << 1);
00120 
00121     /* Allocating input and output buffers. */
00122     _PDM_buffer_two_ms = (uint16_t *) calloc(_PDM_samples_two_ms, sizeof(uint16_t));
00123     _PCM_buffer_n_ms = (int16_t *) calloc(_PCM_samples_one_ms * PCM_BUFFER_SIZE_ms, sizeof(uint16_t));
00124 
00125     /* Allocating support buffers. */
00126     _PDM_buffer_one_ms = (uint16_t *) calloc(_PDM_samples_one_ms, sizeof(uint16_t));
00127 
00128     return COMPONENT_OK;
00129 }
00130 
00131 /**
00132  * @brief  Enabling transmission via USB.
00133  * @param  None.
00134  * @retval "0" in case of success, an error code otherwise.
00135  */
00136 status_t XNucleoCCA02M1::enable_usb(void)
00137 {
00138     /* Initializing the USBAudio object. */
00139     if ((_usb_audio = new USBAudio(32000, 2, _frequency, _channels)) == NULL)
00140         return COMPONENT_ERROR;
00141 
00142     /* Allocating support buffers. */
00143     _USB_PCM_buffer_one_ms = (int16_t *) calloc(_PCM_samples_one_ms + _channels, sizeof(uint16_t));
00144 
00145     _usb_enabled = true;
00146 
00147     return COMPONENT_OK;
00148 }
00149 
00150 /**
00151  * @brief  Disabling transmission via USB.
00152  * @param  None.
00153  * @retval "0" in case of success, an error code otherwise.
00154  */
00155 status_t XNucleoCCA02M1::disable_usb(void)
00156 {
00157     /* Freeing memory for the USBAudio object and support buffers. */
00158     delete _usb_audio;
00159     free(_USB_PCM_buffer_one_ms);
00160 
00161     _usb_enabled = false;
00162 
00163     return COMPONENT_OK;
00164 }
00165 
00166 /*
00167  * @brief  Start recording audio.
00168  * @param  None.
00169  * @retval "0" in case of success, an error code otherwise.
00170  */
00171 status_t XNucleoCCA02M1::record(void)
00172 {
00173     /* Reading microphones via I2S. */
00174     int res = dev_i2s.transfer(
00175         (void *) NULL, 0,
00176         (void *) _PDM_buffer_two_ms, _PDM_samples_two_ms * BYTES_PER_SAMPLE,
00177         event_callback_t(this, &XNucleoCCA02M1::i2s_callback),
00178         I2S_EVENT_ALL
00179     );
00180     if (res != 0)
00181         return COMPONENT_ERROR;
00182 
00183     /* Attaching a callback to send data through the USB at a standard frequency
00184        of 1KHz. */
00185     if (_usb_enabled)
00186         _usb_audio->attachTx(this, &XNucleoCCA02M1::usb_handler);
00187 
00188     return COMPONENT_OK;
00189 }
00190 
00191 /**
00192  * @brief  Attach a user-defined callback that will be executed whenever PCM
00193  *         data are ready, i.e. once each millisecond.
00194  *         The provided PCM buffer will be filled by the microphones.
00195  * @param  fptr Callback to attach.
00196  * @retval None.
00197  */
00198 void XNucleoCCA02M1::attach(void (*fptr) (int16_t *PCM_buffer, uint16_t PCM_buffer_bytes))
00199 {
00200     /* Allocating support buffers. */
00201     if ((_USER_PCM_buffer_one_ms = (int16_t *) calloc(_PCM_samples_one_ms, sizeof(uint16_t))) == NULL)
00202         error("Instantiation of support buffers failed.\r\n");
00203 
00204     /* Attaching the callback. */
00205     _callback.attach(fptr);
00206     _callback_attached = true;
00207 }
00208 
00209 /**
00210  * @brief  Attach a user-defined non-static callback that will be executed
00211  *         whenever PCM data are ready, i.e. once each millisecond.
00212  *         The provided PCM buffer will be filled by the microphones.
00213  * @param  tptr Pointer to an object.
00214  * @param  mptr Pointer to an object's callback.
00215  * @retval None.
00216  */
00217 template<typename T>
00218 void XNucleoCCA02M1::attach(T *tptr, void (T::*mptr) (int16_t *PCM_buffer, uint16_t PCM_buffer_bytes))
00219 {
00220     /* Allocating support buffers. */
00221     if ((_USER_PCM_buffer_one_ms = (int16_t *) calloc(_PCM_samples_one_ms, sizeof(uint16_t))) == NULL)
00222         error("Instantiation of support buffers failed.\r\n");
00223 
00224     /* Attaching the callback. */
00225     _callback.attach(tptr, mptr);
00226     _callback_attached = true;
00227 }
00228 
00229 /**
00230  * @brief  I2S callback which is executed whenever PCM data are ready, i.e. once
00231  *         each millisecond.
00232  * @param  narg Narg flag.
00233  * @retval None.
00234  */
00235 void XNucleoCCA02M1::i2s_callback(int narg)
00236 {
00237     /* Checking for errors. */
00238     if (!(narg & (I2S_EVENT_RX_COMPLETE | I2S_EVENT_RX_HALF_COMPLETE)))
00239         error("Unexpected transmission event.\r\n");
00240 
00241     /* PDM to PCM Conversion. */
00242     if (narg & (I2S_EVENT_RX_COMPLETE | I2S_EVENT_RX_HALF_COMPLETE))
00243     {
00244 #ifdef X_NUCLEO_CCA02M1_DEBUG
00245         _i2s_signal = 1;
00246 #endif
00247 
00248         uint32_t PDM_index = (narg & I2S_EVENT_RX_HALF_COMPLETE ? 0 : _PDM_samples_one_ms);
00249         switch (_channels)
00250         {
00251             case 1:
00252                 /* Scrambling PDM audio data. */
00253                 _pdm2pcm->scramble(_PDM_buffer_one_ms, &_PDM_buffer_two_ms[PDM_index], _PDM_samples_one_ms);
00254                 break;
00255 
00256             case 2:
00257                 /* Demuxing PDM audio data. */
00258                 _pdm2pcm->demux(_PDM_buffer_one_ms, &_PDM_buffer_two_ms[PDM_index], _PDM_samples_one_ms);
00259                 break;
00260         }
00261 
00262         /* Acquiring resources. */
00263         _mutex->lock();
00264 
00265         /* Converting PDM to PCM audio data. */
00266         _pdm2pcm->convert(&_PCM_buffer_n_ms[_PCM_buffer_write_index], _PDM_buffer_one_ms, _volume);
00267 
00268         /* Copying PCM data to the user buffer. */
00269         if (_callback_attached)
00270             memcpy(_USER_PCM_buffer_one_ms, &_PCM_buffer_n_ms[_PCM_buffer_write_index], _PCM_samples_one_ms * BYTES_PER_SAMPLE);
00271 
00272         /* Updating write index. */
00273         _PCM_buffer_write_index += _PCM_samples_one_ms;
00274         _PCM_buffer_write_index %= (PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms);
00275 
00276         /* Releasing resources. */
00277         _mutex->unlock();
00278 
00279         /* Executing user-defined callback. */
00280         static bool first_time = true;
00281         if (_callback_attached && first_time)
00282         {
00283             _callback.call(_USER_PCM_buffer_one_ms, _PCM_samples_one_ms * BYTES_PER_SAMPLE);
00284             first_time = !first_time;
00285         }
00286 
00287 #ifdef X_NUCLEO_CCA02M1_DEBUG
00288         _i2s_signal = 0;
00289 #endif
00290     }
00291 
00292     /* Start sending data through the USB whenever the PCM buffer has been
00293        filled up to the medium threshold. */
00294     if (_usb_enabled)
00295     {
00296         static bool done = false;
00297         static uint32_t calls = 0;
00298         if (!done)
00299             if (calls++ >= PCM_BUFFER_TH_MED_ms)
00300             {
00301                 usb_handler();
00302                 done = true;
00303             }
00304     }
00305 }
00306 
00307 /**
00308  * @brief  Sending PCM data via USB.
00309  * @param  None.
00310  * @retval None.
00311  */
00312 void XNucleoCCA02M1::usb_handler(void)
00313 {
00314 #ifdef X_NUCLEO_CCA02M1_DEBUG
00315     _usb_signal = 1;
00316 #endif
00317 
00318     /* Acquiring resources. */
00319     _mutex->lock();
00320 
00321     /* Computing the delta-data to send through the USB depending on the
00322        dis-alignment between the read and write index of the PCM buffer,
00323        which may happen whenever the I2S and the USB do not works at the
00324        same frequency (i.e. 1KHz). */
00325     int32_t PCM_diff = _PCM_buffer_write_index - _PCM_buffer_read_index;
00326     PCM_diff = (PCM_diff >= 0 ? PCM_diff : PCM_diff + PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms);
00327     int32_t PCM_buffer_high_index = PCM_BUFFER_TH_HIG_ms * _PCM_samples_one_ms;
00328     int32_t PCM_buffer_low_index = PCM_BUFFER_TH_LOW_ms * _PCM_samples_one_ms;
00329     USBAudio::AudioSampleCorrectType PCM_delta_samples = USBAudio::NoCorrection;
00330 
00331     if (PCM_diff >= PCM_buffer_high_index) {
00332         PCM_delta_samples = USBAudio::AddOneSample;
00333 #ifdef X_NUCLEO_CCA02M1_DEBUG
00334         _buffer_overrun = 1;
00335         _buffer_underrun = 0;
00336 #endif
00337     }
00338     else if (PCM_diff <= PCM_buffer_low_index) {
00339         PCM_delta_samples = USBAudio::RemoveOneSample;
00340 #ifdef X_NUCLEO_CCA02M1_DEBUG
00341         _buffer_overrun = 0;
00342         _buffer_underrun = 1;
00343     } else {
00344         _buffer_overrun = _buffer_underrun = 0;
00345 #endif
00346     }
00347 
00348     /* Writing data through the USB. */
00349     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))
00350         _USB_PCM_buffer_one_ms[i++] = _PCM_buffer_n_ms[j++];
00351     _usb_audio->writeSync((uint8_t *) _USB_PCM_buffer_one_ms, PCM_delta_samples);
00352 
00353     /* Updating read index. */
00354     _PCM_buffer_read_index += _PCM_samples_one_ms + ((uint32_t) PCM_delta_samples) * _channels;
00355     _PCM_buffer_read_index %= (PCM_BUFFER_SIZE_ms * _PCM_samples_one_ms);
00356 
00357     /* Releasing resources. */
00358     _mutex->unlock();
00359 
00360 #ifdef X_NUCLEO_CCA02M1_DEBUG
00361     _usb_signal = 0;
00362 #endif
00363 }