Wednesday, January 15, 2014

The Duino Light Controller - Slave Module

The Duino Light Controller - Slave Module

The purpose of this project is to create a flexible mega328 based light controller able to control a large number of channels with a variety of effects.  Each module will be standalone, but connected via a RF link to a central controller.  Basic Networking functionality will allow each node to act as a repeater to extend the reach of an RF network to allow for many nodes.  The master controller will store multiple programs and initiate sequenced effects on multiple child nodes.

Software within each node will offer

  • Multiple Light Modes Flexible configuration with effects for SnowFall, SnowFlake, Tree, Arches, Icicle, String.
  • Support for up to 6 registers consisting of 8 channels.
  • State based mode control, including a bitmap mode which will allow for remote pattern upload.
  • Rf24 Integration for remote mode selection


Hardware within each node will offer

  • Atmel ATMEGA 328
  • 74hc595 Shif Register driven via a NON SPI interface
  • ULN2803 line drivers capable of driving up to 50v at around 200ma
  • NRF24L01+ RF module providing network connectivity - utilising the SPI port.




Here is the sketch:

// Libs
char foo;  // Basically WTF but it's needed to get printf.h to work.

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


// Configure Radio
RF24 radio(9,10);
const uint64_t pipe = 0xF0F0F0F0D2LL;
static uint32_t message_count = 0;

// Define the Interrupt handler for the Radio
void check_radio(void);


#define SHIFTPWM_NOSPI
const int ShiftPWM_latchPin=3;
const int ShiftPWM_dataPin = 4;
const int ShiftPWM_clockPin = 5;
const bool ShiftPWM_invertOutputs = false;
const bool ShiftPWM_balanceLoad = false;
#include <ShiftPWM.h>   // include ShiftPWM.h after setting the pins!

unsigned char maxBrightness = 255;
unsigned char pwmFrequency = 75;
int numRegisters = 5;
int numOutputs = numRegisters*8;
int numRGBLeds = numRegisters*8/3;
int pinsPerRegister = 8;
int pinmap[64];
int gamma[256];

// gamma correction lookup
String commandBuffer = "";

// a string to hold incoming data
String command ="";
boolean stringComplete = false;


int gamma12(int i) {
    int result;
    result = pow(float(i)*16/4096,2.2)*256;
    // 'i' is a 8-bit source value, 0 to 255 and gamma 2.2
    return result;
}


void setup() {
    
          // Sets the number of 8-bit registers that are used.
          ShiftPWM.SetAmountOfRegisters(numRegisters);
          ShiftPWM.SetPinGrouping(1);
          
          //This is the default, but I added here to demonstrate how to use the funtion
          ShiftPWM.Start(pwmFrequency,maxBrightness);
          ShiftPWM.SetAll(0);
          
          for (int pin = 0; pin <= numOutputs; pin+=1) {
               pinmap[pin]=numOutputs-1;
          }
        
        //5,3,7,6,4,2,1,0,8,9,10,11,12,13,14,15,16
        pinmap[0]=5;
        pinmap[1]=3;
        pinmap[2]=7;
        pinmap[3]=6;
        pinmap[4]=4;
        pinmap[5]=2;
        pinmap[6]=1;
        pinmap[7]=0;
        pinmap[8]=8;
        pinmap[9]=9;
        pinmap[10]=10;
        pinmap[11]=11;
        pinmap[12]=12;
        pinmap[13]=13;
        pinmap[14]=14;
        pinmap[15]=15;
        pinmap[15]=16;

    for (int fadeValue = 0 ; fadeValue <= 255; fadeValue +=1) {
        gamma[fadeValue] = gamma12 (fadeValue);
    }
    Serial.begin(57600);
      printf_begin();
    // reserve 200 bytes for the commandBuffer:
    commandBuffer.reserve(200);


     radio.begin();
     radio.enableAckPayload();
     radio.openReadingPipe(1,pipe);
     radio.startListening();
    radio.printDetails();
     attachInterrupt(0, check_radio, FALLING);

}


void loop() {
    // Let's not be boring
//         maxBrightness=48;
        Pulsate (30,6);
//    Sparkle ( 30,0,300,700,3000,2000,10000);

    // Check Command Buffer for an active command
    if (stringComplete) {
        Serial.print("RECV ->");
        Serial.println (commandBuffer);
// maxBrightness=255;
        command=commandBuffer;
        commandBuffer="";
        // Acknowled that the command is being processed
        stringComplete=false;
        switch (command.charAt(0)) {
            case 'a':    
                  // void Chaser ( long Duration,int Invert, int DotCount,int AttackRate, int DecayRate, int Separation  )
                 Chaser (60,0,1,100,300,50);
                 break;
            case 'b':    
                  // void Chaser ( long Duration,int Invert, int DotCount,int AttackRate, int DecayRate, int Separation  )
                 Chaser (60,0,1,300,700,100);
                 break;
            case 'c':    
                  // void Sparkle2 ( long Duration,int Invert, int AttackRate, int DecayRate )
                 Sparkle (60,0,10,200,2000,100,10000);
                 break;
            case 'd':    
                              // void Bounce ( long Duration,int Invert, int DotCount,int AttackRate, int DecayRate, int Separation  ) {
                  Bounce (60,0,1,150,420,100);
                  break;
            case 'e':    
                              Pulsate(60,2);
                  break;
            case 'i':    
                              ShiftPWM.PrintInterruptLoad();
                              radio.printDetails();
                  break;

            case '+':    
                  maxBrightness = maxBrightness +32;
                      if (maxBrightness > 255 ) {
                          maxBrightness =255;
                      }
                      Serial.print (maxBrightness);
                 break;
            case '-':    
                  maxBrightness = maxBrightness -32;
                      if (maxBrightness < 33 ) {
                          maxBrightness =0;
                      }
                      Serial.print (maxBrightness);
                      break;
            default:
                  // turn all the LEDs off:
            for (int thisPin = 2; thisPin < 7; thisPin++) {
                digitalWrite(thisPin, LOW);
            }
        }
    }
}


void serialEvent() {
    while (Serial.available()) {
        // get the new byte:
        char inChar = (char)Serial.read();
        // add it to the commandBuffer:
        commandBuffer += inChar;
        // if the incoming character is a newline, set a flag
        // so the main loop can do something about it:
        if (inChar == '\n') {
            stringComplete = true;
        }
    }
}

void check_radio(void)
{
//     printf ("in Interrupt\n\r");
     // What happened?
     bool tx,fail,rx;
     radio.whatHappened(tx,fail,rx);

     // Have we successfully transmitted?
//     if ( tx ) printf("Ack Payload:Sent\n\r");
    

     // Have we failed to transmit?
     if ( fail ) printf("Ack Payload:Failed\n\r");

  
     // Did we receive a message?
     if ( rx )
     {
          // If we're the receiver, we've received a time message
           // Get this payload and dump it
           char ReceivePackage[32];
           radio.read( &ReceivePackage, sizeof(ReceivePackage) );
//           printf("Got payload %s\n\r",ReceivePackage);
           commandBuffer=ReceivePackage;
           switch (commandBuffer.charAt(0)) {
            case '+':    
                  
                      if (maxBrightness > 245 ) maxBrightness =255;
                                else maxBrightness = maxBrightness + 10;
                      
                 break;
            case '-':    

                      if (maxBrightness < 12 ) maxBrightness =0;
                                else maxBrightness = maxBrightness - 10;
                                
                      break;
                        default:
                              stringComplete = true;
            }
           
           // Add an ack packet for the next time around.  This is a simple
           // packet counter
            radio.writeAckPayload( 1, &message_count, sizeof(message_count) );
            
           ++message_count;
     } // if rx
  
}
// Sparkle
void FlamingCandle ( long Duration, int Fill ) {
    long StartMillis = millis();
    long CurrentMillis = millis ();
    int MaxPins = 8;
    float FlareInterval = 800;
    //milliseconds
    float FlickerInterval = 150;
    //milliseconds
    float Flare=0;
    long NextFlare=StartMillis+1000;
    long LastFlare=0;
    long FlarePeak = MaxPins * maxBrightness ;
    long FlareMin = 512;
    int DecayDuration = round(FlareInterval/2);
    // Number of Milli Seconds to decay over.
    long FlareIntensity=0;
    long NextFlicker=0;
    float FlickerIntensity=0;
    do {
        CurrentMillis = millis ();
        // Check Current Interval - If Interval has decreased to 0 then reset it and set brightness to 255.
        if (NextFlare < CurrentMillis) {
            NextFlare = CurrentMillis + random(FlareInterval);
            FlareIntensity = random (FlareMin,FlarePeak);
        }
        // Flicker the flame
        if (NextFlicker < CurrentMillis) {
            NextFlicker = CurrentMillis + random(FlickerInterval);
            FlickerIntensity = random (80,100);
        }
        // Trend the flare value towards the destination Intensity over a period set by Decay Duration
        Flare = Flare -  ((Flare-FlareIntensity)/DecayDuration);
        // Now set the LED brightness starting at LSB and moving to MSB
        for (int led=0;led<MaxPins;led++) {
            // In Range of this LED
            if ( Flare > (led * 255) && ( Flare < ((led+1) * 255) ) ) {
                ShiftPWM.SetOne(pinmap[led], gamma[round(Flare - (led*maxBrightness))]);
            }
            // Above Range
            if ( Flare > ((led+1) * 255) ) {
                ShiftPWM.SetOne(pinmap[led], gamma[round(Fill*maxBrightness*FlickerIntensity/100)]);
            }
            // Below Range
            if ( Flare < (led * 255) ) {
                ShiftPWM.SetOne(pinmap[led], gamma[round(0)]);
            }
        }
    }
    while ( millis () < StartMillis+Duration*1000);
}

void Sparkle ( long Duration,int Invert, int AttackRate, int DecayRate, int MaxInterval, int MinInterval, int BeatInterval ) {
    long CurrentMillis = millis ();
    long BeginMillis;
    long EndMillis=0;
    int  Brightness[numOutputs];
    int  Random = CurrentMillis;
    long Torrent[numOutputs];
    int Register=0;
    int CalcPin=0;
    float WobulRatio = BeatInterval;
    float WobulPeak =  MaxInterval;
    float WobulOffset = MinInterval;
    float something = CurrentMillis/WobulRatio;
    float Wobulator = WobulOffset + WobulPeak/2 + WobulPeak/2 * sin( something * 2.0 * PI  );
    // Setup Begin time  
    BeginMillis=millis();
    for (Register = 0; Register < numRegisters; Register++) {
        // Give Each torrent a random start
        for (int thisPin = 0; thisPin < 8; thisPin++) {
            // Determine logical pin address in output matrix
            CalcPin = thisPin+(Register*pinsPerRegister);
            Torrent[CalcPin] = CurrentMillis + random (WobulPeak);
            Brightness[CalcPin]=0;
        }
        // For Pin
    }
    // For Register
    do {
        // Set the Current Time
        CurrentMillis = millis();
        // Now do the Sparkle effect
    //    do {
            for ( Register = 0; Register < numRegisters; Register++) {
                CurrentMillis = millis ();
                // The wobulator introduces a sine wave to the randomness
                something = CurrentMillis/WobulRatio;
                Wobulator = WobulOffset + WobulPeak/2 + WobulPeak/2 * sin( something * 2.0 * PI  );
                for (int thisPin = 0; thisPin < 8; thisPin++) {
            
                    // Determine which pin in the Mega Array to address.
                    CalcPin = thisPin+(Register*pinsPerRegister);
            
                            // Ended so Set next one - ZERO
                            if ((CurrentMillis > Torrent[CalcPin] + DecayRate) ) {
                        Brightness [CalcPin] =  0;
                        // Check if timer has expired for the sparkle,  if yes then set a new one
                        Torrent[CalcPin]=CurrentMillis + random(Wobulator);
                    }
                    
                    // UP
                    if ((CurrentMillis < Torrent[CalcPin]) && (CurrentMillis > Torrent[CalcPin] - (AttackRate))) {
                        Brightness [CalcPin] =   maxBrightness - maxBrightness * ( Torrent[CalcPin]- CurrentMillis)/(AttackRate);
                    }
                    
                    // Down
                    if ((CurrentMillis < Torrent[CalcPin] + DecayRate)&&(CurrentMillis > Torrent[CalcPin])) {
                        Brightness [CalcPin] =  maxBrightness - maxBrightness * ( CurrentMillis - Torrent[CalcPin] )/DecayRate;
                    }
                    
                    // Write to the PIN      
                    ShiftPWM.SetOne(pinmap[CalcPin], gamma[round(abs(Invert-Brightness[CalcPin]))]);
                }
                // for  Register Pin
            }
            // for Register
            // Check for Serial events
            serialEvent();
    //    }
    //    while ( (millis () < EndMillis) && (!stringComplete));
    }
    while ( (millis () < BeginMillis+Duration*1000)  && (!stringComplete));
    //Make sure all LED's are off on exit.
    ShiftPWM.SetAll(0);
}


void Pulsate ( long Duration, int beatFreqSeconds ) {
    int MinBrightness =100;
    long StartMillis = millis();
    long CurrentMillis = millis ();
    int Brightness;
    float WobulRatio = beatFreqSeconds*1000;
    long NextSparkle[8] = {
        0,0,0,0,0,0,0,0
    }
    ;
    long LastSparkle[8] = {
        0,0,0,0,0,0,0,0
    }
    ;
    do {
        CurrentMillis = millis ();
        float something = CurrentMillis/WobulRatio;
        float Wobulator = MinBrightness + (maxBrightness-MinBrightness)/2 + (maxBrightness-MinBrightness)/2 * sin( something * 2.0 * PI  );
        ShiftPWM.SetAll(gamma[round(Wobulator)]);
        // Check for Serial events
        serialEvent();

    }
    while ( (millis () < StartMillis+Duration*1000)  &&
        (!stringComplete));
    //Make sure all LED's are off on exit.
    ShiftPWM.SetAll(0);

}
// Pulsate
void Chaser ( long Duration,int Invert, int DotCount,int AttackRate, int DecayRate, int Separation  ) {
    long CurrentMillis = millis ();
    long BeginMillis;
    long EndMillis=0;
    int  Brightness[numOutputs];
    int  DecayDuration = Separation*6;
    // Number of Milli Seconds to decay over.
    int  Random = CurrentMillis;
    long Torrent[numOutputs];
    long StartMillis[numRegisters];
    int  Register=0;
    int  CalcPin=0;
    // Setup Begin time  
    BeginMillis=millis();
    EndMillis=0;
    do {
        // Set the Current Time
        CurrentMillis = millis();
        for (Register = 0; Register < numRegisters; Register++) {
            // Give Each torrent a random start
            StartMillis[Register]= millis() + random(3000);
            // Determine the end of the very last light so that we can ensure loop does not finish early
            if ( EndMillis < (StartMillis[Register] + DecayDuration + 800) ) {
                EndMillis = StartMillis[Register] + DecayDuration + 800;
            }
            ;
            for (int thisPin = 0; thisPin < 8; thisPin++) {
                // Determine logical pin address in output matrix
                CalcPin = thisPin+(Register*pinsPerRegister);
                if (DotCount == 1) {
                    Torrent[CalcPin] = StartMillis[Register]+(thisPin*Separation);
                    Brightness[CalcPin]=0;
                } else {
                    Torrent[CalcPin] = StartMillis[Register]+round(thisPin%4)*Separation;
                    Brightness[CalcPin]=0;
                }
            }
            // For Pin
        }
        // For Register
        do {
            for ( Register = 0; Register < numRegisters; Register++) {
                CurrentMillis = millis ();
                for (int thisPin = 0; thisPin < 8; thisPin++) {
                    // Determine which pin in the Mega Array to address.
                    CalcPin = thisPin+(Register*pinsPerRegister);
                    if ((CurrentMillis > Torrent[CalcPin] + DecayRate) ) {
                        Brightness [CalcPin] =  0;
                    }
                    // Zero
                    if ((CurrentMillis < Torrent[CalcPin]) && (CurrentMillis > Torrent[CalcPin] - (AttackRate))) {
                        Brightness [CalcPin] =   maxBrightness - maxBrightness * ( Torrent[CalcPin]- CurrentMillis)/(AttackRate);
                    }
                    // Going Up
                    if ((CurrentMillis < Torrent[CalcPin] + DecayRate)&&(CurrentMillis > Torrent[CalcPin])) {
                        Brightness [CalcPin] =  maxBrightness - maxBrightness * ( CurrentMillis - Torrent[CalcPin] )/DecayRate;
                    }
                    //Going Down
                    // Write to the PIN      
                    ShiftPWM.SetOne(pinmap[CalcPin], gamma[round(abs(Invert-Brightness[CalcPin]))]);
                }
                // for  Register Pin
            }
            // for Register
            // Check for Serial events
            serialEvent();
        }
        while ( (millis () < EndMillis) &&
              (!stringComplete));
    }
    while ( (millis () < BeginMillis+Duration*1000)  &&
        (!stringComplete));
    //Make sure all LED's are off on exit.
    ShiftPWM.SetAll(0);
}

void Bounce ( long Duration,int Invert, int DotCount,int AttackRate, int DecayRate, int Separation  ) {
    long CurrentMillis = millis ();
    long BeginMillis;
    long EndMillis=0;
    int  Brightness[numOutputs];
    int  DecayDuration = Separation*6;
    // Number of Milli Seconds to decay over.
    int  Random = CurrentMillis;
    long Torrent[numOutputs];
    long StartMillis[numRegisters];
    int  Register=0;
    int  CalcPin=0;
    // Setup Begin time  
    BeginMillis=millis();
    EndMillis=0;
    do {
        // Set the Current Time
        CurrentMillis = millis();
        for (Register = 0; Register < numRegisters; Register++) {
            // Give Each torrent a random start
            StartMillis[Register]= CurrentMillis;
            // Determine the end of the very last light so that we can ensure loop does not finish early
            if ( EndMillis < (StartMillis[Register] + DecayDuration) ) {
                EndMillis = StartMillis[Register] + DecayDuration;
            }
            ;
            for (int thisPin = 0; thisPin < 8; thisPin++) {
                // Determine logical pin address in output matrix
                CalcPin = thisPin+(Register*pinsPerRegister);
                if (DotCount == 1) {
                    Torrent[CalcPin] = StartMillis[Register]+(thisPin*Separation);
                    Brightness[CalcPin]=0;
                } else {
                    Torrent[CalcPin] = StartMillis[Register]+round(thisPin%4)*Separation;
                    Brightness[CalcPin]=0;
                }
            }
            // For Pin
        }
        // For Register
        do {
            for ( Register = 0; Register < numRegisters; Register++) {
                CurrentMillis = millis ();
                for (int thisPin = 0; thisPin < 8; thisPin++) {
                    // Determine which pin in the Mega Array to address.
                    CalcPin = thisPin+(Register*pinsPerRegister);
                    if ((CurrentMillis > Torrent[CalcPin] + DecayRate) ) {
                        Brightness [CalcPin] =  0;
                                                // Check if timer has expired for the sparkle,  if yes then set a new one
//                        Torrent[CalcPin]=CurrentMillis + random(Wobulator);

                    }
                    // Zero
                    if ((CurrentMillis < Torrent[CalcPin]) && (CurrentMillis > Torrent[CalcPin] - (AttackRate))) {
                        Brightness [CalcPin] =   maxBrightness - maxBrightness * ( Torrent[CalcPin]- CurrentMillis)/(AttackRate);
                                        
                    }
                    // Going Up
                    if ((CurrentMillis < Torrent[CalcPin] + DecayRate)&&(CurrentMillis > Torrent[CalcPin])) {
                        Brightness [CalcPin] =  maxBrightness - maxBrightness * ( CurrentMillis - Torrent[CalcPin] )/DecayRate;
                    }
                    //Going Down
                    // Write to the PIN      
                    ShiftPWM.SetOne(pinmap[CalcPin], gamma[round(abs(Invert-Brightness[CalcPin]))]);
                }
                // for  Register Pin
            }
            // for Register
            // Check for Serial events
            serialEvent();
        }
        while ( (millis () < EndMillis) &&
              (!stringComplete));

        // Set the Current Time
        CurrentMillis = millis();

        for (Register = 0; Register < numRegisters; Register++) {

                 // Give Each torrent a random start
            StartMillis[Register]= CurrentMillis;
    
                  // Determine the end of the very last light so that we can ensure loop does not finish early
            if ( EndMillis < (StartMillis[Register] + DecayDuration + 800) ) {
                EndMillis = StartMillis[Register] + DecayDuration;
            }
            ;
            for (int thisPin = 0; thisPin < 8; thisPin++) {
                // Determine logical pin address in output matrix
                CalcPin = thisPin+(Register*pinsPerRegister);
                if (DotCount == 1) {
                    Torrent[CalcPin] = StartMillis[Register]+((7-thisPin)*Separation);
                    Brightness[CalcPin]=0;
                } else {
                    Torrent[CalcPin] = StartMillis[Register]+round((7-thisPin)%4)*Separation;
                    Brightness[CalcPin]=0;
                }
            }
            // For Pin
        }
        // For Register
        do {
            for ( Register = 0; Register < numRegisters; Register++) {
                CurrentMillis = millis ();
                for (int thisPin = 0; thisPin < 8; thisPin++) {
                    // Determine which pin in the Mega Array to address.
                    CalcPin = thisPin+(Register*pinsPerRegister);
                    if ((CurrentMillis > Torrent[CalcPin] + DecayRate) ) {
                        Brightness [CalcPin] =  0;
                    }
                    // Zero
                    if ((CurrentMillis < Torrent[CalcPin]) && (CurrentMillis > Torrent[CalcPin] - (AttackRate))) {
                        Brightness [CalcPin] =   maxBrightness - maxBrightness * ( Torrent[CalcPin]- CurrentMillis)/(AttackRate);
                    }
                    // Going Up
                    if ((CurrentMillis < Torrent[CalcPin] + DecayRate)&&(CurrentMillis > Torrent[CalcPin])) {
                        Brightness [CalcPin] =  maxBrightness - maxBrightness * ( CurrentMillis - Torrent[CalcPin] )/DecayRate;
                    }
                    //Going Down
                    // Write to the PIN      
                    ShiftPWM.SetOne(pinmap[CalcPin], gamma[round(abs(Invert-Brightness[CalcPin]))]);
                }
                // for  Register Pin
            }
            // for Register
            // Check for Serial events
            serialEvent();
        }
        while ( (millis () < EndMillis) &&
              (!stringComplete));

    }
    while ( (millis () < BeginMillis+Duration*1000)  &&
        (!stringComplete));
    //Make sure all LED's are off on exit.
    ShiftPWM.SetAll(0);
}

No comments:

Post a Comment