Skip to content

Instantly share code, notes, and snippets.

@baltuonis
Last active December 19, 2015 11:09
Show Gist options
  • Save baltuonis/5945177 to your computer and use it in GitHub Desktop.
Save baltuonis/5945177 to your computer and use it in GitHub Desktop.
Farm ventilation control Arduino
// 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