RGB LED strip



  • I built a RGB LED strip controllable via mysensor, based on a 12V RGB LED strip and three IRF540 MOSFETs. The arduino is a 5V pro mini clone that I feed with 12V on the RAW pin.

    I followed the guide on https://learn.adafruit.com/rgb-led-strips/usage for wiring the MOSFETS and the LED strip. I used the Newbie PCB by @sundberg84 for holding the arduino and the radio, but put the MOSFETs on a separate prototyping board (maybe next time I'll try to solder them to the prototyping area of the Newbie PCB).

    I implemented a fading feature in the sketch, so it can fade slowly between colors or on/off. The speed is controlled by sending a V_VAR1 message with the speed (typical value could be around 200-300 for a nice effect). Speed = 0 means fading is off.

    Here is the code:

    /**
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * version 2 as published by the Free Software Foundation.
     *
     * LED STRIP sketch for Mysensors
     *******************************
     *
     * REVISION HISTORY
     * 1.0 
     *   Based on the example sketch in mysensors
     * 1.1
     *   fadespeed parameter (send as V_VAR1 message)
     *   HomeAssistant compatible (send status to ack)
     */
    
    #define MY_OTA_FIRMWARE_FEATURE
    #define MY_NODE_ID AUTO
    
    #define MY_RADIO_NRF24
    
    #include <MySensors.h>
    
    #define CHILD_ID_LIGHT 1
    
    #define SN "LED Strip"
    #define SV "1.1"
    
    MyMessage lightMsg(CHILD_ID_LIGHT, V_LIGHT);
    MyMessage rgbMsg(CHILD_ID_LIGHT, V_RGB);
    MyMessage dimmerMsg(CHILD_ID_LIGHT, V_DIMMER);
    
    byte red = 255;
    byte green = 255;
    byte blue = 255;
    byte r0 = 255;
    byte g0 = 255;
    byte b0 = 255;
    char rgbstring[] = "ffffff";
    
    int on_off_status = 0;
    int dimmerlevel = 100;
    int fadespeed = 0;
    
    #define REDPIN 6
    #define GREENPIN 5
    #define BLUEPIN 3
    
    void setup()
    {
      // Fix the PWM timer. Without this the LEDs will flicker.
      TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
    
      // Output pins
      pinMode(REDPIN, OUTPUT);
      pinMode(GREENPIN, OUTPUT);
      pinMode(BLUEPIN, OUTPUT);
      
    }
    
    void presentation()
    {
    	// Send the Sketch Version Information to the Gateway
    	sendSketchInfo(SN, SV);
    	present(CHILD_ID_LIGHT, S_RGB_LIGHT);
    }
    
    void loop()
    {
      static bool first_message_sent = false;
      if ( first_message_sent == false ) {
        Serial.println( "Sending initial state..." );
        set_hw_status();
        send_status();
        first_message_sent = true;
      }
    }
    
    void receive(const MyMessage &message)
    {
      int val;
      
    	if (message.type == V_RGB) {
    		Serial.println( "V_RGB command: " );
        Serial.println(message.data);
        long number = (long) strtol( message.data, NULL, 16);
    
        // Save old value
        strcpy(rgbstring, message.data);
        
        // Split it up into r, g, b values
        red = number >> 16;
        green = number >> 8 & 0xFF;
        blue = number & 0xFF;
    
        send_status();
        set_hw_status();
    
    	} else if (message.type == V_LIGHT || message.type == V_STATUS) {
        Serial.println( "V_LIGHT command: " );
        Serial.println(message.data);
        val = atoi(message.data);
        if (val == 0 or val == 1) {
          on_off_status = val;
          send_status();
          set_hw_status();
        }
        
      } else if (message.type == V_DIMMER || message.type == V_PERCENTAGE) {
        Serial.print( "V_DIMMER command: " );
        Serial.println(message.data);
        val = atoi(message.data);
        if (val >= 0 and val <=100) {
          dimmerlevel = val;
          send_status();
          set_hw_status();
        }
        
      } else if (message.type == V_VAR1 ) {
        Serial.print( "V_VAR1 command: " );
        Serial.println(message.data);
        val = atoi(message.data);
        if (val >= 0 and val <= 2000) {
          fadespeed = val;
        }
        
    	} else {
    		Serial.println( "Invalid command received..." );
    		return;
    	}
    
    }
    
    void set_rgb(int r, int g, int b) {
      analogWrite(REDPIN, r);
      analogWrite(GREENPIN, g);
      analogWrite(BLUEPIN, b);
    }
    
    void set_hw_status() {
      int r = on_off_status * (int)(red * dimmerlevel/100.0);
      int g = on_off_status * (int)(green * dimmerlevel/100.0);
      int b = on_off_status * (int)(blue * dimmerlevel/100.0);
    
      if (fadespeed >0) {
        
        float dr = (r - r0) / float(fadespeed);
        float db = (b - b0) / float(fadespeed);
        float dg = (g - g0) / float(fadespeed);
        
        for (int x = 0;  x < fadespeed; x++) {
          set_rgb(r0 + dr*x, g0 + dg*x, b0 + db*x);
          delay(100);
        }
      }
    
      set_rgb(r, g, b);
     
      r0 = r;
      b0 = b;
      g0 = g;
      
    }
    
    
    void send_status() {
      send(rgbMsg.set(rgbstring));
      send(lightMsg.set(on_off_status));
      send(dimmerMsg.set(dimmerlevel));
    }
    

    I have a MQTT gateway and I can turn the strip on, set the color to pink and fade speed to 500 by the following commands:

    mosquitto_pub -t mysensors-in/240/1/1/0/40 -m ff6060
    mosquitto_pub -t mysensors-in/240/1/1/0/24 -m 500
    mosquitto_pub -t mysensors-in/240/1/1/0/2 -m 1
    

    A couple of pics:

    0_1493844732314_File_000 (4).jpeg
    0_1493844743322_File_001.jpeg


  • Hardware Contributor

    Nice project! The code looks really clean, I might just copy some of that for the next version of firmware for my rgb(w) node. Have you looked into how to fix the non linear fading when using pwm?
    I had started to but never finished that.


  • Hardware Contributor

    @maghac said in RGB LED strip:
    Nice work!! I have some led strips laying around... maybe I have to start using them.

    The arduino is a 5V pro mini clone that I feed with 12V on the RAW pin.

    Just a hint, this makes me nervous because Ive seen many clones with 12specs but the voltage reg is so cheap/bad it will not make it for long. Be careful using 12v on raw on the clones.



  • @LastSamurai Thanks!

    I started looking into it but realized it's a bit complex since you probably need to consider the characteristics of the MOSFETs as well. Also, when playing with it I've realized that fading between colours actually looks linear (e.g moving from ff8080 to 00ff80 is actually quite smooth), which means you need to consider the overall intensity too.

    Another interesting situation is when the user sets colour 808080 and intensity 100% compared to colour ffffff and intensity 50%. In my sketch this ends up as the same value.



  • @sundberg84 Thanks.

    Yes, I've noticed that some of them are a bit unreliable when fed with 12V. Maybe I will put a separate regulator to drive the board with 5V instead.



  • I noticed that the indents in the sketch are a bit off, I guess there's a mix of spaces and tabs in the file :) I blame the crappy editor in the Arduino IDE.

    As a side note, has anyone managed to set up a Arduino development environment based on Notepad++ or some other editor? I know about platformio.org but I never managed to make friends with Atom so I haven't looked into it.


  • Hero Member

    @maghac You can setup Notepad++ to run the arduino IDE

    0_1493896237105_upload-8e11d470-d379-485c-ac24-e40cd9b1920a

    There is a good instruction (in German ;-), use google translate if you need to ) here


  • Hero Member

    @maghac The FastLed library has some functions for linear fading. The characteristics of the Mosfet's can be ignored. When using PWM these are used as switches (i.e. on/off).
    For inspiration: this sketch was for a RGBW strip and uses the FastLED nscale8() for smooth fading. Also there is a non-blocking version of the fading routine and a state machine to have button control.
    (the 'W' in the RGBW can easily be removed. It is calculated here as a kind of greatest common divisor of RGB)

    /*
     PROJECT: MySensors / RGB light NEOPIXEL
     PROGRAMMER: AWI
     DATE: october 10, 2015/ last update: september 20, 2016
     FILE: AWI_Wall_LIght_x.ino
     LICENSE: Public domain
    
     Hardware: RGBW Pro Mini and MySensors 2.0,
    		
     Special:
    	uses Fastled library  (great & fast RBG/HSV universal library) 			https://github.com/FastLED/FastLED
    	
     SUMMARY:
    	
    	Different patterns and brightness settings
    	
    	Button switches on/off and color with long press
    	
     Remarks:
    	Fixed node-id
    	
     Change log:
     20160915 - Updated to MySensors 2.0
     20160920 - Changed state change to dimmer i.s.o. switch()
     20161120 - Various small adjustments
     20170120 - Change to RGBW non addressable strip
    
    */
    //****  MySensors *****
    // Enable debug prints to serial monitor
    #define MY_DEBUG 
    #define MY_RADIO_NRF24										// Enable and select radio type attached
    
    #define MY_NODE_ID 63										// 62
    #define NODE_TXT "W 63"										// Text to add to sensor name
    
    // change the pins to free up the pwm pin for led control
    #define MY_RF24_CE_PIN   4 									//<-- NOTE!!! changed, the default is 9
    #define MY_RF24_CS_PIN   10  								// default is 10
    //#define RF24_PA_LEVEL RF24_PA_MAX
    
    // helpers
    #define LOCAL_DEBUG											// enable if print wanted 
    
    #ifdef LOCAL_DEBUG
    #define Sprint(...) (Serial.print( __VA_ARGS__))			// macro as substitute for print, enable if no print wanted
    #define Sprintln(...) (Serial.println( __VA_ARGS__))		// macro as substitute for println
    #else
    #define Sprint(a)										
    #define Sprintln(a)
    #endif
    
    
    #include <SPI.h>											// My Sensors
    #include <MySensors.h>
    
    #include <FastLED.h>										// https://github.com/FastLED/FastLED
    #include "Button.h"											// https://github.com/JChristensen/Button
    
    // Arduino pin attached to drivers
    const int RED_PIN  = 5 ; 
    const int WHITE_PIN = 9 ;
    const int GREEN_PIN = 3 ;
    const int BLUE_PIN = 6 ;
    
    const int buttonPin = 7 ;									// push button
    
    const int RGB_LightChild = 0 ;								// Child Id's, standard light child on/off/ dim
    const int RGB_RGBChild = 1 ;								// RGB light child (on/off/dim/color, if controller supports V_RBG))
    
    CRGB strip ;												// RGB(W) strip (Fastled)
    uint8_t stripW = 0 ;										// White component
    uint8_t actualBrightness = 0xFF ;							// actual strip brightness
    
    // Kelving colors: Light & daylight (in Fastled reference only)
    /// 1900 Kelvin Candle=0xFF9329 /* 1900 K, 255, 147, 41 */,
    /// 2600 Kelvin Tungsten40W=0xFFC58F /* 2600 K, 255, 197, 143 */,
    /// 2850 Kelvin Tungsten100W=0xFFD6AA /* 2850 K, 255, 214, 170 */,
    /// 3200 Kelvin	Halogen=0xFFF1E0 /* 3200 K, 255, 241, 224 */,
    /// 5200 Kelvin CarbonArc=0xFFFAF4 /* 5200 K, 255, 250, 244 */,
    /// 5400 Kelvin HighNoonSun=0xFFFFFB /* 5400 K, 255, 255, 251 */,
    /// 6000 Kelvin DirectSunlight=0xFFFFFF /* 6000 K, 255, 255, 255 */,
    /// 7000 Kelvin OvercastSky=0xC9E2FF /* 7000 K, 201, 226, 255 */,
    /// 20000 Kelvin ClearBlueSky=0x409CFF /* 20000 K, 64, 156, 255 */
    
    char setRGBvalue[] = "FEBC5400";
    CRGB curRGB = 0xFEBC54 ;	 								// Controller sent RGB(W) value, default tungsten40W "FFC58F"
    uint16_t curBrightness = 0xFF, setBrightness = 0xFF ;		// Brightness globals (actualBrightness)
    unsigned long updateBrightnessDelay, lastBrightnessUpdate ; // Brightness timers
    int RGBonoff ;												// OnOff flag
    
    unsigned long idleTimer = millis() ;						// return to idle timer
    const unsigned long idleTime = 10000UL;						// return to idle after 10 secs
    
    const unsigned long dimTime = 1000UL;						// dim period
    
    const unsigned long heartbeatInterval = 1 * 60UL * 1000UL ;	// heartbeatinterval, just to let the controller know I am alive
    unsigned long heartbeatCounter = 0 ;
    
    MyMessage lightRGBMsg(RGB_LightChild, V_RGB);				// standard messages, light
    MyMessage lightdimmerMsG(RGB_LightChild ,V_DIMMER);	
    MyMessage lightOnOffMessage(RGB_LightChild, V_STATUS);
    
    Button myBtn(buttonPin, true, true, 20);					//Declare the button (pin, pull_up, invert, debounce_ms)
    
    // Simple state machine for button state
    enum {sIdle, sBrightness, sPattern} ;						// simple state machine for button press
    int State ;
    
    void setup() {
    	pinMode(RED_PIN,   OUTPUT);
    	pinMode(GREEN_PIN, OUTPUT);
    	pinMode(BLUE_PIN,  OUTPUT);
    	pinMode(WHITE_PIN, OUTPUT); 
    	request(RGB_LightChild, V_RGBW) ;
    
    	/*for(int i = 0 ; i < 6 ; i++) {			// get color value from EEPROM (8 char for RGBW)
    		setRGBvalue[i] = loadState(i) ;
    		}
    	
    	curRGB = strtol( setRGBvalue, NULL, 16);				// copy the RGB value to the led Strip (convert to long)
    	showAnalogRGB() ;
    	*/
    }
    
    void presentation(){
    // MySensors
    	sendSketchInfo("AWI RGBW strip " NODE_TXT, "2.0"); wait(50) ;
    	present(RGB_RGBChild, S_RGB_LIGHT, "RGB RGBW strip " NODE_TXT); wait(50) ;
    }
    
    // read button and act accordingly
    // short press: on/off
    // longer press: set patterns with following short press
    // long press: set brightness increase 
    void loop() {
    	myBtn.read();               							//Read the button (only read)
    	unsigned long now = millis(); 							// loop timer reference
    	switch (State) {
    		case sIdle:											// default state, browse through patterns
    			if (myBtn.wasReleased()){						// light on/ off in idle
    				RGBonoff = !RGBonoff ;						// invert light state
    				setLightBrightness((RGBonoff == 1)?setBrightness:0, dimTime);
    				send(lightOnOffMessage.set(RGBonoff));		// and update controller	
    			} else if (myBtn.pressedFor(800)){				// move to Pattern update state with long press
    				idleTimer = now ;							// return to idle after ...
    				State = sPattern ;
    			}
    			break ;
    		case sPattern:										// entered after long press/ change colors (tbd)
    			if (myBtn.pressedFor(4000)){					// when press even longer move to Brightness update
    				State = sBrightness ;
    			} else if (myBtn.wasPressed()){
    				//setPattern = (setPattern + 1) % lastPatternIdx ;  // increase pattern and wrap
    				//setLightPattern((setPattern), 500 );
    				idleTimer = now ;
    			} else if ( now > idleTime + idleTimer  ){		// return to idle after ...
    				State = sIdle ;
    			}
    			break ;
    		case sBrightness:									// entered after looong press
    			if (myBtn.wasPressed()){						// if pressed again increase brightness
    				setLightBrightness((curBrightness+0x1F) % 0xFF, 0) ; // increase brightness and wrap (0..0xFF)
    				idleTimer = now ;
    			} else if ( now > idleTime + idleTimer  ){		// return to idle after ...
    				State = sIdle ;
    			}
    			break ;
    		default :
    			State = sIdle ;
    			break ;
    	}
    	updateLightBrightness();								// update Brightness if time
    	if ( now > heartbeatCounter  + heartbeatInterval){		// heartbeat every hour
    	    sendHeartbeat();
    		heartbeatCounter = now ; 
    	}
    }
    
    // Sets the light brightness, takes value and time (ms) as input
    void setLightBrightness(int newBrightness, unsigned long updateTime){
    	// global: curBrightness, actualBrightness, updateBrightnessDelay
    	updateBrightnessDelay = updateTime / 0xFF ;				// delay = time / max steps
    	curBrightness = newBrightness ;							// set curBrightness to new value, rest is done in update
    }	
     
    // Update the light brightness if time
    void updateLightBrightness(){
    	// global: curBrightness, actualBrightness, updateBrightnessDelay, lastBrightnessUpdate ;
    	//static byte actualBrightness ;							// store real brightness state for slow dim
    	unsigned long now = millis() ;
    	if (now > lastBrightnessUpdate + updateBrightnessDelay){// check if time for update
    		if ( actualBrightness > curBrightness) {
    			--actualBrightness ;
    			showAnalogRGB() ;
    		} else if ( actualBrightness < curBrightness){
    			++actualBrightness ;
    			showAnalogRGB() ;
    			}
    		lastBrightnessUpdate = now ;
    	}
    }
    
    // Incoming messages from MySensors
    void receive(const MyMessage &message){
    	int ID = message.sensor;
    	Serial.print("Sensor: ");
    	Serial.println(ID);
    	switch (ID){
    		case RGB_LightChild:									// same behaviour as RGB child/ fall through
    		case RGB_RGBChild:										// if controller can handle V_RGB
    			if (message.type == V_RGB) {						// check for RGB type
    				strcpy(setRGBvalue, message.getString());		// get the payload
    				curRGB = strtoul( setRGBvalue, NULL, 16) ;	// copy the RGB value to the led Strip (convert to long)
    			} else if (message.type == V_RGBW) {						// check for RGB type
    				strcpy(setRGBvalue, message.getString());		// get the payload
    				curRGB = strtoul( setRGBvalue, NULL, 16) >> 8;	// copy the RGB value to the led Strip (convert to long)
    			} else if (message.type == V_DIMMER) {				// if DIMMER type, adjust brightness
    				setBrightness = map(message.getInt(), 0, 100, 0, 255);
    				setLightBrightness(setBrightness, dimTime) ;
    			} else if (message.type == V_STATUS) {				// if on/off type, toggle brightness
    				RGBonoff = message.getInt();
    				setLightBrightness((RGBonoff == 1)?setBrightness:0, dimTime);
    			}
    			break ;
    	}
        showAnalogRGB();
    	dispRGBstat();
    }
    
    
    // showAnalogRGB: this is like FastLED.show(), but outputs on 
    // analog PWM output pins instead of sending data to an intelligent,
    // pixel-addressable LED strip.
    void showAnalogRGB(){
    	strip = curRGB ;
    	strip.nscale8(actualBrightness)  ;
    	int commonRGB = min(min(strip.r, strip.g), strip.b) ;		// smallest value is White component
    	strip.r -= commonRGB ;
    	strip.g -= commonRGB ;
    	strip.b -= commonRGB ;
    	stripW = commonRGB ;
    	Sprint("Color RGBW ") ;
    	Sprint(strip.r, HEX); 
    	Sprint(" ") ;
    	Sprint(strip.g, HEX); 
    	Sprint(" ") ;
    	Sprint(strip.b, HEX); 
    	Sprint(" ") ;
    	Sprintln(stripW, HEX) ;
    	analogWrite(RED_PIN,   strip.r );
    	analogWrite(GREEN_PIN, strip.g );
    	analogWrite(BLUE_PIN,  strip.b );
    	analogWrite(WHITE_PIN, stripW ) ;
    }
    
    
    
    // debug	
    // display the status of all RGB: controller, requested, real
    void dispRGBstat(void){
        Serial.print(" Color: "); Serial.print(setRGBvalue); 
        Serial.print(" Brightness: "); Serial.println(setBrightness);
    }
    
    
    	```


  • @AWI Looks good, I'll see if I can incorporate this in my solution. Thanks!



  • I'd thought I'd share a pic now that the led strip has been mounted. Unfortunately the iphone camera doesn't have enough dynamic range to capture the light correctly - it looks a lot better in reality!

    Through some HomeAssistant/tellstick magic I can also turn it on and off using a normal 433Mhz remote instead of having to send MQTT messages from the command line. This increased the WAF considerably :)

    0_1494318228883_File_000.jpeg


Log in to reply
 

Looks like your connection to MySensors Forum was lost, please wait while we try to reconnect.