Skip to main content
Explorer II
August 22, 2024
Solved

STM32F439 I2S double the audio frequency

  • August 22, 2024
  • 5 replies
  • 3540 views

Hi all,

I reach out to you all to give some pointers or advice for me where to look into to solve this mystery.

I set up my Nuc-STM32F439ZI eval board i2s peripheral to play my audio sine wav file.

I used Audacity to generate 13KHz sine wave audio file with 48KHz sampling, 16-bit PCM wav file, then using Wav2Code to generate the array binary.

For STM32, I set I2S with 48KHz Sampling rate, Half-Duplex Master, Mode Master Transmit, I2S Philips, 16 bits data on 16 bits frame, DMA half word, Circular mode.

I2S generated correct signal, bit clock, frame rate, data valid but when I feed this sine wave to an amplifier and monitor the output of the amplifier on the scope, I saw my sine wave double in frequency which is 22KHz to 26KHz.

I then tried different way to create the sine wave with the look up table from the sine wave math function with the frequency of 1KHz, and the result is the same at the amplifier output, I got 2KHz sine wave.

Does anyone else this behavior or anything you experienced?

    This topic has been closed for replies.
    Best answer by waclawek.jan

    As I've said, I2S is *stereo*, so you have to have two uint16_t per sample.

    For example, if you want the buffer to hold samples for 1 ms and the sample rate is 48kHz, then you need 96 uint16_t

    values in the buffer. In pseudocode:

    uint16_t buf[nrOfSamples];
    for (i = 0; i < nrOfSamples; i++) {
     buf[2 * i] = LeftChannelData(i);
     buf[2 * i + 1] = RightChannelData(i);
    }

    A good test may be to have different signals in the left channel and in the right channel, for example sinewave in left and rectangular (possibly of different frequency) in the right. This then can be clearly seen as different waveforms on the outputs.

    JW

    5 replies

    Super User
    August 22, 2024

    > I2S generated correct signal, bit clock, frame rate,

    How did you verify that?

    > data valid

    What's that?

    > then tried different way to create the sine wave with the look up table from the sine wave math function with the frequency of 1KHz, and the result is the same at the amplifier output, I got 2KHz sine wave.

    There are many ways how to do a mistake when generating signal. Show how do you do it.

    JW

    Jtron.11Author
    Explorer II
    August 22, 2024

    > I2S generated correct signal, bit clock, frame rate,

    How did you verify that?

    I used logic analyzer to check the I2S signal from STM32's I2S pins, bit clock, frame, data all decoded from the array data.

    >data valid meaning my I2S signals matching with my prepared data array.

    For the manual way to generate the data for sine wave, you are correct there are so many way, and I used the references online showing how to generate the buffer of data using sine wave in math.h header file.

    The details is here

    https://www.phippselectronics.com/i2s-audio-tutorial-variable-frequency-sine-wave-output/?srsltid=AfmBOooTV4wQHR4cb-eJ_hegJ-KJ6lrh3qPybHbS98dyWNejJAjF8iUq

    In short

    #include "limits.h"
    #include "math.h"

    const int Fs = 48000; // sample rate (Hz)
    const int LUT_SIZE = 1024; // lookup table size

    int16_t LUT[LUT_SIZE]; // our sine wave LUT

    for (int i = 0; i < LUT_SIZE; ++i)
    {
    LUT[i] = (int16_t)roundf(SHRT_MAX * sinf(2.0f * M_PI * (float)i / LUT_SIZE));
    } // fill LUT with 16 bit sine wave sample values

    // frequency we want to generate (Hz)
    int f = 1000;
     
    // Generate sine wave with chosen frequency
    const int BUFF_SIZE = 4096;  // size of output buffer (samples)
    int16_t buff[BUFF_SIZE];     // output buffer
     
    // frequency we want to generate (Hz)
     
    const float delta_phi = (float) f / Fs * LUT_SIZE;
                                   // phase increment
     
    float phase = 0.0f;          // phase accumulator
     
    // generate buffer of output
    for (int i = 0; i < BUFF_SIZE; ++i)
    {
        int phase_i = (int)phase;        // get integer part of our phase
        buff[i] = LUT[phase_i];          // get sample value from LUT
        phase += delta_phi;              // increment phase
        if (phase >= (float)LUT_SIZE)    // handle wrap around
            phase -= (float)LUT_SIZE;
    }
     
    Once you have the buffer data (or array of data) just send it to start it with HAL_I2S_Transmit_DMA function call
     
    @waclawek.jan, may I ask which method you used to create your sine wave and how did you configured your I2S and how would you verify your signal so maybe I can follow and try to follow your steps to create a sine wave with the correct frequency?
     
    Super User
    August 22, 2024

    > int16_t buff[BUFF_SIZE]; // output buffer

    And you then play back from this buffer?

    Don't forget, that I2S is *stereo*, i.e. you have to have *two* values per sample.

    JW

    Jtron.11Author
    Explorer II
    August 24, 2024

    @waclawek.jan wrote:

    > int16_t buff[BUFF_SIZE]; // output buffer

    And you then play back from this buffer?

    Yes, from the prepare buffer buff, you can just call the function the HAL_I2S_Transmit_DMA(&hi2s2, buff, BUFF_SIZE) once only if you already set up your I2S to use the circular DMA for TX.

    @waclawek.jan , you see anything wrong with the implementation?

    Do you know the proper way to create a sine wave data and play it back from I2S peripheral?

     

     

     

    Graduate II
    August 24, 2024

    Seems you dont know how I2S works. Is serial bus normative for traffic two separate frames in one stream.

    LRCK define channel switching, then your buffer simply send one sample to L and second to R circular = both channels have only half samples from your buf = result freq 2x and one sample phase shift LR. 

    Too your fill buff omit condition of continuity, then you can based on f 1000Hz place in buff not complete sinewave and this circular playback generate distorted signals. If you only plan sinewaves calculate size of buff for one complete sinewave or limit in call

     

    HAL_I2S_Transmit_DMA(&hi2s2, buff, one wave count)

     

      When you plan work with real music, DMA must work other way and use half and full complete irq to refill sended half buff with new data.

    Super User
    August 24, 2024

    As I've said, I2S is *stereo*, so you have to have two uint16_t per sample.

    For example, if you want the buffer to hold samples for 1 ms and the sample rate is 48kHz, then you need 96 uint16_t

    values in the buffer. In pseudocode:

    uint16_t buf[nrOfSamples];
    for (i = 0; i < nrOfSamples; i++) {
     buf[2 * i] = LeftChannelData(i);
     buf[2 * i + 1] = RightChannelData(i);
    }

    A good test may be to have different signals in the left channel and in the right channel, for example sinewave in left and rectangular (possibly of different frequency) in the right. This then can be clearly seen as different waveforms on the outputs.

    JW

    Jtron.11Author
    Explorer II
    August 26, 2024

    Thank you both @waclawek.jan and @MM..1 for your explanation about I2S implementation.  Please spare couple minutes with me about this issue.

    I think when I tried to provide completely information some how it made the problem that I tried to trouble shoot more complicated to explain.

    Let me go over with 1 implementation only and please point it out if my understanding is incorrect.

    My goal is create a 13KHz sinewave continuously out of I2S peripheral.

    Step#1: Using Audacity to create a sine.wav file with 48KHz Sampling rate 16-bit PCM data, the content of this wave file is the 33ms of the 13KHz sine wave stereo track.  (33ms is just a arbitrary chosen). 

    Step#2: Using Wave2Code software to convert the sine.wav file, the result is the sample_buffer array which is about 965 of int16_t (this number can be changed depend on the length of the sine.wav file).  I believe this sample_buffer array contains both Left and Right channels data.

    Step#3: For STM32, I setup the I2S peripheral with 48KHz Audio Frequency (I don't know why STM32 didn't use the term Sampling Frequency here), Half-Duplex Master, Mode Master Transmit, I2S Philips, 16 bits data on 16 bits frame, DMA half word, Circular mode.

    Step#4: Once the I2S is done set up, I only call HAL_I2S_Transmit_DMA(&hi2s2, sample_buffer, 965) once.

    My understanding:

    Once you set the I2S DMA in the circular mode, once the DMA is done transfer the last bytes, it will come back from the beginning.  I verified this by using the Logic Analyzer to see the output of the I2S pins' and I confirm the data is being output continuously.

    Question:

    1. Will this continuously transfer data from memory to I2S peripheral will satisfy the condition of the number of samples to be sent out for Left + Right channels?  The sample_buffer is big enough to cover complete number of sine wave periods.

    I am lost to understand why my data is not sufficient for L+R channel to output which make sinewave output frequency almost 2x the input frequency (input 13KHz, on scope measure 21KHz to 22KHz)

     

     

     

    Graduate II
    August 26, 2024

    Little teory: SR 48000Hz = max coded freq 24000Hz require two circular samples only for I2S ...

    Then for example 12000Hz require four samples only... no 965 or any other num...

    Create 13000Hz is little more complicated because sine values not repeat. Num minimum required samples is 3,69...

    Here you require define accepted freq error for circular DMA use.... For example 369 samples in buff create repeat sine 100 waves ... 13 008,13Hz

    For generate precise 13kHz you cant use static buffer data...

    Super User
    August 26, 2024

    > I believe this sample_buffer array contains both Left and Right channels data.

    That's not matter of belief. Get some proof.

    48kHz stereo means 48000x2 samples per second. That's around 3000 uint16_t per 0.033s (=33ms), not around 1000.

    As I've said, it's easier to generate the samples, and generate them so that they are distinctly different in the left and right channel, e.g. sinewave and rectangular wave.

    JW

    Jtron.11Author
    Explorer II
    August 26, 2024

    Thank you both @waclawek.jan and @MM..1  for your pointers.

    I got the correct sine wave output as my source with the same frequency.  It is all from I2S needs to have the sample buffer = 2x my source sample buffer.

    So for anyone interested the way to create a sample_buffer array using Audacity + WAVToCode

    After step#2 above, we need to double it by this pseudocode from JW

    uint16_t sample_buf[nrOfSamples * 2];
    for (i = 0; i < nrOfSamples; i++) {
    buf[2 * i] = original_data(i);
    buf[2 * i + 1] = original_data(i);
    }

    Step#4: Once the I2S is done set up, you only need to call HAL_I2S_Transmit_DMA(&hi2s2, sample_buf, nrOfSamples * 2) once if you would like the code generate one frequency one amplitude of the output wave.