Decibels and dB SPL

(Note: This is a slightly modified excerpt of Chapter 1 from a book I’ve been working on entitled “Digital Audio for C++ Programmers.”)

The decibel is perhaps one of the most confusing and misunderstood topics in audio. It has a confusing formula that appears to change based on the context. It’s also used in a variety of applications beyond audio. In fact, much of the documentation you’ll find is actually related electronics and telecommunications. And to muddy things even more, by itself the plain ole decibel doesn’t even really convey much meaning. It merely relates one power value to another. So, if after reading this article, you still find decibels confusing, don’t fret. You’re in good company.

The decibel originated with telephony in the early 1900’s. It was used as a way to describe the power efficiency of phone and telegraph transmission systems. It was formally defined as 1/10th of something called a bel. Interestingly, the bel was rarely used. The decibel got all the glory. The decibel has since found its way into all sorts of other domains, such as optics, electronics, digital imaging, and, most importantly to me, audio.

There are two benefits to using the decibel. The first is that switching to a logarithmic scale converts an awkward range of values (e.g., 0.0002 Pascals – 20 Pascals) to something much easier to reason about (e.g., 0 dB SPL – 120 dB SPL) . The other benefit applies to audio – a logarithmic scale is much closer to how the human ear actually hears. With a linear scale, like Pascals, doubling the value doesn’t usually feel like a doubling of loudness. With decibels, we actually get a scale that’s much closer to how to perceive loudness.

The decibel, in the generic sense, is not strictly a physical unit. When we think of physical units, we typically think about things like Amperes (number of moving electrons over time), Pascals (pressure), meters (distance), Celsius (temperature), etc. These are absolute units that correspond to physical things. The decibel isn’t like that. It’s a relative unit. It provides a relation of two things, which are themselves physical units. And it does this on a logarithmic scale.

The general formula for the decibel is as follows.

The decibel, abbreviated dB, is the logarithmic ratio between two power values. One of these two values is a reference value. The other is a measured value.

You may notice the phrase “power value” in that formula. In physics, this means the amount of energy transferred over time. The unit for power is usually the watt. However, there are plenty of units used that aren’t power values (such as Pascals in acoustic audio). So we have to convert those units into something related to power. This typically just means squaring the measured and reference values. The decibel formula ends up looking like so.

With logarithms, we can pull that exponent out and turn it into a multiplication.

This can be simplified even further like so.

And this is the formula you’ll most likely encounter when applying the decibel to measured and reference units which aren’t power-based (like Pascals in acoustic audio). It’s just a derivation of the original formula with the measured and reference values tweaked.

Standard Reference Values

A lot of domains, such as electronics and audio, have standardized reference values for the various things being measured. When we talk about these standardized flavors of the decibel, we add suffixes to the dB abbreviation. Examples of this are dBV (voltage based), dBm (radio power), dBZ (radar power), etc. The one we’re most concerned with in the field of acoustic audio is dB SPL.


dB SPL is the most common flavor of decibel for indicating the loudness of acoustic audio. SPL stands for sound pressure level. The reference value used in calculating dB SPL is the threshold of human hearing – 0.000020 Pa. We plug this into the decibel formula along with a measured value, also in Pascals, to come up with a dB SPL value.

A measurement of 0 dB SPL is considered the threshold of human hearing. That is, it’s the quietest sound that the human ear is capable of hearing. On the upper end of the scale, somewhere between 130 dB SPL and 140 dB SPL, is what’s referred to as the threshold of pain. When the volume of sound approaches this level, it can result in physical discomfort and some amount of hearing loss is almost certain.

The following two tables shows some common sounds and their approximate sound pressure measurements. The first table shows measurements in Pascals. The second table shows them in dB SPL. Compare them and you’ll see that dB SPL is much less awkward to use.

Sound SourceDistance from EarPascals
Jet Engine1 meter632
Threshold of PainAt ear20 – 200
Yelling Human Voice1 inch110
Instantaneous Hearing Loss Can OccurAt ear20
Jet Engine100 meters6.32 – 200
Chainsaw1 meter6.32
Traffic on a Busy Road10 meters0.2 – 0.63
Hearing Loss from Prolonged ExposureAt ear0.36
Typical Passenger Car10 meters0.02 – 0.2
Television (typical volume)1 meter0.02
Normal Conversation1 meter0.002 – 0.02
Calm RoomAmbient0.0002 – 0.0006
Leaf rustlingAmbient0.00006
Threshold of HearingAt ear0.00002
Sound Pressure Measured in Pascals, “Sound pressure” Wikipedia: The Free Encyclopedia. Wikimedia Foundation, Inc, 22 July 2004, Accessed 29 Nov. 2022.

Sound SourceDistance from EardB SPL
Jet Engine1 meter150
Threshold of PainAt ear130-140
Yelling Human Voice1 inch135
Instantaneous Hearing Loss Can OccurAt ear120
Jet Engine100 meters110-140
Chainsaw1 meter110
Traffic on a Busy Road10 meters80-90
Hearing Loss from Prolonged ExposureAt ear85
Typical Passenger Car10 meters60-80
Television (typical volume)1 meter60
Normal Conversation1 meter40-60
Calm RoomAmbient20-30
Leaf rustlingAmbient10
Threshold of HearingAt ear0
Sound Pressure Measured in dB SPL, “Sound pressure” Wikipedia: The Free Encyclopedia. Wikimedia Foundation, Inc, 22 July 2004, Accessed 29 Nov. 2022.

There are instruments available that measure sound pressure levels and report dB SPL. One such instrument is shown below. This happens to be my personal db SPL meter.

These devices are fun to take to concerts or demolition derbys if for no other reason than giving you the intellectual authority to complain about permanantly damaged hearing.


Hopefully, this article has helped demystify the decibel. Mathematically, they’re not something to be feared. It’s usually the logarithms that scare folks away. And if you’ve long since forgotten how logarithms work, go brush up on them and come back to this article a second time. It will make a lot more sense.

If you found this content useful, or if something could have been explained better, please leave me a comment below.

Until next time.

Radio Controlled Audio with Arduino

In the first article of this series, I spent some time looking at the DFPlayer – a small, affordable MP3 playback device. I demonstrated how it can be connected to an Arduino and driven over a serial connection. In the second article of the series, I looked at RC radio controllers and how they can also be used in an Arduino project. In this final article, I’m going to be combining the concepts from the previous articles to construct a sound board that allows me to trigger audio playback through a radio controller. This will provide an audio solution for a robotics project that my daughter and I worked on together a few months ago.

There’s not a whole lot of theory here. I’m going to assume that you’ve read the previous two articles. If something is confusing, or if it seems like I skipped over an explanation, please go check out Digital Audio With the DFPlayer and Radio Controllers With Arduino. If something still seems missing, please reach out to me and I’ll fix it.

Physical Construction

Let’s start by seeing how I wired everything together. Below is a quick layout I did with Fritzing.

And here is what the actual board looked like when I was finished.

The biggest component on the board is an Arduino Nano.
Next to the Nano is the DFPlayer. Soldered to the top of the board is a connector for power. And beneath the DFPlayer are two 2-pin JST connectors. One of the JST connectors is for the speaker and the other is for the PWM data from the RC radio receiver.

I used a solder bridge to connect the components where I could. And where I couldn’t, I added some bridge wire.

If you read my DFPlayer article, you might note a difference in serial connections here. Originally, I had opted for a software-based serial connection from the Nano to DFPlayer using pins 10 and 11. I tried that arrangement here, but then the DFPlayer started locking up frequently when triggered from the radio. I thought there might be some timing/buffering issues at play, so I switched to the hardware UART, which requires use of the RX and TX pins on the Nano. And the problem went away. I’m still not convinced I understand why. I’ll save this investigation for another day. Using the RX and TX pins, however, means I can’t write new code to the Nano without disconnecting the DFPlayer first (e.g., desoldering). I shouldn’t need to do that unless I want to add new functionality down the road.

The Code

What follows is the code I wrote that ties everything together. Because we already saw how much of this works in the first two articles, I’m not going to explain the code line-by-line. I’ll instead give you a quick tour of the functions of interest, as well as a high-level overview of what they’re trying to accomplish.

#include "Arduino.h"
#include "DFRobotDFPlayerMini.h"
// These are connected to RC channels 5 and 6, respectively.
static const int AUDIO_TRIGGER_PIN = 2;
static const int VOLUME_KNOB_PIN = 3;
// For serial communication to the DFPlayer.
static DFRobotDFPlayerMini g_dfPlayer;
// The total number of files on the DFPlayer's SD card.
static int g_totalFileCount = 0;
 * Checks to see if anything has changed on the audio trigger
 * channel. And if so, plays the next sound.
void handleFilePlayback()
    static int lastTriggerValue = 0;
    static int lastFilePlayed = 1;
    int pulseValue = pulseIn(AUDIO_TRIGGER_PIN, HIGH);
    if (pulseValue == 0)
    int audioTrigger = (pulseValue < 1500) ? 0 : 1;
    if (audioTrigger != lastTriggerValue)
        if (lastFilePlayed >= g_totalFileCount)
            // We've already played the last file. Time to loop back around.
            lastFilePlayed = 1;
        lastTriggerValue = audioTrigger;
 * Checks to see if anything has changed on the volume adjustment
 * channel. If so, adjusts the volume on the DFPlayer accordingly.
void handleVolumeAdjustment()
    static int lastVolumeValue = 0;
    int pulseValue = pulseIn(VOLUME_KNOB_PIN, HIGH);
    if (pulseValue == 0)
    int volumeKnob = map(pulseValue, 990, 2000, 0, 30);
    if (volumeKnob != lastVolumeValue)
        lastVolumeValue = volumeKnob;
 * Enters into an endless loop, flashed the specified
 * LED at the specified rate.
void haltAndFlashLED(int ledId, int flashRateInMillis)
    bool ledOn = false;
    unsigned long timeLastToggled = millis();
        unsigned long currTime = millis();    
        if ((currTime - timeLastToggled) > flashRateInMillis)
            digitalWrite(ledId, (ledOn ? HIGH : LOW));
            ledOn = !ledOn;        
            timeLastToggled = currTime;
    } while(true);
void setup()
    // We'll use the built-in LED to indicate a communications
    // problem with the DFPlayer.
    digitalWrite(LED_BUILTIN, LOW);
    // Let's give the DFPlayer some time to startup.
    if (!g_dfPlayer.begin(Serial))
        haltAndFlashLED(LED_BUILTIN, 1000);
    g_totalFileCount = g_dfPlayer.readFileCounts(DFPLAYER_DEVICE_SD);
    if (g_totalFileCount <= 0)
        haltAndFlashLED(LED_BUILTIN, 500);
    // Valid values for volume go from 0-30.
    // Plays the first file found on the filesystem.;
void loop()
    // Consumes any data that might be waiting for us from the DFPlayer.
    // We don't do anything with it. We could check it and report an error via the
    // LED. But we can't really dig ourselves out of a bad spot, so I opted to
    // just ignore it.
    if (g_dfPlayer.available());
    // Consumes any data that might be waiting for us from the DFPlayer.
    // We don't do anything with it. We could check it and report an error via the
    // LED. But we can't really dig ourselves out of a bad spot, so I opted to
    // just ignore it.
    if (g_dfPlayer.available());

Let’s start by looking at the setup() function. If you’ve read the previous two articles, this should look somewhat familiar to you. We start with initializing pins, the serial port, and the DFPlayer. Something new here is that we query the number of files on the SD card by calling the g_dfPlayer’s readFileCounts() member function. We then cache this value so we later know when to loop back to the first file. If we get a 0 for this value, something is wrong. In that case, we light up the LED and halt. Otherwise, we set the volume to a reasonable value and play the first file.

Next up, let’s look at the loop() function. It’s pretty simple. It just repeatedly calls handleFilePlayback() and handleVolumeAdjustment() over and over.

The handleFilePlayback() function is dedicated to checking whether or not the toggle channel value on the RC receiver has changed and reacting appropriately. If the toggle switch hasn’t moved, we do nothing. If it has moved, we then check to see if we’ve exceeded the number of files on the SD card. If we’ve already played all the files, we loop back around to file 1. If we haven’t, then we play the next file.

The handleVolumeAdjustment() function checks whether or not the volume knob on the radio has changed value and then reacts appropriately. If the knob value hasn’t changed, we do nothing. Otherwise, we map the PWM value range of 990 to 2000 to the DFPlayer’s volume range, which goes from 0 to 30, and then update the DFPlayer.


And that’s it! All I needed to do after writing the code to the Arduino was connect the speaker, RC receiver, and battery and give it a whirl.

Everything worked great, with one exception. I noticed if my sound board was too near the RC receiver, I started hearing noise in the speaker. For me, it was an easy fix. I just moved the RC receiver further away from the sound board. But if you have a tighter fit, you might need to experiment with shielding.

Radio Controllers with Arduinos

In the previous article, I introduced my need to add sound capabilities to a radio-controlled robot project that I had been working on with my daughter. I focused on the DFPlayer as a potential solution and discussed how to control it using only an Arduino. However, I didn’t discuss radio control at all. Because the DFPlayer discussion was already pretty lengthy, I wanted to defer that discussion and write a separate article for that.

And here we are.

This time around, we’re going focus entirely on radio control. I’ll give a quick primer on how radio control and PWM works. We’ll also learn how to integrate radio control into an Arduino project. By the end, we’ll be ready to combine the concepts introduced in this article and the previous one to produce a radio-controlled audio player. We’ll save the final implementation for a third, and final, article.

Radio Controllers

A radio controller is typically a pair of devices – a radio transmitter and radio receiver. They cover the gamut in terms of cost and functionality. Whether you’re building a drone, an RC car, a robot, or a fun Halloween prop, there are a lot of radio solutions to choose from. For my purposes, I was just adding functionality to a pre-existing project using a radio that was already in use – the Flysky FS-i6x. That’s the radio controller I’ll be discussing here. But the concepts should carry over to pretty much any consumer-grade radio controller you buy off the shelf. I picked up the FS-i6x from Amazon for $59. (Although, at the time of this writing it appears the price has gone up slightly.)

The Flysky FS-i6x is a 10-channel 2.4 GHz radio transmitter and comes with a 6-channel receiver that’s pre-bound to the radio. It supports tank-style channel mixing (after some configuration), a few common stick configurations, and a few extra switches and knobs that can be used for whatever you need.

Channels and Gigahertz, Oh My!

If you’re new to the radio controller world, you may be unfamiliar with the channels and frequency specifications that are used to characterize radio controllers.

The frequency number refers to the frequency band the radio transmitter uses to exchange information with the receiver. For newer RC systems, 2.4 GHz is commonplace. As far as the FCC is concerned, 2.4 GHz is part of the industrial, scientific, and medical unlicensed band. It’s a crowded band that’s shared by cordless phones, Bluetooth, WiFi, baby monitors, etc.

The number of channels a radio controller has refers to the number of separate “features” it supports. Each channel is tied to a separate control on the transmitter and has its own independent data associated with it. If you look at the picture of the transmitter above, you’ll see it has 2 sticks. Each of these sticks is associated with two channels – one for up and down and one for left and right. Each toggle switch is also a separate channel. And each knob is a separate channel too. (2 sticks x 2 channels per stick) + 4 toggle switches + 2 knobs = 10 channels. Other radio transmitters can have support for more channels and others can have less.

How Radio Controllers Work

Off the shelf, consumer-grade radio controllers are typically unidirectional as far as communication is concerned. Data flows from the radio transmitter to the radio receiver. There are some common exceptions to this rule. For instance, receiver-side battery strength and radio signal strength are often transmitted back to the transmitter for the user to monitor. But, in general, data is one way.

The way the data looks as it flows from transmitter to receiver will vary by manufacturer. There are no standards for consumer grade radio controllers that I’m aware of. If your receiver dies and needs replacing, or you simply want to buy another one so that you can use your transmitter in another project, you’ll need to find a receiver that’s compatible with your transmitter (usually from the same manufacturer).

On the receiver side, each channel will have a separate physical connector that can be used to connect to a servo, flight controller, general purpose microcontroller, Raspberry Pi, etc. The data that leaves these connectors is typically delivered as PWM signals.


PWM stands for Pulse Width Modulation. The idea behind PWM is that we start with a low voltage and periodically pulse a higher voltage for a specific length of time. The length of this pulse is interpreted to mean something. What that something is depends entirely on your application and what you want it to mean.

I’ll show some code in a bit that allows us to see the range of PWM values emitted from the FS-i6x receiver.

Assigning Channels for the Sound Control

Channels 1-4 on my transmitter are reserved for the sticks, but channels 5 and 6 can be reassigned. I assigned channel 5 to the first toggle switch, which I’ll be using this to trigger audio playback. And I assigned the first knob to channel 6, which I’ll use to control volume.

On the receiver side, I just had to connect the signal pins for channels 5 and 6 to something that can read PWM. I connected the signal pins for these channels to the Arduino Nano’s pins 2 and 3, respectively. Pins 2 and 3 on the Nano can be especially useful here because they’re the only two pins on the Nano that support interrupts. At this point, I don’t actually need to use interrupts. I’m undecided if I’ll use them in the final project.

My connections looks like so.

In addition to the channel connections, both the Arduino and the RC receiver are connected to a 5v power source. That’s what the red and black wires coming off both devices are for.

Now it’s time to see some code. For this test, I wanted to make sure that the Arduino is able to read data from the RC receiver, as well as find out the range of values delivered via PWM. The min and max range will be important to know in the final Arduino sketch for my project. My RC test sketch looks as follows.

void setup() 
    pinMode(2, INPUT);
    pinMode(3, INPUT);
    // For writing to the Serial Monitor on the PC.
void loop() 
    int audioTrigger = pulseIn(2, HIGH);
    int volumeKnob = pulseIn(3, HIGH);
    Serial.print("Trigger value: ");
    Serial.print("Volume value: ");

Short and sweet.

In the setup() function, I set the pin mode for pins 2 and 3 to INPUT and then set the baud rate for the serial port to 9600. The serial port is only important here for communication with the Arduino IDE’s Serial Monitor. It’s not needed for anything PWM related.

In the loop() function, I call pulseIn() for each pin. I pass HIGH so that pulseIn() measures the time the signal is high. I could have passed LOW in to measure the opposite, if it made sense to do so. This function returns the number of microseconds (not milliseconds) the pulse is in the specified state. After that, I simply send the values over the serial connection for display.

After uploading the sketch and powering everything up, both channels were initially reporting values in the vicinity of 1 ms (1000 microseconds).

Trigger value: 1002
Volume value: 994

When I toggled switch A on my radio, the trigger value jumped within the vicinity of 2 ms (2000 microseconds).

Trigger value: 1994

Since the trigger switch is either in position A or B, I can probably safely assume that any value less than 1.5ms is one value and any value equal to or higher than 1.5ms is the other.

When I turned the VRA knob on my radio, the volume values also began approaching a maximum value of 2 ms (2000 microseconds).

Volume value: 994
Volume value: 1326
Volume value: 1786
Volume value: 1998

Assuming the range is between 990 and 2000, I can map that to the range of values used to adjust the DFPlayer’s volume.

So now I have all the information I need to put together the final sketch, which we’ll do in the next article.