DIY Remote Controlled Lights (Part 1)

Lights, RF24, Arduino

I’ve been in a tinkering mood lately but couldn’t settle on a good project. Finally one came to mind that I have been wanting to do for a while but never had the motivation for. My night vision is freaking awful, and we keep our bedroom incredibly dark to help my fiancee sleep, so when I wake up in the morning I usually end up bashing my knee into a few things and stepping on one of the various animals in the room. I want a way to easily turn on some lights in key areas, while also keeping most of the room dark to not wake up my fiancee. My chosen solution is an RF24 transceiver pair, one to act as the remote (with bonus mini light source), and the other to act as the light source. The remote will be able to turn the light on and off, as well as control the brightness of the LEDs.

Components

To follow along with this part, you will need the following components:

Transmitter

We are going to start with the transmitter, which will have a single LED for some minor directional light that I can use like a flashlight, and which will also have a potentiometer I can use to control the brightness of all of the LEDs around the room.

Wiring

transmitter breadboard diagram

Code

Alright so now we have an RF24 transceiver, an LED, and a potentiometer wired up. Make sure you have the proper libraries installed. Using the Arduino IDE, you can use the Manage Libraries tool to find and install the Adafruit Neopixel library and the RF24 library. If you would like to skip the explanations and go directly to the full transmitter script, click here

Once we have that we can import all of our libraries:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Adafruit_NeoPixel.h>
#include "printf.h"

First we will initialize our single Neopixel. We only have 1, and we will use pin 8 as a control pin, so we can initialize it with this code:

Adafruit_NeoPixel pixels(1, 8, NEO_GRB + NEO_KHZ800);

Next we setup our RF24 transmitter. We have wired to pin 9 and 10 for the CE and CSN pins respectively, so we initialize like so:

RF24 radio(9, 10); // CE, CSN         
const byte address[6] = "00001"; //Byte of array representing the address. This is the address where we will send the data. This should be same on the receiving side. 0x3130303030

Our address here is very important, we need to use the same value in the receiver in order for the two modules to communicate properly.

Now we can setup our important global trackers:

int potPin = A2; // The pin tracking the potentiometer state
int powerPin = 4; // We set this to HIGH to give the potentiometer power
int brightness = 0; // Tracker for the current brightness

Now we are ready for our setup function. In our setup we need to do the following:

  • Initialize the NeoPixel
  • Send power to the potentiometer
  • Initialize the Serial monitor for debugging
  • Ensure the RF24 module is working
  • Make the RF24 module a transmitter and set the payload size
void setup() {
   pixels.begin();
   Serial.begin(115200);
   digitalWrite(powerPin, HIGH);
   if (!radio.begin() || !radio.isChipConnected()) {
     Serial.println(F("radio hardware is not responding!!"));
     while (1) {} // hold in infinite loop
   }
   radio.openWritingPipe(address); //Setting the address where we will send the data
   radio.setPALevel(RF24_PA_LOW);  //Set minimum PA level since they will be close together
   radio.stopListening();          //This sets the module as transmitter
   radio.setPayloadSize(sizeof(brightness));

   // For debugging info
 //  printf_begin();             // needed only once for printing details
 //  radio.printPrettyDetails(); // function that prints human readable data
}

IMPORTANT: The setPayloadSize() function was required in order for my transceiver pair to actually work, for some reason without it the receiver never picked up the payload.

Now in our loop all we have to do is check the current potentiometer setting and use that to set the brightness of our single NeoPixel as well as transmit the value so the other lights can use it. I used a 5KΩ potentiometer because it was pretty close to outputting values between 0 and 100 which is what the NeoPixel takes in as a brightness value. We will also add a check to only transmit if the brightness has actually changed.

void loop() {
  int potState = analogRead(potPin);
  bool brightnessChanged = brightness != potState;
  brightness = potState;
  pixels.setBrightness(brightness);
  pixels.setPixelColor(0, pixels.Color(255, 255, 255)); // Pixel is just set to white
  pixels.show();

  if (brightnessChanged) {
    unsigned long start_timer = micros();                    // start the timer
    bool report = radio.write(&brightness, sizeof(brightness));  //Sending the message to receiver 
    unsigned long end_timer = micros();                      // end the timer
  
    if (report) {
      Serial.print(F("Transmission successful! "));          // payload was delivered
      Serial.print(F("Time to transmit = "));
      Serial.print(end_timer - start_timer);                 // print the timer result
      Serial.print(F(" us. Sent: "));
      Serial.println(brightness);
    } else {
      Serial.println(F("Transmission failed or timed out")); // payload was not delivered
    }
  }
  delay(300);
}

This works decently well, but we get a lot of voltage spikes if the potentiometer is not at one of the extremes of its arc. I could fix that by using a capacitor for voltage control, but I will tackle that later if at all. I don’t mind a bit of flickering in the light for now.

Receiver

Wiring

receiver breadboard diagram

Code

This code is very similar to the transmitter, so we include the same imports:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Adafruit_NeoPixel.h>
#include "printf.h"

And we have 5 NeoPixels for a strip rather than 1, so we initialize like this:

Adafruit_NeoPixel pixels(5, 8, NEO_GRB + NEO_KHZ800);

Now the main difference is that we set our transceiver to be in receive mode, and open a reading pipe in our setup:

int payload = 0;

RF24 radio(9, 10); // CE, CSN         
const byte address[6] = "00001";     //Byte of array representing the address. This is the address where we will send the data. This should be same on the receiving side. 0x3130303030
void setup() {
  pixels.begin();
  Serial.begin(57600);
  // initialize the transceiver on the SPI bus
  if (!radio.begin() || !radio.isChipConnected()) {
    Serial.println(F("radio hardware is not responding!!"));
    while (1) {} // hold in infinite loop
  }
  radio.openReadingPipe(1, address); //Setting the address where we will receive the data
  radio.setPALevel(RF24_PA_LOW);     //You can set it as minimum or maximum depending on the distance between the transmitter and receiver.
  radio.startListening();            //This sets the module as receiver
  radio.setPayloadSize(sizeof(payload));

  // For debugging info
//   printf_begin();             // needed only once for printing details
//   radio.printPrettyDetails(); // (larger) function that prints human readable data
}

Then, in our loop, we just check if there is a message available and set our brightness in the LED strip appropriately:

void loop() {
  if (radio.available()) {             // is there a payload? get the pipe number that recieved it
    uint8_t bytes = radio.getPayloadSize(); // get the size of the payload
    radio.read(&payload, bytes);            // fetch payload from FIFO
    Serial.print(F("Received "));
    Serial.print(bytes);                    // print the size of the payload
    Serial.print(F(" bytes on pipe "));
    Serial.print(F(": "));
    Serial.println(payload);                // print the payload's value
    pixels.setBrightness(payload);
    pixels.setPixelColor(0, pixels.Color(255, 255, 255));
    pixels.setPixelColor(1, pixels.Color(255, 255, 255));
    pixels.setPixelColor(2, pixels.Color(255, 255, 255));
    pixels.setPixelColor(3, pixels.Color(255, 255, 255));
    pixels.setPixelColor(4, pixels.Color(255, 255, 255));
    pixels.show();
  } else {
    Serial.println(F("No payload"));
  }
  delay(300);
}

After we have all this uploaded and ready to go, when these boot up turning the potentiometer should increase the brightness for all LEDs.

Adding Battery Packs

For the remote and the LED strip, I want them to be battery powered. In order to power the Arduino, we need 3 AA batteries to give the 5V necessary to power the Arduino (actually more like 4.7V but it works). Now that we have the basic concept working, we can add battery packs to actually power the damn things, as well as power switches to save some battery power.

Wiring

It is pretty simple getting the Arduino to run on batteries, you just have to wire to the VIN pin and ground and you are good to go.

transmitter with batteries breadboard diagram

receiver with batteries breadboard diagram

Once we have the batteries all wired up, when we flip the switch and move the potentiometer we finally get some lights!

working remote and lights

Next Time

Now that we have the POC and all the code written, I want to make a remote and case out of wood so this stuff is actually usable. Stay tuned for the next installment where I make an inevitably terrible looking remote and LED strip case out of scrap wood. But hey, it will definitely work and that’s all I care about.

Full Code

Transmitter

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Adafruit_NeoPixel.h>
#include "printf.h"

Adafruit_NeoPixel pixels(1, 8, NEO_GRB + NEO_KHZ800);

RF24 radio(9, 10); // CE, CSN         
const byte address[6] = "00001"; //Byte of array representing the address. This is the address where we will send the data. This should be same on the receiving side. 0x3130303030
int potPin = A2; // The pin tracking the potentiometer state
int powerPin = 4; // We set this to HIGH to give the potentiometer power
int brightness = 0; // Tracker for the current brightness

void setup() {
  pixels.begin();
  Serial.begin(115200);
  digitalWrite(powerPin, HIGH);
  if (!radio.begin() || !radio.isChipConnected()) {
    Serial.println(F("radio hardware is not responding!!"));
    while (1) {} // hold in infinite loop
  }
  radio.openWritingPipe(address); //Setting the address where we will send the data
  radio.setPALevel(RF24_PA_LOW);  //You can set it as minimum or maximum depending on the distance between the transmitter and receiver.
  radio.stopListening();          //This sets the module as transmitter
  radio.setPayloadSize(sizeof(brightness));

  // For debugging info
//  printf_begin();             // needed only once for printing details
//  radio.printPrettyDetails(); // function that prints human readable data
}

void loop() {
  int potState = analogRead(potPin);
  bool brightnessChanged = brightness != potState;
  brightness = potState;
  pixels.setBrightness(brightness);
  pixels.setPixelColor(0, pixels.Color(255, 255, 255));
  pixels.show();

  if (brightnessChanged) {
    unsigned long start_timer = micros();                    // start the timer
    bool report = radio.write(&brightness, sizeof(brightness));  //Sending the message to receiver 
    unsigned long end_timer = micros();                      // end the timer
  
    if (report) {
      Serial.print(F("Transmission successful! "));          // payload was delivered
      Serial.print(F("Time to transmit = "));
      Serial.print(end_timer - start_timer);                 // print the timer result
      Serial.print(F(" us. Sent: "));
      Serial.println(brightness);
    } else {
      Serial.println(F("Transmission failed or timed out")); // payload was not delivered
    }
  }
  delay(300);
}

Receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Adafruit_NeoPixel.h>
#include "printf.h"

Adafruit_NeoPixel pixels(5, 8, NEO_GRB + NEO_KHZ800);

int payload = 0;

RF24 radio(9, 10); // CE, CSN         
const byte address[6] = "00001";     //Byte of array representing the address. This is the address where we will send the data. This should be same on the receiving side. 0x3130303030
void setup() {
  pixels.begin();
  Serial.begin(57600);
  // initialize the transceiver on the SPI bus
  if (!radio.begin() || !radio.isChipConnected()) {
    Serial.println(F("radio hardware is not responding!!"));
    while (1) {} // hold in infinite loop
  }
  radio.openReadingPipe(1, address); //Setting the address where we will receive the data
  radio.setPALevel(RF24_PA_LOW);     //You can set it as minimum or maximum depending on the distance between the transmitter and receiver.
  radio.startListening();            //This sets the module as receiver
  radio.setPayloadSize(sizeof(payload));

  // For debugging info
//   printf_begin();             // needed only once for printing details
//   radio.printPrettyDetails(); // (larger) function that prints human readable data
}

void loop() {
  if (radio.available()) {             // is there a payload? get the pipe number that recieved it
    uint8_t bytes = radio.getPayloadSize(); // get the size of the payload
    radio.read(&payload, bytes);            // fetch payload from FIFO
    Serial.print(F("Received "));
    Serial.print(bytes);                    // print the size of the payload
    Serial.print(F(" bytes on pipe "));
    Serial.print(F(": "));
    Serial.println(payload);                // print the payload's value
    pixels.setBrightness(payload);
    pixels.setPixelColor(0, pixels.Color(255, 255, 255));
    pixels.setPixelColor(1, pixels.Color(255, 255, 255));
    pixels.setPixelColor(2, pixels.Color(255, 255, 255));
    pixels.setPixelColor(3, pixels.Color(255, 255, 255));
    pixels.setPixelColor(4, pixels.Color(255, 255, 255));
    pixels.show();
  } else {
    Serial.println(F("No payload"));
  }
  delay(300);
}
arduino  led 

See also

comments powered by Disqus