RP2040 using Arduino IDE

I have RP2040 board and am using the library .... RP2040 by Earle F. Philhower for audio I2S.
I am trying out the Simple tone example in the installation. I get it to work ok but the changing variable for amplitude does not change the actual volume or size of the audio signal out.
amplitude is declared as..... const int amplitude = 500; // amplitude of square wave
Then the sample is set to this value .... int16_t sample = amplitude; // current sample value.
Then the signal in inverted periodically to create square wave...

void loop() {
if (count % halfWavelength == 0) {
// invert the sample every half wavelength count multiple to generate square wave
sample = -1 * sample;
}

// write the same sample twice, once for left and once for the right channel
i2s.write(sample);
i2s.write(sample);

// increment the counter for the next sample
count++;

}

Also it writes to both L & R even with one of the i2s.write(sample) commented out..??
Setting amplitude to 0 stops the square wave output... so it sets to zero OK but setting it to any other value even setting it to 1 gives me an audio square wave out the same as setting to to 100...??

What have you connected to the I2S bus? (eg. a DAC, a digital amplifier, etc.)

You talk about square wave, don't confuse I2S with PWM. It's not even the same.

Why can I not see this in your code?

Please post all your code and in the correct code tags. See the getting the best out of this forum.

I can't see any connection between the amplitude value and the samples you are generating.

No problem with sending code but as said above it it a basic example from.... this related discussion page...
I2S n00b woes.. · earlephilhower/arduino-pico · Discussion #430 · GitHub

The I2S device I am using is PT8211 DAC and I have used it before on other micros and it works here but.... I am querying (the sketch I think) why volume doesnt change and why it writes to both L&R even with only 1 write expression sent....?

/*
  This example generates a square wave based tone at a specified frequency
  and sample rate. Then outputs the data using the I2S interface to a
  MAX08357 I2S Amp Breakout board.

  created 17 November 2016
  by Sandeep Mistry
  modified for RP2040 by Earle F. Philhower, III <earlephilhower@yahoo.com>


    bool setBCLK(pin_size_t pin);
    - This assigns two adjacent pins - the pin after this one (one greater)
      is the WS (word select) signal, which toggles before the sample for
      each channel is sent

    bool setDATA(pin_size_t pin);
    - Sets the DOUT pin, can be any valid GPIO pin
*/

#include <I2S.h>

// Create the I2S port using a PIO state machine
I2S i2s(OUTPUT);

// GPIO pin numbers
#define pBCLK 20
#define pWS (pBCLK+1)
#define pDOUT 22

const int frequency = 1000; // frequency of square wave in Hz
const int amplitude = 500; // amplitude of square wave
const int sampleRate = 16000; // minimum for UDA1334A

const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave

int16_t sample = amplitude; // current sample value
int count = 0;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 1);

  Serial.begin(115200);
  Serial.println("I2S simple tone");

  i2s.setBCLK(pBCLK);
  i2s.setDATA(pDOUT);
  i2s.setBitsPerSample(16);

  // start I2S at the sample rate with 16-bits per sample
  if (!i2s.begin(sampleRate)) {
  //  Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }

}

void loop() {
  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * (sample);
  }

  // write the same sample twice, once for left and once for the right channel
  i2s.write(sample);
  i2s.write(sample);

  // increment the counter for the next sample
  count++;
    
}

Well the code runs very fast and so it will write to one channel and then the next time round the loop it will write to the other channel. So the left and right channel will be one sample away from each other.

I am away from my hardware at the moment. I will try it on my equipment this evening and see what results.

Yes....mmmmm.....thanks mike....now when you mention that....the sample is written either once or twice on each loop of the loop.....but for i2s the sample L&R should be written on each toggle of the WS clock L&R....so the WS clock is probably faster than the loop changes the sample value so writes to both...?????..... so probably need a tie up between the i2s write and the WS clock

Yes ...... this works .....If we set a variable "int16_t samp = 0" and first write the "sample value" followed by the "samp value" it writes the "sample actual value" to say "Left" and then the "samp = 0 value" to the "Right". This means always 2 writes and sound pitch/frequency of the sounding channel is constant. But as was in original sketch with 1 write the pitch/frequency was twice that.....???? and sounding on both channels....?? ..... so no good for audio as such if pitch is depending on what else is running in loop..??
Also the "volume" problem still there....????

void loop() {
  int16_t samp = 0;
  if (count % halfWavelength == 0) {
    // invert the sample every half wavelength count multiple to generate square wave
    sample = -1 * (sample);
  }

  // write the same sample twice, once for left and once for the right channel
  i2s.write(sample);
  i2s.write(samp);

  // increment the counter for the next sample
  count++;
   
}

That is odd because when I test the same program on my machine, I get the expected output when I measure it on an oscilloscope.
Value of amplitude -> voltage out
150 -> 24mV
250 -> 43mV
500 -> 82mV
1000 -> 164mV
2000 -> 338mV
4000 -> 660mV
8000 -> 1330mV
16000 -> 2660mV

This is a scope trace at the 16000 value

I am using the pico audio board from Pimoroni
pico audio pack
Which uses the PCM5100A stereo DAC. So I had to change the pin numbers in the code, that is all.

Thanks for yout tests. I have looked at mine on the scope, getting a good square wave OK but amplitude change not a good as yours. .......
What I get roughly is....
Amp at 0 gives zero out 0 volt wave centered on DCof 1.6 volt
Amp at 1 gives me 0.8 volt out centered on DC of 1.2 volt
Not much change up until 3200 and get 0.75 volt wave centered on DC of 2.0 volt
then....
4000 gives 0.7 volt wave centered on 2.0 volt DC
8000 gives 0.5 volt wave centered on 2.0 volt DC
16000 gives 0.4 volt wave centered on 2.0 volt DC
32000 gives 0 volt wave centered on 2.0 volt DC
48000 gives 0.4 volt wave centered on 2.0 volt DC
65000 gives 0.8 volt wave centered on 2.0 volt DC

Initially in the sketch the value was 500 for amplitude and I had the analog out conneted to linein on PC and also watched the output on Audacity but I didn't see any noticable change or hear any noticable volume change when I doubled or trebled the value so it gave me the impression it was not changing...??. and now it look like it is working in reverse..?? Max vol at low values and Zero at 32000 and max again at 65000. ....???.
It may be due to the format of the I2S data bits. The PT8211 datasheet says must be 2's complement, MSB first, Least Significant Bit Justified LSBJ. Haven't figured out yet how to do that with this EarlePhilhower library. and also it says this library is for "the I2S interface to a
MAX08357 I2S Amp Breakout board.". I tried to google this board but only found MAX98357, I wonder is this the same...??. I have tried the PT8211 on a Teensy and it works well all as normal but I had to configure the I2S out for LSBJ.

Yes, the PT8211 needed LSBJ format, and I figured out how to do in in the sketch...

It just needed the extra setLSBJ function added in setup to the other I2S setups..
i2s.setBCLK(pBCLK);
i2s.setDATA(pDOUT);
i2s.setBitsPerSample(16);
i2s.setLSBJFormat(); // added this line.

Ran a quick test to see amplitude changes compared to output changes ......

The Range now seems to be from 0 to 32000 to give 0 volt p/p to 1.6 volt p/p centred on a 1.6 volt DC volts.

amp 0 gives 0 volts all below are square wave p/p approx
amp 1000 gives 0.1 volt
amp 4000 gives 0.2 volt
amp 8000 gives 0.4 volt
amp12000 gives 0.6 volt
amp 16000 gives 0.8 volt
amp 20000 gives 1.0 volt
amp 24000 gives 1.2 volt
amp 28000 gives 1.4 volt
amp 32000 gives 1.4 volt
amp 32760 gives 1.6 volt

And output is a good clean square wave freq 250 Hz as measured as 176 samples in Audacity at 44100 sample rate. The Frequency set in the sketch was 500 and samplerate 44100 so actual frequency is half the frequency set in the sketch.

Thanks for you help in pointing me in right direction.....

Error....The line ........ amp 32000 gives ... should read "amp 32000 gives 1.6 volt"

You are very welcome, glad to be of service.

It looks like that it was down to the differences between the I2S boards.

When I try this I get the error message:-

'class I2S' has no member named 'setLSBJFormat'

So I am wondering where this came from?

To answer my own question, it appeared when I updated to the latest version of the Earlephilhower library.

I2S.setLSBJFormat(); was what I added to the original sketch, it was needed for my PT8211 DAC. Your board must not need it due to probably having a different format I2S.

I have modified the sketch to play 2 arrays, one to Left and other to Right.

question... Is speed of playing the arrays controlled just by speed of Loop or is the I2S in control...??

one of the arrays is 100 point and other is 400 point of 1 cycle sawtooth in each so the pitch of one is 4 times the other and mixes together via L&R speakers so it sounds alright.
I can alter the pitch (to any pitch) by changing the sample rate but both change together.
I was wondering if its possible to have 2 instances of I2S to control each sound...???

also at the minute if I stop one sound the other sound plays an octave higher because only 1 write....... but I can make it write Zero.
Would welcome your thoughts, sketch copy below...

/*
  This example generates a square wave based tone at a specified frequency
  and sample rate. Then outputs the data using the I2S interface to a
  MAX08357 I2S Amp Breakout board.

  created 17 November 2016
  by Sandeep Mistry
  modified for RP2040 by Earle F. Philhower, III <earlephilhower@yahoo.com>


    bool setBCLK(pin_size_t pin);
    - This assigns two adjacent pins - the pin after this one (one greater)
      is the WS (word select) signal, which toggles before the sample for
      each channel is sent

    bool setDATA(pin_size_t pin);
    - Sets the DOUT pin, can be any valid GPIO pin

   xxxx ... Now User-Changed to generate any arbitrary wave from an array L & R ...  Mar 2023 xxxx
*/


#include <I2S.h>

// Create the I2S port using a PIO state machine
I2S i2s(OUTPUT);

// GPIO pin numbers
#define pBCLK 20
#define pWS (pBCLK+1)
#define pDOUT 22


long saw400array[] = {
0,
152,
303,
455,
606,
758,
910,
1062,
1213,
1365,
1517,
1668,
1820,
1971,
2123,
2275,
2426,
2578,
2730,
2882,
3033,
3185,
3336,
3488,
3640,
3791,
3943,
4095,
4247,
4398,
4550,
4701,
4853,
5005,
5156,
5308,
5459,
5611,
5763,
5915,
6066,
6218,
6370,
6521,
6673,
6824,
6976,
7128,
7279,
7431,
7583,
7735,
7886,
8038,
8189,
8341,
8493,
8644,
8796,
8947,
9099,
9251,
9403,
9554,
9706,
9858,
10009,
10161,
10312,
10464,
10616,
10768,
10919,
11071,
11223,
11374,
11526,
11677,
11829,
11981,
12132,
12284,
12436,
12588,
12739,
12891,
13042,
13194,
13346,
13497,
13649,
13800,
13952,
14104,
14256,
14407,
14559,
14711,
14862,
15014,
15165,
15317,
15469,
15621,
15772,
15924,
16076,
16227,
16379,
16530,
16682,
16834,
16985,
17137,
17289,
17440,
17592,
17744,
17895,
18047,
18199,
18350,
18502,
18653,
18805,
18957,
19109,
19260,
19412,
19564,
19715,
19867,
20018,
20170,
20322,
20474,
20625,
20777,
20929,
21080,
21232,
21383,
21535,
21687,
21838,
21990,
22142,
22293,
22445,
22597,
22748,
22900,
23052,
23203,
23355,
23506,
23658,
23810,
23962,
24113,
24265,
24417,
24568,
24720,
24871,
25023,
25175,
25327,
25478,
25630,
25781,
25933,
26085,
26236,
26388,
26540,
26691,
26843,
26995,
27146,
27298,
27450,
27601,
27753,
27905,
28056,
28208,
28359,
28511,
28663,
28815,
28966,
29118,
29270,
29421,
29573,
29724,
29876,
30028,
30179,
30331,
-30317,
-30166,
-30014,
-29862,
-29711,
-29559,
-29407,
-29256,
-29104,
-28952,
-28801,
-28649,
-28497,
-28346,
-28194,
-28042,
-27891,
-27739,
-27588,
-27436,
-27284,
-27132,
-26981,
-26829,
-26678,
-26526,
-26374,
-26223,
-26071,
-25919,
-25768,
-25616,
-25464,
-25313,
-25161,
-25009,
-24858,
-24706,
-24554,
-24403,
-24251,
-24100,
-23948,
-23796,
-23644,
-23493,
-23341,
-23190,
-23038,
-22886,
-22735,
-22583,
-22431,
-22279,
-22128,
-21976,
-21825,
-21673,
-21521,
-21370,
-21218,
-21066,
-20915,
-20763,
-20611,
-20460,
-20308,
-20156,
-20005,
-19853,
-19701,
-19550,
-19398,
-19247,
-19095,
-18943,
-18791,
-18640,
-18488,
-18337,
-18185,
-18033,
-17882,
-17730,
-17578,
-17426,
-17275,
-17123,
-16972,
-16820,
-16668,
-16517,
-16365,
-16213,
-16062,
-15910,
-15758,
-15607,
-15455,
-15303,
-15152,
-15000,
-14849,
-14697,
-14545,
-14394,
-14242,
-14090,
-13938,
-13787,
-13635,
-13484,
-13332,
-13180,
-13029,
-12877,
-12725,
-12573,
-12422,
-12270,
-12119,
-11967,
-11815,
-11664,
-11512,
-11360,
-11209,
-11057,
-10905,
-10754,
-10602,
-10450,
-10299,
-10147,
-9996,
-9844,
-9692,
-9541,
-9389,
-9237,
-9085,
-8934,
-8782,
-8631,
-8479,
-8327,
-8176,
-8024,
-7872,
-7720,
-7569,
-7417,
-7266,
-7114,
-6962,
-6811,
-6659,
-6508,
-6356,
-6204,
-6052,
-5901,
-5749,
-5597,
-5446,
-5294,
-5143,
-4991,
-4839,
-4688,
-4536,
-4384,
-4232,
-4081,
-3929,
-3778,
-3626,
-3474,
-3323,
-3171,
-3019,
-2867,
-2716,
-2564,
-2413,
-2261,
-2109,
-1958,
-1806,
-1655,
-1503,
-1351,
-1199,
-1048,
-896,
-744,
-593,
-441,
-290
};




long saw100array[] = {
331,
937,
1544,
2151,
2757,
3364,
3971,
4577,
5184,
5790,
6397,
7004,
7610,
8217,
8824,
9430,
10037,
10643,
11250,
11857,
12463,
13070,
13677,
14283,
14890,
15496,
16103,
16710,
17316,
17923,
18530,
19136,
19743,
20349,
20956,
21563,
22169,
22776,
23383,
23989,
24596,
25202,
25809,
26416,
27022,
27629,
28236,
28842,
29449,
30055,
-30138,
-29531,
-28925,
-28318,
-27712,
-27105,
-26498,
-25892,
-25285,
-24678,
-24072,
-23465,
-22859,
-22252,
-21645,
-21039,
-20432,
-19825,
-19219,
-18612,
-18006,
-17399,
-16792,
-16186,
-15579,
-14972,
-14366,
-13759,
-13153,
-12546,
-11940,
-11333,
-10726,
-10119,
-9513,
-8906,
-8300,
-7693,
-7087,
-6480,
-5873,
-5266,
-4660,
-4053,
-3447,
-2840,
-2234,
-1627,
-1020,
-413
};


const int amplitude = 95; // amplitude of output wave percent
const int sampleRate = 44100; // to change wave frequency

int16_t sample = amplitude; // current sample value
int16_t samp = amplitude;
int count = 0;
int count2 = 0;

void setup() {
//  pinMode(LED_BUILTIN, OUTPUT);
//  digitalWrite(LED_BUILTIN, 1);

 // Serial.begin(115200);
  //Serial.println("I2S simple tone");

  i2s.setBCLK(pBCLK);
  i2s.setDATA(pDOUT);
  i2s.setBitsPerSample(16);
   i2s.setLSBJFormat();      // added this line for Left justified PT8211 DAC 

  // start I2S at the sample rate with 16-bits per sample
  if (!i2s.begin(sampleRate)) {
  //  Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }

}

void loop() {

if(count >= 100) count = 0;    
sample = saw100array[count]*amplitude/100;   // High Pitch Tone
i2s.write(sample);

if(count2 >= 400) count2 = 0;
samp = saw400array[count2]*amplitude/400;    // Low Pitch Tone
 i2s.write(samp);

  count++;
  count2++;

}

It is my understanding that it is both.

If the loop function is not fast enough then the I2S can't output any data and there will be a temporary break in the audio. If the loop function is faster then the write to the I2S will hold the data in a buffer until it is time, determined by the sample rate set, to send it out.

I come to these conclusions from my work with I2S on the RP2040 using the Pico C/C++ libraries supplied by The Raspberry Foundation. These allow you to set the size of a I2C buffer.

I don't know.
However controlling the pitch by altering the sample rate is, in my opinion, is not the way to go.

What I do is to control the increment through the sample waveform array, to control the pitch. This means the number of samples of a waveform will change with frequency, with higher frequencies having less samples per second than lower ones. That is until you get the step increment less than one. This does not affect the quality of the sound because of the ear's sensitivity to harmonic distortion drops at higher frequencies.

I do this trick by using floating point numbers for both the sample pointer and sample pointer increment. Of course when reading the sample I only use the integer part of the sample pointer. I could use scaled integers to do this for extra speed, but I have not found I need to use them with anything I have tried before on this processor.

Also for long tables I prefer to generate them by software in the code before they are read and output.

You might be interested in this code that will generate four different waveforms:-

/*
  This example generates a square wave based tone at a specified frequency
  and sample rate. Then outputs the data using the I2S interface to a
  MAX08357 I2S Amp Breakout board.

  created 17 November 2016
  by Sandeep Mistry
  modified for ESP8266 by Earle F. Philhower, III <earlephilhower@yahoo.com>

Only 16 bitsPerSample are allowed by the PIO code.  Only write, no read.

  Extended by Mike Cook (March 2023) to give four different types of waveforms
  and use a Pimoroni pico audio board. Frequency & amplitude controlled
  by variables in the code
*/

#include <I2S.h>
// Create the I2S port using a PIO state machine
I2S i2s(OUTPUT); // only an output with the Pimoroni pico audio board

// GPIO pin numbers default ones commented out
// this is for Pimoroni pico audio board 
//#define pBCLK 26
//#define pDOUT 28
#define pBCLK 10
#define pWS (pBCLK+1)
#define pDOUT 9

// set the size of the wave lookup table
#define WAVE_TABLE_LEN 512
static int16_t wave_table[WAVE_TABLE_LEN];
const int sampleRate = 16000; // minimum for UDA1334A
const int amplitude = 6015; // amplitude to give 1V ptp output

uint16_t sample; // current sample value
float wavePointerInc = 7.1; // sets frequency of the output waveform
float wavePointer = 0.0; // set place to get sample from

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 1); // show it is producing a waveform  
  Serial.begin(9600); // yes but you are only going to use this for debugging
  delay(2000); // delay to allow printer port to start
  Serial.println("I2S tone");
  makeWave(2); //type 0 = sine, 1 = saw, 2 = triangle, 3 = square
  
  i2s.setBCLK(pBCLK);  // Must be 28 or less.
  i2s.setDATA(pDOUT); // Must be 29 or less.  Default is 28

  // start I2S at the sample rate with 16-bits per sample
  if (!i2s.begin(sampleRate)) {
    Serial.println("Failed to initialize I2S!");
    while (1){
      delay(2000);// do nothing
      Serial.println("Failed to initialize I2S!");
    }
  }
} // end of setup

void loop() {
  // frequency determined by sample rate & increment of wave pointer
  wavePointer += wavePointerInc; // move to next sample
  if (wavePointer >= (float) WAVE_TABLE_LEN) wavePointer -= (float)WAVE_TABLE_LEN; // wrap round
  sample = wave_table[(uint16_t)wavePointer]; // get sample
  // write the same sample twice, once for left and once for the right channel
  i2s.write(sample);
  i2s.write(sample);
} // end of loop function

void makeWave(int8_t type){ // type 0 = sine, 1 = saw, 2 = triangle, 3 = square
    int16_t increment = 0;
    int16_t point = 0;
    if(type == 0){ // sine wave
        for (int16_t i = 0; i < WAVE_TABLE_LEN; i++) {
            wave_table[i] = amplitude * cosf(i * 2 * (float) (M_PI / WAVE_TABLE_LEN));
    }
   }
    if(type == 1){ // saw tooth
        point = -amplitude;
        increment = (amplitude * 2) / (float)WAVE_TABLE_LEN;
        for (int16_t i = 0; i < WAVE_TABLE_LEN; i++) {
            wave_table[i] = point;
            point += increment;
    }
   }
   if(type == 2){ // triangle
       point = -amplitude;
       increment = (amplitude * 4) / (float)WAVE_TABLE_LEN;
       for (int16_t i = 0; i < WAVE_TABLE_LEN; i++) {
           wave_table[i] = point;
           point += increment;
           if(i == WAVE_TABLE_LEN / 2) increment = - increment;           
    }
   }
   if(type == 3){ // square
       point = -amplitude;
       for (int16_t i = 0; i < WAVE_TABLE_LEN; i++) {
           wave_table[i] = point;
           //if(i == 256) point = amplitude;
           if(i == WAVE_TABLE_LEN / 2) point = amplitude;
       }
   }
} // end of makeWave function

These are the traces I get:-

The ringing on the waveforms are just a result of not using the ground on the probe I am using but using it from another probe for convenience, so they are a bit of an artifact.

Thanks very much, very good sketch and well commented and described.
It works well and I added a calculation to convert from the requested frequency to the wavePointerInc. I assume in the original you just enter the say 7.1 and measure the frequency to see what you have got and then by trial and error adjust.
I added this bit ..... I measured the change in freq per change in pointerInc and find it is linear and quite accurate, I used this as a factor as below...

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// set the size of the wave lookup table
#define WAVE_TABLE_LEN 256

//   WAVETYPE....  0 = sine, 1 = saw, 2 = triangle, 3 = square, 4 = Arbitrary Array   WAVETYPE....
#define Wtype 4   
        
static int16_t wave_table[WAVE_TABLE_LEN];
const int sampleRate = 44100; // set the sample rate 
const int amplitude = 15000; // set amplitude... PT8211 range 0 to 30000
const int waveamp = 20; // 

uint16_t sample; // current sample value

float basefreq = 220.00;  // base = output when Rate=44100 and Interval=1.
float interval = 1.5;  // interval/note above base to give the required output eg. 1.0 = 220=A, 2.0 = 440=A, 1.5 = 330=E 
float factor = 172.2656;   //  factor change in freq per change in PointerInc 

// float wavePointerInc = 7.1; // sets frequency of the output waveform ... was original line

float wavePointerInc = basefreq * interval / factor; // calcs wavepointerInc to give actual output frequency
float wavePointer = 0.0; // set place to get sample from

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// Also added this bit ... makeWave type 4, to read a soundarray and change the amplitude by percent value

 if(type == 4){ // arbitary array wave

        for (int16_t i = 0; i < WAVE_TABLE_LEN; i++) {
            wave_table[i] = chirp256array[i] * waveamp / 100;
       }
   }

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

One other point..... is there any point for mono sounds like these doing I2Swrite twice to write to L&R..?? If I I2Swrite once it seems to copy to the other channel anyway...?? and gives twice freq. Does that mean more time in loop to do other things such as control buttons, leds etc OR does it mean less time for other things because it is faster ..??

Does the second I2Swrite actually overwrite what had been copied to the other channel by the first I2Swrite...??

Is there an optimum between a low samplerate and higher Inc OR higher samplerate and lower Inc. ....??

will need to experiment more......Thanks

Thank you.

Yes sort of. I copied what I had written for this project:-

In that project I was doing 10 voice changing effects, where the frequency of an output was controlled mainly by a rotary encoder. You can still get back issues in PDF form free from the MagPi magazine site.

Well they both will actually change the frequency but basically the more different samples per cycle, the lower is the harmonic distortion of the note. However, the faster the sampling the further into the ultrasonic you push up the quantitation noise. So it is a bit of a balance.

In practice you run the sample rate as fast as you can and make the waveform table longer. If you can't sample at the approximate ideal of 44100 sps then you then, in theory have to add analogue filters on the output to reduce the sampling noise. This is sometimes called a reconstruction filter.

I think if writing only once and getting twice the frequency you are better off writing twice. But I don't know the inner working of the I2S code.

As you might have guessed I have been doing digital audio for some time. I started off in the mid 70's doing some post graduate work on digital compression. In those days micro computers were not fast enough or had enough memory to do anything with sound, so I had to make everything with TTL logic circuits. I was researching delta pulse code modulation (DPCM).

Then when the BBC computer I arrived I did some projects with an audio digitiser using the screen memory to store samples.

When the Arduino came along I did a lot of work with sound and audio processing using the Uno. I even wrote a book:-
Arduino Audio Projects

The Pico sound was the first thing I had done with this processor.

Thanks for all the info..... youare a mountain of experience and knowledge. keep up the good work.....

// xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// I set up the second I2S like this...........

 I2S i2s2(OUTPUT); // 2nd output for RP2040 pico audio board

   #define pBCLK2 13
   #define pWS2 (pBCLK2+1)
   #define pDOUT2 15

   i2s2.setBCLK(pBCLK2);  // Must be 28 or less.
   i2s2.setDATA(pDOUT2); // Must be 29 or less.  Default is 28
  
   i2s2.setLSBJFormat();      // added this line for Left justified PT8211 DAC xxxxxxxxxxx

  // was getting glitches and breakup of audio so setBuffer larger...??
  //  ... bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0); ...
   i2s.setBuffers(32,16,0);
   i2s2.setBuffers(128,64,0); // numbers being too big seems to cause problems as well

// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Probably not setting this correctly...... works most of the time but the glitches and crackling re-appears maybe just on 1 channel and other plays OK.....then change settings and recompile and sorts it out again for a while....??

even this works most times ....

   i2s.setBuffers(0,0,0);
  i2s2.setBuffers(16,8,0);

// OR just set i2s2 only......??

  //  i2s.setBuffers(0,0,0);
     i2s2.setBuffers(16,8,0);
// if don't set i2s2 buffer will get distortion and glitches all the time.

// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// tried various combinations of four i2swrite to L&R all work, don't seem to cause the glitches

  wavePointer += wavePointerInc; // move to next sample
  if (wavePointer >= (float) WAVE_TABLE_LEN) wavePointer -= (float)WAVE_TABLE_LEN; // wrap round
  sample = wave_table[(uint16_t)wavePointer]; // get sample ... amplitude of wave is about 20%
  sample2 = sample * 2;     //  same sample multiplied by 2
  // write the sample to Left and sample2 to Right for each I2S channel
  i2s.write(sample);
  i2s.write(sample2);
  i2s2.write(sample);
  i2s2.write(sample2;

// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  

  sample2 = sample * 0;     //  same sample multiplied by 0 to switch off or just sample2 = 0;
  // write the sample to Left and sample2 = 0 to Right for first I2S channel
  i2s.write(sample);
  i2s.write(sample2);
// write the sample to Left and sample2 = sample x 2 to Right for second I2S channel
 sample2 = sample * 2;
  i2s2.write(sample);
  i2s2.write(sample2;

// this works OK pitch is same because 4 writes even sample2 = 0.

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  

  sample2 = sample * 0;     //  same sample multiplied by 0 to switch off or just sample2 = 0;
  // write sample to Left and Right for first I2S channel
  i2s.write(sample);
//  i2s.write(sample2);
// write sample2 (at sample mult. by 2) to Left and Right for second I2S channel
 sample2 = sample * 2;
//  i2s2.write(sample);
  i2s2.write(sample2;

// the pitch is doubled because only 2 writes, sample is output on both L&R of i2s and
// sample2 is output on both L&R of i2s2
// For some reason there was also distortion i2s2 channel while i2s channel was OK. 
// going back to 4 writes all was OK again

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

I have now left it at..... i2s2.setBuffers(16,8,0); and doing 2 i2s.writes to each channel.

I have also played 2 array waves together one sent to L and other sent to R of each i2s port.
They were both Arbitrary Arrays with wave1 on i2s having pointerInc=1 so played all of the array samples (low freq), and wave2 on i2s2 with pointerInc=4 so played every 4th sample (high freq).
They played in tune and sounded steady OK. I also mixed the waves (wave1 + wave2)/2 or with lower amplitude just added (wave1 + wave2) and sent the mix to L&R of both ports, all played and sounded OK.

//.................................
So I have learned alot in this thread about the basics of audio and how to simplify the processing etc. I certainly now have a different view of the capabilities of the RaspberryPi Pico and now with the latest RP2040 plugin libraries for the Arduino IDE I can program the RP2040 just like another board......... I will keep exploring................

Excellent attitude.

While there is lots of potential in the RP2040 and its amazing price, it is not the best processor on the market.

May I suggest that you have a look at the Teensy 4.1 it is a way more powerful than the RP2040. But the thing is that the makers have put a lot of effort into making a graphical interface for making DSP structures. They also have a lot of tutorials about the fundamentals of DSP, as well as there own forum and lots of examples.

I have used Teensy alot...they are magic and good support. I've had the Rpi boards a while now but it took your help and the Arduino librarys to get me going......cheers ! !

1 Like