Subversion Repositories Arduino.arduino

Rev

Rev 37 | Blame | Compare with Previous | Last modification | View Log | Download

#include <TimedAction.h>

void ta_DS18B20temperature();
TimedAction readDS18 = TimedAction(2500,ta_DS18B20temperature);
void ta_waterlevel();
TimedAction readLVL4cm = TimedAction(5000,ta_waterlevel);
void ta_FlowSensor();
TimedAction readFlow = TimedAction(2000,ta_FlowSensor);
void ta_NTCtemperature();
TimedAction readNTC = TimedAction(5000,ta_NTCtemperature);

// Include the libraries we need
#include <OneWire.h>
#include <DallasTemperature.h>

// Define serial debugging: _SER_DEBUG is debugging on
#define _SER_DEBUG

// Modbus Registers Offsets (0-9999)
const short OFFSET_HREG = 0;

// Ethernet shield ENC28J60
//#include <EtherCard.h>
#include <Modbus.h>
#include <ModbusIP_ENC28J60.h>

// ModbusIP object
ModbusIP mb(2502);

// long representation in modbus format, two words 
union mb_long {
  unsigned long value;
  struct mb {
    word hword;
    word lword;
  };
  struct mb words;
};

struct ds18_sensor {
  // Data wire is plugged into port 3 on the Arduino
  byte one_wire_bus = 4;

  int resolution = 12; //resolution = 0.0625
  int index = 0; //sensor index
  // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
  //OneWire oneWire(one_wire_bus);
  OneWire *oneWire = new OneWire((int)one_wire_bus);

  // Pass our oneWire reference to Dallas Temperature. 
  //DallasTemperature sensors(&oneWire);
  DallasTemperature *sensors = new DallasTemperature(oneWire);
  float temperature_celsius = 0.0;
  word status = 0; //status woord, 0 = ok
};

ds18_sensor ds18;

struct lvl_sensor {
  
  byte power_pin  =  7;  //Sensor is powered with pin 7
  byte signal_pin = A3;  //Sensor AO pin to Arduino pin A0

  int level = 0;         //Variable to store the incomming data
  float level_avg = 0.0; //average over 10 samples
  int level10 = 0;       //level10 = 0; level < 460; low low
                         //level10 = 10; level < 522; low
                         //level10 = 20; level < 570; medium
                         //level10 = 30; level < 585; high
                         //level10 = 40; level > 586; max
                         //level10 = 40; level > 586; max
  word status = 0;       //status woord, 0 = ok
};

lvl_sensor lvl4cm;

struct flow_sensor {
  byte sensor_pin = 3;

  volatile unsigned long pulseCount = 0;  
  // The hall-effect flow sensor outputs approximately 4.5 pulses per second per
  // litre/minute of flow.
  //float calibrationFactor = 5.5; //plastic
  float calibrationFactor = 11.0;  //copper

  float flowRate = 0.0;

  float oneLitreTotal = 0.0;
  unsigned long totalLitres = 0;
  union mb_long mb_litres;  

  unsigned long oldTime = 0;
  word status = 0;       //status woord, 0 = ok
}; //flow_sensor

struct flow_sensor flow;

struct ntc_sensor {
  byte ntc_current_pin     = A1;
  byte ntc_analog_read_pin = A0;

  const double NTC_START_RESISTANCE = 60000.0;
  double ntc_resistance = NTC_START_RESISTANCE;
  float ntc_temperature_kelvin = 0.0;
  float ntc_temperature_celsius = 0.0;
  float ntc_temperature_fahrenheit = 0.0;
  union mb_long mb_resistance;
  word status = 0;       //status woord, 0 = ok
};

struct ntc_sensor ntc;
void charge_input_capacitor(unsigned long charge_time);

/*
 * The setup function. We only start the sensors here
 */
void setup(void)
{
  // start serial port
  #ifdef _SER_DEBUG
  Serial.begin(9600);
  //Serial.println("Dallas Temperature IC Control Library Demo");
  #endif
  
  // The media access control (ethernet hardware) address for the shield
  byte mac[] = { 0x52, 0x4c, 0x44, 0x6b, 0x30, 0x40 };
  // The IP address for the shield
  byte ip[] = { 192, 168, 2, 177 };

  //Config Modbus IP
  mb.config( mac, ip);

  // Add OFFSET_HREG register - Use addHreg() for Holding registers
  mb.addHreg( (word) 0, (word)(ds18.temperature_celsius * 10.0));   // temperature short * 10 value
  mb.addHreg( (word) 1);                                            // status word
  mb.addHreg( (word) 2, (word)(ds18.temperature_celsius * 100.0));  // temperature short * 100 value
  mb.addHreg( (word) 3);                                            // status word
  mb.addHreg( (word) 4, (word)(lvl4cm.level * 1.0));                // level sensor short * 1 value; resistence
  mb.addHreg( (word) 5);                                            // status word
  mb.addHreg( (word) 6, (word)(lvl4cm.level10 * 1.0));              // level sensor short * 1 value; level10
  mb.addHreg( (word) 7);                                            // status word
  mb.addHreg( (word) 8);                                            // flow sensor short * 10 value
  mb.addHreg( (word) 9);                                            // status word
  mb.addHreg( (word)10);                                            // fl sensor short * 100 value
  mb.addHreg( (word)11);                                            // status word
  mb.addHreg( (word)12);                                            // fl sensor long total high
  mb.addHreg( (word)13);                                            // fl sensor long total low
  mb.addHreg( (word)14);                                            // status word
  mb.addHreg( (word)15);                                            // flow sensor ntc resistance long hword
  mb.addHreg( (word)16);                                            // flow sensor ntc resistance long lword
  mb.addHreg( (word)17);                                            //status word
  mb.addHreg( (word)18); // flow sensor ntc kelvin short * 10 value
  mb.addHreg( (word)19);                                            //status word
  mb.addHreg( (word)20); // fl sensor ntc kelvin short * 100 value
  mb.addHreg( (word)21);                                            //status word
  mb.addHreg( (word)22); // flow sensor ntc celsius short * 10 value
  mb.addHreg( (word)23);                                            //status word
  mb.addHreg( (word)24); // fl sensor ntc celsius short * 100 value
  mb.addHreg( (word)25);                                            //status word

  // Start up the onewire library
  ds18.sensors->begin();
  ds18.sensors->setResolution(ds18.resolution); 

  //level sensor initialisation
  pinMode(lvl4cm.power_pin, OUTPUT);   // configure D7 pin as an OUTPUT
  digitalWrite(lvl4cm.power_pin, LOW); // turn the sensor OFF 

  // The Hall-effect sensor is connected to pin 2 which uses interrupt 0.
  // Configured to trigger on a FALLING state change (transition from HIGH
  // state to LOW state)
  pinMode(flow.sensor_pin, INPUT);
  digitalWrite(flow.sensor_pin, HIGH);
  attachInterrupt(digitalPinToInterrupt(flow.sensor_pin), pulseCounter, FALLING);

  // ntc sensor pins used
  pinMode(ntc.ntc_current_pin, OUTPUT);
  pinMode(ntc.ntc_analog_read_pin, INPUT);
}

/*
 * Main function, get and show the temperature
 */
int i = 0;
void loop(void)
{ 
  
  // write ds18 temperature * 10 as short in one holding register
  mb.Hreg( (word) 0, (word)(ds18.temperature_celsius * 10.0));
  mb.Hreg( (word) 1, ds18.status);
  mb.Hreg( (word) 2, (word)(ds18.temperature_celsius * 100.0));
  mb.Hreg( (word) 3, ds18.status);

  //write level and level10 as short in holding registers
  mb.Hreg( (word) 4, (word)(lvl4cm.level * 1.0));
  mb.Hreg( (word) 5, lvl4cm.status);
  mb.Hreg( (word) 6, (word)(lvl4cm.level10 * 1.0));
  mb.Hreg( (word) 7, lvl4cm.status);

  //write flow and flow-total as short and long in holding registers
  mb.Hreg( (word) 8, (word)(flow.flowRate * 10.0));
  mb.Hreg( (word) 9, flow.status);
  mb.Hreg( (word)10, (word)(flow.flowRate * 100.0));
  mb.Hreg( (word)11, flow.status);
  flow.mb_litres.value = (unsigned long)flow.totalLitres;
  mb.Hreg( (word)12, flow.mb_litres.words.lword);
  mb.Hreg( (word)13, flow.mb_litres.words.hword);
  mb.Hreg( (word)14, flow.status);

  //write level and level10 as short in holding registers
  ntc.mb_resistance.value = (unsigned long)ntc.ntc_resistance;
  mb.Hreg( (word)15, ntc.mb_resistance.words.lword);
  mb.Hreg( (word)16, ntc.mb_resistance.words.hword);
  mb.Hreg( (word)17, ntc.status);
  mb.Hreg( (word)18, (word)(ntc.ntc_temperature_kelvin * 10.0));
  mb.Hreg( (word)19, ntc.status);
  mb.Hreg( (word)20, (word)(ntc.ntc_temperature_kelvin * 100.0));
  mb.Hreg( (word)21, ntc.status);
  mb.Hreg( (word)22, (word)(ntc.ntc_temperature_celsius * 10.0));
  mb.Hreg( (word)23, ntc.status);
  mb.Hreg( (word)24, (word)(ntc.ntc_temperature_celsius * 100.0));
  mb.Hreg( (word)25, ntc.status);
  
  mb.task();

  //sensor timed actions
  readDS18.check();
  readLVL4cm.check();
  readFlow.check();
  readNTC.check();
}

void ta_DS18B20temperature() {
  
  // call sensors.requestTemperatures() to issue a global temperature 
  // request to all devices on the bus
  ds18.sensors->requestTemperatures(); // Send the command to get temperatures

  // We use the function ByIndex, and as an example get the temperature from the first sensor only.
  ds18.temperature_celsius = ds18.sensors->getTempCByIndex(ds18.index);

  ds18.status = (ds18.temperature_celsius == DEVICE_DISCONNECTED_C) ? 1 : 0;

  #ifdef _SER_DEBUG
  if(ds18.temperature_celsius == DEVICE_DISCONNECTED_C) {
    Serial.print("Error Could not read temperature data ");
  }
  Serial.print("Temperature:");
  Serial.println(ds18.temperature_celsius);
  #endif
}

void ta_waterlevel() {

  digitalWrite(lvl4cm.power_pin, HIGH);  // turn the sensor ON
  delay(10);                      // wait 10 milliseconds
  lvl4cm.level = analogRead(lvl4cm.signal_pin); // read the analog value from sensor
  digitalWrite(lvl4cm.power_pin, LOW);   // turn the sensor OFF

  //filter measurement, calculate mean over 10 samples
  lvl4cm.level_avg = ((9.0 * lvl4cm.level_avg) + ((float)lvl4cm.level)) / 10.0;
  int ilevel = round(lvl4cm.level_avg);
  int level10 = 100; //max level for domoticz switch
  if (ilevel < 635) level10 = 80;
  if (ilevel < 630) level10 = 60;
  if (ilevel < 620) level10 = 40;
  if (ilevel < 600) level10 = 20;
  if (ilevel < 560) level10 =  0;
  lvl4cm.level10 = level10;

  lvl4cm.status = 0; // reading input never fails

  #ifdef _SER_DEBUG
  Serial.print("Resistance:");
  Serial.print(lvl4cm.level);
  Serial.print(",Level10:");
  Serial.println(lvl4cm.level10);
  #endif
}

void ta_FlowSensor() {

  // Because this loop may not complete in exactly 1 second intervals we calculate
  // the number of milliseconds that have passed since the last execution and use
  // that to scale the output. We also apply the calibrationFactor to scale the output
  // based on the number of pulses per second per units of measure (litres/minute in
  // this case) coming from the sensor.
  unsigned long get_duration = millis() - flow.oldTime;
  flow.flowRate = (((1000.0 / get_duration) * flow.pulseCount) / flow.calibrationFactor) * 60.0;

  // Divide the flow rate in litres/minute by 60 to determine how many litres have
  // passed through the sensor in this 1 second interval, then multiply by 1000 to
  // convert to millilitres.
  //flow.flowMilliLitres = (unsigned int)((flow.flowRate * 1000.0) / 3600.0);
  
  // Add the millilitres passed in this second to the cumulative total
  flow.oneLitreTotal += ((flow.flowRate * (float)get_duration)/36.0E+5);
  if (flow.oneLitreTotal > 1.0) {

    unsigned long ul_temp = (unsigned long)flow.oneLitreTotal;
    flow.totalLitres += ul_temp;
    flow.oneLitreTotal -= (float)flow.oneLitreTotal;
  }
        
  // Reset the pulse counter so we can start incrementing again
  flow.pulseCount = 0;
  
  // Note the time this processing pass was executed. Note that because we've
  // disabled interrupts the millis() function won't actually be incrementing right
  // at this point, but it will still return the value it was set to just before
  // interrupts went away.
  flow.oldTime = millis();
  
  // Enable the interrupt again now that we've finished sending output
  attachInterrupt(digitalPinToInterrupt(flow.sensor_pin), pulseCounter, FALLING);

  flow.status = 0; // reading input never fails

  #ifdef _SER_DEBUG
    // Print the flow rate for this second in litres / minute
    Serial.print("Flow:");
    Serial.print(flow.flowRate);  // Print the integer part of the variableflowRate

    // Print the cumulative total of litres flowed since starting
    Serial.print(",Quantity:");        
    Serial.println(flow.totalLitres);
    //Serial.print("EIMSK:");
    //Serial.print(EIMSK);
    //Serial.print(", PCMSK2:");
    //Serial.println(PCMSK2);
  #endif
}

/*
 * Insterrupt Service Routine
 */
void pulseCounter()
{
  // Increment the pulse counter
  flow.pulseCount++;
}

void ta_NTCtemperature() {

  charge_input_capacitor( 50);
    
  int raw_thermistor_reading = read_thermistor_data();
  #ifdef _SER_DEBUG
    Serial.print("raw_thermistor_reading: ");
    Serial.println(raw_thermistor_reading);
  #endif

  double unfiltered_resistance = calculate_resistance_from_analog_read(raw_thermistor_reading);
    
  if (ntc.ntc_resistance == ntc.NTC_START_RESISTANCE)
    ntc.ntc_resistance = filter_resistance_reading(unfiltered_resistance);
  else
    ntc.ntc_resistance = ((4.0 * ntc.ntc_resistance) + filter_resistance_reading(unfiltered_resistance))/5.0;
  ntc.ntc_temperature_kelvin = calculate_temperature_from_resistance(ntc.ntc_resistance);
  ntc.ntc_temperature_celsius = ntc.ntc_temperature_kelvin - 273.15;
  ntc.ntc_temperature_fahrenheit = ((ntc.ntc_temperature_kelvin*9)/5.0) + 32.0;
  
  // reading input fails, temperature fails if it is out of range (room temperature)
  if (isnan(ntc.ntc_temperature_kelvin) || (raw_thermistor_reading >= 1023) || (raw_thermistor_reading <= 0)) ntc.status = 1;
  else ntc.status = 0;
    
  #ifdef _SER_DEBUG
    Serial.print("Resistance:");
    Serial.print(ntc.ntc_resistance);
    Serial.print(",Kelvin:");
    Serial.print(ntc.ntc_temperature_kelvin);
    Serial.print(",Celsius:");
    Serial.println(ntc.ntc_temperature_celsius);
  #endif
}

/*
 * NTC functions
 */
// This function charges up the ADC input capacitor
// and does not continue until it is charged
void charge_input_capacitor(unsigned long charge_time = 100) {
  
      // Start by charging up the capacitor on the input pin
    digitalWrite(ntc.ntc_current_pin, HIGH);
    
    // Wait 100 milliseconds for the input capacitor to fully charge.
    // Currently delay() is used as a placeholder.
    // For most applications we will want to use a non-blocking timer function.
    delay(charge_time);
} // End charge_input_capacitor function

// This function records and returns an analog reading.
// It also turns off the current pin once complete
int read_thermistor_data(){
    
    // Read analog data from charged capacitor.
    int raw_thermistor_reading = analogRead(ntc.ntc_analog_read_pin);
  
    // Turn off the thermistor current pin to minimize self-heating of temperature sensor
    digitalWrite(ntc.ntc_current_pin, LOW);
    
    return raw_thermistor_reading;
} // End read_thermistor_data function


/** 
 This function calculates the rough resistance of the thermistor but does not filter the results for more accuracy. 
 That is handled in another function.
 For the math here, the full scale range of the analog read is 0 to 1023, (1024 steps) because the arduino nano has a 10bit ADC.
 2^10 = 1024
 raw_thermistor_reading / (1023.0 - raw_thermistor_reading) calculates the proportion of the voltage across the thermistor in the voltage divider comparated to the voltage acrross the constant resistor in the voltage divider.
 Once the proportion of that voltage is known, we can calulate the resistance of the thermistor by multiplying that proportion by the resitance of the constant resistor in the voltage divider.
**/ 
double calculate_resistance_from_analog_read(int raw_thermistor_reading){
  
    // The resistance of the 10 kΩ resistor in the voltage divider is included here as a local variable.
    // If you have a more precise reading of the resistor you can change it here for more accuracy 
    double voltage_divider_resistor = 51900; //51100.0;
    
    // If there is a full scale reading (1023) there is an open circuit, and we end the function early and simply return 999999 to avoid dividing by 0
    if(raw_thermistor_reading >= 1023){
      return 999999.9;
    }
  
    // See function comment for more explanation of the math
    double unfiltered_resistance = voltage_divider_resistor * (
        raw_thermistor_reading / (1023.0 - raw_thermistor_reading)
                                                                 );                                                           
    return unfiltered_resistance;
} // End calculate_resistance_from_analog_read function


/**
 This function filters the resistance reading. Filtering gives better results because no measurement system is perfect.
 In this case, measuring the voltage of the thermistor absorbs some of it's current during the read process, and slightly alters the true voltage of the thermistor. 
 This function compensates that and returns resistance readings much closer to their true value.
 **/
double filter_resistance_reading(double unfiltered_resistance){

    // These compensation values are specific to the ADC of the Arduino Nano or Uno, to the resistance of the voltage divider, capacitance at the input, and wait time between measurements. 
    // If any of those parameters change, the values will likely have to be adjusted.
    double calibration_factor = -3.27487396E-07 * unfiltered_resistance +
        8.25744292E-03;
  
    double filtered_resistance = unfiltered_resistance * (1 + calibration_factor);
  
    return filtered_resistance;
} // end filter_resistance_reading function


/**
  This function uses the 4 term Steinhart-Hart equation to determine the temperature of a thermistor from its resistance.
  Go to https://www.northstarsensors.com/calculating-temperature-from-resistance
  for more information about the Steinhart-Hart equation
**/
float calculate_temperature_from_resistance(double thermistor_resistance){
    // These constant values are for a North Star Sensors, curve 44, 10 kΩ at 25 °C thermistor.
    // They are generated from 4 data points at 0, 25, 50, and 70 °C.
    // If you are measuring outside that range, use constants specialized with data points in the range you need.
    double SH_A_constant = 1.044305137076E-03;  //1.044305137076E-03
    double SH_B_constant = 1.6012306448E-04;    //1.6012306448E-04
    double SH_C_constant = 5.898824753308E-06;  //5.898824753308E-06
    double SH_D_constant = -8.948520131542E-08; //-8.948520131542E-08
  
    // In arduino log() calculates the natural logarithm sometimes written as ln, not log base 10
    double natural_log_of_resistance = log(thermistor_resistance);
  
    // pow() is a function which rases a number to its power.
    // For example x to the power of 2, x^2, is pow(x, 2)
    float thermistor_temperature_kelvin = 1.0 / ( SH_A_constant +
                                                  SH_B_constant * natural_log_of_resistance +
                                                  SH_C_constant * pow(natural_log_of_resistance, 2) +
                                                  SH_D_constant * pow(natural_log_of_resistance, 3)
                                              );
    return thermistor_temperature_kelvin;
} // end calculate_temperature_from_resistance function