Trying to do a 3 dimmer Led controller with motion



  • This is based on the main page of the builds dimmable LED but I want to use 3 Dimmers for RGB LED strip. All on full brightness equalls white! But the code is giving me some trouble this is what I have so far but only 1 Dimmer and Motion show up in my Vera, the 2 other dimmers are not showing or working any help would be nice. 🙂

    /***
     * 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.
     * 
     * DESCRIPTION
     * This sketch provides a 3 Color Dimmable LED Light using PWM and based Henrik Ekblad 
     * <henrik.ekblad@gmail.com> Vera Arduino Sensor project.  
     * And Based on Bruce Lacey, inspired by Hek's MySensor's example sketches.
     * 
     * The circuit uses a MOSFET for Pulse-Wave-Modulation to dim the attached LED or LED strip.  
     * The MOSFET Gate pin is connected to Arduino pin 3,5,6 (LED_PIN), the MOSFET Drain pin is connected
     * to the LED negative terminal and the MOSFET Source pin is connected to ground.  
     *
     *
     * REVISION HISTORY
     * Version 1.0 - February 15, 2014 - Bruce Lacey
     * Version 1.1 - August 13, 2014 - Converted to 1.4 (hek)
     * Version 1.2 - Hacked up by Jeff Wooding (DrJeff)
     *
     ***/
    #define SN "DimmableColorLED"
    #define SV "1.2"
    #define NODE_ID AUTO  //change to a number to assign a specific ID
    
    #include <MySensor.h> 
    #include <SPI.h>
    
    #define RLED_PIN_CHILD 0   //ID of the RED LED child
    #define GLED_PIN_CHILD 1   //ID of the GREEN LED child
    #define BLED_PIN_CHILD 2   //ID of the BLUE LED child
    #define MOTION_CHILD 3    //ID of the MOTION child
    
    #define RLED_PIN 3      // Arduino pin attached to MOSFET Gate pin
    #define GLED_PIN 5      // Arduino pin attached to MOSFET Gate pin
    #define BLED_PIN 6      // Arduino pin attached to MOSFET Gate pin
    #define MOTION_PIN  4  // Arduino pin tied to trigger pin on the motion sensor.
    
    #define FADE_DELAY 10  // Delay in ms for each percentage fade up/down (10ms = 1s full-range dim)
    
    //MySensor gw(0,9);
    MySensor gw; //Don't need to define pins unless they are changing from the default
    
    static int currentLevel = 0;  // Current dim level...
    MyMessage dimmerMsg(0, V_DIMMER);
    MyMessage lightMsg(0, V_LIGHT);
    
    //added 1,2 
    MyMessage dimmerMsg1(1, V_DIMMER);
    MyMessage lightMsg1(1, V_LIGHT);
    
    MyMessage dimmerMsg2(2, V_DIMMER);
    MyMessage lightMsg2(2, V_LIGHT);
    
    //motion sensor
    MyMessage motionMsg(MOTION_CHILD, V_TRIPPED);
    uint8_t lastMotion = 0;
    
    
    unsigned long previousMillis = 0; // last time update //see http://stackoverflow.com/questions/10773425/performing-a-function-after-x-time for more details on this
    unsigned long motionDelay = 10000; // interval at which to keep motion sensor trippped (milliseconds).  Used to prevent too frequent updates to Vera. 
    
    boolean metric = true; 
    
    /***
     * Dimmable LED initialization method
     */
    void setup()  
    { 
      Serial.println( SN ); 
      gw.begin( incomingMessage );
      
      // Register the LED Dimmable Light with the gateway
      gw.present( RLED_PIN_CHILD, S_DIMMER );
      gw.present( GLED_PIN_CHILD, S_DIMMER );
      gw.present( BLED_PIN_CHILD, S_DIMMER );
      gw.present( MOTION_CHILD, S_MOTION );
      
      gw.sendSketchInfo(SN, SV);
      metric = gw.getConfig().isMetric;
      // Pull the gateway's current dim level - restore light level upon sendor node power-up
      gw.request( RLED_PIN_CHILD, V_DIMMER );
      gw.request( GLED_PIN_CHILD, V_DIMMER );
      gw.request( BLED_PIN_CHILD, V_DIMMER );
      
    }
    
    /***
     *  Dimmable LED main processing loop 
     */
    void loop() 
    {
      gw.process();
      
      
       //motion sensor code
      unsigned long currentMillis = millis();
      
         if(currentMillis - previousMillis > motionDelay){
          uint8_t motionDetect = digitalRead(MOTION_PIN);
      
          if(motionDetect != lastMotion){
    //        Serial.print("motionDetect Value: ");
    //        Serial.println(motionDetect);
            gw.send(motionMsg.set(motionDetect));  // Send tripped value to gw
            
            if(motionDetect == 1){
              previousMillis = currentMillis;  //"Tripped" delay 
            }
            else{
              previousMillis = currentMillis - motionDelay + 1000; //"Not tripped" delay for 1 second to stop rapid "not tripped" and "tripped" updates to Vera
            }
      
             lastMotion = motionDetect; 
          }    
    }
    }
    /***
     *  This method provides a graceful fade up/down effect
     */
    void fadeToLevel( int toLevel ) {
    
      int delta = ( toLevel - currentLevel ) < 0 ? -1 : 1;
      
      while ( currentLevel != toLevel ) {
        currentLevel += delta;
        analogWrite( RLED_PIN, (int)(currentLevel / 100. * 255) );
        delay( FADE_DELAY );
      }
    while ( currentLevel != toLevel ) {
        currentLevel += delta;
        analogWrite( GLED_PIN, (int)(currentLevel / 100. * 255) );
        delay( FADE_DELAY );
      }
      while ( currentLevel != toLevel ) {
        currentLevel += delta;
        analogWrite( BLED_PIN, (int)(currentLevel / 100. * 255) );
        delay( FADE_DELAY );
      }
    }
    
    
    
    void incomingMessage(const MyMessage &message) {
      if (message.type == V_LIGHT || message.type == V_DIMMER) {
        
        //  Retrieve the power or dim level from the incoming request message
        int requestedLevel = atoi( message.data );
        
        // Adjust incoming level if this is a V_LIGHT variable update [0 == off, 1 == on]
        requestedLevel *= ( message.type == V_LIGHT ? 100 : 1 );
        
        // Clip incoming level to valid range of 0 to 100
        requestedLevel = requestedLevel > 100 ? 100 : requestedLevel;
        requestedLevel = requestedLevel < 0   ? 0   : requestedLevel;
        
        Serial.print( "Changing level to " );
        Serial.print( requestedLevel );
        Serial.print( ", from " ); 
        Serial.println( currentLevel );
    
        fadeToLevel( requestedLevel );
        
        // Inform the gateway of the current DimmableLED's SwitchPower1 and LoadLevelStatus value...
        gw.send(lightMsg.set(currentLevel > 0 ? 1 : 0));
        //added 
        gw.send(lightMsg1.set(currentLevel > 0 ? 1 : 0));
        gw.send(lightMsg2.set(currentLevel > 0 ? 1 : 0));
    
        // hek comment: Is this really nessesary?
        gw.send( dimmerMsg.set(currentLevel) );
        //added 
        gw.send( dimmerMsg1.set(currentLevel) );
        gw.send( dimmerMsg2.set(currentLevel) );
    
        }
    
    }
    
    

    I would also like to add a Rotary encoder to this but I think I'm tapped out on the PWM pins to make it work. Any Ideas? Another thought would be to use a separate board/pro mini and dim it remotely?


  • Hero Member

    By using built-in PWM you are limited to 3 outputs (3, 5 and 6), as pins 9, 10 and 11 are being used to connect to nRF radio).

    To read the rotaries you need at least two pins for each ( = "2-bit gray code"). Most rotaries have a 3rd microswitch, and work as a push-button as well, so a extra pin will allow you to have a on-off switch by pushing (instead turning) the rotary .

    Below is my sketch with 3 dimmers (using timers). If you implement timers, you can use any pin. (and it also works with AC-Triacs --- Be careful!!!) . If you are dimming AC bulbs, you need to adjust the 'freqStep' variable according to the AC frequency of your region.

     int freqStep = 84;                   // Microseconds of each tic  =  1s / 60Hz * 100 steps 
    

    I also recommend a 16Mhz Arduino, so you have enough speed to achieve good fade effects in all 3 channels while not missing any radio message.

    Dimmer 1 & 2 can be controlled by rotaries connected on (A3,A4) and (A5,A6). I also use A0 and A1 to immediate on/off by pushing each rotary. Dimmer 3 can only be increased/decreased via Incoming message (although it has a local on/off switch on pin A2).

    Hope it helps!

    /*** 
     * 
     * 3x Bulb/Led dimmer
     * 
     ***/
    
    #include <TimerOne.h> 
    #include <MySensor.h> 
    #include <SPI.h>
    #include <IRLib.h>
    
    #define SN "theDimmer"
    #define SV "1.3"
    
    // Version history
    // 1.2 4-Apr-15: Include "fade to level" logic
    // 1.3 11-Apr-15:  Changed A0-A6 input pins order; code cleanup
    
    // MySensor gateway, dimmer & light messages
    MySensor gw; 
    MyMessage msg1[3];
    MyMessage msg2[3]; 
    
    // Infrared Send module 
    IRsend irsend;
    
    // Warning message
    MyMessage var1( 0 , V_VAR1); 
    volatile boolean AC_found = false; 
    
    // Dim Level 
    int level[3] ;    // Current dim level on OUTPUT pins (0 to 100)
    int level_gw[3];  // Current dim level at Controller 
    int level_tg[3];  // Current dim level (target)
    float gap; 
    
    // Output pins (to Triacs / MOSFETs)
    int out_pin[3] = { 5, 6, 7 };  // Output PINS
    
    // Input: local rotary encoders (only 2) - 2-bit gray code
    int a,b,oldA[2],oldB[2];
    int rotary[2][2] = {  { A3 , A4 }  ,  { A5, A6 } };  // INPUT PINS
    
    // Input: local switches
    boolean newSw, oldSw[3]; 
    long lastSw[3];  
    int sw[3] = { A0, A1, A2 };  // INPUT PINS 
    
    // Infrared Emiiter Pin
    #define IR_PIN 3
    unsigned long raw[400];   // raw sequence of IR codes
    
    // Clock ticker (100 tics = 1 AC wave cycle = 1s / 120Hz = 8333 milisecs
    volatile int counter = 100;          // Timer tick counter
    int freqStep = 84;                   // Microseconds of each tic  =  1s / 60Hz * 100 steps 
    volatile boolean zero_cross = false; // Zero-cross flag
    int zero_cross_pin = 2;             // Input PIN for zero-cross detection 
    volatile int int_ct = 0 ; 
    
    // Idle counter (for gw update); 
    boolean idle; 
    int idle_ct; 
    
    void setup() { 
    
      // Serial init
      Serial.begin(115200); 
      
      // Start radio Controller
      gw.begin( incomingMessage );
      gw.sendSketchInfo( SN, SV );
    
      // IR output available? 
      pinMode( IR_PIN, INPUT );
      delay(50); 
      if ( digitalRead( IR_PIN ) == LOW ) { 
        pinMode( IR_PIN , OUTPUT );   
        gw.present( 4 , S_IR ); 
        Serial.println("IR init"); 
      } 
    
      // Setup IN/OUT pins
      for (int i = 0; i < 3; i++ ) { 
    
        // levels / step
        level[i] = 0;
        level_gw[i]=0;
        level_tg[i]=0; 
        
        // Create return messages
        msg1[i] = MyMessage( i , V_DIMMER ) ; 
        msg2[i] = MyMessage( i , V_LIGHT ) ; 
    
        // Rotary Encoders
        if ( i < 2) { 
          pinMode( rotary[i][0] , INPUT_PULLUP );
          pinMode( rotary[i][1] , INPUT_PULLUP ); 
          oldA[i] = digitalRead( rotary[i][0] );
          oldB[i] = digitalRead( rotary[i][1] );
        } 
    
        // Input Switches/PBs
        pinMode( sw[i] , INPUT_PULLUP );
        oldSw[i] = digitalRead( sw[i] ); 
        lastSw[i] = millis(); 
    
        // Triac/MOSFET out
        pinMode( out_pin[i], INPUT ); 
        delay(50);
        if ( digitalRead( out_pin[i] ) == LOW ) { 
    
          pinMode( out_pin[i] , OUTPUT ); 
          digitalWrite( out_pin[i], LOW );  
    
          // Register Dimmable Lights with the gateway
          gw.present( i, S_DIMMER );
         
          // Fetchs previous state from controller
          gw.request( i , V_DIMMER ); 
    
          Serial.print("Dimmer init CH "); 
          Serial.println( i );  
    
        } 
    
      } 
    
      // Zero-cross detection on Pin 2 
      pinMode( zero_cross_pin , INPUT_PULLUP);
      attachInterrupt( 0, zero_cross_detect, RISING );  
    
      // Clock ticker
      Timer1.initialize(); 
      Timer1.attachInterrupt( dim_check , freqStep ); 
    
    } 
    
    // Zero-cross
    void zero_cross_detect() { 
      
      zero_cross = true;
      AC_found = true; 
      counter = 100; 
      for (int i=0; i<3; i++)  digitalWrite( out_pin[i], LOW ) ; 
    
    } 
    
    // Timer Ticker - check
    void dim_check() { 
    
      int j = 0; 
    
      if ( zero_cross == true ) { 
    
        for (int i=0; i<3; i++)  
          if ( counter <= level[i] ) { 
            digitalWrite( out_pin[i] , HIGH ); 
            j++;
          } 
       
    
        // If all 3 output fires, avoid further checks... 
        if ( j == 3) zero_cross = false; 
      } 
    
      // Tic-tac...
      counter--; 
    
      // No zero-cross circuit?  As contingency, emulate it based on timer 
      // (Should work at least for PWM led running by  MOSFET )
      if ( counter == 0 ) { 
    
        zero_cross = true;
        counter = 100; 
        for (int i=0; i<3; i++)  digitalWrite( out_pin[i], LOW ) ; 
      } 
    
    }
    
    void loop() { 
    
      // Automation controller
      gw.process();
    
      // Idle flag
      idle = true; 
    
      // gap step calculation 
      gap = 0.0; 
      
      // Smooth transition to target level 
      for (int i=0; i<3; i++) { 
        if ( level[i] != level_tg[i] )  { 
           gap = ( ( level_tg[i] - level[i] ) / 25.0 );
           if ( gap > 0.0 && gap < 1.0 ) gap = 1.0 ;
           if ( gap < 0.0 && gap > -1.0 ) gap = -1.0;        
                 level[i] += gap;              
        } 
      }  
      
      // Small delay to produce the fade effect 
      if ( gap != 0 )   delay(27);  // Aprox. .7s  (=700/25) of ramp up/down 
           
      // Read the Rotary Encoders
      for (int i=0; i<2; i++) { 
    
        // Read current state
        a = digitalRead( rotary[i][0] );
        b = digitalRead( rotary[i][1] ); 
    
        // Any change? 
        if ( a != oldA[i] || b != oldB[i] ) { 
    
          idle = false; 
          idle_ct = 0; 
    
          if ( oldA[i] == LOW && oldB[i] == LOW ) {  
    
            a == HIGH ?  level[i]-=8 : level[i]+=8 ; 
    
            if ( level_tg[i] > 100 )  level_tg[i] = 100; 
            if ( level_tg[i] < 0 )    level_tg[i] = 0; 
    
          }
      
          // Save to use later... 
          oldA[i] = a;
          oldB[i] = b; 
          delay(3);  
        }  
      } 
    
      // Read local Push button
      for (int i=0; i<3; i++) { 
        
        // Check SWs
        newSw = digitalRead( sw[i] ); 
    
        // Any change? 
        if ( newSw != oldSw[i] ) { 
    
          // Not idle anymore... 
          idle = false; 
          idle_ct =  0; 
    
          // Minimum push to swap state
          if ( millis() - lastSw[i] > 500 ) { 
    
            level_tg[i] > 0 ? level_tg[i] = 0 : level_tg[i] = 100; 
    
          } 
    
          // Save to use later... 
          oldSw[i] = newSw; 
          lastSw[i] = millis();
          delay(50); 
    
        } 
    
      }
    
      if (idle) { 
    
        if ( ++idle_ct > 15000 ) {   // (about 1s) 
    
          // Check for not-updated levels on GW-controller
          for (int i=0; i<3; i++) { 
    
            // Update Level in GW-controller
            if ( level_gw[i] != level[i] ) { 
    
              // update GW-controller
              // Send new state and request ack back
              gw.send( msg1[i].set( level[i] ) ); 
              gw.send( msg2[i].set( level[i] > 0 ? true : false ) );  
              level_gw[i] = level[i]; 
    
            } 
    
            // Warning message for no zero-cross detected 
            if ( !AC_found ) { 
              gw.send( var1.set( "No AC?") ) ; 
              AC_found = true ; 
            } 
          }
    
          // Reset idle counter
          idle_ct = 0; 
        } 
    
      } 
    
    } 
    
    
    void incomingMessage(const MyMessage &message)
    {
    
      // ACK
      if (message.isAck()) {
        // Nothing to do, just ACK from Gateway
        //irsend.send(NEC, 0x1EE17887, 32); // Vol up yamaha ysp-900
       // irsend.send(NEC, 0x1EE1F807, 32); // Vol down yamaha ysp-900
      } 
      else { 
    
        // IR Output 
        if ( message.type == V_IR_SEND )
        { 
          
         // TO DO 
          
        } 
        
        // Light/Dimmer messages 
        if ( message.type == V_LIGHT || message.type == V_DIMMER ) 
        {
    
          //  Retrieve the power or dim level from the incoming request message
          int requestedLevel = atoi( message.data );
    
          // Channel
          int i = message.sensor; 
    
          // Adjust incoming level if this is a V_LIGHT variable update [0 == off, 1 == on]
          requestedLevel *= ( message.type == V_LIGHT ? 100 : 1 );
    
          // Clip incoming level to valid range of 0 to 100
          requestedLevel = requestedLevel > 100 ? 100 : requestedLevel;
          requestedLevel = requestedLevel < 0   ? 0   : requestedLevel;
    
          // Set new level
          level_tg[i] = requestedLevel; 
          level_gw[i] = requestedLevel; 
    
        }
      }
    }
    
    


  • @rvendrame said:

    3 dimmers (using timers). If you implement timers, you can use any pin. (and it also works with AC-Triacs --- Be careful!!!) .

    What is the caution for? Just that I'm working with 120V or is there something I'm Missing here? Will it go up in flames based on the timer circuit?

    I love the way you did this I was trying to do the A/C dimmer (asked in another thread), The hardware was pretty simple but the code was sooooo hard to figure out I will give this a shot! I will also use this with my LED dimmer you have fixed two of my problems with, wait three you got IR in here also! Yippe!

    You Rock! @rvendrame


  • Hero Member

    Thanks. Working with Triacs is a way more dangerous than relays, in terms of isolation from AC mains. The terminals are very close, and in many triacs models the heat terminal (where you attach the heat sink) is electrically connected to the input/output terminals.

    You must know what you are doing, under the risk of serious injury to you or someone else.

    In many countries only a certified electrician can work on AC mains. And only certified devices can be installed in wall boxes, to avoid risk of fire.

    Be careful! 😉



  • @rvendrame Do share your circuit I would love to compare. I found the inmojo circuit to work well. My only missing piece is I want to power the circuit with s tranformerless power supply.


  • Hero Member

    @DrJeff , I plan to document everything and post at the forum later, probably once I incorporate the IR emitter in my project. 😉

    FQZNYV7H8CVG9TK.MEDIUM.jpg

    I forgot to mention the zero-cross detection (on pin2), you will need it if you dimm regular AC bulbs (doesn't matter how many dimmer channels you have, only one zero-cross is enough).

    The picture above show the zero-cross on top and one dimmer channel on bottom.


Log in to reply
 

Suggested Topics

  • 8
  • 44
  • 3
  • 1
  • 1
  • 2

21
Online

11.4k
Users

11.1k
Topics

112.7k
Posts