Microcontroller projects

Audio on microcontroller tips and tricks

last updated: 23/12/19

From WAV or Mp3 to C

Sometimes we only want to play a short sound sample on our microcontroller. With an ESP32 we can use the internal DAC. With other controller the analogWrite() method gives us a PWM (PCM). In the following example I use a little speaker (one of those contained in birthday cards) connected do pin D7 (GPIO13) of an ESP8266 (Wemos D1 mini pro).

Audacity

First we open the file in Audacity a free powerful open source software. Then we eliminate one channel if it is a stereo file. This can be done by clicking on the file name and choosing Split Stereo to Mono and after this by deleting one of the channels.

audacity

Next we choose a sample rate of 22.050 kHz in the Project Rate window at the left bottom of the window. We can't choose a lesser frequency because the PWM frequency also would be audible.

audacity

After this we export the file in an unsigned 8 bit format. Click on File > Export > Export as WAV. In the new window we chose Other uncompressed files as file format and Unsigned 8-bit PWM in Encoding (no header).

Python

The WAV file has a header of 44 byte. This has to be removed. The remaining bytes contain the 8 bit (0-255) values of our PWM. As we use Arduino (C, C++) we need an array variable to contain the data. As these variables are quite big, we will write them in a separate header file. For such tasks python is a cool programming language. The following script does the work:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    # gpio_blink.py

    import sys
    from functools import partial

    if len(sys.argv) < 2:
      sys.exit('Usage: %s file' % sys.argv[0])

    old_file = sys.argv[1]
    variable_name = sys.argv[1].split('.')
    variable_name = variable_name[0]
    new_file = variable_name + ".h"
    print (new_file)

    with open(new_file, 'w') as outfile:
        outfile.write("const byte ")
        outfile.write(variable_name)
        outfile.write("[] PROGMEM = {")

        n = 0
        with open(old_file, "rb") as in_file:
            for c in iter(partial(in_file.read, 1), b''):
                if n > 44:
                    outfile.write("0x%02X," % ord(c))
                n += 1
                if (n % 16 == 0) and (n > 44):
                    outfile.write("\n")
        outfile.write("};")

Save the file as wav2c.py and run the program with (python instead of python3 on Windows).

    python3 wav2c.py xmas_22050.wav

The argument is the wav file, and as result we get a header file (same name, but with extension .h).

Arduino

The new header file has to reside in the same folder than the sketch. As the wav header file respectively our array variable can get very big, we save the data in flash by using PROGMEM:

    const byte xmas_22050[] PROGMEM = {0x7C,0x7F,0x83,0x83,0x7F,0x78,
    ...
    ...
    0x84,0x83,0x83,0x80,0x83,0x82,0x7F,0x79,0x7C,0x7E,0x7D,0x7A,0x7C};

Here it would be a good idea to use the SPIFFS file system to store the file, but we want to keep the code simple for this tips and tricks example.

    // play_wav.ino
    // play wav files (8 bit, sr 22050Hz) on ESP8266
    // weigu.lu

    #include "xmas_22050.h"

    #define wav xmas_22050
    unsigned long now_micros, prev_micros, count;

    void setup() {
      analogWriteFreq(22050); // 22kHz PWM
      analogWriteRange(255);  // reduce to 8 bit
    }

    void loop() {
      count = 0;
      prev_micros = micros();
      while (count != sizeof(wav)) {
        now_micros = micros();
        if ((now_micros - prev_micros) > 45) {
          prev_micros = now_micros;
          byte a_value = pgm_read_byte(wav+count);
          analogWrite(D7,a_value);
          count++;
          yield(); // to avoid watchdog reset
        }
      }
      delay(2000);
    }

By default the PWM range for the ESP8266 is 0-1023, so we reduce it to 8 bit. We use the micros function to call analogWrite() every 45µs (1/22050Hz). We can also use the ticker library or timer interrupts. The yield() function is necessary because we block the main loop and so the software watchdog gets nervous after a certain time.

That's it folks :)


Downloads