Skip to content

Instantly share code, notes, and snippets.

@BlvckBytes
Last active April 16, 2025 14:31
Show Gist options
  • Select an option

  • Save BlvckBytes/1dd17f3b229ed16ba8876507a3a93559 to your computer and use it in GitHub Desktop.

Select an option

Save BlvckBytes/1dd17f3b229ed16ba8876507a3a93559 to your computer and use it in GitHub Desktop.
/*
Author: BlvckBytes <blvckbytes@gmail.com>
Created On: 05/18/2022
This small state machine manages controlling a pump's on/off cycles based on the parameters
the macros provide. The sensor is in essence a constant current source, which will operate at
a range of voltages (mine goes from around 8 to 32 volts) and will always drive the same current
through it's in-series load, which is linear to the pressure measured. In this case: 4mA = 0 bar
and 20mA = 10 bar.
When the pressure sinks below BAR_LO, the pump will activate, until the pressure stayed above BAR_HI
for STABLE_HI_S seconds of time.
Update: 04/16/2025
As to not have to crawl down into the well when the system's to be drained at its lowest point for
the pipes to not freeze up in winter, I installed a solenoid valve, which is now also controlled
by this little program: via a state-selection pushbutton.
*/
#include <Arduino.h>
#include <EEPROM.h>
// #define DEBUG
// I/O
#define PUMP_PIN 15
#define SENSOR_PIN A0
#define DRAIN_SOLENOID_PIN 5
#define PUSHBUTTON_PIN 4
// ADC parameters
#define SENSOR_SHUNT_R 161
#define ADC_MAX_VALUE 1023
#define ADC_VOLTAGE 3.3
// Sensor specific parameters
#define SENSOR_ZERO_A 0.004
#define SENSOR_MAX_A 0.020
#define SENSOR_ZERO_BAR 0
#define SENSOR_MAX_BAR 10
#define SENSOR_PRECISION 1000
// Calculated sensor parameters
#define SENSOR_ZERO_V (SENSOR_SHUNT_R * SENSOR_ZERO_A)
#define SENSOR_ZERO_ADC (SENSOR_ZERO_V / ADC_VOLTAGE * ADC_MAX_VALUE)
#define SENSOR_MAX_V (SENSOR_SHUNT_R * SENSOR_MAX_A)
#define SENSOR_MAX_ADC (SENSOR_MAX_V / ADC_VOLTAGE * ADC_MAX_VALUE)
// State machine parameters
#define BAR_LO 1.20
#define BAR_HI 4.75
#define STABLE_HI_S 2
// Push button mode states
#define MODE_PUMP_ON 0
#define MODE_DRAIN_ON 1
#define MODE_ALL_OFF 2
#define MODE_TOTAL_COUNT 3
#define PUSHBUTTON_DEBOUNCE_MS 1000
// State variables
static uint32_t last_poll;
static long hi_int_begin;
static bool pump_state;
static uint8_t current_mode;
static uint32_t last_pushbutton_press;
void load_current_mode()
{
EEPROM.begin(1);
current_mode = EEPROM.read(0);
if (current_mode >= MODE_TOTAL_COUNT)
current_mode = 0;
EEPROM.end();
#ifdef DEBUG
Serial.printf("Loaded mode %" PRIu8 "\n", current_mode);
#endif
}
void store_current_mode()
{
EEPROM.begin(1);
EEPROM.write(0, current_mode);
EEPROM.commit();
EEPROM.end();
#ifdef DEBUG
Serial.printf("Stored mode %" PRIu8 "\n", current_mode);
#endif
}
void setup()
{
pinMode(SENSOR_PIN, INPUT);
pinMode(PUMP_PIN, OUTPUT);
digitalWrite(PUMP_PIN, LOW);
pinMode(PUSHBUTTON_PIN, INPUT_PULLUP);
pinMode(DRAIN_SOLENOID_PIN, OUTPUT);
digitalWrite(DRAIN_SOLENOID_PIN, LOW);
load_current_mode();
#ifdef DEBUG
Serial.begin(115200);
#endif
}
void pump_control_loop(uint32_t current_millis)
{
// Keep the output in sync with the local state
digitalWrite(PUMP_PIN, pump_state);
int raw_read = analogRead(SENSOR_PIN);
// Read the ADC value (in bars) and scale it by the precision value
long adc_bar_scaled = map(
raw_read,
SENSOR_ZERO_ADC, SENSOR_MAX_ADC,
SENSOR_ZERO_BAR * SENSOR_PRECISION, SENSOR_MAX_BAR * SENSOR_PRECISION
);
// Constrain towards zero
adc_bar_scaled = adc_bar_scaled < 0 ? 0 : adc_bar_scaled;
// Undo scaling to receive the proper floating point value
float adc_bar = adc_bar_scaled / (float) SENSOR_PRECISION;
// HI level reached or exceeded and the pump is actually on
if (adc_bar >= BAR_HI)
{
if (pump_state)
{
// Interval not active, begin
if (hi_int_begin == 0)
hi_int_begin = current_millis;
// Interval reached stable duration
if (current_millis - hi_int_begin >= STABLE_HI_S * 1000)
{
// Cancel the interval and turn off the pump
hi_int_begin = 0;
pump_state = false;
#ifdef DEBUG
Serial.println("Turning pump OFF");
#endif
}
}
}
// Cancel the interval again when falling below HI
else
hi_int_begin = 0;
// LO level reached or subceeded
if (adc_bar <= BAR_LO)
{
hi_int_begin = 0;
if (!pump_state) {
pump_state = true;
#ifdef DEBUG
Serial.println("Turning pump ON");
#endif
}
}
#ifdef DEBUG
Serial.print("bar=");
Serial.print(adc_bar);
Serial.print("/");
Serial.print(SENSOR_MAX_BAR);
Serial.print(", raw=");
Serial.print("");
Serial.println(raw_read);
#endif
last_poll = current_millis;
}
void loop()
{
uint32_t current_millis = millis();
if (digitalRead(PUSHBUTTON_PIN) == LOW)
{
if (current_millis - last_pushbutton_press >= PUSHBUTTON_DEBOUNCE_MS)
{
if (++current_mode >= MODE_TOTAL_COUNT)
current_mode = 0;
last_pushbutton_press = current_millis;
store_current_mode();
}
}
if (current_mode == MODE_DRAIN_ON)
{
digitalWrite(PUMP_PIN, LOW);
digitalWrite(DRAIN_SOLENOID_PIN, HIGH);
return;
}
if (current_mode == MODE_PUMP_ON)
{
digitalWrite(DRAIN_SOLENOID_PIN, LOW);
pump_control_loop(current_millis);
return;
}
// All off
digitalWrite(PUMP_PIN, LOW);
digitalWrite(DRAIN_SOLENOID_PIN, LOW);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment