cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F439 I2S double the audio frequency

Jtron.11
Senior

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?

1 ACCEPTED SOLUTION

Accepted Solutions

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

View solution in original post

10 REPLIES 10

> 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

> 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?
 

> 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


@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?

 

 

 

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.

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

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)

 

 

 

> 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

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...