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);
}