2
\$\begingroup\$

I never had to deal with sound and I am pretty sure some of you guys have some experience in that kind of application.

My question is more about getting some tips on the different ways to implement an embedded system based on an AVR that plays sounds. More precisely:

  • What library should I use?
  • Which sound format should I choose? Do I necessarily need a dedicated memory chip aside the MCU? If yes, which one would you recommend?
  • Do you have some code sample to share?

As always, I know that everything depends on the target application. Here are a few precisions:

  • I want to make a toy for the kids of a friend of mine, the idea is to have a short sound lasting no more than 30[s]
  • I am not interested in high fidelity sound
  • Power consumption matters
\$\endgroup\$

closed as primarily opinion-based by Matt Young, placeholder, PeterJ, Chetan Bhargava, Daniel Grillo Aug 18 '14 at 11:39

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • \$\begingroup\$ Do you have a specific AVR in mind? There are some AVR MCUs that have a DAC, which is the preferred solution, but ones such as the Atmega328p do not have a DAC \$\endgroup\$ – Funkyguy Aug 17 '14 at 3:33
5
\$\begingroup\$

I used an ATtiny85 to drive a speaker through a LPF via two complementary PWM outputs (OC1B/nOC1B) upon pressing a button. Less than 0.5k of code leaves 7.5k for the audio. Larger chips leave more space for audio or the ability to use real SPI or I2C for storage. Volume depends on the supply voltage, but you can still get decent performance from a half-dead Li-ion cell.

What the heck, I'm feeling generous. Note that none of this attempts to be very power-conscious though; see the license for modification details. And don't try to drive too large a speaker through it. And beware the crappy commenting.

/***
 * Copyright © 2014 Ignacio Vazquez-Abrams
 * This work is free. You can redistribute it and/or modify it under the
 * terms of the WTFPL, Version 2,
 * as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
 */

#define F_CPU 16000000

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/power.h>

#include "audio8k.h"

// Generate audio using PWM on OC1B/~OC1B H-bridge on ATtinyX5
// This leaves the USI pins available for expansion
// Timer 1 is used to drive PWM, timer 0 is used to load the next
// sample

// References:
// http://playground.arduino.cc/Code/PCMAudio
// http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_binarydata

// play state information
typedef enum {
  STATE_STOPPED,
  STATE_STARTING,
  STATE_PLAYING,
  STATE_STOPPING
} playState;

playState currentState __attribute__ ((section (".noinit")));

// how far into the audio
unsigned long sampleCount __attribute__ ((section (".noinit")));

static void start_playing();
static void ramp_up();
static void play();
static void ramp_down();
static void stop_playing();

void start_playing()
{
  cli();

  // connect OC1B
  GTCCR |= (_BV(PWM1B) | _BV(COM1B0));
  DDRB |= (_BV(PB4) | _BV(PB3));

  // clki/o/8 (2MHz) prescaling for timer 0
  TCCR0B = (_BV(CS01));

  // tune timer 0 to 32kHz
  OCR0A = 63;

  // pck (64MHz, 500kHz PWM) prescaling for timer 1
  TCCR1 = (_BV(CS10));

  // prime the PWM
  OCR1B = 0x00;

  // unprescale clock
  clock_prescale_set(clock_div_1);

  // prepare for liftoff
  sampleCount = 0;
  currentState = STATE_STARTING;

  sei();
}

void ramp_up()
{
  unsigned char firstSample = pgm_read_byte(&audio8k[0]);
  OCR1B += 1;
  if (OCR1B >= firstSample)
  {
    OCR1B = firstSample;
    currentState = STATE_PLAYING;
  }
}

void play()
{
  // audio from PROGMEM
  OCR1B = pgm_read_byte(&audio8k[sampleCount++ >> 2]);
  if ((sampleCount >> 2) >= (audio8k_end - audio8k))
  {
    currentState = STATE_STOPPING;
  }
}

void ramp_down()
{
  if (OCR1B > 1)
  {
    OCR1B -= 1;
  }
  else
  {
    OCR1B = 0;
    stop_playing();
  }
}

void stop_playing()
{
  cli();

  // clki/o/8 (2MHz) prescaling for timer 0
  TCCR0B = (_BV(CS01));

  // tune timer 0 to 7812.5Hz
  OCR0A = 0x00;

  // prescale clock by 256
//XX UNCOMMENT FOR PRODUCTION  clock_prescale_set(clock_div_256);

  // kthxbye
  currentState = STATE_STOPPED;
  sampleCount = 0;

  sei();
}

void setup()
{
  // ssshhhh....
  cli();

  // speaker is connected to pins 3 and 2, PB4/PB3 (OC1B/~OC1B)
  DDRB |= (_BV(PB4) | _BV(PB3));

  // button pullup goes here
  PORTB |= _BV(PB2);

  // use CTC on timer 0 for finer tuning
  TCCR0A = _BV(WGM01);

  // enable the timer 0 output compare A interrupt
  TIMSK |= _BV(OCIE0A);

  // enable async clock (PCK) for timer 1
  PLLCSR |= _BV(PCKE);

  // set sleep mode for later, leave i/o running
  set_sleep_mode(SLEEP_MODE_IDLE);

  // sane defaults
  stop_playing();

  // and... go!
  sei();
}

// state machine dispatch
ISR(TIM0_COMPA_vect)
{
  switch (currentState)
  {
    case STATE_STOPPED:
    {
      if (!(PINB & _BV(PB2)))
      {
        start_playing();
      }

      break;
    }
    case STATE_STARTING:
    {
      ramp_up();

      break;
    }
    case STATE_PLAYING:
    {
      play();

      break;
    }
    case STATE_STOPPING:
    {
      ramp_down();

      break;
    }
  }
}

void loop()
{
// this loop should do nothing; everything is handled by interrupts
  sleep_enable();
  sleep_cpu();
  sleep_disable();
}

int main()
{
  setup();
  while (1)
    loop();
}
\$\endgroup\$
3
\$\begingroup\$

Which sound format should I choose ?

.wav file format would be easier to decode.

Do I necessarily need a dedicated memory chip aside the MCU ? If yes, which one would you recommend ?

You can use EEPROM or flash memory to store the data. See this question: How to store audio data for AVR project?

Do you have some code sample to share ?

Searching for avr wav player will give you a lot of sample source codes. But most of them will be SD card based like this. See Ignacio Vazquez-Abrams's answer.

\$\endgroup\$
3
\$\begingroup\$

Take a look at this proof of concept project of mine.

It is basically programmed with musical notes in a simple text file melodies/<melodyFile>. The notes in this text file are then converted using a small perl script (./converter) to numbers used to generate the required frequency using one of the AVR's timers (melody.c). When the controller is programmed, the melody is included with the player source and flashed into the controller as a whole.

Here is a video of the project, the sound from the controller is not amplified so a bit low.

Advantages:

  • small memory footprint
  • simple code
  • requires no extra components other than a piezo speaker

Disadvantages:

  • requires some patience to program a new melody
  • best results at 8MHz, but that can be derived from the internal RC oscillator

I didn't test power consumption as it is just a simple proof of concept, but there is room for power efficiency improvement when required, by putting the controller in sleep mode while it waits for the next 'tick'. A second timer should be set up for that.

The converter tool is written in Perl and although it works just fine, it can do with more comments.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.