PWM Audio Synthesis: How to Generate Sound and Voice in Embedded Systems Using Pulse Width Modulation

If you have worked with PWM (Pulse Width Modulation), you probably know it for motor control, LED dimming, or switching regulators. But PWM can also generate audio signals: beeps, tones, melodies, and even human voice playback.

This technique is common in cost-sensitive embedded products where a dedicated DAC (Digital-to-Analog Converter) is too expensive, too power-hungry, or simply unnecessary. Toys, alarms, IoT devices, industrial indicators, and even some musical instruments use PWM audio.

The trade-off is clear: PWM audio costs almost nothing (just a timer peripheral and a passive filter), but quality is limited compared to dedicated audio hardware.

This article explains:

  • How PWM generates analog signals from digital values
  • Why PWM frequency matters for audio quality
  • The Nyquist theorem and its role in PWM audio design
  • Minimum PWM frequency requirements for speech and music
  • Low-pass filtering and component selection
  • Practical implementation examples (STM32, ESP32, Arduino)
  • When to use PWM vs. I²S DAC vs. external audio codec

1. The Core Principle: PWM as a Digital-to-Analog Converter

PWM modulates a digital signal’s duty cycle (the percentage of time the signal is HIGH) while keeping the frequency constant.

When you pass a PWM signal through a low-pass filter, the high-frequency switching component is removed, leaving only the average voltage. This average voltage is proportional to the duty cycle.

PWM duty cycle to voltage mapping

If your MCU runs PWM at 3.3V logic:

  • 0% duty cycle → 0V average (after filtering)
  • 50% duty cycle → 1.65V average (after filtering)
  • 100% duty cycle → 3.3V average (after filtering)

This creates a simple, low-cost DAC.

Why this works for audio

Audio signals are continuous waveforms (sine waves, voice spectra, music) that vary in amplitude over time. If you can control voltage over time, you can recreate audio.

The trick is to:

  1. Sample the audio signal at regular intervals (digitization).
  2. Convert each sample into a PWM duty cycle.
  3. Update the PWM duty cycle fast enough to recreate the waveform.
  4. Filter out the high-frequency PWM carrier, leaving only the audio.
flowchart LR
  A[Digital Audio Sample] --> B[Map to PWM Duty Cycle]
  B --> C[Timer Peripheral PWM Output]
  C --> D[Low-Pass Filter]
  D --> E[Analog Audio Signal]
  E --> F[Speaker or Headphones]

2. Why PWM Frequency Matters: Understanding the Nyquist Theorem

The Nyquist-Shannon sampling theorem states:

To perfectly reconstruct a signal, you must sample it at at least twice its highest frequency component.

In PWM audio, the situation is reversed but related:

  • Your audio signal has frequency components up to some maximum (e.g., 20 kHz for full human hearing, 4 kHz for intelligible speech).
  • Your PWM frequency is the “carrier” that encodes the audio signal.
  • To avoid distortion and aliasing, your PWM frequency must be significantly higher than the audio bandwidth.

Practical rule: PWM frequency should be 10× the audio bandwidth

While Nyquist requires 2×, practical PWM audio needs more margin because:

  • Filtering is not perfect: Real low-pass filters have gradual roll-off, not brick-wall cutoff.
  • PWM quantization noise: Low PWM frequency means fewer duty cycle steps, introducing distortion.
  • Harmonic content: Audio signals have harmonics that extend beyond the fundamental frequency.

Minimum practical PWM frequencies for different audio types:

Audio TypeBandwidthMinimum PWM FrequencyRecommended PWM Frequency
Simple beeps/tones1 kHz10 kHz20 kHz
Intelligible speech4 kHz40 kHz50–64 kHz
Music/high-quality audio20 kHz200 kHz250–500 kHz

Why you cannot use 8 kHz PWM for voice

If you try to generate 4 kHz speech with 8 kHz PWM:

  • Your filter must cut off above 4 kHz but pass audio below 4 kHz.
  • Your PWM carrier is at 8 kHz, very close to the audio band.
  • The filter cannot separate them: you get audio mixed with PWM switching noise.
  • The result is distorted, buzzy, or unintelligible.

Correct approach: Use 50 kHz PWM for speech, giving a 46 kHz gap between audio (4 kHz) and carrier (50 kHz). This allows a gentle low-pass filter to work effectively.


3. PWM Resolution and Effective Bit Depth

PWM resolution is the number of discrete duty cycle steps available.

Calculating PWM resolution

If your timer runs at f_timer and you set PWM frequency to f_pwm, resolution in bits is:

Resolution (bits) = log₂(f_timer / f_pwm)

Example 1: STM32 at 80 MHz timer clock, 50 kHz PWM

Resolution = log₂(80,000,000 / 50,000) = log₂(1600) ≈ 10.6 bits

You get about 1600 PWM steps, roughly 10-bit DAC equivalent.

Example 2: STM32 at 80 MHz timer clock, 250 kHz PWM

Resolution = log₂(80,000,000 / 250,000) = log₂(320) ≈ 8.3 bits

You get about 320 PWM steps, roughly 8-bit DAC equivalent.

Trade-off: frequency vs. resolution

Higher PWM frequency improves filtering but reduces resolution. Lower PWM frequency improves resolution but makes filtering harder and reduces audio bandwidth.

For speech (4 kHz bandwidth), 50 kHz PWM with 10-bit resolution is a good balance.

For high-quality music, you need higher resolution (12–16 bits) and higher PWM frequency (250 kHz or more), which many MCUs cannot sustain. In that case, use a dedicated I²S DAC instead.


4. The Low-Pass Filter: Converting PWM to Analog

The low-pass filter removes the PWM carrier frequency, leaving only the audio signal.

RC low-pass filter (simplest)

A single-stage RC filter is the most common PWM audio filter.

PWM Output ---[R]---+--- Audio Out
                    |
                   [C]
                    |
                   GND

Cutoff frequency:

f_c = 1 / (2π × R × C)

Design example for speech (4 kHz audio, 50 kHz PWM):

Target cutoff: ~8 kHz (above audio, below PWM carrier).

Choose R = 2.2 kΩ, C = 10 nF:

f_c = 1 / (2π × 2200 × 10×10⁻⁹) ≈ 7.2 kHz

This filter passes audio up to ~4 kHz and attenuates the 50 kHz PWM carrier by about -20 dB at 50 kHz.

Multi-stage filters for better quality

A single RC stage has only -20 dB/decade roll-off. For cleaner audio, use a second-order (or higher) filter:

  • Two-stage RC filter: Two RC sections in series, ~40 dB/decade roll-off.
  • Sallen-Key active filter: Better performance, requires op-amp.
  • LC filter: Sharper cutoff, but inductors are larger and more expensive.

Speaker impedance and output stage

Most MCU GPIO pins cannot drive speakers directly (limited current, typically 4–20 mA).

Solutions:

  1. High-impedance speaker or buzzer: Some piezo buzzers work with direct GPIO drive.
  2. BJT or MOSFET buffer: Simple NPN transistor or N-channel MOSFET amplifies PWM output current.
  3. Audio amplifier IC: LM386, PAM8403, or class-D amp for higher power.

Typical circuit with transistor buffer:

PWM Output ---[R1]---+---[R2]---+--- Audio Out (to speaker via C_out)
                     |          |
                    [C1]      [NPN Base]
                     |          |
                    GND       [NPN Emitter] --- GND
                              [NPN Collector] --- Vcc

R1 and C1 form the low-pass filter. R2 drives the transistor base. The speaker connects to the collector through a DC-blocking capacitor.


5. Practical Implementation Examples

Example 1: Arduino Uno – Simple Tone Generation (8-bit PWM, 31.25 kHz)

Arduino’s analogWrite() uses Timer0/Timer2 with 8-bit resolution at ~31 kHz PWM frequency on most pins.

Code: Generate 1 kHz sine wave tone

// 1 kHz sine wave at 8 kHz sample rate
// PWM frequency: 31.25 kHz (Timer2 fast PWM, 16 MHz / 512)
// Low-pass filter: R=2.2kΩ, C=10nF

const int pwmPin = 9; // Pin 9: Timer1 (can set higher PWM freq)
const int sampleRate = 8000; // 8 kHz sample rate
const int toneFreq = 1000; // 1 kHz tone

void setup() {
  pinMode(pwmPin, OUTPUT);
  
  // Timer1: Phase-correct PWM, 62.5 kHz
  // 16 MHz / 256 (prescaler=1, TOP=256)
  TCCR1A = _BV(COM1A1) | _BV(WGM10);
  TCCR1B = _BV(WGM12) | _BV(CS10);
}

void loop() {
  static unsigned long lastUpdate = 0;
  static float phase = 0.0;
  
  unsigned long now = micros();
  if (now - lastUpdate >= 125) { // 8 kHz sample rate
    lastUpdate = now;
    
    // Generate sine wave sample
    int sample = (sin(phase) + 1.0) * 127.5; // 0-255 range
    analogWrite(pwmPin, sample);
    
    phase += 2.0 * PI * toneFreq / sampleRate;
    if (phase >= 2.0 * PI) phase -= 2.0 * PI;
  }
}

Hardware:

  • PWM output on pin 9
  • RC filter: 2.2kΩ resistor + 10nF capacitor
  • Speaker (8Ω) via 100µF capacitor

Example 2: STM32 – Voice Playback from Memory (16-bit timer, 50 kHz PWM)

STM32 timers offer 16-bit resolution. For voice, use 50 kHz PWM and DMA to stream samples.

Code: PWM setup (STM32 HAL)

// TIM3 Channel 1 on PA6: 50 kHz PWM, 10-bit effective resolution
// Timer clock: 80 MHz, Prescaler: 0, ARR (period): 1599
// PWM frequency = 80 MHz / 1600 = 50 kHz

TIM_HandleTypeDef htim3;

void PWM_Audio_Init(void) {
  __HAL_RCC_TIM3_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 0;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 1599; // 50 kHz PWM
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_PWM_Init(&htim3);
  
  TIM_OC_InitTypeDef sConfigOC = {0};
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
  
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

void PWM_Set_DutyCycle(uint16_t value) {
  // value: 0-1599 (10-bit range mapped to timer period)
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, value);
}

// Play audio samples using DMA + timer interrupt at 8 kHz
void Play_Audio_Sample(uint16_t *samples, uint32_t length) {
  // Set up timer interrupt at 8 kHz to update PWM duty cycle
  // DMA can transfer samples to TIM3->CCR1 automatically
}

Hardware:

  • PWM output on PA6
  • Two-stage RC filter: R1=2.2kΩ, C1=10nF, R2=2.2kΩ, C2=10nF
  • Audio amplifier: LM386 or PAM8403
  • Speaker (4Ω or 8Ω)

Example 3: ESP32 – High-Resolution PWM Audio (80 MHz LEDC, 13-bit)

ESP32’s LEDC peripheral can generate high-resolution PWM up to 80 MHz clock.

Code: ESP32 Arduino

// ESP32 LEDC: 80 MHz clock, 13-bit resolution, 9.77 kHz PWM
// For audio: use 62.5 kHz PWM, 10-bit resolution

const int pwmPin = 25;
const int pwmChannel = 0;
const int pwmFreq = 62500; // 62.5 kHz
const int pwmResolution = 10; // 10-bit (0-1023)

void setup() {
  ledcSetup(pwmChannel, pwmFreq, pwmResolution);
  ledcAttachPin(pwmPin, pwmChannel);
}

void loop() {
  // Generate 1 kHz sine wave at 8 kHz sample rate
  static float phase = 0.0;
  
  int sample = (sin(phase) + 1.0) * 511.5; // 0-1023 range
  ledcWrite(pwmChannel, sample);
  
  phase += 2.0 * PI * 1000.0 / 8000.0; // 1 kHz tone, 8 kHz sample rate
  if (phase >= 2.0 * PI) phase -= 2.0 * PI;
  
  delayMicroseconds(125); // 8 kHz sample rate
}

Hardware:

  • PWM output on GPIO 25
  • RC filter: 2.2kΩ + 10nF
  • Class-D amp or LM386
  • Speaker

6. PWM Audio vs. Dedicated DAC vs. I²S Codec

FeaturePWM AudioMCU DACI²S/I²C Codec
Cost~$0.10 (resistor + cap)$0 (built-in)$1-$5
Quality (SNR)40-60 dB60-80 dB90-110 dB
Bit depth8-10 bits effective10-12 bits16-24 bits
CPU loadMedium (sample updates)Low (DMA friendly)Very low (DMA + I²S)
Max sample rate8-16 kHz practical100 kHz+8 kHz-192 kHz
Use caseAlarms, beeps, simple voiceMedium-quality audioMusic, voice calls, pro audio

When to use PWM audio:

  • Cost-sensitive products (toys, alarms, notifications)
  • Simple audio feedback (beeps, status tones)
  • Microcontrollers without DAC peripheral
  • Low to medium audio quality acceptable

When to use MCU DAC:

  • MCU has built-in DAC (STM32, ESP32, some Cortex-M)
  • Medium-quality audio (voice prompts, simple music)
  • Need stereo (some MCUs have dual DAC)

When to use I²S codec:

  • High-quality music playback or recording
  • Voice calls, VoIP, telephony
  • Professional audio equipment
  • Stereo or multi-channel audio

7. Common Mistakes and How to Avoid Them

Mistake 1: PWM frequency too low

Symptom: Audio sounds buzzy or distorted, high-pitched whine audible.

Cause: PWM carrier frequency is too close to audio band.

Fix: Increase PWM frequency to at least 10× the audio bandwidth. For speech (4 kHz), use 50 kHz or higher.


Mistake 2: No low-pass filter or wrong cutoff frequency

Symptom: Loud PWM switching noise, speaker makes clicking sounds.

Cause: PWM carrier not filtered out.

Fix: Add RC low-pass filter with cutoff between audio bandwidth and PWM frequency.


Mistake 3: Speaker connected directly to GPIO without current buffer

Symptom: Very low volume, MCU resets or crashes, GPIO damage.

Cause: GPIO cannot source enough current for speaker.

Fix: Use transistor buffer or audio amplifier IC (LM386, PAM8403, etc.).


Mistake 4: Sample rate too low

Symptom: Audio sounds robotic, muffled, or distorted.

Cause: Nyquist theorem violated: sample rate must be at least 2× highest audio frequency.

Fix: For 4 kHz speech, use at least 8 kHz sample rate. For 8 kHz audio bandwidth, use 16 kHz sample rate.


Mistake 5: PWM resolution too low (few bits)

Symptom: Grainy, quantized audio with audible steps.

Cause: Not enough PWM duty cycle steps.

Fix: Increase timer resolution or lower PWM frequency (but keep it above 10× audio bandwidth).


8. Real-World Applications

Embedded voice prompts

Many consumer electronics use PWM audio for voice feedback:

  • Battery testers: “Battery good” or “Battery weak”
  • Medical devices: “Place finger on sensor”
  • Smart home devices: “Connected to Wi-Fi”

Implementation: Pre-recorded 8 kHz PCM samples stored in flash, played via PWM + DMA.


Musical instruments and synthesizers

DIY synths, MIDI controllers, and chiptune devices use PWM audio for square waves, sawtooth waves, and simple voice synthesis.

Implementation: Real-time waveform generation with lookup tables or direct calculation.


Alarm and notification systems

Fire alarms, industrial warnings, and doorbells use PWM to generate tones, sirens, and beeps.

Implementation: Simple tone generation with frequency sweeps or pattern playback.


Telephony and intercom systems

Some low-cost intercoms and telephony endpoints use PWM audio for voice transmission.

Implementation: 8 kHz PCM codec (G.711), PWM playback at 50 kHz.


9. Summary and Key Takeaways

PWM audio is a practical, low-cost technique for generating sound in embedded systems:

  1. PWM encodes audio as duty cycle: Average voltage (after filtering) represents audio amplitude.
  2. Nyquist theorem applies: PWM frequency must be much higher than audio bandwidth (10× recommended).
  3. Minimum PWM frequencies:
    • Speech: 50 kHz
    • Music: 250 kHz+
  4. Resolution matters: Higher PWM frequency reduces effective bit depth. Balance frequency and resolution.
  5. Low-pass filter is essential: RC filter removes PWM carrier, leaving audio.
  6. Speaker drive requires buffering: Use transistor or amplifier IC for sufficient current.
  7. PWM audio is not high-fidelity: Use dedicated DAC or I²S codec for music and professional audio.

PWM audio is a powerful tool when used correctly: cheap, simple, and good enough for many embedded applications.


References and Further Reading

  • Nyquist-Shannon Sampling Theorem: Understanding sampling and reconstruction
  • STM32 Timer Application Notes: AN4013, TIM peripheral usage
  • ESP32 LEDC Documentation: High-resolution PWM configuration
  • Audio Filtering: Op-amp filter design, Sallen-Key topology
  • I²S and Audio Codecs: When to move beyond PWM

If you found this guide useful, explore more embedded systems and real-time audio topics on this blog.