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 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 LEDS

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!

Conclusion

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: https://www.thingiverse.com/thing:1703104

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