Monday, September 30, 2013

Project 1 Process: Code for LED control


/*
 * Name: tank_control.pde
 * Author: User "sink" at plantedtank.net forums
 * URL: http://bitbucket.org/akl/tank-control
 *
 * This is control code for an aquarium lighting system.  It is intended to be
 * run on an Arduino microcontroller board.  It allows independant
 * high-resolution control of two PWM outputs (normally connected to LED
 * drivers) and complete flexibility with respect to intensity, timing
 * schedules, and sunrise/sunset.
 *
 * This code requires the following libraries: Wire, TimerOne, Time, DS3231RTC.
 * A bundle of the required libraries (except for Wire, which you should
 * already have) is located in the downloads section of the URL above.  You can
 * always find the latest copy of the code at that location.
 */

/*
 * Copyright (c) 2011, User "sink" at plantedtank.net forums
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <TimerOne.h>
#include <Time.h>
#include <Wire.h>
//#include <DS1307RTC.h>
#include <DS3231RTC.h>

/*
 * IMPORTANT:  These *must* be the pins corresponding to the Timer1 timer on
 * the ATmega168/328.  These are digital pins 9 and 10 on the Uno/Duemilanove.
 */
const int kChan0Pin = 9; // Channel 0 Pin
const int kChan1Pin = 10; // Channel 1 Pin

// All times are in seconds since midnight (valid 0 - 86399)
const long kTurnOn = 32400; // time dawn begins - 0900hrs
const long kTurnOff = 75600; // time sunset begins - 2100hrs

/*
 * Light "state" represents the PWM duty cycle for each channel This normally
 * dictates light intensity. It is an array { duty_chan_1, duty_chan_2 }.
 * Possible values for duty cycle are 0 - 1023.
 */
const int kDayState[] = { 600, 400 }; // daytime LED state
const int kNightState[] = { 0, 0 }; // nighttime LED state

/*
 * Duration (in seconds) of fade.  At the moment the only fades are sunrise and
 * sunset but this value will apply to any other fades you came up with
 */
const long kFadeDuration = 1200; // 20 minutes

long ctr;

/* hold state info */
int state_chan1, state_chan2;

/*
 * fader -- Determine output state for a given time to provide smooth fade from
 * one state to another.
 *     Args:
 *     start_time  -- time (in seconds) of start of fade
 *     start_state -- beginning state
 *     end_state   -- ending state
 *     out         -- array to update with state
 */
void fader(long start_time, const int start_state[], const int end_state[], int out[2]) {

  float per_second_delta_0 = (float) (end_state[0]-start_state[0])/kFadeDuration;
  float per_second_delta_1 = (float) (end_state[1]-start_state[1])/kFadeDuration;

  long elapsed = ctr-start_time;

  out[0] = start_state[0] + per_second_delta_0 * elapsed;
  out[1] = start_state[1] + per_second_delta_1 * elapsed;
}

// return seconds elapsed since midnight
long seconds_since_midnight() {
  time_t t = now();
  long hr = hour(t);
  long min = minute(t);
  long sec = second(t);
  long total = hr * 3600 + min * 60 + sec;
  return total;
}

// set output state
void set_state(const int state[]) {
  if (state[0] >= 0 && state[0] <= 1023) {
    Timer1.setPwmDuty(kChan0Pin, state[0]);
    state_chan1 = state[0]; }
  if (state[1] >= 0 && state[1] <= 1023) {
    Timer1.setPwmDuty(kChan1Pin, state[1]);
    state_chan2 = state[1]; }
}

/*
 * determine_state -- This is where the actual timing logic resides.  We
 * examine ctr (seconds since midnight) and then set output state accordingly.
 * Variable ctr rolls back to 0 at midnight so stages that cross midnight (ie:
 * nighttime) are broken up into two stages.
 */
void determine_state() {
  if ( ctr >= 0 && ctr < kTurnOn ) { // night
      set_state(kNightState);
  } else if ( ctr >= kTurnOn && ctr <= (kTurnOn+kFadeDuration) ) { // sunrise
    int foo[2];
    fader(kTurnOn, kNightState, kDayState, foo);
    set_state(foo);
  } else if ( ctr > (kTurnOn+kFadeDuration) && ctr < kTurnOff ) { // day
    set_state(kDayState);
  } else if ( ctr >= kTurnOff && ctr <= (kTurnOff+kFadeDuration) ) { // sunset
    int foo[2];
    fader(kTurnOff, kDayState, kNightState, foo);
    set_state(foo);
  } else if ( ctr > (kTurnOff+kFadeDuration) && ctr < 86400 ) { // night
    set_state(kNightState);
  }
}

/*
 * Utility function for pretty digital clock time output
 * From example code in Time library -- author unknown
 */
void printDigits(int digits) {
  Serial.print(":");
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*
 * Display time
 * Adapted from example code in Time library -- author unknown
 */
void digitalClockDisplay() {
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(month());
  Serial.print("/");
  Serial.print(day());
  Serial.print("/");
  Serial.print(year());
  Serial.println();
}

void setup() {
  Serial.begin(115200); // Max for Arduino Uno
  setSyncProvider(RTC.get);
  Timer1.initialize(6666); // 150Hz PWM
  pinMode(kChan0Pin, OUTPUT);  
  Timer1.pwm(kChan0Pin, 0);
  pinMode(kChan1Pin, OUTPUT);  
  Timer1.pwm(kChan1Pin, 0);
}

void loop () {
  ctr = seconds_since_midnight();
  determine_state();

  if (Serial.available() >= 5) {
    char data[4];
    for (int i=0; i<5; i++) {
      data[i] = Serial.read();
    }

    Serial.flush(); // ensure we never have more than 5 bytes buffered

    if (data[0] == 'A') { // send current time
      time_t longInt = now();
      unsigned char byteArray[4];
             
      // convert from an unsigned long int to a 4-byte array
      byteArray[0] = (int)((longInt >> 24) & 0xFF);
      byteArray[1] = (int)((longInt >> 16) & 0xFF);
      byteArray[2] = (int)((longInt >> 8) & 0XFF);
      byteArray[3] = (int)((longInt & 0XFF));
      // send time
      Serial.print("Z");
      Serial.print(byteArray[0]);
      Serial.print(byteArray[1]);
      Serial.print(byteArray[2]);
      Serial.print(byteArray[3]);
    }
    else if (data[0] == 'B') { // set time
      union u_tag {
        byte b[4];
        unsigned long ulval;
      } u;

      u.b[0] = data[4];
      u.b[1] = data[3];
      u.b[2] = data[2];
      u.b[3] = data[1];

      RTC.set(u.ulval);
      setTime(u.ulval);
      Serial.print("Z0000");
    }
    else {
      Serial.print("X0000");
    }
  }

  delay(250);

/*
  Serial.print("ctr: ");
  Serial.print(ctr); // display counter
  Serial.println();
  Serial.print("channel 1, 2: ");
  Serial.print(state_chan1);
  Serial.print(", ");
  Serial.print(state_chan2);
  Serial.println();
  digitalClockDisplay(); //display time
  Serial.println();
*/
}

No comments:

Post a Comment