Simple Sound just an Arduino, a transistor, a battery and a speaker.

I teach a high school STEM class. This project is teaching basic sound. I show them a speaker playing a low tone and they can see and feel the cone going out and in. We talk about pressure waves, etc.
In the project I created for them, they can play simple songs by coding in tones, octaves, and times (beats).
Everything seems to work except octave 0. I look at the pulses with an oscilloscope and everything looks right but when I listen to it, octave 0 is messed up. The tones are way wrong. I could probably attach a sound file if you would like but it's quite easy to build if someone is willing. Circuit description is in the beginning remarks of the sketch.
I am really puzzled!

/*     0093 Play Seven Notes
        Simple Circuit:
        Use an NPN transistor to control the current through the speaker.
        Use a separate battery pack for powering the speaker.  2 AA (3 volts) work with most speakers.
        D4 to a resistor to the base of the transistor.  Anything 300 to 10K works.
        Ground from Arduino to battery pack negative to emitter of the transistor.
        Collector of the transistor either directly to one lead of the speaker or go through a 10 ohm resistor to the speaker.
        Positive voltage from the battery pack to the other lead of the speaker.
*/
int spkrPin = 4;
int Tempo = 1250; // multiplier for time for 1 count. If a 1/4 note gets 2 counts then time is 2 * Tempo to get milliseconds

float C[] = {16.4 , 32.7 , 65.4 , 130.8, 261.6, 523.3 , 1046.5 , 2093.0 , 4186.0 };          //   Frequencies of notes by octave.     C[4] is middle C   261.6 Hz
float Cs[] = {17.3 , 34.7 , 69.3 , 138.6 , 277.2 , 554.4 , 1108.7 , 2217.5 , 4434.9 };
float D [] = { 18.4 , 36.7 , 73.4 , 146.8 , 293.7 , 587.3 , 1174.7 , 2349.3 , 4698.6};
float Ds [] = { 19.5 , 38.9 , 77.8 , 155.6 , 311.1 , 622.3 , 1244.5 , 2489.0 , 4978.0};
float Ef [] = { 19.5 , 38.9 , 77.8 , 155.6 , 311.1 , 622.3 , 1244.5 , 2489.0 , 4978.0};
float E [] = { 20.6 , 41.2 , 82.4 , 164.8 , 329.6 , 659.3 , 1318.5 , 2637.0 , 5274.0 };
float F [] = { 21.8 , 43.7 , 87.3 , 174.6 , 349.2 , 698.5 , 1396.9 , 2793.8 , 5587.7 };
float Fs [] = { 23.1 , 46.3 , 92.5 , 185.0 , 370.0 , 740.0 , 1480.0 , 2960.0 , 5919.9};
float Gf [] = { 23.1 , 46.3 , 92.5 , 185.0 , 370.0 , 740.0 , 1480.0 , 2960.0 , 5919.9};
float G [] = { 24.5 , 49.0 , 98.0 , 196.0 , 392.0 , 784.0 , 1568.0 , 3136.0 , 6271.9 };
float Gs [] = {26.0 , 51.9 , 103.8 , 207.7 , 415.3 , 830.6 , 1661.2 , 3322.4 , 6644.9 };
float Af [] = {26.0 , 51.9 , 103.8 , 207.7 , 415.3 , 830.6 , 1661.2 , 3322.4 , 6644.9 };
float A [] = { 27.5 , 55.0 , 110.0 , 220.0 , 440.0 , 880.0 , 1760.0 , 3520.0 , 7040.0 };
float As [] = { 29.1 , 58.3 , 116.5 , 233.1 , 466.2 , 932.3 , 1864.7 , 3729.3 , 7458.6 };
float Bf [] = { 29.1 , 58.3 , 116.5 , 233.1 , 466.2 , 932.3 , 1864.7 , 3729.3 , 7458.6 };
float B [] = { 30.9 , 61.7 , 123.5 , 246.9 , 493.9 , 987.8 , 1975.5 , 3951.1 , 7902.1 };
int rest = 0;  //  rest   pause

unsigned long endTime = 0;
long nTime = 0;   //  time to play this note

void setup()
{
  pinMode(spkrPin, OUTPUT);
  Serial.begin(9600);
}
void playNote(int Beats, float Note) // This is function "playNote". Two parameters are passed to this function.  (Beats and Note)
{
  // first calculate the length of time in microseconds of each pulse at a given frequency
  // middle C is 262 Hz. 1 / 262 = .003822 seconds .001911 for a half cycle which equals 1911 microseconds
  long pTime = 1 / Note / 2 * 1000000;  //  This is the time in microseconds to hold one pulse high or low
  Serial.print(Note);
  Serial.print("  ");
  Serial.println(pTime);

  nTime = Beats * Tempo;         //  This is the time to keep playing one note.   Tempo allows an easy way to speed up or slow down a song.
  endTime = millis() + nTime;
  while (millis() < endTime)
  {
    digitalWrite (spkrPin, HIGH);
    delayMicroseconds (pTime);
    digitalWrite (spkrPin, LOW);
    delayMicroseconds (pTime);
  }
  delay(15); // a tiny pause between notes to hear each one
}

void loop()
{
  // Octave 0  (frequencies 16.4 to 30.9) don't play well.  After that, they play fine.
  playNote(3, 27.5);     //    ( time to play the note, frequency of the note )   could use  A[0]
  playNote(3, 29.1);     //     could use     As[0]       A sharp        A#
  playNote(3, 30.9);
  playNote(3, 32.7);
  playNote(3, Cs[1]);
  playNote(3, D[1]);
  playNote(3, Ds[1]);

  delay(3000);

/*
  //    The following will play all the notes in the array in order from low to high
  for (int octave = 0; octave <= 8; octave ++)
  {
    playNote(1, C[octave]);
    playNote(1, Cs[octave]);  //   Cs is C sharp
    playNote(1, D[octave]);
    playNote(1, Ds[octave]);
    playNote(1, E[octave]);
    playNote(1, F[octave]);
    playNote(1, Fs[octave]);
    playNote(1, G[octave]);
    playNote(1, Gs[octave]);
    playNote(1, A[octave]);
    playNote(1, As[octave]);
    playNote(1, B[octave]);
  }
  delay(2000); // delay between runs
  Tempo *= .8; // speed it up
  if (Tempo <= 50)
  {
    Tempo = 250;
  }
  */
}

Everything seems to work except octave 0. I look at the pulses with an oscilloscope and everything looks right but when I listen to it, octave 0 is messed up.

But, octave 0 is OK on the 'scope?

...It takes a "big" subwoofer and a "big" amplifier to reproduce low-frequency notes. You're probably just hearing harmonics & noise.

The notes above 29.1 Hz sound fine for these purposes without an amp or woofer. I'm not trying to get quality base tones. I'm just trying to teach very basic sound with an Arduino. I realize it's asking a lot for someone to build this little circuit (although it is fun). I'll attach a file with the sound. Maybe that will help.

:frowning: It wouldn't accept a .m4a file. Maybe it won't accept any sound file. ???

delayMicroseconds() will overflow with higher values than 16383 (pTime in your sketch). Looks like it does not work with more than 14 bit.

See reference: https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/:

Currently, the largest value that will produce an accurate delay is 16383.

So it will overflow with pTime 18181 and you will get about 276 Hz instead of 27.5 Hz,
with pTime 17182 you get about 618 Hz instead of 29.1 Hz.

Lower values of pTime will work fine.

You could work around the problem with something like this - quick and dirty :slight_smile:

...  
  if (pTime > 16000) {
    digitalWrite (spkrPin, HIGH);
    delayMicroseconds(16000);
    delayMicroseconds( pTime - 16000);
    digitalWrite (spkrPin, LOW);
    delayMicroseconds(16000);
    delayMicroseconds( pTime - 16000);
  }
  else {
    digitalWrite (spkrPin, HIGH);
    delayMicroseconds (pTime);
    digitalWrite (spkrPin, LOW);
    delayMicroseconds (pTime);
  }
...

Thank you so very much. I was so stumped. You nailed it.
I must not have looked at it correctly with the scope.

Thank you. Thank you. Thank you. :slight_smile: :slight_smile: :slight_smile:

uxomm:
delayMicroseconds() will overflow with higher values than 16383 (pTime in your sketch). Looks like it does not work with more than 14 bit.

See reference: https://www.arduino.cc/reference/en/language/functions/time/delaymicroseconds/:
So it will overflow with pTime 18181 and you will get about 276 Hz instead of 27.5 Hz,
with pTime 17182 you get about 618 Hz instead of 29.1 Hz.

Lower values of pTime will work fine.

You could work around the problem with something like this - quick and dirty :slight_smile:

...  

if (pTime > 16000) {
    digitalWrite (spkrPin, HIGH);
    delayMicroseconds(16000);
    delayMicroseconds( pTime - 16000);
    digitalWrite (spkrPin, LOW);
    delayMicroseconds(16000);
    delayMicroseconds( pTime - 16000);
  }
  else {
    digitalWrite (spkrPin, HIGH);
    delayMicroseconds (pTime);
    digitalWrite (spkrPin, LOW);
    delayMicroseconds (pTime);
  }
...

A much better workaround is to write a new delay function that does delay correctly for large values:

void delayMicros(unsigned long us)
{
  while (us >= 16000)
  {
    delayMicroseconds (16000) ;
    us -= 16000 ;
  }
  delayMicroseconds (us) ;
}

and use that function everywhere....