Topics

► Games

► Sound & Music

► Watches & Clocks

► GPS

► Power Supplies

► Computers

► Graphics

► Thermometers

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0-series and 1-series

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny3216

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28

ARM

► ATSAMD21

► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon

Feeds

RSS feed

Audio Sample Player

29th September 2014

This project describes a simple ATtiny85 application to output a short sample of digital audio through a loudspeaker. It can play a one-second sample of any digitised audio using just a loudspeaker and no other components:

Audio.jpg

It takes advantage of the special 64MHz clock option in the ATtiny85 which you can use to drive Timer/Counter1 for fast digital-to-analogue conversion. As far as I know the only other Atmel chip with this feature is the ATtiny861.

Applications have already been published to show how to output audio from a data chip [1], or SD memory card [2], using an ATtiny85. I wanted to see how simple I could get the project by using the memory in the ATtiny85 itself to store a short sample. The initial motivation for this project was to provide a more interesting alert for a timer than a simple beep.

The circuit

The circuit is about as simple as it gets:

AudioSamplePlayer.png

The program

The program to output the audio samples is very small, leaving almost the full 8K of flash memory in the ATtiny85 available to store our audio sample. Using 8-bit samples at a sampling rate of 8kHz this is enough for a one second sample.

Here's the procedure I used to include the data in the ATtiny85 memory, starting from an audio file of the sample.

Downsample the data

Assuming your audio sample is normal quality, the first step is to downsample it to 8-bit, mono, at an 8kHz sample rate. Many audio editors will do this; I used iTunes, as follows.

  • Load the sample into your iTunes library.
  • On the General tab in Preferences click the Import Settings... button.
  • Select WAV Encoder, Custom....
  • In the WAV Encoder dialog box select 8.000 kHz, 8-bit, Mono:

WAVEncoder.gif 

  • Click OK to close each dialog box and the Preferences dialog box.
  • Select the original track in iTunes.
  • From the File menu choose Choose Create New Version -> Create WAV Version.

The smaller version will be created as a separate track in iTunes, and you can drag it out onto the desktop.

Extract the raw audio data

The WAV file format contains a 44-byte header followed by the raw data. On a Mac or Unix computer you can dump the data in a C-friendly text format using the built-in xxd command in a Terminal window:

  • Copy the audio file into your home directory.
  • Open the Terminal application.
  • Type the following command, substituting the name of your WAV file:
xxd -i -s +44 quack.wav

The -i parameter tells xxd to output the data in C include format, and the -s parameter specifies an offset to omit the first 44 bytes of the file. The output will look something like:

unsigned char quack_wav[] = {
  0x7d, 0x7c, 0x7e, 0x7e, 0x7d, 0x7b, 0x7b, 0x7c, 0x7c, 0x7e, 0x7f, 0x7f,
  0x81, 0x83, 0x84, 0x86, 0x88, 0x8a, 0x8b, 0x8c, 0x88, 0x7f, 0x76, 0x6f,
  ...
  0x82, 0x7f, 0x80
};
unsigned int quack_wav_len = 1467;

It also gives you a variable containing the length of the data, which we will use to detect the end of the audio.

  • Cut and paste this from your Terminal window to your program in the Arduino project window.
  • Change unsigned char in the first line to PROGMEM prog_uchar, to instruct the compiler to put the data into program memory rather than RAM.

The setup routine

The key part of the program is to set up the two timer/counters in setup():

void setup () {
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<<PCKE | 1<<PLLE;     
 
  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                              // Timer interrupts OFF
  TCCR1 = 1<<PWM1A | 2<<COM1A0 | 1<<CS10; // PWM A, clear on match, 1:1 prescale
  GTCCR = 1<<PWM1B | 2<<COM1B0;           // PWM B, clear on match
  OCR1A = 128; OCR1B = 128;               // 50% duty at start

  // Set up Timer/Counter0 for 8kHz interrupt to output samples.
  TCCR0A = 3<<WGM00;                      // Fast PWM
  TCCR0B = 1<<WGM02 | 2<<CS00;            // 1/8 prescale
  TIMSK = 1<<OCIE0A;                      // Enable compare match
  OCR0A = 124;                            // Divide by 1000

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  pinMode(4, OUTPUT);
  pinMode(1, OUTPUT);
}

The first section turns on the 64MHz Phase-Locked Loop (PLL), and specifies this as the clock source for Timer/Counter1.

Timer/Counter1 is then set up in PWM mode, to make it act as a two-channel digital-to-analogue converter, using the values in OCR1A and OCR1B to vary the duty cycle. The frequency of the square wave is specified by OCR1C; we leave it at its default value, 255, which divides the clock by 256, giving a 250kHz square wave.

Finally Timer/Counter0 is used to generate an 8kHz interrupt to output the samples.

The interrupt service routine

Everything else is then done by the interrupt service routine:

// Sample interrupt
ISR (TIMER0_COMPA_vect) {
  char sample = pgm_read_byte(&quack_wav[p++]);
  OCR1A = sample; OCR1B = sample ^ 255;
  // End of data? Go to sleep
  if (p == quack_wav_len) {
    adc_disable();
    sleep_enable();
    sleep_cpu();  // 1uA
  }
}

This outputs one sample to the PWM registers. To avoid the need for a coupling capacitor between the ATtiny85 outputs and the loudspeaker, the loudspeaker is directly coupled to two outputs, which are fed complementary versions of the audio to avoid any DC being fed to the speaker. Note that if you want to feed the output to an audio amplifier you should include a low-pass filter; otherwise the 250kHz carrier could damage the amplifier.

Once we've reached the end of the waveform the interrupt service routine puts the processor to sleep to save battery power, and avoid the need for an on/off switch. The standby current consumption is under 1µA, which is negligible.

I compiled the program using Spence Konde's ATTiny Core, which supersedes the various earlier ATtiny cores [3]. Select the ATtiny25/45/85 option under the ATtinyCore heading on the Boards menu. Then choose Timer 1 Clock: CPUB.O.D. DisabledATtiny858 MHz (internal) from the subsequent menus. Choose Burn Bootloader to set the fuses appropriately. Then upload the program using ISP (in-system programming); I used Sparkfun's Tiny AVR Programmer Board; see ATtiny-Based Beginner's Kit.

Here's the whole program, including the data for the "quack" sound I used in the prototype: Audio Sample Player Program.

Other applications

Here are some suggested applications for this project: a clock that chimes the hours like an old grandfather clock, a mystery box that goes "cheep" randomly every few minutes, or a greetings card that says "Happy Birthday!" or "I love you" in your own voice. You can say quite a lot in a second!

Update

23rd October 2017: Made a minor change to the program to make it compile under the latest Arduino IDE, and updated the instructions to use Spence Konde's ATTiny Core.


  1. ^ Trinket Audio Player on Adafruit.
  2. ^ Simple SD Audio Player on ELM-ChaN.
  3. ^ ATTinyCore on GitHub.

blog comments powered by Disqus