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)
        return;
 
    int audioTrigger = (pulseValue < 1500) ? 0 : 1;
    if (audioTrigger != lastTriggerValue)
    {
        g_dfPlayer.stop();
        delay(200);
        if (lastFilePlayed >= g_totalFileCount)
        {
            // We've already played the last file. Time to loop back around.
            g_dfPlayer.play(1);
            lastFilePlayed = 1;
        }
        else
        {
            g_dfPlayer.next();
            ++lastFilePlayed;
        }
        delay(200);
        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)
        return;
 
    int volumeKnob = map(pulseValue, 990, 2000, 0, 30);
    if (volumeKnob != lastVolumeValue)
    {
        g_dfPlayer.volume(volumeKnob);
        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();
    do
    {    
        unsigned long currTime = millis();    
        if ((currTime - timeLastToggled) > flashRateInMillis)
        {
            digitalWrite(ledId, (ledOn ? HIGH : LOW));
            ledOn = !ledOn;        
            timeLastToggled = currTime;
        }        
        delay(0);
    } while(true);
}
 
void setup()
{
    // We'll use the built-in LED to indicate a communications
    // problem with the DFPlayer.
    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);
 
    pinMode(AUDIO_TRIGGER_PIN, INPUT);
    pinMode(VOLUME_KNOB_PIN, INPUT);
 
    // Let's give the DFPlayer some time to startup.
    delay(2000);
 
    Serial.begin(9600);
 
    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.
    g_dfPlayer.volume(20);
    // Plays the first file found on the filesystem.
    g_dfPlayer.play(1);
}
 
void loop()
{
    handleFilePlayback();
 
    // 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())
        g_dfPlayer.read();
 
    handleVolumeAdjustment();
 
    // 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())
        g_dfPlayer.read();
}

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.

Conclusion

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.

3D Printed Baby Grogu (Baby Yoda)

Early last year, I went in search for a Baby Yoda 3D model that I could use for a concrete mold project and came across a beauty from Mariano Rey.

After a lot of trial and error (and money spent on silicon), I finally decided to cut my losses with the whole concrete mold thing and just finish the project as a 3D print. The video below shows the process from start to finish. Enjoy!

Model by Mariano Rey

Download: https://www.cgtrader.com/3d-print-models/games-toys/toys/baby-yoda-using-the-force-with-cup-the-mandalorian

Building a Pit Droid

Many years ago, I came across a post from someone who had built a pit droid from wood and plaster (I’ve since lost the link to it and Google is failing me at the moment). What the author managed to build from scratch was astonishing. It was a static, life-sized model that could have easily passed for a movie prop. And I suddenly found myself wanting to build one too. Unfortunately, the author’s blog was more or less a photo stream that showed progress at various stages and contained no drawings, measurements, or anything else that might have helped me in my quest. Disappointed, I relegated the idea of building my own pit droid to a “maybe someday” activity.

“Maybe someday” arrived late last year. While perusing the Interwebs, I came across a fellow by the name of Dave Moog that was selling a life-size 3D-printable pit droid model of his own design. It was beautiful and fully articulated, allowing for the most creative of poses. He was charging $25 for the files. So coughing up the cash was a no-brainer. I could finally build a Pit Droid! (Dave has since produced even more great Star Wars-inspired models. At the time of this writing, he even has his own Etsy shop. Visit the Droid Division Facebook community to learn more!)

Of course, now there was a problem. I had these beautiful STL files that I could drool over all day using online 3D viewers, but I had no 3D printer. And I had no experience with 3D printers.

3D Printing

I started checking out a number of online 3D printing services. The cost of printing even the smallest of parts through these services was/is insane. It was a hard pass on them. My second thought was to check in with the local libraries to see if any of them would be able to help. Unfortunately, most of them were slow to respond. And while their fees were less than some of the online services, they still tended to be unrealistic. The Maine State Library was my saving grace, at least initially. The MSL had been working on putting together a STEM/maker space for a while. They had a set of Prusa printers itching to be used. Best of all, their fees were reasonable (we’re talking less than $10 for even some of the bigger parts). The catch? The MSL is in Augusta, which is 45 minutes away from my home and in the opposite direction of where I work. Incredibly inconvenient. But I figured I’d give them a whirl anyway.

Every few weeks, I’d send MSL a couple of parts to print and then I’d pick a day to work from there to minimize some of the pain. It was a slow process. As you can imagine it got old pretty fast. It lasted maybe two months before I got tired of trekking back and forth. And all I really managed to get printed were some feet and toes.

At the height of my frustration, I came across the Ender 3. This printer only cost $175 and from photos it appeared to produce remarkable prints, comparable to some of the more expensive options. It was easy to justify $175. So I ordered it and waited. And waited. And waited. It took a long time to get here. So long, in fact, that I began to wonder if I had gotten scammed. But the printer eventually arrived.

The Ender 3 is an entry level printer, but that doesn’t mean it’s easy to use. It’s also a budget printer, which really means you have to work for your supper. That’s not really a bad thing if you’re patient and willing to learn. The Ender 3 arrives as a kit. You have to put the whole thing together. And this took me DAYS. After that, I produced garbage prints for a while as I (somewhat expensively) learned about bed leveling, infill, supports, plate adhesion, the virtue of glass beds, etc. It was a little overwhelming at first. I even began questioning whether all of this was worth it. But my stubbornness won out and I eventually got my head around it. Before I knew it I was cranking out some quality pieces.

The bed on the Ender 3 is small, coming in at 220mm x 220mm. This meant that I had to split some pieces, which required me to develop a working knowledge of tools like Tinkercad and Meshmixer (the learning never stops!).

A few months later, my pit droid parts were all printed.

Post Processing

Before I could start painting, there was some cleanup that needed to happen. A few of the split pieces needed to be glued together and the resulting seams needed to be hidden. Also, there were really bad print lines on all the parts that bothered me. And, of course, all of the parts printed using supports came out extremely rough where the supports attach. All of this needed to be dealt with.

For gluing parts together, I opted for E6000. This glue is popular among R2 Builders, so I figured I’d give it a shot here. If you’ve never used this stuff before, it’s essentially a craft glue. It bonds various materials pretty well. It even stays strong when subjected to washing machine cycles. I think a lot of builders like it because it’s not really truly permanent. Parts can be separated later if you really need them to be. (Warning: It’s strongly advised to use a respirator when using this stuff, because the fumes are strong and dangerous.) I filled any seams with Bondo putty.

For the print lines and the rough spots where the supports attached, my workflow was as follows:

  1. Dry sand with 80-grit sandpaper.
  2. If there are any pits, gaps, or badly printed spots, repair with Bondo putty and sand again with 80-grit sandpaper.
  3. Apply a couple of thin coats of filler-primer.
  4. Dry sand with 120-grit sandpaper.
  5. Dry sand with 220-grit sandpaper.
  6. Wet sand with 400-grit sandpaper.
  7. Wet sand with 1000-grit sandpaper.
  8. Wet sand with 1500-grit sandpaper.

If that seems like a lot of work, it is. But it’s worth it. The PLA felt as smooth as glass. It might have actually been overkill to use a grit that high. But it made me feel better. 🙂

Hardware

All of the fasteners used on the pit droid are M6 furniture screws of varying lengths, along with caps and barrel nuts. The specific lengths are actually detailed in a document that accompanied the Pit Droid model.

On the back of the pit droid is a small panel that covers the hollowed out chest piece. For that, I initially used some #4 wood screws. But then I realized that it prevented me from accessing the screws for the shoulders. And that made it very hard to pose, so I ditched the screws in favor of a friction fit.

At the time of this writing, the lens is actually the top of a cupcake holder. It’s pretty flimsy, so I’ll mostly likely swap it out for something else at a later point.

The antennae are 4mm brass rods cut to size.

Painting

I used five different paints on the Pit Droid.

  1. Rust-Oleum Rusty Metal Primer – Yeah, not a paint. But it has the color I like.
  2. Rust-Oleum Black Matte Finish – For part of the neck.
  3. Rust-Oleum Black Lacquer – For the insides of the eye.
  4. Rust-Oleum Metallic Satin Nickel – For the joints.
  5. Liquitex BASICS Acrylic Paint, Unbleached Titanium – For the outer edge of the head and eye.

Remaining Work

What’s a pit droid without a serious amount of weathering? I’ve yet to weather him. I finished the build mid-summer and I have a few other projects that need some time “outdoor-time” (summer is short in Maine). So I’ll probably save the weathering for a winter-time activity.

Wiring. If you look closely at the pit droids in Episode I or The Mandalorian, you’ll notice some thin wires connected to the back of the arms. This is on my to-do list.

Sounds (Maybe?). There was a collection of pit droid sounds circulating on the Droid Division Facebook page. I haven’t decided if I want to use them or not. If I do, I’ll detail the electronics I use in a subsequent blog post.

Conclusion

This was a FUN project. Really. I learned a lot. And the 3D printing bug has bitten me bad. My queue of models (most of them smaller in scale 🙂 ) waiting to be printed is growing by the day. If you’d like to try building your own pit droid, join the Droid Division Facebook group and give Dave’s model a try. It’s top-notch.