Arduino I2S sine wave

2019-08-02 16:52发布

问题:

I'm working on a project where I want to generate (simple) sound by combining different sine waves. I'm using an arduino mkrZero, as it has I2S interface built in and it seems to have enough processing power for what I want.

I have wired my system exactly like in the tutorial for arduino I2S simpleTone:

And the tutorial code works just fine, and I get a simple square wave tone from the speaker.

Now I have modified the code to generate sine wave, there is a lookup table for the sin function to make it fast enough:

#include <I2S.h>

uint16_t isin16[] = {
 0, 1144, 2287, 3430, 4571, 5712, 6850, 7987, 9121, 10252, 11380,
 12505, 13625, 14742, 15854, 16962, 18064, 19161, 20251, 21336, 22414,
 23486, 24550, 25607, 26655, 27696, 28729, 29752, 30767, 31772, 32768,

 33753, 34728, 35693, 36647, 37589, 38521, 39440, 40347, 41243, 42125,
 42995, 43851, 44695, 45524, 46340, 47142, 47929, 48702, 49460, 50203,
 50930, 51642, 52339, 53019, 53683, 54331, 54962, 55577, 56174, 56755,

 57318, 57864, 58392, 58902, 59395, 59869, 60325, 60763, 61182, 61583,
 61965, 62327, 62671, 62996, 63302, 63588, 63855, 64103, 64331, 64539,
 64728, 64897, 65047, 65176, 65286, 65375, 65445, 65495, 65525, 65535,
 }; //0-90


const int sincount = 2;
int freqs[] = {50*360,51*360};
float amps[] ={0.1,0.1}; 

const int sampleRate = 8000; // sample rate in Hz

short sample = 0;
double t = 0;
double dt = 1.0/(1.0*sampleRate);

short LR[] = {0,0};

void setup() {
 Serial.begin(115200);
  // start I2S at the sample rate with 16-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, sampleRate, 16)) {
    while (1); // do nothing
  }
}

void loop() {
    sample = 0;
    for(int n= 0;n<sincount;n++)
    {
      sample += fSin(freqs[n]*t)*amps[n];
    }
    t += dt;
    LR[0] = sample;
    LR[1] = sample;

   I2S.write(LR[0]);//left channel
   I2S.write(LR[1]);//right channel
}


float fSin(long x)
{
 boolean pos = true;  // positive - keeps an eye on the sign.
 if (x < 0)
 {
   x = -x;
   pos = false;  
 }  
 if (x >= 360) x %= 360;  
 if (x > 180)
 {
   x -= 180;
   pos = !pos;
 }
 if (x > 90) x = 180 - x;
 if (pos) return isin16[x]; // = /65535.0
 return isin16[x];
}

This also works fine.

BUT!

If I change the code a bit and I write

I2S.write(LR,2);

instead of

I2S.write(LR[0]);//left channel
I2S.write(LR[1]);//right channel

Everything just breaks, the sound from the speaker sounds like a horrible scream

From the I2S library reference:

Description

Writes binary data to the I2S interface. This data is sent as a sample or series of samples.

Syntax

I2S.write(val) // blocking

I2S.write(buf, len) // not blocking

Parameters

val: a value to send as a single sample

buf: an array to send as a series of samples

len: the length of the buffer

Returns

byte write() will return the number of bytes written, though reading that number is optional.

I'd like to use the latter version of the write function, because it is not blocking and I can generate new samples while the previous ones are playing.

Any ideas on how to make the buffered version work correctly as well?

回答1:

In your code, you declare LR as an array of 2 short, a short in Arduino has size 2 bytes.

When you write this:

I2S.write(LR[0]); //left channel
I2S.write(LR[1]); //right channel

given

size_t I2SClass::write(uint8_t data)
{
    return write((int32_t)data);
}

size_t I2SClass::write(int sample)
{
    return write((int32_t)sample);
}

your short values should be automatically promoted to fit into the int type. Now, I am a bit unsure on the size of int you are dealing with, because the only I2S.h library I found is for the SAMD board on which an int has 4 bytes size, but normally on Arduino the an int has size of 2 bytes. In either case, the value should be taken as is, without problems.

When you write this:

I2S.write(LR,2);

given

size_t I2SClass::write(const uint8_t *buffer, size_t size)
{
    return write((const void*)buffer, size);
}

size_t I2SClass::write(const void *buffer, size_t size)
{
    ...
    written = _doubleBuffer.write(buffer, size);
    ...
}

your array of 2 short should be cast to const void *.

From this code in I2SDoubleBuffer:

size_t I2SDoubleBuffer::write(const void *buffer, size_t size) {
  size_t space = availableForWrite();

  if (size > space) {
    size = space;
  }

  if (size == 0) {
    return 0;
  }

  memcpy(&_buffer[_index][_length[_index]], buffer, size);

  _length[_index] += size;

  return size;
}

it looks like _doubleBuffer is not aware of the declared size of a sample (you declared it to be of 16 bits), it merely copies size bytes from the input to the internal buffer.

Therefore, I guess that what really happens is that when you ask for 2 shorts to be buffered only 2 bytes are actually copied.


Example:

Assume that your LR contains the following values

short LR[2] = { 0x89AB, 0xCDEF };

then this is what I imagine it happens

I2S.write(LR[0]); // actual left sample is 0x89AB
I2S.write(LR[1]); // actual right sample is 0xCDEF

I2S.write(LR, 2); // actual left sample is 0x89AB
                  // 0xCDEF is not copied
/* next loop iteration */
I2S.write(LR, 2); // actual right sample is 0x89AB 
                  // 0xCDEF is not copied

Proposed Solution:

Try to ask for 4 bytes to be copied:

I2S.write(LR, 4); // actual left sample is 0x89AB 
                  // actual right sample is 0xCDEF