Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- Wendy's Lights Beta.
- Using TLC5940 PWM driver chips and Jez's Perlin Simplex noise generator functions modified slightly
- Now with multiple lamp code and pots for control.
- 25/09/2012 Adding photoresistor and PIR code. PIR triggers an interrupt on falling edge.
- 02/10/2012 Added to SVN. Rev 3.
- Re-added PIR and photoresistor code after overwrite.
- 12/10/2012 Adding Pot control code. 3 pots
- 1. light_duration: How long the lights stay on for after movement is detected. Tuning so 0 = 30 sec,
- 1 - 1000 linear scale up to 6 hours. Over 1000 = 24 hours.
- 2. time_inc: How fast we progress through the colour space.
- 3. lamp_offset: The difference between where each lamp is in the colour space.
- 14/10/2012 Hardware finalised. This code seems close to final. Moving to alpha.
- */
- // Handles the grunt work of talking to the PWM chips. Must be edited to match
- // the number of TLC5940s if you have more than one. This implementation uses 2.
- #include "Tlc5940.h"
- // Defines and constants
- // Update this to match LED driver resolution
- #define PWM_RESOLUTION 12 // bits
- #define LAMPS 6 // how many lamps we're controlling
- #define COLOURS 3 // 3 colours, red, green and blue
- #define FRAME_DELAY 5 // milliseconds to display each frame for
- // Map colour names to array postions
- #define RED 0
- #define GREEN 1
- #define BLUE 2
- // Pot pins
- #define AMBIENT_LIGHT_SENSOR 0 // The photoresistor
- // Adjustment potentiometers are on these analog pins
- #define ADJ_ONE 1 // light_duration
- #define ADJ_TWO 2 // time_inc
- #define ADJ_THREE 3 // lamp_offset
- // The ambient light level above which the lights will not be triggered. This is adjustable
- // in the hardware via a trim pot. The value was chosen after experimenting with the available
- // range from the hardware. 300 gives a nice range in both directions with the 50k pot in the
- // middle position. Turning the pot clockwise will
- #define AMBIENT_LIGHT_MIN_LEVEL 300
- #define PIR_PIN 2 // must be a pin we can attach hardware interrupts to (2 is int0)
- #define FADE_UP_TIME 500 // milliseconds to fade the lights up
- #define FADE_DOWN_TIME 5000 // milliseconds to fade the lights down
- // Simplex noise parameters:
- // obtained empirically and used in scaling returned values to match the PWM_RESOLUTION
- const double NOISE_LIMIT = 0.312768; // the noise generator returns values between -/+ this (on the Arduino)
- // Increment range, smaller is a slower walk through the noise space,
- // which means smaller colour differences between frames and a slower colour progression.
- double time_inc; // set by a pot
- // This is volatile as it could be changed inside the interrupt routine.
- volatile unsigned long end_time = 0; // used to track whether the lights should be on or not
- // used to store the current position in the noise space. Global so it's persistent between calls
- double offset = 0;
- // how much of a difference between where each lamp is in the walk. This will affect how much
- // of a colour difference there is between each individual lamp in any given frame.
- double lamp_offset; // adjustable via a pot.
- // seconds for the light to stay on after movement is detected
- unsigned long light_duration; // Adjusted by a pot.
- // globals for the simplex generation
- // calulate the highest allowable value from the bit resolution. Used in scaling
- // Simplex noise returned values to the limits of the PWM_RESOLUTION available.
- int LED_MAX_LEVEL = (1 << PWM_RESOLUTION) - 1;
- // some working containers used by the Perlin noise generator
- // These are modified in multiple subs. Easier to make them global than keep passing them around
- int i, j, k, A[] = {0, 0, 0};
- double u, v, w, s;
- const double onethird = 0.333333333;
- const double onesixth = 0.166666667;
- // Not sure what these represent. They're used in the noise generation
- int T[] = {0x15, 0x38, 0x32, 0x2c, 0x0d, 0x13, 0x07, 0x2a}; // {21, 56, 50, 44, 13, 19, 7, 42)
- // To hold the values from the Simplex generator
- int colours[COLOURS];
- // To hold the next frame of colours to be displayed on each lamp
- int frame[LAMPS][COLOURS];
- // Globals for the light levels
- int red_max = LED_MAX_LEVEL;
- int green_max = LED_MAX_LEVEL;
- int blue_max = LED_MAX_LEVEL;
- int led_min_level = 0;
- void setup() {
- /* Call Tlc.init() to setup the tlc.
- You can optionally pass an initial PWM value (0 - 4095) for all channels.*/
- Tlc.init(0);
- // The TLC doesn't initialise properly unless there's a delay between initialising
- // and sending data to it. The rest of the code will usually take care of that,
- // but leaving it here just in case.
- delay(1); // OMG come on start already!
- attachInterrupt(0,isr_MovementDetected,FALLING); // Hook the interrupt for the PIR switch
- testTwo(1); // Show that we're alive
- makePotAdjustments(); // Set the values to whatever the pots are set to for starters
- }
- void loop() {
- // Wait for something to happen.
- if (end_time != 0) { // movement detected
- if (analogRead(AMBIENT_LIGHT_SENSOR) < AMBIENT_LIGHT_MIN_LEVEL) {
- simplexNoiseWalk(); // will keep running as long as the PIR sensor keeps getting tripped
- end_time = 0; // fell out of the loop, so reset this
- fadeToBlank(FADE_DOWN_TIME);
- }
- }
- delay(5); // spin wheels for a tick. Going to replace with a proper sleep mode.
- }
- void isr_MovementDetected() {
- // Interrupt Service Routince for the PIR sensor
- // end_time is usually 0, so setting this notifies the main loop that something has happened
- end_time = millis() + light_duration * 1000;
- }
- void showFrame() {
- // Show the current frame
- int tlc_pin = 0;
- // set the new values to display
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- Tlc.set(tlc_pin,frame[l][c]);
- tlc_pin++;
- }
- }
- // send the values to the driver chip
- Tlc.update();
- }
- void fadeUpToFrame(int fade_up_time) {
- // fade up from blank to the current frame taking the given number of milliseconds (give or take)
- float incr_values[LAMPS][COLOURS];
- // need a floating point copy to do an accurate fade up in cases where the incr value is non-integer
- float ghost_frame[LAMPS][COLOURS] = {0};
- // number of steps and frame delay dictates how long the fade up will take
- int frame_delay = 1; // milliseconds
- float steps = fade_up_time/frame_delay;
- // work out the increment factors to bring each lamp up to brightness evenly
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- incr_values[l][c] = frame[l][c]/steps;
- }
- }
- // fade up
- for (double i=0;i<steps;i++) {
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- ghost_frame[l][c] += incr_values[l][c];
- frame[l][c] = ghost_frame[l][c];
- }
- }
- showFrame();
- delay(frame_delay);
- }
- }
- void fadeToBlank(int fade_down_time) {
- // Fade the frame to blank taking the given number of milliseconds (give or take)
- float decr_values[LAMPS][COLOURS];
- float ghost_frame[LAMPS][COLOURS] = {0}; // need a floating point copy to keep track when decr < 1
- // number of steps and frame delay dictates how long the fade will take
- int frame_delay = 1; // milliseconds
- float steps = fade_down_time/frame_delay;
- // work out the decrement factors to bring each lamp down to blank evenly and initialize the ghost array
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- decr_values[l][c] = frame[l][c]/steps;
- ghost_frame[l][c] = frame[l][c];
- }
- }
- for (double i=0;i<steps;i++) {
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- ghost_frame[l][c] -= decr_values[l][c];
- frame[l][c] = ghost_frame[l][c];
- }
- }
- showFrame();
- // check if there's been any movement during the fade down and cancel it if there was
- if (end_time != 0) {
- return;
- }
- delay(frame_delay);
- }
- // make sure everything is off before leaving
- blankFrame();
- showFrame();
- }
- void blankFrame() {
- // Reset the frame to all zeroes
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- frame[l][c] = 0;
- }
- }
- }
- void testOne(int repetitions) {
- // Turn each channel on and off again in turn
- int time_delay = 100; //milliseconds
- for (int r=0;r<repetitions;r++) {
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- frame[l][c] = LED_MAX_LEVEL;
- showFrame();
- delay(time_delay);
- frame[l][c] = 0;
- showFrame();
- delay(time_delay);
- }
- }
- }
- }
- void testTwo(int repetitions) {
- // Turn each colour on and off again in turn
- int time_delay = 250; //milliseconds
- for (int r=0;r<repetitions;r++) {
- for (int c=0;c<COLOURS;c++) {
- for (int l=0;l<LAMPS;l++) {
- frame[l][c] = LED_MAX_LEVEL;
- }
- fadeUpToFrame(time_delay);
- delay(time_delay);
- for (int l=0;l<LAMPS;l++) {
- frame[l][c] = 0;
- }
- showFrame();
- delay(time_delay);
- }
- }
- }
- void allOn(int duration){
- // Turn all channels on
- for (int l=0;l<LAMPS;l++) {
- for (int c=0;c<COLOURS;c++) {
- frame[l][c] = LED_MAX_LEVEL;
- }
- }
- showFrame();
- delay(duration * 1000);
- }
- void makePotAdjustments () {
- // Read the pots and set values as appropriate
- // work out how long to leave the lights on for
- int pot_one = analogRead(ADJ_ONE);
- // Using a staggered setting here. Fully off is 30 seconds
- if (pot_one == 0) {
- light_duration = 30;
- } else if (pot_one <= 1000) {
- // scaling this range to 31 - 21596 (approx 6 hours)
- light_duration = pot_one * 21.59 + 6;
- } else { // pot is over 1000
- light_duration = 81600; // 24 hours
- }
- // how fast to move through the colour space
- int pot_two = analogRead(ADJ_TWO);
- // Scale the input range from barely moving to fairly fast changes
- time_inc = (double)pot_two/512000; // This seemed to give a nice range
- // offset between the lamps
- int pot_three = analogRead(ADJ_THREE);
- lamp_offset = (double)pot_three/1024;
- }
- void simplexNoiseWalk() {
- // Walk through a simplex noise space generating values for the implementation's number
- // of LAMPS and COLOURS
- const int LIMIT = 16384; // noise pattern stops changing after this, so reset if we reach it
- // generate the first frame and fade up to it
- for (int l=0;l<LAMPS;l++) {
- generateSimplexNoisePattern(offset - (lamp_offset * l));
- for (int c=0;c<COLOURS;c++) {
- frame[l][c] = colours[c];
- }
- offset += time_inc; // how fast we move through the space
- }
- fadeUpToFrame(500);
- while (millis() < end_time) {
- makePotAdjustments(); // this may make changes to some globals used in the colour generation
- for (int l=0;l<LAMPS;l++) {
- generateSimplexNoisePattern(offset - (lamp_offset * l));
- for (int c=0;c<COLOURS;c++) {
- frame[l][c] = colours[c];
- }
- offset += time_inc; // how fast we move through the space
- }
- // make brightness adjustments
- for (int l=0;l<LAMPS;l++) {
- if (frame[l][RED] > red_max) {frame[l][RED] = red_max;}
- if (frame[l][GREEN] > green_max) {frame[l][GREEN] = green_max;}
- if (frame[l][BLUE] > blue_max) {frame[l][BLUE] = blue_max;}
- if (frame[l][RED] < led_min_level) {frame[l][RED] = led_min_level;}
- if (frame[l][GREEN] < led_min_level) {frame[l][GREEN] = led_min_level;}
- if (frame[l][BLUE] < led_min_level) {frame[l][BLUE] = led_min_level;}
- }
- showFrame();
- delay(FRAME_DELAY);
- // wrap around if we ever get to the end of the space
- if (offset > LIMIT) {
- offset = 0;
- }
- // interrupt only triggers on a falling edge, so we need to check if movement
- // is still being detected.
- if (digitalRead(PIR_PIN) == LOW) {
- end_time = millis() + light_duration * 1000;
- }
- }
- }
- void generateSimplexNoisePattern( double yoffset) {
- // Calculate simplex noise for LEDs that are nodes:
- // Store raw values from simplex function
- double r = SimplexNoise(yoffset,0, 0);
- double g = SimplexNoise(yoffset,1, 0);
- double b = SimplexNoise(yoffset,2, 0);
- // Convert values from raw noise to scaled r,g,b and feed to LEDs.
- // Raw noise is +/-NOISE_LIMIT.
- // Result converted to int here to save a step, some detail is lost, but not enough to worry about.
- // Difference is about +/- 2 with a 12 bit range (0-4095) or 0.05%
- colours[RED] = (r + NOISE_LIMIT) * LED_MAX_LEVEL/(NOISE_LIMIT*2);
- colours[GREEN] = (g + NOISE_LIMIT) * LED_MAX_LEVEL/(NOISE_LIMIT*2);
- colours[BLUE] = (b + NOISE_LIMIT) * LED_MAX_LEVEL/(NOISE_LIMIT*2);
- }
- /*****************************************************************************/
- // Simplex noise code:
- // From an original algorithm by Ken Perlin.
- // Returns a value in the range of about [-0.313 .. 0.347] <-- this may not be true depending on your c/c++ implementation and target processor
- double SimplexNoise(double x, double y, double z) {
- // Skew input space to relative coordinate in simplex cell
- s = (x + y + z) * onethird;
- i = fastfloor(x+s);
- j = fastfloor(y+s);
- k = fastfloor(z+s);
- // Unskew cell origin back to (x, y , z) space
- s = (i + j + k) * onesixth;
- u = x - i + s;
- v = y - j + s;
- w = z - k + s;;
- A[0] = A[1] = A[2] = 0;
- // For 3D case, the simplex shape is a slightly irregular tetrahedron.
- // Determine which simplex we're in
- int hi = u >= w ? u >= v ? 0 : 1 : v >= w ? 1 : 2;
- int lo = u < w ? u < v ? 0 : 1 : v < w ? 1 : 2;
- return k_fn(hi) + k_fn(3 - hi - lo) + k_fn(lo) + k_fn(0);
- }
- int fastfloor(double n) {
- return n > 0 ? (int) n : (int) n - 1;
- }
- double k_fn(int a) {
- s = (A[0] + A[1] + A[2]) * onesixth;
- double x = u - A[0] + s;
- double y = v - A[1] + s;
- double z = w - A[2] + s;
- double t = 0.6f - x * x - y * y - z * z;
- int h = shuffle(i + A[0], j + A[1], k + A[2]);
- A[a]++;
- if (t < 0) return 0;
- int b5 = h >> 5 & 1;
- int b4 = h >> 4 & 1;
- int b3 = h >> 3 & 1;
- int b2 = h >> 2 & 1;
- int b = h & 3;
- double p = b == 1 ? x : b == 2 ? y : z;
- double q = b == 1 ? y : b == 2 ? z : x;
- double r = b == 1 ? z : b == 2 ? x : y;
- p = b5 == b3 ? -p : p;
- q = b5 == b4 ? -q: q;
- r = b5 != (b4^b3) ? -r : r;
- t *= t;
- return 8 * t * t * (p + (b == 0 ? q + r : b2 == 0 ? q : r));
- }
- int shuffle(int i, int j, int k) {
- return b(i, j, k, 0) + b(j, k, i, 1) + b(k, i, j, 2) + b(i, j, k, 3) + b(j, k, i, 4) + b(k, i, j, 5) + b(i, j, k, 6) + b(j, k, i, 7);
- }
- int b(int i, int j, int k, int B) {
- return T[b(i, B) << 2 | b(j, B) << 1 | b(k, B)];
- }
- int b(int N, int B) {
- return N >> B & 1;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement