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.

Digital Audio with the DFPlayer

My oldest daughter and I recently built one of Mr. Baddeley’s Baby R2s. These units are small, almost cartoonish, radio-controlled R2D2s that are extremely easy to build. And fun! But something missing from the design (at least at the time of this writing) is the ability for these little guys to produce sounds. And what’s an R2D2 with his signature beeps and boops?

For my life-size R2, I incorporated an MP3 Trigger from Sparkfun and paired that with an Arduino. But I couldn’t use that here because the MP3 Trigger is too large. The Baby R2s just can’t accommodate it. So I went in search of something else. And that’s when I came across the DFPlayer from DFRobot.

In this article (the first of three), we’ll be exploring the DFPlayer and beginning our journey into ultimately using an RC radio to trigger audio. If that’s not something that interests you, no worries. This article is focused entirely on the DFPlayer.


DFRobot’s DFPlayer is a tiny (~ 21mm x 21mm) module that’s capable of playing MP3, WMV, and WAV audio data. It features a micro-SD slot for your audio files. It can be connected directly to a small speaker (< 3 W) or an amplifier. And it can be controlled a few different ways, including via a serial connection which works well for my particular needs.

Perhaps the biggest selling point for the DFPlayer is its price – $6 as of this writing. Compare that to the SparkFun MP3 Trigger, which comes in at around $50. The DFPlayer is practically a guilt-free impulse buy. In fact, I picked up a 3 pack from Amazon for around $10.

One of the downsides to this module is that there are various models that exhibit different tolerances to electrical noise, which means you might struggle with an audible hiss. Killzone_kid posted a great writeup on the Arduino forums that examines some of the models and makes some recommendations on possible ways to mitigate the hiss. Some of the flavors also apparently have compatibility issues with the officially supported DFPlayer Arduino library, which we’ll look at in a bit.

Here’s the pin diagram for the DFPlayer.

Left-Side Pins

There’s a Vcc pin and a ground pin as you might expect. These pins are used to power up the device. The source voltage must be between 3.2V and 5V.

The Serial RX and TX pins are used to provide a serial interface for controlling the device. It defaults to 9600 baud, 1 data bit, no check bits, no flow control. The serial protocol is detailed in the datasheet. But there’s also an official support library for Arduino called DFRobotDFPlayerMini that implements the serial protocol and provides a high-level interface for controlling the DFPlayer.

The Amp Out pins are for connecting to an audio amplifier or headphones.

The Spkr pins are for very small speakers – less than 3 watts. This is actually what I’ll be using for my project. I’m connecting the DFPlayer to a small speaker that I harvested from one of my kids’ annoying…er, I mean, broken toys.


Right-Side Pins

On the right-hand side of the pin diagram, you’ll see pairs of pins for I/O and ADKey. These are two other mechanisms for controlling the DFPlayer. We’ll use the I/O pins to test the DFPlayer shortly. But we won’t be using the ADKey pins at all. I won’t be discussing them further. If you want to learn more about them, I advise you to check out the DFPlayer Wiki.

The USB pins allow the DFPlayer to work as a USB device. I haven’t been able to find very much information about this. It’s not discussed much in the DFPlayer manual. Apparently, it provides a mechanism for updating the contents of the SD card from your PC. This could be handy for projects where the DFPlayer ends up hidden away inside of an enclosure that can accommodate a USB connector on the outside. For my project, I don’t need it so I won’t be exploring it. However, if anyone knows where I can find more information about this, please let me know. It could come in handy on another project.

The pin labeled “Playing Status”, referred to as the “Busy” pin, is normally high when the device is idle and goes low when the device is playing audio. The device already has a small LED that lights up when it’s playing files. But if that’s not good enough, you can connect an LED to this pin, or connect it to a microcontroller for more sophisticated behaviors.

Adding Media Files

The DFPlayer manual describes the naming of folders and files on the SD card. With regards to files, it specifies names using 3 digit numbers, such as 001.mp3, 002.wav, etc. Folders can be named similarly. I didn’t actually create any folders on my SD card, and it worked just fine. My file layout looks like so.

Testing the DFPlayer

Before doing anything else, I like to do a quick smoke test. This simply involves powering up devices straight out of the box (if feasible) and seeing if any magic blue smoke appears. I also like to note if any LEDs light up, as there’s often a power LED that will indicate the device is at least turning on. In this case, nothing happened. After a bit of reading, I learned that the device’s single LED only lights up when it’s actually playing media. So at this point, I wasn’t sure if it was even powering up. My benchtop power supply said the DFPlayer was pulling a small amount of current, so something was happening.

Next, I wanted to see if I could get some sound out of the device. I plugged in my micro SD card and connected my speaker. The I/O pins (9 and 11) were the key to this. Grounding I/O pin 1 for a quick duration will cause the DFPlayer to play the “next” track. Grounding it for a long duration lowers the audio level. Grounding I/O pin 2 for a quick duration will cause the DFPlayer to play the “previous” track. Grounding it for a long duration raises the audio level.

I grounded I/O pin 2 for a couple of seconds to get the audio level all the way up and then quickly grounded I/O pin 1 with a quick tap. The device’s LED lit up and I immediately heard some beeps and boops. The device was working. Success!

Now I knew any issues I might have trying to drive it over a serial connection would be limited to my code and/or the serial interface itself.

Connecting the Arduino

For this project, I used a Nano clone from Lavfin. These come with the pins pre-soldered. When I originally bought this, you could get a pack of 3 from Amazon for around $14. The price has since gone up to $28 as of this writing (presumably, because of supply chain issues).

For testing, I’m used a Nano expansion board. This provides convenient screw terminals.


I connected the DFPlayer to the Arduino using the serial pins of the DFPlayer and digital pins 10 and 11 of the Arduino. The DFPlayer’s RX pin connects to the Arduino’s digital pin 11. The DFPlayer TX pin connects to the Arduino’s digital pin 10.

Why didn’t I use the Arduino’s serial pins? I could have. But the process of writing new code to the Arduino makes use of the UART. So I’d have to disconnect and reconnect the DFPlayer every time I wanted to update the software.

I don’t recommend connecting the DFPlayer Vcc and ground pins to the Arduino’s 5v and ground pins unless you REALLY need to leverage the Arduino’s voltage regulator. This is how I’ve seen it wired in the online examples. It’s convenient, sure. But the Arduino has current supply limitations. Having the DFPlayer and the Arduino connected independently to the power source is the better option.

This is how my DFPlayer and Nano are wired together.

The source code for my Arduino/DFPlayer test is as follows.

#include "Arduino.h"
#include "DFRobotDFPlayerMini.h"
#include "SoftwareSerial.h"
static SoftwareSerial g_serial(10, 11);
static DFRobotDFPlayerMini g_dfPlayer;
 * Called when the Arduino starts up.
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(g_serial))
        // There's a problem talking to the DFPlayer. Let's turn on the LED
        // and halt.
        digitalWrite(LED_BUILTIN, HIGH);
    // Valid values for volume go from 0-30.
    // Plays the first file found on the filesystem.;
// Called over and over as long as the Arduino is powered up.
void loop()
    static unsigned long timeLastSoundPlayed = millis();
    // We're going to iterate through the sounds using DFRobotDFPlayerMini's next() function,
    // playing a new sound every five seconds.
    if ((millis() - timeLastSoundPlayed) &gt; 5000)
        timeLastSoundPlayed = millis();
    // 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());

To build this sketch, the DFRobotDFPlayerMini library must be installed. This can be downloaded from GitHub – Installing it is as simple as extracting it to the Arduino libraries directory (e.g., C:\Program Files (x86)\Arduino\libraries).

Line #5 creates an instance of the SoftwareSerial class. I call it g_serial. This is used to emulate a UART over digital pins. I reserved the hardware UART so I could update the software on the Arduino while being connected to DFPlayer. If you’d rather use the hardware UART and the Serial global variable, that’ll work fine too. You just have to disconnect the DFPlayer every time you need to update the code. The two arguments to the SoftwareSerial constructor are the pin numbers for RX and TX, respectively.

Line #2 of the source above includes the DFRobotDFPlayerMini header file, which brings the various DFRobotDFPlayerMini types into scope. I then declare the static global instance of DFRobotDFPlayerMini called  g_dfPlayer. This will be the object we use to interact with the DFPlayer.

The first thing the setup() function does is configure the Arduino’s on-board LED. Since I’m not actually using the Serial Monitor in the IDE, I wanted to light this up if there were problems initiating communications with the DFPlayer.

I then delay execution for 2 seconds to give the DFPlayer time to startup. This is important to note because I didn’t see this happen in any of the sample sketches that cames with the DFRobotDFPlayerMini library. Without the delay, things just wouldn’t work for me. I don’t know if it’s because of my particular flavor of DFPlayer and/or Arduino. But 2 seconds seems to be the delay I need to get the devices to talk to one another.

I call begin() on g_serial with an argument of 9600 baud since this is the baud rate supported by DFPlayer. I then attempt to start communication with the DFPlayer by calling g_dfPlayer’s begin() function, passing it the serial object I want it to use. If an error occurs, I light up the LED and effectively halt. If no error occurs, I crank the volume up to 20 (out of 30 max) and play the first file on the DFPlayer’s file system. If all things are well, I should hear a sound.

In the loop() function, we do two things – 1) check to see if it’s been 5 seconds since we last played a sound and, if so, play one and 2) eat up any data sent to us by the DFPlayer. I should point out that I don’t actually know if we need to consume data if we’re not doing anything with it. But without diving too deep into the DFRobotDFPlayerMini code, I’m erring on the side of caution and hoping to keep some buffer somewhere from filling up.

Once this code was compiled and flashed to the Arduino, I had to restart both the Arduino and the DFPlayer. After a couple of seconds, I started hearing more beeps and boops.

Wrapping Up

This code is a good starting point. I encourage you to explore the sample code that accompanies the DFRobotDFPlayerMini library. There are some good nuggets there.

In the next article, I’ll be focusing on interfacing a radio controller with the Arduino. And then a third article will follow that which will tie everything together so that we can trigger our audio with a radio.

Until next time…

3D Printed LED Bridge Lamp

Because most of my work is done in my basement, I’ve struggled quite a bit finding an adequate light source. Hands-on, detailed work, like electronics or painting, has always been an awkard exercise. I’ve tried simple desk lamps, the adjustable arm types, and even head lamps on occasion. Nothing seemed to work well for me.

Recently, however, this all changed. I came across a project on Thingiverse from a fellow named Janis Jakatis. He designed an interesting 3D printable lamp that essentially creates a 180 degree arch of LEDs that hovers over your entire workspace. I thought it was a fascinating idea and one worth trying to build.

There are three primary components involved in this build. The 3D printed bases pieces which sit on either side of your desk, the 3D printed pieces that snap together to form the arch, and the LED strips themselves.

The Arch

Janis includes two versions of the arch pieces in the project’s file set – a single piece that you can print out in one go, or a flat pack that you have to assemble after printing. The single piece will be stronger. But it also requires a LOT more filament to print to accommodate supports. So I opted to print the flat pack version.

It took some effort to figure out exactly how the individual arch pieces were meant to go together. Janis includes an assembly jig that you can use to help with this. In the end, I printed out parts for 16 separate arch pieces and then did an assembly line of sorts glueing things together with 2-part epoxy and sanding down the ends of pieces that weren’t perfectly flush.



The arch pieces are designed to snap together, but I have to confess that that didn’t work so well for me. When attempting this, some tabs completely broke off. And even those that snapped into place weren’t perfect. I can perhaps blame my Ender 3 and my print settings on much of that. So lots of 2-part epoxy was used and clamps to make sure the arch pieces were positioned correctly while the glue set.

The Base

The base has three pieces – the base proper, a middle piece that you use to customize the orientation, and a flat piece that attaches to the arch. I simply glued all of these pieces together using 2-part epoxy.


The LED strips are the most important component of this build. I wanted something that would be plenty bright and that wouldn’t get very hot. I am sticking them to PLA, after all. 2835s seemed like a good choice as they  balance brightness and heat much better than some of the other options. I bought a 16 ft. long strip of natural white (4000K) 2835 SMD LEDs with power supply off of Ebay for around $14.  

I cut the LED strip into two 8 ft. lengths and ran the strips parallel to each other along the underside of the arch. Like most LED strips you buy, mine already had an adhesive backing. Of course, LED strip adhesive is reknowned for being garbage. I knew this already. But I was impatient and the 3M label on the back made me a little over-confident. (AGAIN! I’ve been bitten by this in some of my drone projects. Learn from my mistakes. Don’t trust the sticky backing, regardless of what brand name is stamped onto it!) Because of this, I’ll spend the next few months with the minor annoyance of fixing random areas where the strips pull away.

(Update 2022-05-12: The adhesive on the back of the LED strips officially failed me. But I found a solution – VHB tape! This stuff is strong. A month later, the LED strips are still holding on strong.)



I should mention that Janis actually has another flavor of the arch pieces that incorporates a slot for you to insert the LED strips into and it works as a diffuser. It also requires you to print using a translucent material and 100% infill to avoid infill patterns. I didn’t go this route because I wanted maximum brightness.

With the LEDs attached, I soldered some bridge connections to reconnect the two strips together. I then plugged in the wall-wart and I was done!


I love this lamp. Seriously. I mean, I’m still fixing places where the LED strips occasionally pull away. I still don’t have a proper switch connected to it. And I still need to print some desk clamps that Janis included in the project files to keep the thing from the sliding around when you bump it. But I’ve already used this lamp on a few projects and it’s perfect. No more shadows. No more of my big head blocking the lamp. And it also works quite well for lighting faces for video. So there’s that.

If you need a great desk lamp and have a 3D printer (or a friend with one), give this project a shot. You won’t be disappointed.

Thingiverse Link: