LED control with Neopixel library and Encoder

Hi guys,

I'm trying to be able to define custom colors for RGB LEDs using Neopixel library and a rotary encoders.

I'd like to be able to run through the whole color spectrum with the encoder as if it was a rainbow mode, and once found the color, to save those values.

I didn't know how I could do it so I tried to adapt a code already existing for a rainbow mode. This is the source: Speed up rgb led strip rainbow wave

Does this code make sense and is it functional?

#include <EEPROM.h>
#define mAddress 0			//Address for mode
#define cmAddress 1			//Address for colorMode
#define brAddress 2			//Address for bright
#define rAddress 3			//Address for red
#define gAddress 4			//Address for green
#define bAddress 5			//Address for blue 

include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 15
#define NUMPIXELS 16
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
bool colorUpdate = false;

#include <Encoder.h>    
Encoder encoder1(A5, A4);      //Encoder channels for RE1 (Left)   
Encoder encoder2(A2, A1);      //Encoder channels for RE2 (Right) 
const int encoder1Button = A3; //ER1 Pushbutton pin Left
const int encoder2Button = A0; //ER2 Pushbutton pin Right

unsigned long timer = 2000;
unsigned long prevMillis1;         
unsigned long prevMillis2;
unsigned long currentMillis;

int mode = 0;       			//counter for left encoder button ER1
int colorMode = 0;				//counter for right encoder button ER2

int button1State = 0;           //current state of left encoder button
int lastButton1State;           //last state of left encoder button
int button2State = 0;           //current state of right encoder button
int lastButton2State;           //last state of right encoder button

long encoder1Pos = -999;        //ER1A Left position variable
long encoder2Pos = -999;        //ER1B Right position variable 

uint8_t r;							//Red color 
uint8_t g;							//Green color
uint8_t b;							//Blue color
uint8_t bright = 126; 			//Led brightness

void setup() {
   	pinMode(encoder1Button, INPUT_PULLUP);
   	pinMode(encoder2Button, INPUT_PULLUP);
    
  	Serial.begin(9600);
    
  	pixels.begin();         
   	prevMillis1 = millis();
	prevMillis2 = millis();

	mode = EEPROM.read(mAddress);						//Read values saved in EEPROM memory
	colorMode = EEPROM.read(cmAddress);					
	bright = EEPROM.read(brAddress);
	r = EEPROM.read(rAddress);
	g = EEPROM.read(gAddress);
	b = EEPROM.read(bAddress);
}

void loop() {
    brightness();
	checkColorMode();
}

void brightness() {
	while (colorUpdate){
		long newPos = encoder1.read() / 4;
		if (newPos != encoder1Pos && newPos > encoder1Pos) {
    		encoder1Pos = newPos;
			bright += 32;
			if (bright > 255) {bright = 255;}
    		setBrightness(bright);
  		}

  		if (newPos != encoder1Pos && newPos < encoder1Pos) {
   			encoder1Pos = newPos;
			bright -= 32;
			if (bright < 0) {bright = 0;}
    		setBrightness(bright);
  		}

		EEPROM.update(bAddress, bright);
	}
}

void checkColorMode() {
	button2State = digitalRead(encoder2Button);
	currentMillis = millis();
	bool activation = false;

	if (button2State != lastButton2State && (unsigned long)(currentMillis - prevMillis2) >= timer && !activation) { 	
		long newPos = encoder2.read() / 4;
		activation = true;
		colorUpdate = true; 
		uint16_t j = 0;

		if (newPos != encoder2Pos && newPos > encoder2Pos) {
			encoder2Pos = newPos;
			if (j < 256) { 
				for (uint16_t i = 0; i < NUMPIXELS; i++) {
					pixels.setPixelColor(i, Wheel((i + j) & 255));
				} 
				pixels.show();
				delay(20);
				j += 16;
			}	
			else { j = 0;}
  		}

		if (newPos != encoder2Pos && newPos < encoder2Pos) {
    		encoder2Pos = newPos;
    		if (j > 0) { 
				for (uint16_t i = 0; i < NUMPIXELS; i++) {
					pixels.setPixelColor(i, Wheel((i + j) & 255));
				} 
				pixels.show();
				delay(20);
				j -= 16;
			}	
			else { j = 240;}
  		}
		prevMillis2 = millis();
	}

	if (button2State != lastButton2State && (unsigned long)(currentMillis - prevMillis2) >= timer && activation ) {
		activation = false;
		colorUpdate = false;
	}

	if ( button2State != lastButton2State && (unsigned long)(currentMillis - prevMillis2) <= timer ) {
		if (button2State == LOW) {
			colorMode++; 
		}
	}
	lastButton2State = button2State;

	if (colorMode > 10) {
		colorMode = 0;
	}
	
	EEPROM.update(rAddress, r);
	EEPROM.update(gAddress, g);
	EEPROM.update(bAddress, b);

	uint32_t c;

	switch(colorMode){
		case 0:		//Red
			c = pixels.Color(255, 0, 0);
		break;
		case 1:		//Orange
			c = pixels.Color(255, 128, 0);
		break;
		case 2:		//Yellow
			c = pixels.Color(255, 255, 0); 
		break;
		case 3:		//Green
			c = pixels.Color(0, 255, 0);
		break;
		case 4:		//Cyan
			c = pixels.Color(0, 255, 255);
		break;
		case 5:		//Blue
			c = pixels.Color(0, 0, 255);
		break;
		case 6:		//Purple
			c = pixels.Color(127, 0, 255);
		break;
		case 7:		//Pink
			c = pixels.Color(255, 0, 255);
		break;
		case 8:		//White
			c = pixels.Color(255, 255, 255);
		break;
		case 9:		//Custom color
			c = pixels.Color(r, g, b);
		break; 
		case 10:	//Off 
			c = pixels.Color(0, 0, 0);
		break; 
		
	}

	for (int i = 0 < NUMPIXELS; i++) {
		pixels.setPixelColor(i, c);
		pixels.show();
		delay(25);
	}
	EEPROM.update(cmAddress, colorMode);
}

uint32_t Wheel(byte WheelPos) {
  	WheelPos = 255 - WheelPos;
  	if(WheelPos < 85) {
	  	r = 255 - WheelPos + 3;
	  	g = 0;
		b = WheelPos * 3;
		return pixels.Color(r, g, b);
  	}
  	if(WheelPos < 170) {
    	WheelPos -= 85;
		r = 0;
		g = WheelPos * 3, 
		b = 255 - WheelPos * 3; 
		return pixels.Color(r, g, b);
  	}
	
  	WheelPos -= 170;
	r = WheelPos + 3;
	g = 255 - WheelPos * 3;
	b = 0;
  	return pixels.Color(r, g, b);
}

Perhaps you should tell us. Does it do what you want? If not, how is what it does different than what you want?

I sadly have not been able to try it myself. I didn't get the components yet so I can't put them all together. I'm developing the code in advance.

Are you attached to that library?

FastLED (providing it works for your LEDs) has a HSV method of addressing the LEDs which would allow you to change between colours for example all with one number (i.e hue of 0 is red, hue of 96 is green etc)

I've mocked this up which seems to be working. I'm using my own enocder.h file here but any will do.

In this, there is one LED (at full brightness and saturation but those would be pretty easy to add in too, as would more LEDs), and the encoder changes the colour. Then, if you press the switch button on the encoder it saves that HSV to an array and prints all of the colours you've saved so far. There's a few 'nice to haves' in regards to the printing etc but you could definitely strip this right down if you wanted.

#define ARRSIZE(x) sizeof(x) / sizeof(x[0])

#define NUM_LEDS 1
#define MAX_SAVED_COLOURS 15
#define ADJUSTMENT_VALUE 10 // Must be less than 255!

#include <FastLED.h>
#define DATA_PIN 12
CRGB leds[NUM_LEDS];

struct HSV_Set {
  byte hue = 0;
  byte saturation = 255;
  byte brightness = 255;
};

HSV_Set savedColours[MAX_SAVED_COLOURS];
byte numSavedCols = 0;

HSV_Set editColour;

#include "encoder.h"
Encoder encoders[] = { { 14, 26, 13 } };
const byte NUM_ENC = ARRSIZE(encoders);



ActionType handleEncoders() {
  ActionType action;
  for (byte i = 0; i < NUM_ENC; i++) {
    action = encoders[i].read();
    switch (action) {
      case NOTHING:
        break;

      case INCREASE:
        editColour.hue += ADJUSTMENT_VALUE;
        break;

      case DECREASE:
        editColour.hue += -ADJUSTMENT_VALUE;
        break;

      case SINGLE_PRESS:
        if (saveCurrentColour()) {
          printSavedColours();
        }
        else
          printSaveError();
        break;
    }
  }
  return action;
}

void printSaveError() {
  Serial.println(F("========================================"));
  Serial.println(F(" ERR! - Max Saves Reached! "));
  Serial.println(F("========================================"));
}

bool saveCurrentColour() {
  if (numSavedCols <= MAX_SAVED_COLOURS - 1) { // Only save if we're under capacity
    savedColours[numSavedCols] = editColour;
    Serial.println(F("========================================"));
    Serial.println(F(" Colour Saved! "));
    numSavedCols++;
    return true;
  }
  else
    return false;
}

void printSavedColours() {
  Serial.println(F("========================================"));
  for (byte i = 0; i < numSavedCols; i++) {
    Serial.print(F(" List: Saved Colour "));
    Serial.print(i);
    Serial.print(F(" = H("));
    Serial.print(savedColours[i].hue);
    Serial.print(F(") S("));
    Serial.print(savedColours[i].saturation);
    Serial.print(F(") V"));
    Serial.print(savedColours[i].brightness);
    Serial.println(F(")"));
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(DATA_PIN, OUTPUT);
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  // Initially show the LED;
  leds[0] = CHSV(editColour.hue, editColour.saturation, editColour.brightness);
  FastLED.show();
}

void loop() {
  if (handleEncoders()) { // Only update the LED if the encoder has registered a change
    leds[0] = CHSV(editColour.hue, editColour.saturation, editColour.brightness);
    FastLED.show();
  }
}

Note: This is just how I would approach it (ie possibly not the best way), just practising my coding and posting it on the off chance it's somewhat helpful.

I could see this easily being changed to three encoders, one each for hue, saturation and brightness.
I haven't added anything for selecting and erasing a saved entry but that wouldn't be too hard to add to it. The max saved entries are defined at the top.

FastLED already has an HSV Data Type. No need to make up your own.

Yeah I should have twigged that! :joy:

Updated:

#define ARRSIZE(x) sizeof(x) / sizeof(x[0])

#define NUM_LEDS 1
#define MAX_SAVED_COLOURS 15
#define ADJUSTMENT_VALUE 10 // Must be less than 255!

#include <FastLED.h>
#define DATA_PIN 12
CRGB leds[NUM_LEDS];


CHSV savedColours[MAX_SAVED_COLOURS];
byte numSavedCols = 0;

CHSV editColour = { 0, 255, 255 };

#include "encoder.h"
Encoder encoders[] = { { 14, 26, 13 } };
const byte NUM_ENC = ARRSIZE(encoders);



ActionType handleEncoders() {
  ActionType action;
  for (byte i = 0; i < NUM_ENC; i++) {
    action = encoders[i].read();
    switch (action) {
      case NOTHING:
        break;

      case INCREASE:
        editColour.hue += ADJUSTMENT_VALUE;
        break;

      case DECREASE:
        editColour.hue += -ADJUSTMENT_VALUE;
        break;

      case SINGLE_PRESS:
        if (saveCurrentColour()) {
          printSavedColours();
        }
        else
          printSaveError();
        break;
    }
  }
  return action;
}

void printSaveError() {
  Serial.println(F("========================================"));
  Serial.println(F(" ERR! - Max Saves Reached! "));
  Serial.println(F("========================================"));
}

bool saveCurrentColour() {
  if (numSavedCols <= MAX_SAVED_COLOURS - 1) { // Only save if we're under capacity
    savedColours[numSavedCols] = editColour;
    Serial.println(F("========================================"));
    Serial.println(F(" Colour Saved! "));
    numSavedCols++;
    return true;
  }
  else
    return false;
}

void printSavedColours() {
  Serial.println(F("========================================"));
  for (byte i = 0; i < numSavedCols; i++) {
    Serial.print(F(" List: Saved Colour "));
    Serial.print(i);
    Serial.print(F(" = H("));
    Serial.print(savedColours[i].hue);
    Serial.print(F(") S("));
    Serial.print(savedColours[i].sat);
    Serial.print(F(") V"));
    Serial.print(savedColours[i].val);
    Serial.println(F(")"));
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(DATA_PIN, OUTPUT);
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  // Initially show the LED;
  leds[0] = editColour;
  FastLED.show();
}

void loop() {
  if (handleEncoders()) { // Only update the LED if the encoder has registered a change
    leds[0] = editColour;
    FastLED.show();
  }
}

See it in action: sketch.ino - Wokwi Arduino Simulator
(click play, click on the white area of the encoder, then you can use the left/right keyboard buttons to increase/decrease and space bar to 'save' and print)

I didn't look closely or use the stimulant @st3v3n92 provides, Imwill when I'm back at the big rig but

It looks like you are pounding on the EEPROM quite hard - they have a limited number of write cycles before failureā€¦ that can happen pretty soon if it's just part of every loop.

a7

I didn't go into EEPROM on mine. I only used the brief description above OPs code that mentioned saving (no hints of EEPROM unless you look at the code.)

In that case I would advise copying any pre-saved colours from EEPROM on start-up, allowing space to 'locally' mess around and add new colours or change them, then once you are finished, have a way to save to EEPROM once before you finish the 'session', to save on write cycles.

As long as I understand from EEPROM library, EEPROM.update will only write in memory if the value has been modified. In my case that is the intention, only to update if mode/colorMode/brightness and r g b values for custom colors only when they have suffered a change. If it was EEPROM.write, as long as I know, in this case it would be writing in memory every loop as you stated.

Thanks a lot st3ven92, I have never tried FastLED neither I know anything about how it works. Give me few days so I can learn about it and try to implement it to my code. :slight_smile:

That makes sense. But it looks like your adjustments come as you twiddle encoder1.

In fact looking closer I think calling brightness() with colorUpdate true will not return and also hit the EEPROM too much.

I would use a button and an LED that would mean you have to press the button to lock in the changes or something like that along the lines of @st3v3n92.

Or make the changes automatically after a certain period of inactivity. A risk of missing something unless you alos add the Hail Mary pass to EEPROM as power sags.

a7

I'll explain how it's supposed to work.

After the encoder2 switch is pressed for 2 seconds, system enters in color update mode. With encoder 2 you will be able to create a custom color and with encoder1, using a flag named colorUpdate you will update the LEDs brightness. Once you get desired brightness and color, you will press the button again for 2 seconds to exit the mode (this done with the flag activation = true/false.

Using the encoder switch should work like the button you suggest.

On the other hand, I can't do it make the changes after a inactivity, just because there's more code and the encoders are intended to have other functionalities if colorMode flag is not active.

I can share the whole code so you could understand better but it's still in developement and not complete. Would you understand better with it, I have no problem posting it.

All of this was done using the methods provided by Neopixel library, in case I updated it to FastLED those brightness() and checkColorMode() methods both might change.

A risk of missing something unless you alos add the Hail Mary pass to EEPROM as power sags. As for that, sorry I didn't fully understand.

https://www.arduino.cc/en/Reference/EEPROMUpdate

This link is only the information about EEPROM.Update.

Thing about EEPROM is that I want those settings to be saved when the device is turned off and back on again so setting everything again is not necessary every time.