The idea behind this project was to create a controller to allows users to “play” light like it is an instrument with custom visuals, gesture control, and brightness/speed dials.
Considering how pricey consumer light controllers can be (often $100 bucks or more- not including the lights!) we decided to try to make a cheaper, more customizable solution!
One of the challenges with a project like this is the number of buttons it would have to include. Even in my more conservative designs, I wanted to have around 8 buttons to manage the different visual sequences, color palettes, and other mode selection. Wiring up that many buttons can be tedious and opens a lot of possibility for one connection to break and ruin the whole performance. Additionally the Arduino we are using (the UNO) only has so many digital inputs that can be used. Luckily by using the the Pmod KYPD we were able to circumvent both these issues!
The Pmod KYPD's small form-factor allows it to fit neatly onto any baseboard without taking up too much real estate. I am using a wood sample I got from my local hardware store for free as my mounting panel.
To wire up this project, first wire up the Pmod KYPD according to this diagram:
Then wire in your potentiometers to Analog Pins A5 (brightness) and A4 (speed).
Attach the LED Strips to Ground and 5V, then wire both signal pins into Digital Pin 11.
Wire up the sound sensor to power and ground, and the white wire to A1 and yellow wire to A0 (if you do not have the connecting cable as reference, the yellow wire is the outside one, and more documentation on the sensor is here.
For the Ping sensor/Ultrasonic rangefinder Trig is on Digital Pin 13 and Echo is on Digital Pin 12 (in addition to power and ground of course).
For the code you will need the FastLED and Keypad library (both found in the Arduino IDE library manager). Keypad is not listed first when you search for it, you will have to scroll down until you find the one by Mark Stanley and Alexander Brevig.
The rest of the code is here:
#include <FastLED.h>
#include <Keypad.h>
// --------- BEGIN CONFIGURABLE SETTINGS ---------
// PIN MAPPINGS //
// //
#define LED_PIN 11 //
// //
#define POT_PIN_01 A5 // Brightness //
#define POT_PIN_02 A4 // Speed //
// //
const int pinAdc = A0;
// //
#define PING_PIN 13 //
#define ECHO_PIN 12 //
// //
// SETUP CONSTANTS //
// //
#define PING_MIN_RANGE 0 //
#define PING_MAX_RANGE 12 //
// //
#define LED_MAX_SPEED 60 //
// //
#define NUM_LEDS 30 //
#define LED_TYPE WS2811 //
#define COLOR_ORDER GRB //
// //
#define SOUND_BUMP_THRESHOLD 20 //
#define AUDIO_UPDATE_FREQ 10000 //
const byte LINE = 4; // 4 lines
const byte COLUMN = 4; // 4 columms
//Declaration of the key of the keypad
char hexaKeys[LINE][COLUMN] =
{
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'0', 'F', 'E', 'D'}
};
//Assignement of pin for the keypad
byte line_pin[LINE] = {2, 3, 4, 5};
byte column_pin[COLUMN] = {6, 7, 8, 9};
Keypad clavier = Keypad( makeKeymap(hexaKeys), line_pin, column_pin, LINE, COLUMN); // creation of object keypad
// //
// ---------- END CONFIGURABLE SETTINGS ----------
// Define color offsets
// Basically we are taking the 16 color pallete and reducing it to 4
#define COLOR_01 0
#define COLOR_02 16
#define COLOR_03 32
#define COLOR_04 48
// GLOBAL VARIABLES
CRGB leds[NUM_LEDS];
CRGBPalette16 currentPalette;
uint8_t led_brightness = 100; // Default only, will be overwritten
uint8_t led_speed = 10; // Default only, will be overwritten
uint8_t visual = 1;
uint8_t palette = 0;
bool btn_01_status = false;
bool btn_02_status = false;
bool btn_03_status = false;
bool btn_04_status = false;
bool btn_05_status = false;
bool btn_06_status = false;
bool btn_07_status = false;
bool btn_08_status = false;
bool btn_09_status = false;
bool btn_10_status = false;
bool btn_11_status = false;
bool btn_12_status = false;
// BEGIN CODE
void setup() {
//Initialize LED Strip
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
FastLED.setBrightness(led_brightness);
//Set pin modes
pinMode(PING_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// Initialize pallete to gray
currentPalette = CRGBPalette16(
CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100),
CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100),
CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100),
CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100), CRGB(100, 100, 100) );
Serial.begin(9600);
}
void loop()
{
char key = clavier.getKey();
Serial.print("Key: ");
Serial.println(key);
// Handle button presses
if (key != 0x00) // if there is not any key, function getKey send NULL character (0x00)
{
if (key == '1') {
if (btn_01_status == false) {
btn_01_status = true;
visual = 1;
}
} else {
btn_01_status = false;
}
if (key == '2') {
if (btn_02_status == false) {
btn_02_status = true;
visual = 2;
}
} else {
btn_02_status = false;
}
if (key == '3') {
if (btn_03_status == false) {
btn_03_status = true;
visual = 3;
}
} else {
btn_03_status = false;
}
if (key == '4') {
if (btn_04_status == false) {
btn_04_status = true;
visual = 4;
}
} else {
btn_04_status = false;
}
if (key == '5') {
if (btn_05_status == false) {
btn_05_status = true;
visual = 5;
}
} else {
btn_05_status = false;
}
if (key == '6') {
if (btn_06_status == false) {
btn_06_status = true;
palette = 1;
}
} else {
btn_06_status = false;
}
if (key == '7') {
if (btn_07_status == false) {
btn_07_status = true;
palette = 2;
}
} else {
btn_07_status = false;
}
if (key == '8') {
if (btn_08_status == false) {
btn_08_status = true;
palette = 3;
}
} else {
btn_08_status = false;
}
if (key == '9') {
if (btn_09_status == false) {
btn_09_status = true;
palette = 4;
}
} else {
btn_09_status = false;
}
if (key == '0') {
if (btn_10_status == false) {
btn_10_status = true;
visual = 6;
}
} else {
btn_10_status = false;
}
if (key == 'C') {
if (btn_11_status == false) {
btn_11_status = true;
palette = 5;
}
} else {
btn_11_status = false;
}
if (key == 'F') {
if (btn_12_status == false) {
btn_12_status = true;
palette = 6;
}
} else {
btn_12_status = false;
}
}
Serial.print("Visual: ");
Serial.print(visual);
Serial.print(" Palette: ");
Serial.println(palette);
// Update variables from potentiometers
led_brightness = map(analogRead(POT_PIN_01), 0, 1023, 0, 255);
led_speed = map(analogRead(POT_PIN_02), 0, 1023, 1, LED_MAX_SPEED);
// Visualizers:
// runVisualFlow();
// runVisualPing();
// runVisualPingBlob();
// runVisualBounce();
// runVisualDoubleBounce();
// runVisualSegments();
// runVisualSound();
// Run visual based on visual id number
// ---------- CHANGE THESE TO CHANGE SELECTED VISUALIZATION ----------
switch (visual) {
case 1:
runVisualFlow();
break;
case 2:
runVisualDoubleBounce();
break;
case 3:
runVisualPing();
break;
case 4:
runVisualPingBlob();
break;
case 5:
runVisualSegments();
break;
case 6:
runVisualSound();
break;
default:
runVisualFlow();
break;
}
// Palettes:
// setPaletteFire(),
// setPaletteSynth(),
// setPaletteParty(),
// setPaletteBlackWhite()
// Map Palettes to visual id number
// ---------- CHANGE THESE TO CHANGE SELECTED PALETTES ----------
switch (palette) {
case 1:
setPaletteFire();
break;
case 2:
setPaletteSynth();
break;
case 3:
setPaletteParty();
break;
case 4:
setPaletteBlackWhite();
break;
case 5:
setChloePalette();
break;
case 6:
setReggaePalette();
break;
default:
break;
}
FastLED.show();
FastLED.delay(1000 / led_speed);
}
// ---------- BEGIN VISUALIZERS ----------
// Turns on leds corresponding to the sound level compared to the last AUDIO_UPDATE_FREQ millisecond period
void runVisualSound() {
Serial.print("VISUAL SOUND");
long soundLevel_00 = 0;
static long soundLevel_01 = 0;
static long soundLevel_02 = 0;
static long soundLevel_03 = 0;
static long soundLevel_04 = 0;
// Get current sound level
for (int i = 0; i < 32; i++)
{
soundLevel_00 += analogRead(pinAdc);
}
soundLevel_00 >>= 5;
long aveSound = (soundLevel_00 + soundLevel_01 + soundLevel_02 + soundLevel_03 + soundLevel_04) / 5;
Serial.print("Current Sound: ");
Serial.print(soundLevel_00);
Serial.print(" Averaged Sound: ");
Serial.println(aveSound);
// Map sound level to a value 0 to NUM_LEDs based on long term min/max
uint8_t ledRange = map(aveSound, 300, 500, 1, NUM_LEDS);
Serial.println(ledRange);
// Light LEDs based on the sound level
for ( int i = 0; i < NUM_LEDS; i++) {
if (i < ledRange) {
leds[i] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
} else {
leds[i] = ColorFromPalette( currentPalette, COLOR_04, led_brightness, LINEARBLEND);
}
}
soundLevel_04 = soundLevel_03;
soundLevel_03 = soundLevel_02;
soundLevel_02 = soundLevel_01;
soundLevel_01 = soundLevel_00;
}
// Creates sets of 7 LEDs colored in order from the palette and shifts them 1 each cycle
void runVisualFlow() {
// Create static var to hand current position
static uint8_t start_position = 0;
// Update start position to "move" LEDs. In this case just a simple shift
if (start_position > NUM_LEDS) {
start_position = getSafeIndex(start_position);
} else {
start_position += 1;
}
uint8_t current_color = COLOR_01;
// The visualizer logic goes here
for ( int i = 0; i < NUM_LEDS; i += 7) {
leds[getSafeIndex(i + start_position)] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_position + 1)] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_position + 2)] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_position + 3)] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_position + 4)] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_position + 5)] = ColorFromPalette( currentPalette, COLOR_04, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_position + 6)] = ColorFromPalette( currentPalette, COLOR_04, led_brightness, LINEARBLEND);
}
}
// Sets a back drop of the "base" color 4 and moves an LED of "primary" color 1 up and down the string
void runVisualBounce() {
// Create static var to hand current position
static uint8_t start_position = 0;
static bool move_direction = true;
// Update start position to "move" LEDs, changing direction when we hit the ends
if (move_direction) {
if (++start_position == NUM_LEDS - 1) {
move_direction = false;
}
} else {
if (--start_position == 0) {
move_direction = true;
}
}
uint8_t current_color = COLOR_01;
// Set all LEDs to "base" color 4
for ( int i = 0; i < NUM_LEDS; i += 1) {
// Added a bit of randomness to make it less static looking
// Mostly makes it flash a bit
leds[i] = ColorFromPalette( currentPalette, COLOR_04 + random(0, 5), led_brightness, LINEARBLEND);
}
// Set lead LED to primary color
leds[start_position] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
// Set first trailer LED
if (move_direction) {
if (start_position - 1 > 0) {
leds[start_position - 1] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
}
} else {
if (start_position + 1 < NUM_LEDS) {
leds[start_position + 1] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
}
}
// Set the second trailer LED
if (move_direction) {
if (start_position - 2 > 0) {
leds[start_position - 2] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
}
} else {
if (start_position + 2 < NUM_LEDS) {
leds[start_position + 2] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
}
}
}
// Sets a back drop of the "base" color 4 and moves an LED of "primary" color 1 up and down the string
void runVisualDoubleBounce() {
// Create static var to hand current position
static uint8_t start_position_1 = 0;
static bool move_direction_1 = true;
static uint8_t start_position_2 = NUM_LEDS;
static bool move_direction_2 = false;
// Update start position to "move" LEDs, changing direction when we hit the ends
if (move_direction_1) {
if (++start_position_1 == NUM_LEDS - 1) {
move_direction_1 = false;
}
} else {
if (--start_position_1 == 0) {
move_direction_1 = true;
}
}
if (move_direction_2) {
if (++start_position_2 == NUM_LEDS - 1) {
move_direction_2 = false;
}
} else {
if (--start_position_2 == 0) {
move_direction_2 = true;
}
}
uint8_t current_color = COLOR_01;
// Set all LEDs to "base" color 4
for ( int i = 0; i < NUM_LEDS; i += 1) {
// Added a bit of randomness to make it less static looking
// Mostly makes it flash a bit
leds[i] = ColorFromPalette( currentPalette, COLOR_04 + random(0, 5), led_brightness, LINEARBLEND);
}
// Set lead LED to primary color
leds[start_position_1] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
leds[start_position_2] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
// Set first trailer LED
if (move_direction_1) {
if (start_position_1 - 1 > 0) {
leds[start_position_1 - 1] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
}
} else {
if (start_position_1 + 1 < NUM_LEDS) {
leds[start_position_1 + 1] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
}
}
if (move_direction_2) {
if (start_position_2 - 1 > 0) {
leds[start_position_2 - 1] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
}
} else {
if (start_position_2 + 1 < NUM_LEDS) {
leds[start_position_2 + 1] = ColorFromPalette( currentPalette, COLOR_02, led_brightness, LINEARBLEND);
}
}
// Set the second trailer LED
if (move_direction_1) {
if (start_position_1 - 2 > 0) {
leds[start_position_1 - 2] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
}
} else {
if (start_position_1 + 2 < NUM_LEDS) {
leds[start_position_1 + 2] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
}
}
if (move_direction_2) {
if (start_position_2 - 2 > 0) {
leds[start_position_2 - 2] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
}
} else {
if (start_position_2 + 2 < NUM_LEDS) {
leds[start_position_2 + 2] = ColorFromPalette( currentPalette, COLOR_03, led_brightness, LINEARBLEND);
}
}
}
// Creates segments of 6 LEDs color from the palette randomly and updates them (and their position) every cycle
void runVisualSegments() {
// Create random start position to begin our segments at so they don't always appear in the same place
uint8_t start_pos = random(0, NUM_LEDS + 1);
for ( int i = 0; i < NUM_LEDS; i += 6) {
// Pick a random color from our color palette
// Remember we are effectively limiting it to 4 colors, which have a gap of 16
uint8_t current_color_offset = random(0, 4) * 16;
leds[getSafeIndex(i + start_pos)] = ColorFromPalette( currentPalette, current_color_offset, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_pos + 1)] = ColorFromPalette( currentPalette, current_color_offset, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_pos + 2)] = ColorFromPalette( currentPalette, current_color_offset, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_pos + 3)] = ColorFromPalette( currentPalette, current_color_offset, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_pos + 4)] = ColorFromPalette( currentPalette, current_color_offset, led_brightness, LINEARBLEND);
leds[getSafeIndex(i + start_pos + 5)] = ColorFromPalette( currentPalette, current_color_offset, led_brightness, LINEARBLEND);
}
}
// Sets the "base" color to palette color 4 and lights up all LEDs lower than the ping distance to the "primary" color 1
void runVisualPing() {
digitalWrite(PING_PIN, LOW);
delayMicroseconds(2);
digitalWrite(PING_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(PING_PIN, LOW);
long inches = pulseIn(ECHO_PIN, HIGH) / 74 / 2;
Serial.print("Distance: ");
Serial.println(inches);
// Translate distance to a value in the ping control range
uint8_t ledRange = constrain(inches, PING_MIN_RANGE, PING_MAX_RANGE);
ledRange = map(ledRange, PING_MIN_RANGE, PING_MAX_RANGE, 0, NUM_LEDS);
for ( int i = 0; i < NUM_LEDS; i++) {
if (i < ledRange) {
leds[i] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
} else {
leds[i] = ColorFromPalette( currentPalette, COLOR_04, led_brightness, LINEARBLEND);
}
}
}
// Sets the "base" color to palette color 4 and light up 5 LEDs around the location of the
void runVisualPingBlob() {
digitalWrite(PING_PIN, LOW);
delayMicroseconds(2);
digitalWrite(PING_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(PING_PIN, LOW);
long inches = pulseIn(ECHO_PIN, HIGH) / 74 / 2;
Serial.print("Distance: ");
Serial.println(inches);
// Translate distance to a value in the ping control range
uint8_t ledRange = constrain(inches, PING_MIN_RANGE, PING_MAX_RANGE);
ledRange = map(ledRange, PING_MIN_RANGE, PING_MAX_RANGE, 0, NUM_LEDS);
for ( int i = 0; i < NUM_LEDS; i++) {
if (i <= ledRange + 2 && i >= ledRange - 2) {
leds[i] = ColorFromPalette( currentPalette, COLOR_01, led_brightness, LINEARBLEND);
} else {
leds[i] = ColorFromPalette( currentPalette, COLOR_04, led_brightness, LINEARBLEND);
}
}
}
// Gets an index we can safely assign to the led array, makes overflow "wrap" around
uint8_t getSafeIndex(uint8_t index) {
return (index % NUM_LEDS);
}
// ---------- END VISUALIZERS ----------
// ---------- BEGIN PALETTES ----------
void setPaletteFire()
{
CRGB color4 = CRGB(180, 0, 0);
CRGB color3 = CRGB(255, 77, 0);
CRGB color2 = CRGB(255, 116, 0);
CRGB color1 = CRGB(255, 154, 0);
currentPalette = CRGBPalette16(
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4);
}
void setPaletteSynth()
{
CRGB color1 = CRGB(212, 0, 120);
CRGB color2 = CRGB(146, 0, 117);
CRGB color3 = CRGB(20, 75, 156);
CRGB color4 = CRGB(0, 0, 0);
currentPalette = CRGBPalette16(
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4);
}
void setPaletteParty()
{
CRGB color1 = CRGB(255, 0, 102);
CRGB color2 = CRGB(255, 235, 0);
CRGB color3 = CRGB(0, 152, 195);
CRGB color4 = CRGB(0, 220, 110);
currentPalette = CRGBPalette16(
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4);
}
void setPaletteBlackWhite()
{
CRGB color1 = CRGB(255, 255, 255);
CRGB color2 = CRGB(200, 200, 200);
CRGB color3 = CRGB(150, 150, 150);
CRGB color4 = CRGB(0, 0, 0);
currentPalette = CRGBPalette16(
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4);
}
void setChloePalette()
{
CRGB color1 = CRGB(148, 0, 211); //148, 0, 211
CRGB color2 = CRGB(255, 105, 180);
CRGB color3 = CRGB(160, 32, 240);
CRGB color4 = CRGB(255, 69, 0); //255, 69, 0
currentPalette = CRGBPalette16(
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4);
}
void setReggaePalette()
{
CRGB color1 = CRGB(255, 0, 0); //148, 0, 211
CRGB color2 = CRGB(255, 215, 0);
CRGB color3 = CRGB(255, 255, 0);
CRGB color4 = CRGB(0, 128, 0); //255, 69, 0
currentPalette = CRGBPalette16(
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4,
color1, color2, color3, color4);
}
Copy and paste the code into the Arduino IDE and click upload. Now it is time to play around with the board! Note- Buttons 3 and 4 are attached to the ping sensor so try putting your hand over the sensor when you activate those visualizers.
Have fun and feel free to expand this project to add more visualizers, sensors, ect!
Comments