Playing Real Audio on Arduino: From .WAV to C-Header

In this tutorial, I will show you how to easily convert any audio file into a C-Header file for your embedded systems projects using my web-based Audio to C-Header Converter tool. From there, we’ll move to the hardware side, where I’ll show you how to build a complete output stage—incorporating a low-pass filter, an Op-Amp, and a speaker—to bring your embedded projects to life with crystal-clear audio. audio. That is, you will learn how to store short sound clips in Arduino or any microcontroller internal memory, as header file and play it directly from the Arduino’s internal memory.

Here is step to step guide.

The Secret: Fast PWM and Timer Interrupts

Standard Arduino analogWrite() is too slow for audio. To get "Real Audio," we need to use two hardware timers working in tandem:

  1. Timer 2 (The Carrier): Operates at a very high frequency (62.5 kHz) to create a Fast PWM signal. This acts as our "Digital-to-Analog Converter."

  2. Timer 1 (The Sample Clock): Acts as a heartbeat, firing exactly 8,000 times per second (8kHz) to tell the Arduino when to load the next byte of audio data.

Step 1: Prepare Your Audio

Arduino has limited Flash memory (32KB on an Uno). To fit audio, we must compress it:

  • Format: Mono, 8-bit unsigned.

  • Sample Rate: 8000 Hz.

Pro-Tip: Use my Audio to C-Header for Embedded Systems web app. Just upload your .wav file, and it will generate a sounddata.h file (or provide another name for the file) containing a PROGMEM array. This ensures the audio is stored in Flash memory, not RAM.

audio to c header file converter web app

Step 2: The Implementation

Below is the optimized C code to handle the playback. This version includes a "safety gate" to ensure the speaker stays completely silent when the audio isn't playing—perfect for projects like temperature alarms.

#include "sounddata.h"

const int tempPin    = A0;
const int speakerPin = 3;  // OC2B = PORTD bit 3

volatile uint32_t sampleIndex = 0;
volatile bool     isPlaying   = false;

void stopAudio() {
  cli();
  isPlaying = false;
  sampleIndex = 0;
  
  // 1. Disable the Timer 1 Interrupt (The Sample Clock)
  TIMSK1 &= ~_BV(OCIE1A); 

  // 2. Disconnect PWM from Pin 3 and stop Timer 2
  TCCR2A = 0; 
  TCCR2B = 0; 
  
  // 3. Force the physical pin LOW
  PORTD &= ~(1 << 3);
  OCR2B = 0;
  
  sei();
}

void startAudio() {
  cli();
  sampleIndex = 0;
  isPlaying = true;

  // 1. Re-enable Timer 1 Interrupt
  TIMSK1 |= _BV(OCIE1A);

  // 2. Setup Timer 2 for Fast PWM again
  TCCR2A = _BV(WGM21) | _BV(WGM20) | _BV(COM2B1);
  TCCR2B = _BV(CS20); 
  
  sei();
}

ISR(TIMER1_COMPA_vect) {
  if (!isPlaying) return;

  if (sampleIndex < SOUNDDATA_LENGTH) {
    OCR2B = pgm_read_byte(&(soundData[sampleIndex]));
    sampleIndex++;
  } else {
    // End of sample — complete stop
    isPlaying = false;
    sampleIndex = 0;
    TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
    TCCR2B = 0;  // Stop Timer 2 completely
    OCR2B = 0;
    TCNT2 = 0;
    PORTD &= ~(1 << 3);
  }
}

void setup() {
  DDRD  |=  (1 << 3);   // pin 3 as output via port register
  PORTD &= ~(1 << 3);   // start LOW

  cli();

  // Initially stop Timer 2 completely
  TCCR2A = 0;
  TCCR2B = 0;  // Timer 2 is stopped
  OCR2B = 0;

  // Timer 1: CTC, 8000 Hz
  TCCR1A = 0;
  TCCR1B = _BV(WGM12) | _BV(CS11);
  OCR1A  = 249;
  TIMSK1 = _BV(OCIE1A);

  sei();
}

void loop() {
  int   adcValue     = analogRead(tempPin);
  float temperatureC = adcValue * (500.0 / 1023.0);

  if (temperatureC >= 40.0) {
    if (!isPlaying) startAudio();
  } else {
    if (isPlaying) stopAudio();
  }

  delay(200);
}

Step 3: Filtering the Output

Since the Arduino outputs a square-wave PWM, you’ll hear a high-pitched hiss if you connect it directly to an amplifier. You must use a Low-Pass Filter.

A simple RC Filter ($R=1.6k\Omega, C=100nF$) will smooth out the PWM carrier and leave you with a clean analog voice. 

Arduino audio generation

This circuit represents a complete system for playing a warning audio message based on a temperature reading. It combines digital logic (the Arduino) with analog signal processing (the filter and amplifier) to drive a speaker.

Here is the step-by-step breakdown of how the signal flows from the sensor to the speaker:

1. Temperature Sensing (The Input)

  • LM35 (U2): This analog temperature sensor outputs a voltage proportional to the temperature ($10mV$ per degree Celsius).

  • In the image, it is reading 40.0°C, which means it is sending $0.4V$ ($400mV$) to the Arduino's A0 analog input pin.

  • Based on your code, this 40°C threshold triggers the Arduino to start generating the audio signal.

2. Audio Generation (The Microcontroller)

  • Arduino UNO (ATmega328p): The code reads the sensor. Since the temperature is $\ge 40^\circ C$, it activates Timer 2 to output a Fast PWM signal on Pin 3.

  • This PWM signal is essentially a very fast digital square wave flipping between $0V$ and $5V$, where the width of the pulses represents the audio waveform.

3. Digital-to-Analog Conversion (The Low-Pass Filter)

  • R1 (1.6kΩ) & C1 (100nF): You cannot feed a raw PWM square wave directly into an audio amplifier; it will sound like harsh buzzing. This RC network acts as a Low-Pass Filter (LPF).

  • It smooths out the high-frequency PWM carrier signal (the rapid 0V/5V switching) and leaves behind only the underlying lower-frequency analog audio waveform.

4. Audio Amplification (The Op-Amp- LM358/LM386)

  • U3 (OPAMP): The smoothed analog signal enters the non-inverting input (+).

  • RV1 (10kΩ Potentiometer) & R2 (1kΩ): These resistors form a feedback loop connected to the inverting input (-), configuring the Op-Amp as a Non-Inverting Amplifier.

  • The potentiometer (RV1) acts as your volume control. By adjusting it, you change the gain (how much the signal is multiplied).

5. DC Blocking and Output (The Speaker)

  • C2 (10μF): The Arduino operates on single-supply power ($0V$ to $5V$), meaning the audio signal has a positive DC offset (it "rides" around $2.5V$ instead of $0V$). Speakers need an AC signal that pushes and pulls around $0V$. This coupling capacitor blocks the DC voltage but allows the AC audio signal to pass through.

  • LS1 (Speaker): Finally, the amplified AC signal drives the speaker coil to produce sound.

The following video shows how to build the Arduino audio circuit and how the circuit works.


Related tutorials:

Post a Comment

Previous Post Next Post