Jump to content

Sardino - a DIY Arduino-based reef controller


Clark

Recommended Posts

Hi all

In October I started learning about the open-source Arduino processor. Like many others, I decided to make my own reef tank controller using this processor and readily available sensors and relays. Here are two shots of the final, installed controller. The first shows the display box which is the only part visible to a visitor. There is a 16x2 backlit LCD display, a RGB status LED, a yellow button, and an Arduino sticker.

post-3419-0-45497200-1388363965_thumb.jp

The second picture shows the "back of house" which consists of another black box mounted on the backside of the shelves that houses the actual Arduino. A DS18B20 thermal sensor line and 3 float switch lines run into this box. An 8 wire cable connects this box to the display box. A 9 wire cable connect the processor box to an 8-outlet box sitting on the floor.

post-3419-0-95022400-1388364040_thumb.jp

The controller is currently responsible for the ATO, heater, and alternating the wave pumps (2 Koralias).

Link to comment
Share on other sites

Inside the display box:

post-3419-0-03022100-1388364572_thumb.jp

The display box contains an RJ45 jack and breakout board. This allows you to connect the wires coming in from the cable to the internal components. Also in here is the 16x2 LCD display with its I2C adapter "backpack", the yellow button, the RGB LED, and a small circuit board for pullup resistors for the button and LCD, and voltage-dropping resistors for the LED.

Oh and here is how I cut out the hole in the lid for the display.

post-3419-0-60449500-1388365340_thumb.jp

Link to comment
Share on other sites

Inside the processor box:

post-3419-0-82181800-1388365218_thumb.jp

Inside here is an Arduino Mega beneath an Adafruit Mega Prototype shield, and two rj45 jacks and breakout boards. The Mega Prototype shield has breakouts for the ground and 5v sources so you can run those to other places as needed. For example, each of the float switches needs a ground line so I tapped into the shield ground in three places for the float sensor lines. The display box needs 5v and so does the outlet box, etc. So my custom wiring for my project is all attached to the Prototype shield. The Arduino board is snugged up against the side of the box so its power jack and programming USB jack are accessible from the outside. Sensor lines from the thermal probe and float switches come in. Lines to control a relay on each of the 8 outlets go out. There are also wires going to the display box and coming back from its button.

Link to comment
Share on other sites

Inside the relay/outlet box:

post-3419-0-25596900-1388365784_thumb.jp

post-3419-0-04138000-1388365843_thumb.jp

post-3419-0-64077900-1388365946_thumb.jp

This is how computers will take over the world. You connect them to relays. A relay allows the Arduino to use its weak output signals to turn on/off high voltage outlets, which we reefers attach to our pumps and heaters. I chose to embed the relay board inside the outlet box itself so compact high voltage wiring is required between the outlets and to the pigtail and relay board. Also inside the box is an rj45 jack and breakout board. I also installed a 12amp fuse inline in the pigtail. BTW, the pigtail is really a replacement cable for power tools. Except for the relays and rj45 jack, everything else here comes from big box hardware stores.

The relays are optically isolated from the Arduino (this is a feature of the relay board).

Link to comment
Share on other sites

So what's it do?

There isn't much to show for the wavemaker function. I simply use code timers to cycle the wave pumps in the DT. There are two and they each use their own outlet.

It's fairly simple to use the thermal sensor in the DT to check the temp and turn on the heater (via its outlet, via the relay on the outlet). It took a bit more thought to build the ATO. I bought two trash bins, a length of PVC, and made a few plastic mounts from a milk jug.

post-3419-0-31294800-1388366448_thumb.jp

From left to right: sump bucket (tan, 6 gallon), sump standpipe, reservoir standpipe, reservoir (white bucket, 11 gallon).

The stand pipe on the left goes into the sump. Water pours into it from a short vinyl supply line coming down from the taller reservoir. It has holes drilled into the bottom for better flow, a low float switch to detect when to turn on the ATO pump, and a high float switch to turn the ATO off when the sump is filled to the proper level. The Arduino is also programmed with a timer failsafe to turn off the ATO if the upper float switch fails. The sump standpipe is tall enough to prevent a siphon from forming. The standpipe on the right goes into the reservoir to position the float switch near the bottom. It allows me to detect when the fresh water reservoir is low so I can inhibit the ATO pump in the bottom of the reservoir until it is refilled.

I'm rather proud that it all fits inside the cabinet under my 55g.

post-3419-0-02749000-1388366956_thumb.jp

Link to comment
Share on other sites

What about the shiny yellow button?

Press it once to wake up the LCD for 1 minute. (I'll tell you why its usually off later).

Press it again when the LCD is on to activate "Feeding Mode". This turns off the wavemakers for 5 minutes.

And that LED?

It slowly pulses green under normal operation.

It quickly pulses blue when the ATO reservoir is nearly empty.

Someday I may make it blink red to show faults like a power outage or zombie apocalypse.

What does the LCD Show?

Top line:

"Temp 78.9 F" or

"Temp 78.7 F Heat" (when the heater is on)

Bottom Line:

"Wave On/Off" (left wavemaker currently on, right wm currently off) or

"Wave Off/Off FDG" (feeding mode)

"Wave On/On ATO" (ATO reservoir is empty)

Link to comment
Share on other sites

And here's the current state of the code:

// WARNING!: DO NOT SEND !!! OVER THE SERIAL LINE. Known issue: will hang serial.
// NOTE: int's are 16 bits!  long's are 32 bits.

#include <OneWire.h> // for Dallas lib
#include <DallasTemperature.h>
#include <Wire.h> // for LCD lib
#include <LiquidCrystal_I2C.h>
#include <avr/wdt.h>

// Controller state
float _currentTempF   = 0.0;
bool  _tempReady      = false;
bool  _allowWaves     = false;
byte  _atoFault       = 0;
bool  _wtdEnabled     = false;
bool  _reservoirEmpty = false;
bool  _LCDEnabled     = false;
bool  _feedingMode    = false;
byte  _LEDOnValue     = 245; // still bright, need to lower voltage?
byte  _LEDRedValue    = 255;
byte  _LEDGreenValue  = 255;
byte  _LEDBlueValue   = 255;
byte  _yellowBtnError = 0; // test value
bool  _daytimeWaves   = true;
bool  _estimateDay    = false;
bool  _daytime        = true;

volatile bool _yellowBtnPressed = false;

// LED PWM signals
#define LED_RED   44
#define LED_GREEN 46
#define LED_BLUE  45

// button. interrupt #4
#define BTN_YELLOW 19

// DS18B20 signal wire is plugged into port 24 on my Arduino
#define ONE_WIRE_BUS 24
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
DeviceAddress tankThermometer;

// set up LCD (hw addr 0x27) and it's backpack pins:2,1,0,4,5,6,7,3
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

//#define HORN PIN?
#define HORN_ON HIGH
#define HORN_OFF LOW

// float sensors on 26,28,30
#define FLOAT_SUMP_LOW 30
#define FLOAT_SUMP_HI 28
#define FLOAT_RESERVOIR 26

//--------------------------------------------------------------------------------
class Countdown
{
public:
	Countdown():m_zero(0){}
	void Set(unsigned long duration,
			 unsigned long currentTime)
	{
		m_zero = currentTime + duration;
	}
	bool Done(unsigned long currentTime)
	{
		return ((long)(currentTime-m_zero))>0;
	}
private:
	unsigned long m_zero;
};
Countdown _temperatureTimer, _waveTimer, _atoRunawayTimer, _ledTimer,
          _yellowBtnTimer, _feedingTimer, _LCDTimer, _dayTimer;

//--------------------------------------------------------------------------------
class FloatSensor
{
public:
  void Setup(int pin)
  {
    m_pin=pin;
    pinMode(m_pin, INPUT_PULLUP);
		m_verifying = false;
		m_verifyingUp = false;
		m_isDown = false;
  }
	void Update(unsigned long t)
	{
		if (m_verifying)
		{
			if (verify.Done(t))
			{
				m_verifying = false;
				m_isDown = (digitalRead(m_pin) == LOW);
			}
			if (digitalRead(m_pin) != LOW)
			{
				m_verifying = false;
				return;
			}
			return;
		}
		
		if (m_verifyingUp)
		{
			if (verifyUp.Done(t))
			{
				m_verifyingUp = false;
				m_isDown = (digitalRead(m_pin) == LOW);
			}
			if (digitalRead(m_pin) != HIGH)
			{
				m_verifyingUp = false;
				return;
			}
			return;
		}
		
		if (digitalRead(m_pin) == LOW)
		{
			m_verifying = true;
			verify.Set(3UL*1000UL, t);
			return;
		}
		if (digitalRead(m_pin) == HIGH)
		{
			m_verifyingUp = true;
			verifyUp.Set(3UL*1000UL, t);
			return;
		}
	}
	
  bool IsDown()
  {
		return m_isDown;
  }
	bool IsUp()
	{
		return !m_isDown;
	}
private:
  int m_pin;
	bool m_isDown; // need unknown?
	bool m_verifying, m_verifyingUp;
	Countdown verify, verifyUp;
};
FloatSensor _floatSumpLow, _floatSumpHi, _floatReservoir;

struct RelayInfo
{
  int pin;
  int onSignal;
  int offSignal;
  int now;
};

// Relay 1,2 are normally on.
// All others are normally off.
RelayInfo relayInfos[] =
{
  {34,HIGH,LOW,HIGH},
  {35,HIGH,LOW,HIGH},
  {36,LOW,HIGH,HIGH},
  {37,LOW,HIGH,HIGH},
  {38,LOW,HIGH,HIGH},
  {39,LOW,HIGH,HIGH},
  {40,LOW,HIGH,HIGH},
  {41,LOW,HIGH,HIGH},
};

// Relay - Device Map
// 0. Lights          (normally on, set LOW to turn off)
// 1. Sump            (normally on, set LOW to turn off)
// 2. Canister Filter (normally off, set LOW to turn on)
// 3. ATO             (normally off, set LOW to turn on)
// 4. Wave Left       (normally off, set LOW to turn on)
// 5. Wave Right      (normally off, set LOW to turn on)
// 6. Fans            (normally off, set LOW to turn on)
// 7. Heater          (normally off, set LOW to turn on)

#define LIGHTS 0
#define SUMP 1
#define CANISTER 2
#define ATO 3
#define WAVE_LEFT 4
#define WAVE_RIGHT 5
#define FANS 6
#define HEATER 7

//--------------------------------------------------------------------------------
void TurnDeviceOn(int device, bool log=false)
{
	digitalWrite(relayInfos[device].pin, relayInfos[device].onSignal);
	relayInfos[device].now = relayInfos[device].onSignal;
	if (log)
	{
		Serial.print("Turn on device ");
		Serial.print(device);
		Serial.print(" on pin ");
		Serial.print(relayInfos[device].pin);
		Serial.print(" with signal ");
		Serial.println(relayInfos[device].onSignal);
	}  
}

void TurnDeviceOff(int device, bool log=false)
{
	digitalWrite(relayInfos[device].pin, relayInfos[device].offSignal);
	relayInfos[device].now = relayInfos[device].offSignal;
	if (log)
	{
		Serial.print("Turn off device ");
		Serial.print(device);
		Serial.print(" on pin ");
		Serial.print(relayInfos[device].pin);
		Serial.print(" with signal ");
		Serial.println(relayInfos[device].offSignal);
	}
}

bool IsDeviceOn(int device)
{
	return (relayInfos[device].now == relayInfos[device].onSignal);
}

// void TurnLEDOn(int channel)
// {
  // analogWrite(channel,_LEDOnValue);
// }

// void TurnLEDOff(int channel)
// {
  // analogWrite(channel,255);
// }


//--------------------------------------------------------------------------------
class LEDPulsar
{
public:
	LEDPulsar()
	{
		m_active = false;
		m_value  = 255;
		m_direction = -1;
		m_pin = 0;
	}
	void Pin(byte pin)
	{
		pinMode(pin,OUTPUT);
		m_pin = pin;
	}
	void Start(float periodInMS, byte onValue, unsigned long t)
	{
		if (m_pin!=0)
		{
			m_active = true;
			m_value = 255;
			m_direction = -1;
			m_delta = 2.0*(255.0-(float)onValue)/periodInMS;
			m_lastT = t;
			m_onValue = onValue;
		}
	}
	void Update(unsigned long t)
	{
		if (m_active)
		{
			long dt = (long)(t-m_lastT);
			m_lastT = t;
			if (dt<0) // wrap
			{
				dt = 0;
				return;
			}

			float newValue = m_value + m_direction*m_delta*(float)dt;
			if (newValue>255)
			{
				m_value = 255;
				m_direction = -1;
			}
			else if (newValue<m_onValue)
			{
				m_value = m_onValue;
				m_direction = 1;
			}
			else
			{
				m_value = newValue;
			}
			analogWrite(m_pin,(byte)m_value);
		}
		else
		{
			analogWrite(m_pin,255);
		}
	}
	void Stop()
	{
		m_active = false;
		analogWrite(m_pin,255);
	}
private:
	float m_value;
	bool m_active;
	float m_delta;
	float m_direction;
	byte m_onValue;
	unsigned long m_lastT;
	byte m_pin;
};
LEDPulsar _redPulsar, _greenPulsar, _bluePulsar;


//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
void BITRelays(unsigned long t)
{
  static bool once = true;
  if (once)
  {
    once = false;
    Serial.println("==================");
    Serial.println("Relay Info Table");
    Serial.println("r,pin,on,off,now");
    for (int d=LIGHTS; d<=HEATER;++d)
    {
      Serial.print(d);
      Serial.print(": ");
      Serial.print(relayInfos[d].pin);
      Serial.print(", ");
      Serial.print(relayInfos[d].onSignal);
      Serial.print(", ");
      Serial.print(relayInfos[d].offSignal);
      Serial.print(", ");
      Serial.println(relayInfos[d].now);
      TurnDeviceOff(d);
    }  
    Serial.println("==================");
  }
  
  static Countdown next;
  static int r = LIGHTS; // ..HEATER
  static int rLast = HEATER;
  if (next.Done(t))
  {
    TurnDeviceOn(r, true);
    TurnDeviceOff(rLast, true);
    rLast = r;
    ++r;
    if (r>HEATER) r=LIGHTS;
    next.Set(3UL*1000UL, t);
  }
}

//--------------------------------------------------------------------------------
bool setupTempSensor()
{
	// locate devices on the bus
	Serial.print("Locating devices...");
	sensors.begin();
	Serial.print("Found ");

	int dc = sensors.getDeviceCount();
	Serial.print(dc, DEC);
	Serial.println(" devices.");
	if (dc==0) return false;

	// report parasite power requirements
	Serial.print("Parasite power is: "); 
	if (sensors.isParasitePowerMode()) Serial.println("ON");
	else Serial.println("OFF");

	if (!sensors.getAddress(tankThermometer, 0)) 
	{
		Serial.println("Unable to find address for Device 0"); 
		return false;
	}

	// set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
	sensors.setResolution(tankThermometer, 12);

	Serial.print("Device 0 Resolution: ");
	Serial.print(sensors.getResolution(tankThermometer), DEC); 
	Serial.println();

	return true;  
}

//--------------------------------------------------------------------------------
// code from http://www.fiz-ix.com/2012/11/low-power-arduino-using-the-watchdog-timer/
// because the official 'wdt_enable(WDTO_2S);' doesn't activate the watch dog.
void watchdogOn() 
{
  _wtdEnabled = true;
  // start our watchdog timer.
//  wdt_enable(WDTO_2S);
//  return; // I have not verified the code below for the mega - Clark

  // Clear the reset flag, the WDRF bit (bit 3) of MCUSR.
  MCUSR = MCUSR & B11110111;
    
  // Set the WDCE bit (bit 4) and the WDE bit (bit 3) 
  // of WDTCSR. The WDCE bit must be set in order to 
  // change WDE or the watchdog prescalers. Setting the 
  // WDCE bit will allow updtaes to the prescalers and 
  // WDE for 4 clock cycles then it will be reset by 
  // hardware.
  WDTCSR = WDTCSR | B00011000; 
  
  // Set the watchdog timeout prescaler value to 1024 K 
  // which will yeild a time-out interval of about 8.0 s.
  WDTCSR = B00100001;
  
  // Enable the watchdog timer interupt.
  WDTCSR = WDTCSR | B01000000;
  MCUSR = MCUSR & B11110111;
}

//--------------------------------------------------------------------------------
void setup(void)
{
	// Speaker broke. Re-enable when replaced.
	// Boot beep
	//digitalWrite(HORN,HORN_OFF);
	//pinMode(HORN,OUTPUT);
	//digitalWrite(HORN,HORN_ON);
	//delay(250);
	//digitalWrite(HORN,HORN_OFF);

	// start serial port for debug text.
	Serial.begin(9600);
	Serial.println("Sardino Is Alive.");
	
	_redPulsar.Pin(LED_RED);
	_greenPulsar.Pin(LED_GREEN);
	_bluePulsar.Pin(LED_BLUE);

	_floatSumpLow.Setup(FLOAT_SUMP_LOW);
	_floatSumpHi.Setup(FLOAT_SUMP_HI);
	_floatReservoir.Setup(FLOAT_RESERVOIR);

	// device initial states
	TurnDeviceOn( LIGHTS );
	TurnDeviceOn( SUMP );
	TurnDeviceOn( CANISTER );
	TurnDeviceOff( FANS );
	TurnDeviceOff( HEATER );
	TurnDeviceOff( ATO );
	TurnDeviceOff( WAVE_LEFT );
	TurnDeviceOff( WAVE_RIGHT );

	for (int d=0;d<8;++d)
	{
		pinMode(relayInfos[d].pin, OUTPUT);  
	}

	// Display
	lcd.begin(16,2);   // initialize the lcd for 16 chars 2 lines, turn on backlight
	lcd.clear();
	lcd.noBacklight();

	// find our temp sensor
	_tempReady = setupTempSensor();

	// start timers
	unsigned long t = millis();
	if (_tempReady) _temperatureTimer.Set(5UL*1000UL,t); // 5s
	_waveTimer.Set(30UL*1000UL,t); // 30s
	_LCDTimer.Set(30UL*1000UL,t);

  // Not using the button for now.  too many false alarms.
	pinMode(BTN_YELLOW,INPUT);
	attachInterrupt(4, YellowBtnPressed, LOW);
	
	_greenPulsar.Start(5000.0,220,t);

  _reservoirEmpty = true;
	
  // this is not working.  Might need a firmware update?
  // do this as last part of setup
  //watchdogOn();
	
	// _estimateDay = true;
	// _dayTimer.Set(1000UL*60UL*60UL*8UL,t); // 8 hrs remaining today
}

//--------------------------------------------------------------------------------
void UpdateWaves(unsigned long t)
{
  static byte wavePhase = 0;
	if (!_estimateDay) _daytimeWaves = false;
	else
	{
		if (_dayTimer.Done(t))
		{
			_daytime = !_daytime;
			_dayTimer.Set(1000UL*60UL*60UL*12UL,t); // 12 hrs
		}
		_daytimeWaves = _daytime;
	}
  
  if (!_allowWaves || _feedingMode)
  {
    TurnDeviceOff( WAVE_LEFT );
    TurnDeviceOff( WAVE_RIGHT );
  }
  else
  {
		TurnDeviceOn( WAVE_RIGHT );
		if (!_daytime) TurnDeviceOff( WAVE_LEFT );
		else
		{
			if ( _waveTimer.Done(t) )
			{
				switch (wavePhase%2)
				{
					case 0:
						TurnDeviceOn( WAVE_LEFT );
						_waveTimer.Set(30UL*1000UL, t);
						break;
					case 1:
						TurnDeviceOff( WAVE_LEFT );
						_waveTimer.Set(30UL*1000UL, t);
						break;
				};
				++wavePhase; // safe to roll over
			}
    }
  }
}

//--------------------------------------------------------------------------------
void UpdateHeating()
{
	// Heater
	if (_currentTempF <  78.7) TurnDeviceOn( HEATER );
	if (_currentTempF >= 79.0) TurnDeviceOff( HEATER );

	// Fan control
	if (_currentTempF >  79.5) TurnDeviceOn( FANS );
	if (_currentTempF <= 79.0) TurnDeviceOff( FANS );

	// Emergency situations...
	
	// Lights cause heat (fixture has its own day/night timer)
	if (_currentTempF <  79.5) TurnDeviceOn( LIGHTS );
	if (_currentTempF >= 80.0) TurnDeviceOff( LIGHTS );

	// Wave pumps cause some heat
	if (_currentTempF <  79.5) _allowWaves = true;
	if (_currentTempF >= 80.0) _allowWaves = false;
}

//--------------------------------------------------------------------------------
void UpdateATO(unsigned long t)
{
//  Serial.print("FLOATS RESERVIOR ");
//  if (_floatReservoir.IsDown()) Serial.print("DOWN    ");
//  else Serial.print("UP      ");
//  Serial.print("UPPER ");
//  if (_floatSumpHi.IsDown()) Serial.print("DOWN    ");
//  else Serial.print("UP      ");
//  Serial.print("LOWER ");
//  if (_floatSumpLow.IsDown()) Serial.println("DOWN    ");
//  else Serial.println("UP      ");
  
	// poll floats
	_floatSumpLow.Update(t);
	_floatSumpHi.Update(t);
	_floatReservoir.Update(t);
	
  if (_atoFault)
	{
		TurnDeviceOff(ATO);
		return;
	}

	if ( IsDeviceOn(ATO) )
	{
		if (_atoRunawayTimer.Done(t))
		{
			_atoFault = 1;
			TurnDeviceOff(ATO);
 			return;
		}
		if (!_floatSumpHi.IsDown())
    {
 			TurnDeviceOff(ATO);
    }
		return; 
	}
  
	if (!_reservoirEmpty)
	{
		if (_floatReservoir.IsDown())
		{
			_reservoirEmpty = true;
			_greenPulsar.Stop();
			_bluePulsar.Start(2000,0,t);
		}
	}
	else
	{
		if (_floatReservoir.IsUp())
		{
			_reservoirEmpty = false;
			_bluePulsar.Stop();
			_greenPulsar.Start(5000,220,t);
		}
	}

	if (_floatSumpLow.IsDown() && !_reservoirEmpty)
	{
		if (!_floatSumpHi.IsDown())
		{
			// todo: flash led or something, but allow it to recover itself
			return;
		}
		_atoRunawayTimer.Set(50UL*1000UL,t); //50 sec
		TurnDeviceOn(ATO);
 	}
};

//--------------------------------------------------------------------------------
void UpdateLCD(unsigned long t)
{   
	if (_LCDEnabled)
  {
		if (_LCDTimer.Done(t)) 
		{
			_LCDEnabled = false;
			lcd.noBacklight();
			return;
		}
		lcd.backlight();
    lcd.setCursor(0,0);
    lcd.print("Temp ");
    lcd.print(_currentTempF,1);
    lcd.print(" F ");
    if ( IsDeviceOn(HEATER) ) lcd.print("Heat");
    else if ( IsDeviceOn(FANS) ) lcd.print("Fan ");
    else lcd.print("    ");
    
    lcd.setCursor(0,1); // row 2
    
    if (_atoFault)
    {
      switch(_atoFault)
      {
        case 1: lcd.print("ATO Runaway!    "); break;
        case 2: lcd.print("Sump Float Error"); break;
      }
    }
    else
    {
      if (false && _feedingMode)
      {
        //Serial.println("Feeding mode on");
        lcd.print("Feeding.    ");
      }
      else
      {
        lcd.print("Wave ");
        if ( IsDeviceOn(WAVE_LEFT) ) lcd.print("On /");
        else lcd.print("Off/");
        if ( IsDeviceOn(WAVE_RIGHT) ) lcd.print("On ");
        else lcd.print("Off");
      }
    
			do 
			{
				if ( _reservoirEmpty ) 
				{
					lcd.print(" H2O");
					break;
				}
				if ( IsDeviceOn(ATO) ) 
				{
						lcd.print(" ATO");  
						break;
				}
				if (_feedingMode) 
				{
					lcd.print(" FDG");
					break;
				}
				lcd.print("    ");
			} while(false);
    }
    //Serial.println("Done updating LCD.");
  }
}

//--------------------------------------------------------------------------------
void UpdateTemperature(unsigned long t)
{
  if (_tempReady && _temperatureTimer.Done(t))
  {
    sensors.requestTemperatures(); // Send the command to get temperatures
    float tempC = sensors.getTempC(tankThermometer);
    _currentTempF = DallasTemperature::toFahrenheit(tempC); // Converts tempC to Fahrenheit
		delay(250); // allow processor current levels to recover
    UpdateHeating();
    _temperatureTimer.Set(30UL*1000UL, t);
  }
}

//--------------------------------------------------------------------------------
void YellowBtnPressed()
{
  if (digitalRead(BTN_YELLOW)==LOW)
  {
    detachInterrupt(4);
    _yellowBtnPressed = true;
  }
  if (digitalRead(BTN_YELLOW) == HIGH)
  {
    _yellowBtnError++;
  }
};

void OnYellowBtnPressed(unsigned long t)
{
  Serial.print("OnYellowBtnPressed()  On: ");
	static byte i = 0;

	if (!_LCDEnabled)
	{
		_LCDTimer.Set(30UL*1000UL,t);
		_LCDEnabled = true;
	}
	else
	{
		if (!_feedingMode)
		{
			_feedingMode = true;
			_feedingTimer.Set(15UL*60UL*1000UL,t); // 15min
		}	
	}
  
	// test code
	// switch(i%3)
	// {
	// case 0:
		// _LEDBlueValue = 255;
		// _LEDRedValue = _LEDOnValue;
		// Serial.println(LED_RED);
		// break;
	// case 1:
		// _LEDRedValue = 255;
		// _LEDGreenValue = _LEDOnValue;
		// Serial.println(LED_GREEN);
		// break;
	// case 2:
		// _LEDGreenValue = 255;
		// _LEDBlueValue = _LEDOnValue;
		// Serial.println(LED_BLUE);
		// break;
	// }
	// ++i;
}

//--------------------------------------------------------------------------------
void loop(void)
{   
  if (_wtdEnabled) wdt_reset(); // I'm still alive!
	
  unsigned long t = millis();
  
  if (_feedingMode && _feedingTimer.Done(t)) _feedingMode = false;

  //BITRelays(t);  
  UpdateTemperature(t); // this will delay the processor for a few seconds
	t = millis();
  UpdateWaves(t);
  UpdateATO(t);
  UpdateLCD(t);
  
  _redPulsar.Update(t);
  _greenPulsar.Update(t);
  _bluePulsar.Update(t);
	
  if (_yellowBtnError>0)
  {
    Serial.print("ISR errors! ");
    Serial.println(_yellowBtnError);
     _yellowBtnError = 0; 
  }
  
	// check and debounce yellow button
  if (_yellowBtnPressed)
  {
    Serial.println("Yellow button pressed.");
		_yellowBtnPressed = false;
		//digitalWrite(BTN_YELLOW,HIGH);
		if (_yellowBtnTimer.Done(t))
		{
			_yellowBtnTimer.Set(1000UL,t);
			OnYellowBtnPressed(t);
		}
  }
  else
  {
   if (_yellowBtnTimer.Done(t) && digitalRead(BTN_YELLOW)==HIGH )
   { 
     attachInterrupt(4, YellowBtnPressed, LOW);    
   }
  }
  
}
Link to comment
Share on other sites

Finally, Learn From My Fails:


Everyone has a plan until they meet Mr Line Noise:

Despite my novice efforts to use capacitors and pullup resistors to filter out spikes and floating wire effects, I was unable to fully eliminate line noise caused by the relay state changes. The noise makes it appear to the code logic that the yellow button has been pressed or a float switch has fallen. Without an oscilloscope I'm just stabbing in the dark. I retreated to software: the button needs to hold its state for a long fraction of a second before I call it pressed. Floats need to be in their up or down position for 3 seconds before I read them. Aka "debouncing".


I2C communication can lock up the Arduino:

I use I2C to drive the LCD. I recommend others go with serial to avoid this problem. I mitigate this risk by using pullup resistors on both ends of the I2C lines but this doesn't make it 100% robust. For a temporary solution I turn off the LCD after 30 seconds and you have to press the button to turn it back on. An ideal solution involves using an oscilloscope to determine the shape of the spikes and build a proper filter. Or shield my cables better. Eventually I want to add a separate "watchdog' that can reset a stuck Arduino. My temporary solution I feel is good enough to run the system while I'm out.


The RGB LED is way too bright and overdriven at max power:

You would use 100-200 ohm resistors to give the LED full power. After testing, I finally settled on 10k (!) resistors to dampen the light enough to make a nice pulsing color.


I overlooked my local Radio Shack for too long:

While many electronic parts are cheaper online, when it comes to the physical interfaces (LED, buttons, project boxes) you just can't beat your local Radio Shack. You can hold each part in your hand and make a better decision about what will fit inside a box and how nice a button or light might look. I wasted alot of money taking guesses on line.


Cabling was harder than I thought:

I found it tough to find cables with enough wires to carry all of my signals between boxes. I also found it hard to find out how to make the jacks; eventually I learned these things are called breakout boards. Anyway, carrying lots of signals between boxes is not a trivial design problem. Even though my system works, I still didn't use "twisted pair" technology correctly; if I had, I may have avoided some of the noise issues.



Well, that's all for now. I hope this helps others in their DIY projects.

Link to comment
Share on other sites

I am interested in doing a DIY controller could you post a schematic/ wire diagram plz if you get the time.

Thanks

or a link.

Since there are so many combinations of features you might go with, the best place to start an Arduino project is by reading the tutorial which will explain how to wire up basic sensors and send basic output signals.

http://arduino.cc/en/Tutorial/HomePage

Then you can find relay examples such as:

http://arduino-info.wikispaces.com/ArduinoPower

Honestly, I did not stray far from basic examples like these. The outlet box is closely based on this project, although I did not locate my Arduino inside the outlet box like he did:

http://www.instructables.com/id/Arduilay/?ALLSTEPS

After you decide on a few features and get to know the Arduino lingo, I'd be happy to help with the individual steps!

Link to comment
Share on other sites

two things i noticed in your code:

as safety features, you should add in a debounce-like feature to your temperature actions.

i.e., if your temp bounces over a set point for a millisecond, you dont want your lights/other devices cycling on and off. make it hold temp for 5-10 minutes before you take an action.

along that same vein, if your temp probe freaks out and reads way above or below your temperature range, you should have it throw and error and ignore actions. specificially, if your temps show greater than 95 or less than 65, you know that those values arent reality and shouldnt result in a taken action.

Link to comment
Share on other sites

Thanks, Victoly

The DS18B20 integrates temperature internally over a few seconds. Surprisingly, it blocks the Arduino loop while it takes the temperature. So I didn't think it needed debounce. Regarding temperature reading failures, I do have a failsafe in that the heater's on-board thermostat will cut off heat at 82 degrees even if the outlet is still on. I will still add an alert state (eg red LED) if the heater is commanded on for a very long period and if a bad temp reading ever shows up.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...