Last active
December 19, 2015 11:09
-
-
Save baltuonis/5945177 to your computer and use it in GitHub Desktop.
Farm ventilation control
Arduino
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Farm ventilation control | |
// Rev. 2013-07-18 | |
// by Futureless (Arduino forum) | |
// Changelog: | |
// - 2013-07-18: | |
// Reduced fan loop delay time to 30sec | |
// Added delays between fan turn offs (supposedly to reduce EMI during turn off) | |
// Reduced moving average 15 > 7 | |
// - 2013-07-07: Initial production release | |
// Components used: | |
// * Arduino UNO Rev3 | |
// * 4 relays array | |
// * DHT21 temp & RH sensor | |
// * LCD keypad shield 16x2 | |
// CODE BEGINS | |
//////////////////////////// | |
#include <SCoop.h> // Scheduler | |
#include <EEPROM.h> | |
#include <EEPROMAnything.h> // EEPROM read/write helper | |
#include <DHT.h> // DHT sensor | |
#include <LiquidCrystal.h> // LCD controls | |
// DEBUG switch - if enable - outputs verbose info to Serial | |
#define debug true | |
//DHT ADA | |
#define DHTTYPE DHT21 // DHT 21 (AM2301) | |
#define DHTPIN 2 //Temperature sensor pin | |
DHT dht(DHTPIN, DHTTYPE); | |
// RELAY CONTROLS - relay ACTIVE on LOW! | |
#define RELAY_ON 0 | |
#define RELAY_OFF 1 | |
//FAN PINS | |
// DO NOT USE 13th PIN AS IT TEST-SWITCHES DURING ARDUINO BOOT | |
#define FAN1 10 | |
#define FAN2 11 | |
#define FAN3 12 | |
#define FAN4 3 | |
//LCD AND BUTTONS SECTION | |
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); | |
// define some values used by the panel and buttons | |
int lcd_key_state = -1; | |
int last_lcd_key_state = -1; // previous state of the button | |
int adc_key_in = 0; | |
int buttonState = 0; // current state of the button | |
#define btnRIGHT 0 | |
#define btnUP 1 | |
#define btnDOWN 2 | |
#define btnLEFT 3 | |
#define btnSELECT 4 | |
#define btnNONE 5 | |
// END OF LCD AND BUTTON SECTION | |
///////////////////////////////// | |
//////////////////////////////// | |
// CONFIGURATION CONSTANTS | |
/////////////////////////////// | |
#define max_target_temp 30.0 | |
#define min_target_temp 10.0 | |
#define target_temp_step 0.5 | |
#define default_target_temp 18.0 // IN CASE OF EEPROM FAILURE | |
#define turn_on_half_threshold 1.01 | |
#define turn_on_all_threshold 1.07 | |
#define turn_off_all_threshold 0.99 | |
#define loop_delay 50 //0.05 sec | |
const long fan_loop_delay = 30000L; // 30 sec | |
const int fan_turn_on_delay = 3000; | |
const int max_failed_readings = 20; | |
const int read_sensor_delay = 2000; // 1000 is too fast | |
const long reprint_screen_delay = 3600000L; // Characters on lcd get scrambled some times - reset every hour | |
// Smoothing | |
const int temp_num_readings = 7; | |
float temp_readings[temp_num_readings]; // the readings from the analog input | |
int temp_index = 0; // the index of the current reading | |
float temp_total = 0; // the running total | |
float avg_temp; // the average | |
int failed_readings = 0; // counter for failed readings (sensor do not respond or return out of range values) | |
// Initialize Temp and RH vars | |
float curr_temp, curr_rh; | |
float target_temp; | |
// 3 scheduled loops | |
defineTask(fan_controller); | |
defineTask(sensor_controller); | |
defineTask(reprint_screen); | |
void setup() { | |
if (debug) { | |
Serial.begin(9600); | |
debug_println("Fan control start"); | |
} | |
dht.begin(); | |
lcd.begin(16, 2); | |
lcd.setCursor(0,0); | |
// 1234567890123456 <-- LCD 16 chars wide | |
lcd.print("Ventiliacija"); | |
lcd.setCursor(0,1); | |
lcd.print("Rev. 2013-07-18"); | |
sleep(2000); | |
load_eeprom_data(); | |
mySCoop.start(); // Start scheduler | |
print_default_screen(); | |
initialize_relays(); //TURN OFF ALL RELAYS, SET PINS TO OUTPUT | |
for (int thisReading = 0; thisReading < temp_num_readings; thisReading++) | |
temp_readings[thisReading] = 0; // initialize all the readings to 0 | |
} | |
// MAIN LOOP | |
void loop() { | |
process_buttons(); | |
print_target_temp(target_temp); | |
print_avg_temp(avg_temp); | |
print_curr_rh(curr_rh); | |
print_fan_status(); | |
sleep(loop_delay); | |
} | |
// Reprints everything on screen from time to time | |
// because stuff on LCD sometimes gets corrupted | |
void reprint_screen::setup(){ | |
} | |
void reprint_screen::loop(){ | |
sleep(reprint_screen_delay); | |
debug_println("Reprinting screen"); | |
print_default_screen(); | |
print_target_temp(target_temp); | |
print_avg_temp(avg_temp); | |
print_curr_rh(curr_rh); | |
print_fan_status(); | |
} | |
// Fan control loop - so fans do not start and stop every 100ms | |
// reacts to avg_temp | |
// Fan turn on sequence 1 > 4 > 3 > 2 | |
// Fan layout in my farm: | |
// 4> <3 | |
// > < | |
// 2> <1 | |
void fan_controller::setup() { | |
} | |
void fan_controller::loop(){ | |
sleep(fan_loop_delay); // first line - do not start fan immediatelly when device starts | |
debug_println("Fan loop start"); | |
if ((avg_temp / turn_on_all_threshold) >= target_temp ) { | |
debug_println("Turn on all"); | |
turn_on(FAN1); | |
sleep(fan_turn_on_delay); | |
turn_on(FAN4); | |
sleep(fan_turn_on_delay); | |
turn_on(FAN3); | |
sleep(fan_turn_on_delay); | |
turn_on(FAN2); | |
} | |
else if ((avg_temp / turn_on_half_threshold) >= target_temp ) { | |
debug_println("Turn on half"); | |
turn_off(FAN1); | |
sleep(fan_turn_on_delay); | |
turn_off(FAN4); | |
sleep(fan_turn_on_delay); | |
turn_on(FAN3); | |
sleep(fan_turn_on_delay); | |
turn_on(FAN2); | |
} | |
else if ((avg_temp / turn_off_all_threshold) < target_temp ) { | |
// Delays on fan turn off - in order to avoid EMI | |
// Will need metal enclosure for this project anyway | |
debug_println("Turn off all"); | |
turn_off(FAN1); | |
sleep(fan_turn_on_delay); | |
turn_off(FAN2); | |
sleep(fan_turn_on_delay); | |
turn_off(FAN3); | |
sleep(fan_turn_on_delay); | |
turn_off(FAN4); | |
} | |
} | |
// Library Has its own 250ms delay - in order not to interrupt other processes (LCD, buttons, etc) create separate loop | |
void sensor_controller::setup() { | |
sleep(2500); | |
} | |
void sensor_controller::loop(){ | |
read_sensors(); | |
process_readings(); | |
if (avg_temp > 45.0) { | |
// turn on fire alarm! | |
} | |
sleep(read_sensor_delay); // Read sensor every 2 sec | |
} | |
///////////////////// | |
// LCD PRINT | |
///////////////////// | |
void print_avg_temp(float temp) { | |
lcd.setCursor(2,0); | |
lcd.print(temp,1); | |
lcd.setCursor(6,0); | |
lcd.print("C"); | |
} | |
void print_curr_rh(int rh) { | |
lcd.setCursor(13,0); | |
lcd.print(rh); | |
} | |
void print_target_temp(float tt){ | |
lcd.setCursor(2,1); | |
lcd.print(tt,1); | |
lcd.setCursor(6,1); | |
lcd.print("C"); | |
} | |
void print_default_screen(void){ | |
lcd.clear(); | |
lcd.setCursor(0,0); | |
lcd.print("T --.-C RH: --%"); | |
lcd.setCursor(0,1); | |
lcd.print("> --.-C 1 2 3 4"); | |
} | |
void print_fan_status(void){ | |
lcd.setCursor(9,1); | |
lcd.print(fan_state_char(read_state(FAN1))); | |
lcd.setCursor(11,1); | |
lcd.print(fan_state_char(read_state(FAN2))); | |
lcd.setCursor(13,1); | |
lcd.print(fan_state_char(read_state(FAN3))); | |
lcd.setCursor(15,1); | |
lcd.print(fan_state_char(read_state(FAN4))); | |
} | |
char* fan_state_char(boolean fan_status){ | |
if (fan_status) | |
return "+"; | |
else | |
return "-"; | |
} | |
//////////////////// | |
// SENSOR & TEMPERATURE OPERATIONS | |
/////////////////// | |
void add_to_target_temp(float i){ | |
if (is_valid_temp(target_temp + i)) { | |
target_temp += i; | |
EEPROM_writeAnything( 0, target_temp); | |
} | |
} | |
boolean is_valid_temp(float i){ | |
if ((i) <= max_target_temp && (target_temp + i) >= min_target_temp) | |
return true; | |
return false; | |
} | |
void process_readings(void) { | |
// subtract the last reading: | |
temp_total = temp_total - temp_readings[temp_index]; | |
temp_readings[temp_index] = curr_temp; // read from the sensor | |
temp_total = temp_total + temp_readings[temp_index]; // add the reading to the total: | |
temp_index = temp_index + 1; // advance to the next position in the array: | |
if (temp_index >= temp_num_readings) // if we're at the end of the array... | |
temp_index = 0; // ...wrap around to the beginning: | |
avg_temp = temp_total / temp_num_readings; | |
} | |
void read_sensors(void){ | |
curr_rh = dht.readHumidity(); | |
curr_temp = dht.readTemperature(); | |
if (debug) { | |
Serial.print("Curr. temp.: " ); | |
Serial.print( curr_temp, 1); | |
Serial.print("C \t"); | |
Serial.print("Avg. temp.: "); | |
Serial.print(avg_temp,1); | |
Serial.print("C \t RH: "); | |
Serial.print( curr_rh); | |
Serial.print("% \n"); | |
} | |
if (isnan(curr_temp) || isnan(curr_rh)) { | |
failed_readings += 1; | |
debug_println("Daviklio klaida NAN"); | |
} | |
else if(curr_temp <= -40.0) { | |
failed_readings += 1; | |
debug_println("Daviklio klaida -40 C"); | |
} | |
else if(curr_temp >= 80.0) { | |
failed_readings += 1; | |
debug_println("Daviklio klaida +80 C"); | |
} | |
else { // Readings are good, reseting counter | |
failed_readings = 0; | |
} | |
if (debug) { | |
Serial.print("Failed readings: "); | |
Serial.print(failed_readings); | |
Serial.print("\n"); | |
} | |
if (failed_readings >= max_failed_readings) { | |
fatal_error("Daviklio klaida", "Vald. isjungtas"); | |
} | |
} | |
/////////////////////// | |
// RELAY OPERATIONS | |
////////////////////// | |
void turn_on(int fan){ | |
digitalWrite(fan, RELAY_ON); | |
} | |
void turn_off(int fan){ | |
digitalWrite(fan, RELAY_OFF); | |
} | |
boolean read_state(int fan){ | |
return !digitalRead(fan); | |
} | |
void initialize_relays() { | |
//-------( Initialize Pins so relays are inactive at reset)---- | |
digitalWrite(FAN1, RELAY_OFF); | |
digitalWrite(FAN2, RELAY_OFF); | |
digitalWrite(FAN3, RELAY_OFF); | |
digitalWrite(FAN4, RELAY_OFF); | |
//---( THEN set pins as outputs )---- | |
pinMode(FAN1, OUTPUT); | |
pinMode(FAN2, OUTPUT); | |
pinMode(FAN3, OUTPUT); | |
pinMode(FAN4, OUTPUT); | |
} | |
///////////////////// | |
// LCD BUTTONS | |
//////////////////// | |
void process_buttons(void) { | |
lcd_key_state = read_LCD_buttons(); // read the buttons | |
if (lcd_key_state != last_lcd_key_state) { | |
switch (lcd_key_state) // depending on which button was pushed, we perform an action | |
{ | |
case btnUP: | |
add_to_target_temp( target_temp_step ); | |
break; | |
case btnDOWN: | |
add_to_target_temp( -target_temp_step ); | |
break; | |
} | |
} | |
last_lcd_key_state = lcd_key_state; | |
} | |
int read_LCD_buttons() | |
{ | |
adc_key_in = analogRead(0); // read the value from the sensor | |
if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result | |
if (adc_key_in < 50) return btnRIGHT; | |
if (adc_key_in < 200) return btnUP; | |
if (adc_key_in < 490) return btnDOWN; | |
if (adc_key_in < 800) return btnLEFT; | |
if (adc_key_in < 790) return btnSELECT; // Not working - shows 1023 always | |
return btnNONE; // when all others fail, return this... | |
} | |
///////////////////////// | |
// EEPROM | |
////////////////////// | |
void load_eeprom_data() { | |
float EEPROM_target_temp; | |
EEPROM_readAnything(0, EEPROM_target_temp ); | |
if (!isnan(EEPROM_target_temp) && is_valid_temp(EEPROM_target_temp)) | |
target_temp = EEPROM_target_temp; | |
else | |
target_temp = default_target_temp; // DEFAULT TEMP | |
} | |
// META | |
void debug_println(char* line) { | |
if (debug) | |
Serial.println(line); | |
} | |
void fatal_error(char* line1, char* line2){ | |
debug_println("FATAL ERROR"); | |
lcd.clear(); | |
lcd.home(); | |
lcd.print(line1); | |
lcd.setCursor(0,1); | |
lcd.print(line2); | |
initialize_relays(); // > TURN OFF ALL RELAYS | |
while(1); // -- Freezes the device (better restart and try again) | |
// DOES NOT WORK IN MY CASE - JUST CORRUPTS THE LCD | |
//delay(2*60*1000); | |
//software_Reset(); // REBOOT AFTER 2 MINS OF FAILED SENSOR | |
} | |
void software_Reset() { // Restarts program from beginning but does not reset the peripherals and registers | |
debug_println("SOFTWARE RESET"); | |
asm volatile (" jmp 0"); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment