A bunch of questions about playback sampled sounds

Hi there! As the subject says, I want to playback a sampled sound without too much stuff.

First of all, I just have a Nano board with ATmega328P (aka V 3.0), and I don't have yet my SD card module; so I wrote all the samples of an almost 2 seconds sound, into the MCU's flash memory (my sketch barely fits into that).
Those are unsigned 8 bit samples, monaural audio and should be played at 16 kHz. So, in theory, I need a delay of aprox. 62 microseconds per sample. I guess everything is fine up to here; but now these are my questions:

  • Since my board doesn't have a true DAC, can I use just a PWM pin to create sound (with a low-pass filter of course), or I should use the R-2R resistor ladder anyways?
  • If I could use PWM, the analogWrite function may cause timing issues? (delays more than 62 microseconds) If yes, please tell me an alternative way to very quickly change the duty cycle.
  • If the R-2R ladder is my only option, could be a bad idea to override the RX pin as a digital output, by using the port manipulation?
  • Would you mind suggest me a good way to amplify the final output? Because I think that the output itself isn't powerful enough to drive a speaker.

I will really appreciate your answers and I hope most of you can understand my questions.

PD: this might be obvious. Since I can't use a SD card yet, then I can't use the TMRpcm library and play WAV files. Yeah, I'm doing this "the hard way", I guess...

1.Since my board doesn't have a true DAC, can I use just a PWM pin to create sound (with a low-pass filter of course), or I should use the R-2R resistor ladder anyways?

The resistor should be MUCH better than PWM. It's still not a proper DAC because it's not "clocked" and you might get audio glitches when the DAC value changes.

2.If I could use PWM, the analogWrite function may cause timing issues? (delays more than 62 microseconds) If yes, please tell me an alternative way to very quickly change the duty cycle.

It shouldn't cause "timing issues", but you'll hear the PWM frequency along with your audio. [u]Here[/u] is some information about changing the PWM frequency.

3.If the R-2R ladder is my only option, could be a bad idea to override the RX pin as a digital output, by using the port manipulation?

I think it's a bad idea. The Nano has 14 I/O pins and you only need 8 for the 8-bit resistor ladder.

4.Would you mind suggest me a good way to amplify the final output? Because I think that the output itself isn't powerful enough to drive a speaker.

You can use regular "powered" computer speakers, or you can plug-into your stereo system*, or get an amplifier. [u]Here[/u] is an example of a small power amp.

  • Be very careful "playing around" if you have a high-power stereo system... You might get a loud glitch that blows your speaker, or you can end-up generating a high-power ultrasonic signal that you can't hear, but that's powerful enough to blow a tweeter or an amp.

The resistor should be MUCH better than PWM.

No it is not. In theory it is the same but in practice it is much less due to the tolerance on resistors.

Would you mind suggest me a good way to amplify the final output?

Any computer speaker or active speaker, these are very cheap.

Look at this great tutorial: Arduino Audio Output

I build a 8-bit, 8 voice synth on an Mega2560 and it sounds great (I used the Timer3-Library to get the sampling right at 20kHz).

First I tried it without the op-amps, but you need at least one buffer circuit (=2 op amps), otherwise the output signal is distorted if you play bass and high frequency at the same time (which sounds great sometimes :slight_smile: ).

The audio output is connected to a hifi-amplifier. Small in-ear headphones will work but even small speakers need much more power (at least you can hear the hi-frequency/noise tones if you listen carefully...).

Look at this great tutorial: Arduino Audio Output

Sorry that is an absolute crap tutorial, and if you think it is great you have a lot to learn.
It does carray a warning sign to say it is crap, this is the word
Instructables
In the URL.

It is crap because you can not achieve monotonicity on 8 bits with an R-2R ladder with the tolerance of resistor that you can actually buy at anything other than an extortionate price. That author has not a clue what he is talking about, he just makes it above the level of the thickos who post comments.

On this forum we are getting fed up acting as tech support for poor instructable articles.

On this forum we are getting fed up acting as tech support for poor instructable articles.

Amen.

Having a bad day?

If you're quite new to electronics, imo this tutorial is the perfect way to start. It introduces all basic concepts and points in the right directions.

The kids who got their arduino at christmas may stop at the end of the page, but if you're really interested, this crappy little circuit is imo the best starting position for experimenting and digging deeper into each single concept.

Maybe in a commercial environment you'd use other circuits, but as a beginner you won't be able to understand them.

And what kind of audio quality do you expect when given a atmega328 with 8 bit resolution and 16kHz sample rate??? I expected almost nothing and it surprised me how far I can go with this simple setup.

Having a bad day?

Not until you showed up spouting rubbish no.

If you're quite new to electronics, imo this tutorial is the perfect way to start. It introduces all basic concepts and points in the right directions.

Just no on about every level you can think of. It does not point in any direction that is not a dead end.

Maybe in a commercial environment you'd use other circuits, but as a beginner you won't be able to understand them.

That is just rubbish.

The correct circuit is much easier to understand because:-

  1. It is simpler.
  2. The decisions you have to make in deciding what the circuit is are logical, rational and what is more you can carry the lessons learned onto other circuits.

The point is that knowledge is accumulative, you should be able to learn from one experience and apply that learning to others. To extend what you know.

It's like building a house, if you start off with crappy foundations then you might be able to build something a bit crappy as a first floor but anything greater will collapse.

And what kind of audio quality do you expect when given a atmega328 with 8 bit resolution and 16kHz sample rate?

A lot better than he achieved with that instructables.

I expected almost nothing and it surprised me how far I can go with this simple setup.

Well that shows you how much you know.

The setup could be simpler and the quality higher if he had done it right.
Take that buffer for example, can you explain why he uses that op amp when there are many that are better and cheaper. Can you also explain why he only uses one op amp in the package and so has to use two packages? Can you explain why he uses two batteries to power them?

What instructables are, are just some rank beginner who has cobbled something together with little or no understanding of what they have done. Then they say like a three year old "look at me". You do exactly what I have done and this will happen. That only works if they do "exactly" what he has done, which in most cases is not possible due to obscure components or inadequate design only working because all the tolerances went in his direction when he cobbled it up. Finally they are hyped up to appear to be ten times more than what they are.

In the mean time people think they have learned something, legitimately try and extend the project as proper learning would dictate, fall flat on their backsides, and come over here wanting help. We have to tell them all they thought they knew is wrong. They are sometimes reluctant to accept this and sometimes go away.

It is simpler to learn stuff that is right than to learn crap that is wrong.

The problem is that anyone can put one of these travesties up on line. There is no quality review or peer review at all. There are plenty of good stuff on line but you have to be able to sort it from the crap. Books on the other hand have some sort of minimum level of competency, but then you have to pay for that filter.

Ok, I understand why you're upset. And thank you very much for your time explaining everything.

What kind of technique would you suggest?

This one here?

What kind of technique would you suggest?
This one here?

That is one way of doing things, and is reasonable in its explanation. It will certainly deliver better results than the one you cited at first. The latest one you cited has a true 8 bit output and better restoration filter than most.

There are many ways to do this. The best way would be to use an proper A/D converter. Home made ones only work to about 5 to 6 bits and any more bits you add only add to the noise on the signal. Using a parallel A/D however is a poor decision because:-

  1. The way the Arduino processor is made means you can't write 8 bits with one instruction, and writing in two or more instruction generates glitches in the signal unless there is a latch inside the A/D.
  2. It simply takes up a lot of pins.
    Using the 2R-R A/D ladder would be much better with only 5 bits and the pins used allocated intelligently so that the sample could be delivered in one direct port access instruction.

The best sort of A/D is one with a serial interface one, just a couple of pins and it is very quick send out data if you do it right. With the same interface you can have 8, 12 or more bits for the same number of interface pins.

There are two factors involved here, sample rate, how fast the samples are produced and quantisation rate, how many bits you have to specify a voltage. Both contribute to the noise and it is a bit of a balancing act as to what is best. The exact situation you are in contributes to that balancing act.

The limitations with an Arduino are mainly memory ones. You can generate signals like that last site you linked to, and you can generate any wave shape as well. For samples, like speech or music, without a source of external memory like an SD card you can only get about 3.5 seconds of audio. Which can be enough at times. I got a "yes" and "no" sample in only a quarter of the available memory.

Grumpy_Mike:
It is crap because you can not achieve monotonicity on 8 bits with an R-2R ladder with the tolerance of resistor that you can actually buy at anything other than an extortionate price.

And even if you do pay the extortionate price, half of those super-accurate resistors are series with the on-resistance of the MOSFET transistors inside the chip. The N-channel (for low) and P-channel (for high) are similar, but not perfectly matched in resistance. Their resistance also changes considerably, so even if you manage to carefully measure the transistor effects and correct your external resistors for the particular chip you have, it'll only match well at the exact temperature where you measured.

The other huge problem with (simple) resistor ladders and also with PWM is the signal is directly derived from the power supply voltage.

Real DACs use a stable reference voltage, just like Arduino's ADC can use the internal reference, or an external one if you really care about an accurate reference.

When you use the power supply as a reference, any changes in the power supply voltage are reflected in the DAC analog output or ADC measured values. For measuring pots, that's exactly what you want, since the power supply changes also change the voltage on the pot, so it using the power supply as a reference tends to cancel out those voltage changes.

But for a DAC, you'd almost never want to use the power supply as your reference voltage. Any fluctuations or noise on the power supply go right into your analog signal.

Whoa guys take it easy!!! I know you wanna help me, and I appreciate that; but please, calm down. Now I'm kind of confused.

Ok guys, I actually don't pretend to take this too far away (until I get my SD card module), for now I'm fine with the "simple stuff".

Now I have a problem. I tried to reproduce the sound with a PWM pin and a RC low-pass filter. Also I used a LM386-based amp to catch up the final output to a pair of earbuds (unfortunately I didn't have any PC speaker on hand). Apart from a lot of white noise, I just heard a PWM signal changing its duty cycle very quickly (before and after the filter), instead of the actual audio. What's wrong? Is not possible by PWM, or the low-pass filter isn't doing its job?

I'm gonna to describe my low-pass filter and tell me if something is wrong.

It's a RC low-pass filter. It uses 200 ohms for resistence, and 0.1 uF (100 nF) for capacitance. Resistor goes in series with the output, and the capacitor is connected; one node between the resistor and the output; and the other to ground (as a RC low-pass filter is supposed to be). With that values, in theory, I should achieve the "cut-off point" at aprox. 8 kHz.
For resistence, I used two 100 ohm resistor in series. And for capacitance, a small orange ceramic capacitor with a "104" printed on it.

Please tell me if I made something wrong. Also I'm gonna attach the source code file in case of something else.

PD: my apologies if my texts are a bit difficult to understand, that's because english is not my native language. And thanks by the way...

AudioSample.ino (171 KB)

I can't read a .ino file on an iPad why did you not just post it normally using code tags.
Have you changed the default frequency of the PWM. It needs to go at least four times the sample rate.

That filter sounds wrong, the capacitor does not go on the output pin but the other end of the resistor to ground. Ceramic is a poor choice for audio work as it is not a very stable capacitance.

Grumpy_Mike:
I can't read a .ino file on an iPad why did you not just post it normally using code tags.

Oh, my bad. Code at the end of the replay.

Have you changed the default frequency of the PWM. It needs to go at least four times the sample rate.

How to do that? Is that "healthy" for the MCU? The change is permanent or just depends of the sketch itself? Will that affect the "delay" functions?

That filter sounds wrong, the capacitor does not go on the output pin but the other end of the resistor to ground. Ceramic is a poor choice for audio work as it is not a very stable capacitance.

Yeah. Another mistake of mine. When I said "output", I mean the output of the signal, not the actual output pin. And yes, one node after the resistors and the other to ground, as I said before. So, do you suggest me a electrolytic capacitor? A electrolytic one of 0.1 uF does actually exist?

And now here's the code (be aware that the samples' array were trimmed due to the character count limit of the post):

/*
 * This is the source code of a built-in-audio player.
 * At this way, it's attempting to reproduce a sound
 * with PWM.
 * 
 * Made by Lucario448
 */

const unsigned char data[28167] PROGMEM =
/*
 * Never loaded into RAM, this very long array is read directly
 * from the flash memory.
 * I don't know if it could be a unsigned byte array, since
 * this is generated by program called bin2h.
 * 
 * This array contains all the necessary samples to reproduce
 * the sound (displayed in hex form). Should be at a rate of 16 kHz.
 */
{
  0x7E, 0x7D, 0x7D, 0x7E, 0x7F 
};
// The actual array were trimmed due to the character count limit of the post.
// The full array is within the file attached in the previous replay.


const int speaker = 9; // using pin 9 as the PWM output.
void setup() {
  pinMode(speaker, OUTPUT);
  // setting up the pin. I don't know if this step is redundant.

}

void loop() {
  // Currently not using a way to stop playing it unless by shutting-down the board.
  // So, this will keep looping the sound forever.
  for (short i; i < sizeof(data); i++) { // Scan throughout the array above
    analogWrite(speaker, pgm_read_byte(data[i]));
    // Assuming that this function takes a few CPU cycles to execute, maybe not true.
    delayMicroseconds(62);
    // If the comment above is true, then this delay should allow to playback the
    // samples at the desired 16 kHz.
  }
}

How to do that?

http://playground.arduino.cc/Code/PwmFrequency

Is that "healthy" for the MCU?

It has zero effect on the CPU, the CPU is not involved with producing PWM it is pure hardware.

The change is permanent or just depends of the sketch itself?

It will act until the next reset of the processor.

Will that affect the "delay" functions?

Only if you play with Timer 0, Using Timers 1 & 2 will not affect anything as the default option is to drive the PWM.

Hey man. I changed the PWM to the fastest possible for pin 9. Still listening a PWM signal (and for some reason, I don't need amplifier anymore, for earbuds). Shall I need even a faster frequency (pin 3), or definitely the capacitor of my low-pass filter is not doing its job?

PD: I hope don't reduce drastically the life span of the flash memory, since the IDE overwrites all the same samples (29 KB) every time I need to upload any minimal change in the code.

I changed the PWM to the fastest possible for pin 9.

No just set it to approximately four times your sample rate.

PD: I hope don't reduce drastically the life span of the flash memory, since the IDE overwrites all the same samples (29 KB) every time I need to upload any minimal change in the code.

Why should it? Any small change results in rewriting all bytes, it is the erase cycle that has a limit on them so it matters not if you change one byte or them all.

It's me once again hehe. Still listening a PWM signal (or whatever it is, but still not the actual sound).
So, it's definitely something wrong with the low-pass filter, or should I jump to the R-2R ladder and give up with PWM?

Look at my current code in case that there is the problem:

/*
   This is the source code of a built-in-audio player.
   At this way, it's attempting to reproduce a sound
   with PWM.

   Made by Lucario448
*/

const unsigned char data[28167] PROGMEM =
  /*
     Never loaded into RAM, this very long array is read directly
     from the flash memory.
     I don't know if it could be a unsigned byte array, since
     this is generated by program called bin2h.

     This array contains all the necessary samples to reproduce
     the sound (displayed in hex form). Should be at a rate of 16 kHz.
  */
{
  0x7E, 0x7D, 0x7D, 0x7E, 
}; // Trimmed for the character count... blah blah blah.


const int speaker = 5; // using pin 5 as the PWM output.
void setup() {
  pinMode(speaker, OUTPUT);
  // setting up the pin. I don't know if this step is redundant.
  setPwmFrequency(speaker, 1);
  // Adjust the PWM frequency to the highest possible. Avoid using pins that mess up
  // the Timer0 (pins 3, 5, 6, or 11), otherwise "delay" functions may not work properly.
  // Temporarily using pin 5 since the maximum frequency possible is required.
}

void loop() {
  // Currently not using a way to stop playing it unless by shutting-down the board.
  // So, this will keep looping the sound forever.
  for (short i; i < sizeof(data); i++) { // Scan throughout the array above
    analogWrite(speaker, pgm_read_byte(data[i]));
    // Assuming that this function takes a few CPU cycles to execute, maybe not true.
    delay(40);
    // If the comment above is true, then this delay should allow to playback the
    // samples at the desired 16 kHz.
  }
}

void setPwmFrequency(int pin, int divisor) {
  // A function that modifies the PWM frequency of the specified pin.
  // Taken from http://playground.arduino.cc/Code/PwmFrequency
  byte mode;
  if (pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if (pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if (pin == 3 || pin == 11) {
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}

Is that the actual code you are using?

If so there is no sample in it.