Bluetooth Audio Digital Signal Processing An Ardui
Bluetooth Audio Digital Signal Processing An Ardui
by the2ndTierney
Sum m a ry
When I think of Bluetooth I think of music but sadly most microcontrollers can't play music via Bluetooth. The
Raspberry Pi can but that is a computer. I want to develop an Arduino based framework for microcontrollers to play
audio via Bluetooth. To fully ex my microcontroller's muscles I'm going to add real-time Digital Signal Processing
(DSP) to the audio (high-pass ltering, low-pass ltering and dynamic range compression). For the cherry on top, I
will add a webserver that can be used to con gure the DSP wirelessly. The embedded video shows the basics of
Bluetooth audio in action. It also shows me using the webserver to perform some high-pass ltering, low-pass
ltering and dynamic range compression. The rst use of Dynamic range compression purposefully causes
distortion as an example of poor parameter choices. The second example eliminates this distortion.
For this project, the ESP32 is the microcontroller of choice. It costs less than £10 and is feature-packed with ADCs,
DACs, Wi , Bluetooth Low Energy, Bluetooth Classic and a 240MHz dual-core processor. The onboard DAC can
technically play audio but it won't sound great. Instead, I'll use the Adafruit I2S stereo decoder to produce a line-
out signal. This signal can easily be sent to any HiFi system to instantly add wireless audio to your existing HiFi
system.
Supplies:
Hopefully, most makers will have breadboards, jumpers, USB cables, power supply soldering irons and will only
have to spend £15 on the ESP32 and the stereo decoder. If not, all the parts required are list below.
3
2
https://youtu.be/bqw40B_JXUc
If you bought the ESP32-PICO-KIT you won't have to solder any pins as it comes pre-soldered. Simply place it on the
breadboard.
be used) but it is the cheapest alternative to a for stability but this isn't necessary for this prototype
soldering iron. Slot the cut-up header on to the system. The pins to slot the headers into are vin, 3vo,
breadboard. You should only need 1 line of 6 pins for gnd, wsel, din and bclk.
the decoder. You can add another six to the other side
Place the Stereo decoder on the push headers (vin, don't really care about the wire colour. The power pins
3vo, gnd, wsel, din and bclk pins) and rmly push are attached as follows:
them together. Again, this ideally should be done with
a soldering iron but I had to improvise. You will notice 3v3 (ESP32) -> to vin on stereo decoder
that all the wires in this instructable are blue. That's
because I didn't have any jumper wires so I cut 1 long gnd (ESP32) -> to gnd on stereo decoder
wire into smaller pieces. Also, I'm colourblind and
If you don't already have them installed install the should add my btAudio library to your Arduino
Arduino IDE and the Arduino core for ESP32. Once you libraries. To use the library you'll have to include the
have them installed visit my Github page and relevant header in the Arduino sketch. You'll see this
download the repository. Within the Arduino IDE in the next step.
under Sketch>>Include Library>> select "Add .ZIP
library". Then select the downloaded zip le. This
Once installed, connect your ESP32 to your computer via micro USB and then connect your stereo decoder to your
speaker with your 3.5mm wire. Before you upload the sketch you will need to change some things in the Arduino
editor. After you have selected your board you will need to edit the partition scheme under Tools >> Partition
Scheme and select either "No OTA (Large APP)" or "Minimal SPIFFS(Large APPS with OTA)". This is necessary because
this project uses both WiFi and Bluetooth which are both very memory heavy libraries. Once you've done this
upload the following sketch to the ESP32.
#include <btAudio.h>
void setup() {
void loop() {
1. Create a global btAudio object that sets the "Bluetooth name" of your ESP32
2. Con gure the ESP32 to receive audio with the btAudio::begin method
3. Set the I2S pins with the btAudio::I2S method.
That's it on the software side! Now all you need to do is initiate the Bluetooth connection to your ESP32. Just scan
for new devices on your phone/laptop/MP3 player and "ESP_Speaker" will appear. Once you are happy that
everything is working (music plays) you can disconnect the ESP32 from your computer. Power it with the USB power
supply and it will remember the last code that you uploaded to it. This way, you can leave your ESP32 hidden
behind your HiFi system forever.
Ext e n d i n g t he R e c e i v e r w i t h D i g i t a l S i g n a l P r o c e s s i n g
If you followed all the steps (and I didn't leave anything out) you now have a fully functioning Bluetooth receiver
for your HiFi system. While this is cool it doesn't really push the microcontroller to its limits. The ESP32 has two cores
operating at 240MHz. That means this project is far more than just a receiver. It has the capacity to be a Bluetooth
receiver with a Digital Signal Processor (DSP). DSPs essentially perform mathematical operations on the signal in
real-time. One useful operation is called Digital Filtering. This process attenuates frequencies in a signal below or
above a certain cuto frequency, depending on whether you are using a high-pass or low pass lter.
Hi g h- p a s s lt ers
High-Pass lters attenuate frequencies below a certain band. I've built a lter library for Arduino systems based on
code from the earlevel.com. The main di erence is that I've changed the class structure to allow for the
construction of higher-order lters more easily. Higher order lters suppress frequencies beyond your cuto more
e ectively but they require much more computation. However, with the current implementation, you can even use
6th order lters for real-time audio!
The sketch is the same as the one found in the previous step except that we have changed the main loop. To enable
the lters we use the btAudio::createFilter method. This method accepts 3 arguments. The rst is the number of
lter cascades. The number of lter cascades is half the order of the lter. For a 6th order lter, the rst argument
should be 3. For an 8th order lter, it would be 4. The second argument is the lter cuto . I've set this to 1000Hz to
have a really dramatic e ect on the data. Finally, we specify the type of ler with the third argument. This should be
highpass for a high-pass lter and lowpass for a low-pass lter. The script below switches the cuto of this
frequency between 1000Hz and 2Hz. You should hear a dramatic e ect on the data.
#include <btAudio.h>
void setup() {
audio.begin();
int bck = 26;
int ws = 27;
int dout = 25;
audio.I2S(bck, dout, ws);
void loop() {
delay(5000);
audio.createFilter(3, 1000, highpass);
delay(5000);
audio.createFilter(3, 2, highpass);
}
Lo w - p a s s lt ers
Low pass lters do the opposite of high pass lters and suppress frequencies above a certain frequency. They can
be implemented in the same way as high pass lters except that they require changing the third argument to
lowpass. For the sketch below I alternate the low-pass cuto between 2000Hz and 20000Hz. Hopefully, you'll hear
the di erence. It should sound quite mu ed when the low-pass lter is at 2000Hz.
void setup() {
audio.begin();
int bck = 26;
int ws = 27;
int dout = 25;
audio.I2S(bck, dout, ws);
void loop() {
delay(5000);
audio.createFilter(3, 2000, lowpass);
delay(5000);
audio.createFilter(3, 20000, lowpass);
}
B a c k g r o un d
Dynamic range compression is a signal processing method that tries to even out the loudness of the audio. It
compresses loud sounds, that rise above a certain threshold, to the level of quiet ones and then, optionally
ampli es both. The result is a much more even listening experience. This came in really useful while I was watching
a show with very loud background music and very quiet vocals. In this case, just increasing the volume didn't help
as this only ampli ed the background music. With dynamic range compression, I could reduce the loud
background music to the level of the vocals and hear everything properly again.
T he C o d e
Dynamic range compression does not just involve lowering volume or thresholding the signal. It's a bit more clever
than that. If you lower the volume quiet sounds will be reduced as well as the loud ones. One way around this is to
threshold the signal but this results in severe distortion. Dynamic range compression involves a combination of soft
thresholding and ltering to minimize the distortion one would get if you were to threshold/clip the signal. The
result is a signal where the loud sounds are "clipped" without distortion and the quiet ones are left as they are. The
code below switches between three di erent levels of compression.
3. No Compression
void setup() {
audio.begin();
int bck = 26;
int ws = 27;
int dout = 25;
audio.I2S(bck, dout, ws);
}
void loop() {
delay(5000);
audio.compress(30,0.0001,0.0001,10,10,0);
delay(5000);
audio.compress(30, 0.0001,0.1,10,10,0);
delay(5000);
audio.decompress();
}
Dynamic range compression is complicated and the btAudio::compress methods has many parameters. I'll try and
explain them (in order) here:
1. Threshold - The level at which the audio gets reduced (measured in decibels)
2. Attack time - The time it takes for the compressor to start working once the threshold has been
exceeded
3. Release time - The time it takes for the compressor to stop working.
4. Reduction Ratio - the factor by which the audio is compressed.
5. Knee Width - The width (in decibels) around the threshold at which the compressor partially
works(more natural sound).
6. The gain (decibels) added to the signal after compression (increase/decrease volume)
The very audible distortion in the rst use of compression is because the threshold is very low and both the attack
time and release time are very short e ectively resulting in a hard thresholding behaviour. This is clearly solved in
the second case by increasing the release time. This essentially causes the compressor to act in a much smoother
way. Here, I've only shown how changing 1 parameter can have a dramatic e ect on the audio. Now it's your turn to
experiment with di erent parameters.
T he Imp l e me n t a t i o n (t he ma g i c ma t he ma t i c s - o p t i o n a l )
I found that naively implementing the Dynamic range compression to be challenging. The algorithm requires
converting a 16-bit integer to decibels and then transforming it back to a 16-bit integer once you have processed
the signal. I noticed that one line of code was taking 10 microseconds to process stereo data. As stereo audio
sampled at 44.1 KHz leaves only 11.3 microseconds for the DSP this is unacceptably slow... However, by combining a
small lookup table (400 bytes) and an interpolation procedure based on Netwon's divided di erences we can obtain
nearly 17 bits precision in 0.2 microseconds. I've attached a pdf document with all the maths for the truly
interested. It's complicated, you've been warned!
Download
https://www.instructables.com/ORIG/FM9/PIMD/KBDNJHBC/FM9PIMDKBDNJHBC.pdf
…
#include<webDSP.h>
#include<btAudio.h>
void setup() {
Serial.begin(115200);
audio.begin();
int bck = 26;
int ws = 27;
int dout = 25;
audio.I2S(bck, dout, ws);
void loop() {
web._server.handleClient();
}
The code assigns an IP address to your ESP32 which you can use to access the webpage. The rst time you run this
code you should have it attached to your computer. That way you can see the IP address assigned to your ESP32 on
your serial monitor. If you want to access this webpage simply enter this IP address into any web browser (tested on
chrome).
By now we should be familiar with the method of enabling the Bluetooth and I2S. The key di erence is the use of a
webDSP object. This object takes your Wi SSID and password as arguments as well as a pointer to the btAudio
object. In the main loop, we continually get the webDSP object to listen for incoming data from the webpage and
then update the DSP parameters. As a closing point, it should be noted that both Bluetooth and Wi use the same
radio on the ESP32. This means that you might have to wait for up to 10 seconds from when you enter parameters
on the webpage to when the information actually reaches the ESP32.
Hopefully, you've enjoyed this instructable and now have Bluetooth Audio and DSP added to your HiFi. However, I
think there's a lot of room for growth in this project and I just wanted to point out some future directions I might
take.
When I do get around to implementing these ideas I'll make more instructables. Or maybe someone else will get
these features implemented. That's the joy of making everything open source!