April 2019

Feather Watermaker 12VDC Automatic Freshwater Flush


The watermaker on our boat has a 120V freshwater flush system.   This system causes fresh water from the boat's water tank to rinse the membrane automatically per week.   It works well when the boat is docked, but it won't work when the boat is on a mooring for an extended period.  

The functional requirements of this system are:

  • Run off of the boat's battery bank (12 VDC)
  • Automatically flush the watermembrane once per week for 1.5 minutes
  • An on/off switch enables/disables the timer (allows pump to be used normally).   The switch state confirmed by an LED or other means.  
  • It must control power to the freshwater pump so that the pump is only on when the freshwater flush is happening.   If it somehow it fails, then the freshwater pump must be left in the powered state.  
  • A counter displays the count of freshwater flush events
  • A LED or other indicator to show when the freshwater flush is underway

 

Components

Brass Liquid Solenoid Valve - 12V - 1/2 NPS   1/2" NPS connection.   140 psi max.   -5°C - 80°C   3A @ 12VDC   rubber gasket made of NBR (Nitrile Rubber)  

Feather M0 with AF 128x32 mono OLED FeatherWing

Click on image below to see the wiring diagram

The bezel was 3D printed by Marando Industries Inc


/*

Adfruit Feather M0 + 128x32 mono OLED FeatherWing

Adafruit Feather M0 Basic Proto - ATSAMD21 Cortex M0
The ATSAMD21G18 ARM Cortex M0 processor is the same as the new Arduino Zero.
https://www.adafruit.com/product/2772
https://learn.adafruit.com/adafruit-feather-m0-basic-proto?view=all
https://cdn-learn.adafruit.com/assets/assets/000/046/244/original/adafruit_products_Feather_M0_Basic_Proto_v2.2-1.png?1504885373
https://learn.adafruit.com/adafruit-feather-m0-basic-proto/adapting-sketches-to-m0

Pins:

GND   ground
USB   You can supply power to the M0 directly to this pin when the USB connector is not used.
EN   connect to ground to disable the 3.3V regulator
3V   output from 3.3V regulator.   500 mA peak.
#0   Serial1 hardware UART Rx
#1   Serial1 harsware UART Tx
#5   GPIO #5 (OLED FeatherWing button)
#6   GPIO #6 (OLED FeatherWing button)
#9   GPIO #9 & A7 for LiPi voltage divider. (OLED FeatherWing button)
#10,#11,#12    GPIO #10, #11, #12
#13   red LED next to the USB jack
A0   Analog input & true output.   0 to 3.3V
#A1 to A5   analog input (12-bit) or digital I/O
ARef   analog reference pin @ 3.3V
#20   I2C SDA.   Use 2.2K-10K pullup
#21   I2C SCL.   Use 2.2K-10K pullup
#24, #23, #22   SPI SCK/MOSI/MISO
RST   connect to ground to reset AVR and launch bootloader manually




Adafruit 128x32 mono OLED FeatherWing
https://www.adafruit.com/product/2900

28x32 monochrome OLED + three user buttons.
Display area: ~25.8mm / ~1.0"

The OLED I2C address is 0x3C and cannot be changed

https://learn.adafruit.com/adafruit-oled-featherwing?view=all
https://learn.adafruit.com/adafruit-gfx-graphics-library?view=all
http://henrysbench.capnfatz.com/henrys-bench/arduino-adafruit-gfx-library-user-guide/
http://engineeringnotes.blogspot.com/2013/07/using-ssd1306-textgraphics-display.html

This sketch demonstrates writing text to the OLED
using the Adafruit GFX Graphics Library.  It is derived
from the Adafruit example at: https://learn.adafruit.com/adafruit-oled-featherwing?view=all

*** WARNING ***
    Using Serial causes the OLED to not be reset when the M0 is initialized.  
    If you use the serial monitor, the OLED will work properly.
    Do not plan on using serial with the OLED in a deployed configuration.  

*/

//////////////////////////////////////////////////////////////////////////////
#include 
#include 
#include 
#include 

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// NOTE: The FeatherWing OLED has an auto-reset chip, so you don't need to declare a reset pin.
// set OLED_RESET to -1 or dont pass it in, the reset button is hard-coded to the OLED reset
Adafruit_SSD1306 display(128, 32, &Wire);
//////////////////////////////////////////////////////////////////////////////
// M0  (Feather M0)
const byte pin_LED = 13;
const byte pin_BATT = A7;
const byte pin_Button_A = 9;
const byte pin_Button_B = 6;
const byte pin_Button_C = 5;
//////////////////////////////////////////////////////////////////////////////
//  Project specific I/O
const byte pin_pump = 10; 
const byte pin_solenoid = 11; // valve is normally closed
const byte pin_switch = 12;
byte lastSWstate = LOW;
/////////////////////////////////////////////////////////////////
//  Controls how long a digital output is on/off.
unsigned long timerA = 0;           
// works if intervalOff > intervalOn or intervalOff < intervalOn
//const unsigned long intervalOff = 15000;  
//const unsigned long intervalOn = 5000;  
// Turn on FWF for 1.5 minutes every 7 days
const unsigned long intervalOff = 604800000;  
const unsigned long intervalOn = 90000;  
byte myDigitalOutputState = LOW;
unsigned long FWF = 0;
/////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//  ADC constants
// Constants below used to conver raw ADC values to mV.
// 10 bit, 0 to 1023, or 1024 steps
// 12 bit, 0 to 4095, or 4096 steps
const float FSadc = 1024.0;
const float FSmV = 3300.0;
//////////////////////////////////////////////////////////////////////////////
//  lipoly battery
//  The pin is defined earlier as pin_BAT
// dAdj_mV is multiplied by the reading at pinADC in mV to get the input voltage to a
// voltage divider or other component. 
// Shunt cal voltage divider; R1 = 10k ohms, R2 = 2.2 k ohms; 
const float dAdj_mVbat = 2.0;
// 300000 ms = 5 min
// 60000 ms = 1 min
// 10000 ms = 10 sec = 0.1 Hz 
// 1000 ms = 1 sec = 1 Hz
// 100 ms = 0.1 sec = 10 Hz
// 10 ms = 0.01 sec = 100 Hz
const unsigned long batInterval = 50000;  
unsigned long lastBat = 0;  // timer
//////////////////////////////////////////////////////////////////////////////
//  Analog input
const byte pinADC = A0;
// dAdj_mV is multiplied by the reading at pinADC in mV to get the input voltage to a
// voltage divider or other component. 
// Shunt cal voltage divider; R1 = 10k ohms, R2 = 2.2 k ohms; 
const float dAdj_mV = (10000.0+2200.0)/2200.0;
// 3.3 V full scale (3300 mV); 0 to 1023 steps (1024 total steps) for 10 bit
// 187 ADC reading * (3300/1024) = 600 mV
int iSamples = 0;
// It takes ~ 1 sec to collect 1000 samples. 
const int iMaxSamples = 1000;
float dCumAvg = 0.0;
float dLastAvg = 0.0;
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Show serial messages when DEBUG = true, otherwise minimize them.
// WARNING:   I experienced the Feather M0 waiting for the serial monitor to be
//            loaded from the IDE before the sketch would continue to run.  
//            For this reason, I am wrapping serial output around a DEBUG check.
//
//    Using Serial causes the OLED to not be reset when the M0 is initialized.  
//    If you use the serial monitor, the OLED will work properly.
//    Do not plan on using serial with the OLED in a deployed configuration.  
#define DEBUG false
//////////////////////////////////////////////////////////////////////////////


void setup() {
  digitalWrite(pin_LED, LOW);
  #if DEBUG
    Serial.begin(9600);
    while (!Serial) {
      digitalWrite(pin_LED, HIGH);
      delay(1);
    }
    Serial.println("Serial ready");
    digitalWrite(pin_LED, LOW);
  #endif

  #if DEBUG
    Serial.println("Adafruit 128x32 mono OLED FeatherWing text command demo");
  #endif
  
  // by default, generate the high voltage from the 3.3v line internally.
  // initialize with the I2C addr 0x3C (for the 128x32)
  delay(100);  // Give the OLED driver time to bring it's internal charge up to voltage.
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    #if DEBUG
      Serial.println("ERROR - AF 128x32 mono OLED FeatherWing");
    #endif
    digitalWrite(pin_LED, HIGH);
  }
  
  // Clear the display buffer.
  display.clearDisplay();
  display.display();

  // Configure the three buttons on the OLED FeatherWing
  // Button A is DIO9 (note this is also used for the battery voltage divider.  
  // *** WARNING:  Must disable the pullup when you analog read to check the battery voltage !!!
  pinMode(pin_Button_A, INPUT_PULLUP);  // On BUTTON_A and used by battery monitor on pin_BATT
  delay(10);
  //pinMode(pin_Button_B, INPUT_PULLUP); // Button B has a 100k pullup on it from the OLED so it will work with the ESP8266.  Therefore do NOT set the internal pullup. 
  delay(10);
  pinMode(pin_Button_C, INPUT_PULLUP);
  delay(10);

  //  Configure project specific I/O
  pinMode(pin_pump, OUTPUT);
  delay(10);
  pinMode(pin_solenoid, OUTPUT);
  delay(10);
  pinMode(pin_switch, INPUT);
  lastSWstate = LOW;
  delay(10);

  // AF 128x32 OLED FeatherWing
  //Note that for .setTextSize(1), a character is 6 pixels wide by 8 pixels high.
  //For a 128x32 OLED, 128/6 = 12 characters; 32/8 = 4 characters
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setTextWrap(false);
  //display.setCursor(horizontal position, vertical position);
  //For .setTextSize(1), rows are: 0, 8, 16, 24
  //                     21 characters horizontally for .setTextSize(1)
  
  display.setTextSize(2);
  display.setCursor(0,0);
  //display.print("0123456789");
  display.print("Freshwater");
  display.setCursor(0,16);
  display.print("Flush Timr");
  display.display(); 
  delay(5000);
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0,0);
  //display.print("012345678901234567890");
  display.print("1 Flush Every Week");

  display.setCursor(0,16);
  display.print("Flush for 1.5 min");

  display.display();
  delay(5000);

  lastSWstate = digitalRead(pin_switch);
  delay(1);
   
  #if DEBUG
    Serial.println("Setup complete");
  #endif
  blinkLED(pin_LED);
} // setup()


void loop() {

  //pin_switch lastSWstate
  byte pinDIOstate = digitalRead(pin_switch); 
  delay(1);
  if (pinDIOstate != lastSWstate) {
    if (pinDIOstate == HIGH) {
      #if DEBUG
        Serial.println("pinDIOstate == HIGH");
      #endif
      // do an immediate freshwater flush
      FWF += 1;
      digitalWrite(pin_pump, HIGH);
      delay(1);
      digitalWrite(pin_solenoid, HIGH);
      yield();
      display.clearDisplay();
      display.setTextSize(1);
      display.setCursor(0,0);
      display.print("Immediate FWF");     
      display.setCursor(0,24);
      display.print(millis()/1000);      
      display.setCursor(0,8);
      display.print("water pump on");      
      display.setCursor(0,16);
      display.print("solenoid on (FWF)");
      printMillis();      
      printFWF();      
      display.display();     
      yield();
      delay(intervalOn);
      yield();
      timerA = millis();      
      if (lastSWstate == LOW) {
        // switch = OFF (timer disabled)
        // Water pump should be ON, solenoid should be OFF
        myDigitalOutputState = LOW;
        digitalWrite(pin_pump, HIGH);
        digitalWrite(pin_solenoid, myDigitalOutputState);
      } else{    
        // switch = ON (timer enabled)
        // Water pump & solenoid OFF unless turned on by timer (myDigitalOutputState)
        digitalWrite(pin_pump, myDigitalOutputState);
        digitalWrite(pin_solenoid, myDigitalOutputState);
      }
    } 
    lastSWstate = pinDIOstate;
  }
  
  // Digital output on/off timer
  // if millis() or timer wraps around, we'll just reset it
  if (timerA > millis())  timerA = millis();
  if (millis() - timerA > intervalOff + intervalOn) {
    // this is the longer duration
    timerA = millis();
    myDigitalOutputState = LOW;
  } else if (millis() - timerA > intervalOff) {
    if (myDigitalOutputState == LOW && lastSWstate == HIGH) FWF += 1;
    myDigitalOutputState = HIGH;
  } 
  // Only implement the timer changes to myDigitalOutputState if pin_switch is on.
  if (lastSWstate == LOW) {
    // switch = OFF (timer disabled)
    // Water pump should be ON, solenoid should be OFF
    myDigitalOutputState = LOW;
    digitalWrite(pin_pump, HIGH);
    delay(1);
    digitalWrite(pin_solenoid, myDigitalOutputState);
    yield();
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("timer disabled");      
    display.setCursor(0,8);
    display.print("water pump on");      
    display.setCursor(0,16);
    display.print("solenoid off");
    printMillis();    
    printFWF();      
    display.display();
  } else {
    // switch = ON (timer enabled)
    // Water pump & solenoid OFF unless turned on by timer (myDigitalOutputState)
    digitalWrite(pin_pump, myDigitalOutputState);
    delay(1);
    digitalWrite(pin_solenoid, myDigitalOutputState);
    yield();
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("timer enabled");     
    printMillis();    
    printFWF();     
    if (myDigitalOutputState == HIGH) {
      display.setCursor(0,8);
      display.print("water pump on");      
      display.setCursor(0,16);
      display.print("solenoid on (FWF)");
    } else {
      display.setCursor(0,8);
      display.print("water pump off");      
      display.setCursor(0,16);
      display.print("solenoid off");
    }
    display.display();
    yield();
  }

  if (digitalRead(pin_Button_A) != HIGH) {
    // do an immediate freshwater flush
    FWF += 1;
    digitalWrite(pin_pump, HIGH);
    digitalWrite(pin_solenoid, HIGH);
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0,0);
    display.print("Immediate FWF");     
    display.setCursor(0,8);
    display.print("water pump on");      
    display.setCursor(0,16);
    display.print("solenoid on (FWF)");
    printMillis();  
    printFWF();      
    display.display();   
    yield();  
    delay(intervalOn);
    yield();
    timerA = millis();  
    if (lastSWstate == LOW) {
      // switch = OFF (timer disabled)
      // Water pump should be ON, solenoid should be OFF
      myDigitalOutputState = LOW;
      digitalWrite(pin_pump, HIGH);
      digitalWrite(pin_solenoid, myDigitalOutputState);
    } else{    
      // switch = ON (timer enabled)
      // Water pump & solenoid OFF unless turned on by timer (myDigitalOutputState)
      digitalWrite(pin_pump, myDigitalOutputState);
      digitalWrite(pin_solenoid, myDigitalOutputState);
    }
    #if DEBUG
      Serial.println("Button A");
    #endif
  }
  if (digitalRead(pin_Button_B) != HIGH) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0,8);
    display.print("Button B");  
    #if DEBUG
      Serial.println("Button B");
    #endif
   }
  if (digitalRead(pin_Button_C) != HIGH) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0,16);
    display.print("Button C");
    #if DEBUG
      Serial.println("Button C");
    #endif
  } 
  
  // The yield() function allows ESP8266 microcontroller to run a 
  // number of utility functions in the background, without causing 
  // the ESP8266 to crash or reset. Include it within any 
  // while() + digitalRead() and other loops;
  //yield();
} // loop()

void printMillis(){
  unsigned int secs = 0;
  unsigned int mins = 0;
  unsigned int hours = 0;
  unsigned int days = 0;
  secs = millis()/1000;
  
  if (secs >= 86400) {
    days = secs/86400;
    secs = secs - days * 86400;
  }
  
  if (secs >= 3600) {
    hours = secs/3600;
    secs = secs - hours * 3600;
  }
  
  if (secs >= 60) {
    mins = secs/60;
    secs = secs - mins * 60;
  }
  
  display.setCursor(0,24);
  display.print(days);    
  display.setCursor(16,24);
  display.print("d");    
  
  display.setCursor(32,24);
  display.print(hours);    
  display.setCursor(48,24);
  display.print("h");    
  
  display.setCursor(64,24);
  display.print(mins);    
  display.setCursor(80,24);
  display.print("m");    
  
  display.setCursor(96,24);
  display.print(secs);    
  display.setCursor(112,24);
  display.print("s");    
} //printMillis()

void printFWF(){
  //80, 104
  display.setCursor(96,0);
  display.print("FWF");      
  display.setCursor(116,0);
  display.print(FWF);      
} //printFWF()


void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
} //blinkLED()

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.