From 11b5b84193f8a5edc7222a31ba28587d09b08e9b Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 13 May 2024 13:30:12 -0600 Subject: [PATCH] Add method to obtain precise audio sample rate Actual LRClk I2S sample rate is not precisely equal to the "ideal" sample rate due to peripheral clock division requirements. This adds a method to return a float representing the "actual" hardware audio sample rate for more precise DSP --- src/hid/audio.cpp | 13 ++++++++++++- src/hid/audio.h | 33 +++++++++++++++++++-------------- src/per/sai.cpp | 45 +++++++++++++++++++++++++++++++++------------ src/per/sai.h | 35 ++++++++++++++++++++--------------- 4 files changed, 84 insertions(+), 42 deletions(-) diff --git a/src/hid/audio.cpp b/src/hid/audio.cpp index 211e83cf2..e2a7eb00a 100644 --- a/src/hid/audio.cpp +++ b/src/hid/audio.cpp @@ -31,7 +31,7 @@ class AudioHandle::Impl // Interface AudioHandle::Result Init(const AudioHandle::Config config, SaiHandle sai); AudioHandle::Result - Init(const AudioHandle::Config config, SaiHandle sai1, SaiHandle sai2); + Init(const AudioHandle::Config config, SaiHandle sai1, SaiHandle sai2); AudioHandle::Result DeInit(); AudioHandle::Result Start(AudioHandle::AudioCallback callback); AudioHandle::Result Start(AudioHandle::InterleavingAudioCallback callback); @@ -60,6 +60,12 @@ class AudioHandle::Impl float GetSampleRate() { return sai1_.GetSampleRate(); } + float GetPreciseSampleRate(uint32_t sai_idx) + { + return sai_idx == 0 ? sai1_.GetPreciseSampleRate() + : sai2_.GetPreciseSampleRate(); + } + AudioHandle::Result SetPostGain(float val) { if(val <= 0.f) @@ -525,6 +531,11 @@ float AudioHandle::GetSampleRate() return pimpl_->GetSampleRate(); } +float AudioHandle::GetPreciseSampleRate(uint32_t sai_idx) +{ + return pimpl_->GetPreciseSampleRate(sai_idx); +} + AudioHandle::Result AudioHandle::SetSampleRate(SaiHandle::Config::SampleRate samplerate) { diff --git a/src/hid/audio.h b/src/hid/audio.h index 47fbc4744..0e99f62e4 100644 --- a/src/hid/audio.h +++ b/src/hid/audio.h @@ -6,9 +6,9 @@ namespace daisy { /** @brief Audio Engine Handle - * @ingroup audio + * @ingroup audio * @details This class allows for higher level access to an audio engine. - * If you're using a SOM like the DaisySeed or DaisyPatchSM (or any + * If you're using a SOM like the DaisySeed or DaisyPatchSM (or any * board that includes one of those objects) then the intialization * is already taken care of. * If you're setting up your own custom hardware, or need to make changes @@ -49,17 +49,17 @@ class AudioHandle }; /** Non-Interleaving input buffer - * Buffer arranged by float[chn][sample] + * Buffer arranged by float[chn][sample] * const so that the user can't modify the input */ typedef const float* const* InputBuffer; /** Non-Interleaving output buffer - * Arranged by float[chn][sample] + * Arranged by float[chn][sample] */ typedef float** OutputBuffer; - /** Type for a Non-Interleaving audio callback + /** Type for a Non-Interleaving audio callback * Non-Interleaving audio callbacks in daisy will be of this type */ typedef void (*AudioCallback)(InputBuffer in, @@ -72,12 +72,12 @@ class AudioHandle */ typedef const float* InterleavingInputBuffer; - /** Interleaving Output buffer + /** Interleaving Output buffer ** audio is prepared as { L0, R0, L1, R1, . . . LN, RN } */ typedef float* InterleavingOutputBuffer; - /** Interleaving Audio Callback + /** Interleaving Audio Callback * Interleaving audio callbacks in daisy must be of this type */ typedef void (*InterleavingAudioCallback)(InterleavingInputBuffer in, @@ -102,7 +102,7 @@ class AudioHandle /** Returns the Global Configuration struct for the Audio */ const Config& GetConfig() const; - /** Returns the number of channels of audio. + /** Returns the number of channels of audio. ** ** When using a single SAI this returns 2, when using two SAI it returns 4 ** If no SAI is initialized this returns 0 @@ -111,20 +111,25 @@ class AudioHandle */ size_t GetChannels() const; - /** Returns the Samplerate as a float */ + /** Returns the (idealized) Samplerate as a float */ float GetSampleRate(); + /** Returns the actual, hardware-precise Samplerate as a float + * @param sai_idx The index (0 or 1) of the SAI to use as the source of the sample rate. + */ + float GetPreciseSampleRate(uint32_t sai_idx = 0); + /** Sets the samplerate, and reinitializes the sai as needed. */ Result SetSampleRate(SaiHandle::Config::SampleRate samplerate); /** Sets the block size after initialization, and updates the internal configuration struct. - ** Get BlockSize and other details via the GetConfig + ** Get BlockSize and other details via the GetConfig */ Result SetBlockSize(size_t size); /** Sets the amount of gain adjustment to perform before and after callback. - ** useful if the hardware has additional headroom, and the nominal value shouldn't be 1.0 - ** + ** useful if the hardware has additional headroom, and the nominal value shouldn't be 1.0 + ** ** \param val Gain adjustment amount. The hardware will clip at the reciprical of this value. */ Result SetPostGain(float val); @@ -139,8 +144,8 @@ class AudioHandle /** Starts the Audio using the non-interleaving callback. */ Result Start(AudioCallback callback); - /** Starts the Audio using the interleaving callback. - ** For now only two channels are supported via this method. + /** Starts the Audio using the interleaving callback. + ** For now only two channels are supported via this method. */ Result Start(InterleavingAudioCallback callback); diff --git a/src/per/sai.cpp b/src/per/sai.cpp index 869111e24..4d1d19a03 100644 --- a/src/per/sai.cpp +++ b/src/per/sai.cpp @@ -28,6 +28,7 @@ class SaiHandle::Impl // Utility functions float GetSampleRate(); + float GetPreciseSampleRate() const; size_t GetBlockSize(); float GetBlockRate(); @@ -43,6 +44,9 @@ class SaiHandle::Impl /** Offset stored for weird inter-SAI stuff.*/ size_t dma_offset; + /** Precise sample rate based on peripheral clock division */ + float sr_precise; + /** Callback that dispatches user callback from Cplt and HalfCplt DMA Callbacks */ void InternalCallback(size_t offset); @@ -201,6 +205,14 @@ SaiHandle::Result SaiHandle::Impl::Init(const SaiHandle::Config& config) return Result::ERR; } + uint32_t per_freq = (sai_idx == 0) + ? HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI1) + : HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI2); + uint32_t mck_div = sai_a_handle_.Init.Mckdiv; + + // Actual LRClk sample rate is subject to integer peripheral clock division + sr_precise = static_cast(per_freq) / (mck_div * 256.0f); + return Result::OK; } @@ -230,10 +242,10 @@ void SaiHandle::Impl::InitDma(PeripheralBlock block) hsai = &sai_a_handle_; hdma = &sai_a_dma_handle_; dir = config_.a_dir == Config::Direction::RECEIVE - ? DMA_PERIPH_TO_MEMORY - : DMA_MEMORY_TO_PERIPH; - req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_A - : DMA_REQUEST_SAI2_A; + ? DMA_PERIPH_TO_MEMORY + : DMA_MEMORY_TO_PERIPH; + req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_A + : DMA_REQUEST_SAI2_A; if(sai_idx == int(Config::Peripheral::SAI_1)) hdma->Instance = DMA1_Stream0; @@ -245,10 +257,10 @@ void SaiHandle::Impl::InitDma(PeripheralBlock block) hsai = &sai_b_handle_; hdma = &sai_b_dma_handle_; dir = config_.b_dir == Config::Direction::RECEIVE - ? DMA_PERIPH_TO_MEMORY - : DMA_MEMORY_TO_PERIPH; - req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_B - : DMA_REQUEST_SAI2_B; + ? DMA_PERIPH_TO_MEMORY + : DMA_MEMORY_TO_PERIPH; + req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_B + : DMA_REQUEST_SAI2_B; if(sai_idx == int(Config::Peripheral::SAI_1)) hdma->Instance = DMA1_Stream1; @@ -349,6 +361,10 @@ float SaiHandle::Impl::GetSampleRate() default: return 48000.f; } } +float SaiHandle::Impl::GetPreciseSampleRate() const +{ + return sr_precise; +} size_t SaiHandle::Impl::GetBlockSize() { // Buffer handled in halves, 2 samples per frame (1 per channel) @@ -365,10 +381,10 @@ void SaiHandle::Impl::InitPins() GPIO_InitTypeDef GPIO_InitStruct; GPIO_TypeDef* port; dsy_gpio_pin* cfg[] = {&config_.pin_config.fs, - &config_.pin_config.mclk, - &config_.pin_config.sck, - &config_.pin_config.sa, - &config_.pin_config.sb}; + &config_.pin_config.mclk, + &config_.pin_config.sck, + &config_.pin_config.sa, + &config_.pin_config.sb}; // Special Case checks dsy_gpio_pin sck_af_pin = {DSY_GPIOA, 2}; is_master = (config_.a_sync == Config::Sync::MASTER @@ -570,6 +586,11 @@ float SaiHandle::GetSampleRate() return pimpl_->GetSampleRate(); } +float SaiHandle::GetPreciseSampleRate() +{ + return pimpl_->GetPreciseSampleRate(); +} + size_t SaiHandle::GetBlockSize() { return pimpl_->GetBlockSize(); diff --git a/src/per/sai.h b/src/per/sai.h index bec5c06e9..c3e2901f3 100644 --- a/src/per/sai.h +++ b/src/per/sai.h @@ -6,20 +6,20 @@ namespace daisy { -/** +/** * Support for I2S Audio Protocol with different bit-depth, samplerate options - * Allows for master or slave, as well as freedom of selecting direction, + * Allows for master or slave, as well as freedom of selecting direction, * and other behavior for each peripheral, and block. - * + * * DMA Transfer commands must use buffers located within non-cached memory or use cache maintenance * To declare an unitialized global element in the DMA memory section: * int32_t DSY_DMA_BUFFER_SECTOR my_buffer[96]; * - * Callback functions will be called once per half of the buffer. In the above example, + * Callback functions will be called once per half of the buffer. In the above example, * the callback function would be called once for every 48 samples. - * + * * Use SAI Handle like this: - * + * * SaiHandle::Config sai_config; * sai_config.periph = SaiHandle::Config::Peripheral::SAI_1; * sai_config.sr = SaiHandle::Config::SampleRate::SAI_48KHZ; @@ -70,7 +70,7 @@ class SaiHandle SAI_32BIT, }; - /** Specifies whether a particular block is the master or the slave + /** Specifies whether a particular block is the master or the slave ** If both are set to slave, no MCLK signal will be used, and it is ** expected that the codec will have its own xtal. */ @@ -118,16 +118,16 @@ class SaiHandle /** Returns the current configuration */ const Config& GetConfig() const; - /** Callback Function to be called when DMA transfer is complete and half complete. + /** Callback Function to be called when DMA transfer is complete and half complete. ** This callback is prepared however the data is transmitted/received from the device. ** For example, using an AK4556 the data will be interleaved 24bit MSB Justified - ** + ** ** The hid/audio class will be allow for type conversions, de-interleaving, etc. */ typedef void (*CallbackFunctionPtr)(int32_t* in, int32_t* out, size_t size); - /** Starts Rx and Tx in Circular Buffer Mode - ** The callback will be called when half of the buffer is ready, + /** Starts Rx and Tx in Circular Buffer Mode + ** The callback will be called when half of the buffer is ready, ** and will handle size/2 samples per callback. */ Result StartDma(int32_t* buffer_rx, @@ -138,15 +138,20 @@ class SaiHandle /** Stops the DMA stream for the SAI blocks in use. */ Result StopDma(); - /** Returns the samplerate based on the current configuration */ + /** Returns the (idealized) samplerate based on the current configuration */ float GetSampleRate(); - /** Returns the number of samples per audio block + /** Returns the exact samplerate based on the current configuration. + * This is only valid after successful initialization. + */ + float GetPreciseSampleRate(); + + /** Returns the number of samples per audio block ** Calculated as Buffer Size / 2 / number of channels */ size_t GetBlockSize(); - /** Returns the Block Rate of the current stream based on the size - ** of the buffer passed in, and the current samplerate. + /** Returns the Block Rate of the current stream based on the size + ** of the buffer passed in, and the current samplerate. */ float GetBlockRate();