Feedback/Improvements to my Project - Car Intercooler Sprayer

Hi All not sure where to put this I ahve browsed the categories and this seemed the best place for it. I have finished (well mostly finished I'm waiting on other parts) the software side of my first project. Everything seems to work as it should on the bench. I was hoping some more experienced users could look at it and tell me how to improve the really naff bits. I'm not that happy with the menu 'system' but it is functional and my initial plan didn;t include a menu!

In short my project controls an Intercooler Sprayer for my car, at high temperature it will spray water on the Intercooler(a radiator) to aid in cooling air going into the engine. I have ordered a pressure(Boost) sensor from China and will be adding that in as another criteria to build the strategy when it arrives.

Only thing I am functionally not happy with is the encoder often clicks multiple intervals. Have tried adding delays and increasing the delay for bouncing but it does not seem to make much difference.

Inputs
Button to enable the sprayer
Temp sensor (IAT Intake Air Temperature)
Level Switch from the water tank (closed is empty)
Encoder
Output
Pump 5v to relay

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

const int tempPin = 0; //temp sensor
const int PinCLK = 2; // Generating interrupts using CLK signal
const int PinDT = 3;  // Reading DT signal
const int PinSW = 4;  // Reading Push Button switch
const int buttonPin = 5; //on/off button
const int levelPin = 6;//tank level
const int pumpPin = 13;// pump trigger
//eeprom addresses
const int startTempAdd = 0;
const int sprayTimeAdd = 4;
const int delayTimeAdd = 8;
LiquidCrystal_I2C lcd(0x27, 16, 2);


volatile boolean TurnDetected;  // need volatile for Interrupts
volatile boolean rotationdirection;  // CW or CCW rotation
float tempC;

float startTemp = 25.0; //temp to start spraying
int sprayTime = 2000; //length of spray
int delayTime = 10000; //delay between sprays

//Menu variables
int startTempMenu = 0;
float sprayTimeMenu = 0.0;
float delayTimeMenu = 0.0;

//eeprom variables
int startTempEep = 0;
int sprayTimeEep = 0;
int delayTimeEep = 0;

int menuDelayTime = 20000; // time before droping out of the menu
//flags
boolean sprayFlag = 0;
boolean saveFlag = 0;
boolean emptyTankFlag = 0;
//time counters
unsigned long previousInput = 0; //Last input from encoder
unsigned long previousSpray = 0; //Last Spray
unsigned long previousScreenRefresh = 0; // Last Screen Refresh
unsigned long previousEmptyTank = 0; // Last Time Empty Tank Set


// Interrupt routine runs if CLK goes from HIGH to LOW
void isr ()  {
  delay(4);  // delay for Debouncing
  if (digitalRead(PinCLK)) {
    rotationdirection = digitalRead(PinDT);
  } else {
    rotationdirection = !digitalRead(PinDT);
  }
  TurnDetected = true;
  previousInput = millis();
}

void setup()
{
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.backlight();
  lcd.clear();
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(levelPin, INPUT_PULLUP);
  pinMode(pumpPin, OUTPUT);

  pinMode(PinCLK, INPUT);
  pinMode(PinDT, INPUT);
  pinMode(PinSW, INPUT);
  digitalWrite(PinSW, HIGH);
  attachInterrupt (0, isr, FALLING);
  //get values from eeprom check if they are sensible and if so apply them
  EEPROM.get(startTempAdd,startTempEep);
  EEPROM.get(sprayTimeAdd,sprayTimeEep);
  EEPROM.get(delayTimeAdd,delayTimeEep);

  if (startTempEep >= 20 && startTempEep <= 90)
  {
    startTemp = startTempEep;
  }

  if (sprayTimeEep >= 100 && sprayTimeEep <= 5000)
  {
    sprayTime = sprayTimeEep;
  }
  if (delayTimeEep >= 1000 && delayTimeEep <= 99000)
  {
    delayTime = delayTimeEep;
  }
  startTempMenu = startTemp;
  sprayTimeMenu = sprayTime; 
  sprayTimeMenu = sprayTimeMenu/ 1000;
  delayTimeMenu = delayTime; 
  delayTimeMenu = delayTimeMenu / 1000;

}

void loop()
{
  //enter menu
  if (digitalRead(PinSW) == LOW) {
    previousInput = millis();
    menuMain();
  }
  //get temp reading
  int tempReading = analogRead(tempPin);
  tempC = tempCalc(tempReading);
  tempPrint();

  //checks if the tank is empty
  if (digitalRead(levelPin) == LOW) {
    emptyTank();
  } else {
    if (emptyTankFlag && millis() - previousEmptyTank > 20000) {
      clearLcdRow(1);
      emptyTankFlag = 0;
    }
  }
  //try to spray
  sprayStrat();
}

void sprayStrat() {
  if (!emptyTankFlag && digitalRead(buttonPin) == LOW && millis() - previousSpray >= delayTime) {
    if (tempC > startTemp)
    {
      digitalWrite(pumpPin, HIGH);
      previousSpray = millis();
      sprayFlag = 1;
      lcd.setCursor(0, 1);
      lcd.print("Spraying");
    }
  }

  if (sprayFlag && millis() - previousSpray >= sprayTime) {
    digitalWrite(pumpPin, LOW);
    clearLcdRow(1);
    sprayFlag = 0;
  }
}

void emptyTank()
{
  digitalWrite(pumpPin, LOW);
  lcd.setCursor(0, 1);
  lcd.print("Tank Empty");
  emptyTankFlag = 1;
  previousEmptyTank = millis();
}

//temp sensor interpretation
float tempCalc(int tempReading) {
  double tempK = log(10000.0 * ((1024.0 / tempReading - 1)));
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK )) * tempK );
  float tempC = tempK - 273.15;
  return tempC;
}

void clearLcdRow(int row) {
  lcd.setCursor(0, row);
  lcd.print("                ");
}

void tempPrint() {
  if (millis() - previousScreenRefresh >= 500) {
    lcd.setCursor(0, 0);
    lcd.print("IAT       C");
    lcd.setCursor(5, 0);
    lcd.print(tempC);
    previousScreenRefresh = millis();
  }
}
//
void menuMain() {
  menuTemp();
  menuSpray();
  menuDelay();
  menuSave();
  delay(500);
}
void menuTemp() {
  lcd.clear();
  delay(500);
  while (millis() - previousInput < menuDelayTime) {
    menuTempPrint();
    if (TurnDetected)  {
      if (rotationdirection) {
        if (startTempMenu <= 90)//max value
        {
          startTempMenu = startTempMenu + 1;
        }
      }
      else {
        if (startTempMenu > 10)//minvalue
        {
          startTempMenu = startTempMenu - 1;
        }
      }

      TurnDetected = false;
      delay(100);
    }
    if (digitalRead(PinSW) == LOW) {
      break;
    }
  }
  lcd.clear();
}

void menuTempPrint() {
  if (millis() - previousScreenRefresh >= 500) {
    lcd.setCursor(0, 0);
    lcd.print("Temp Set");
    lcd.setCursor(0, 1);
    lcd.print(startTempMenu);
    lcd.print("C");
    previousScreenRefresh = millis();
  }
}

void menuSpray() {
  lcd.clear();
  delay(500);
  while (millis() - previousInput < menuDelayTime) {
    menuSprayPrint();
    if (TurnDetected)  {
      if (rotationdirection) {
        if (sprayTimeMenu <= 5.0)//max value
        {
          sprayTimeMenu = sprayTimeMenu + 0.1;
        }
      }
      else {
        if (sprayTimeMenu > 0.1)//min value
        {
          sprayTimeMenu = sprayTimeMenu - 0.1;
        }
      }
      TurnDetected = false;
      delay(100);
    }
    if (digitalRead(PinSW) == LOW) {
      break;
    }
  }
  lcd.clear();
}

void menuSprayPrint() {
  if (millis() - previousScreenRefresh >= 500) {
    lcd.setCursor(0, 0);
    lcd.print("Spray Set");
    lcd.setCursor(0, 1);
    lcd.print(sprayTimeMenu);
    lcd.print(" Seconds");
    previousScreenRefresh = millis();
  }
}

void menuDelay() {
  lcd.clear();
  delay(500);
  while (millis() - previousInput < menuDelayTime) {
    menuDelayPrint();
    if (TurnDetected)  {
      if (rotationdirection) {
        if (delayTimeMenu <= 99)//max value
        {
          delayTimeMenu = delayTimeMenu + 0.5;
        }
      }
      else {
        if (delayTimeMenu > 1)//min value
        {
          delayTimeMenu = delayTimeMenu - 0.5;
        }
      }
      TurnDetected = false;
      delay(100);
    }
    if (digitalRead(PinSW) == LOW) {
      break;
    }
  }
  lcd.clear();
}

void menuDelayPrint() {
  if (millis() - previousScreenRefresh >= 500) {
    lcd.setCursor(0, 0);
    lcd.print("Spray Delay Set");
    lcd.setCursor(0, 1);
    lcd.print(delayTimeMenu);
    lcd.print(" Seconds");
    previousScreenRefresh = millis();
  }
}

void menuSave() {
  lcd.clear();
  saveFlag = 0;
  delay(500);
  while (millis() - previousInput < menuDelayTime) {
    Serial.println("callmsp");
    menuSavePrint();
    if (TurnDetected)  {
      if (rotationdirection) {
        saveFlag = 1;
      }
      else {
        saveFlag = 0;
      }
      TurnDetected = false;
    }
    if (digitalRead(PinSW) == LOW) {
      if (saveFlag) {
        //updates the variables
        startTemp = startTempMenu;
        sprayTime = sprayTimeMenu * 1000;
        delayTime = delayTimeMenu * 1000;
        //save info to eeprom all saved as ints for ease (hence why startTempMenu)
        EEPROM.put(startTempAdd, startTempMenu);
        EEPROM.put(sprayTimeAdd,sprayTime);
        EEPROM.put(delayTimeAdd,delayTime);
      }
      break;
    }
  }

  lcd.clear();
}

void menuSavePrint() {
  if (millis() - previousScreenRefresh >= 500) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Save Changes");
    lcd.setCursor(0, 1);
    if (saveFlag) {
      lcd.print("  /Yes");
    } else {
      lcd.print("No/   ");
    }
    previousScreenRefresh = millis();
  }
}
  delay(4);  // delay for Debouncing

delay() does not work in an ISR because interrupts are disabled

I worked on a project where we had a similar problem with encoder signals. My partner fixed it with some Schmidt triggers.

That is lifted straight from the example code that came with my Arduino Starter kit :rofl:

Thanks for letting me know.

Cheers, I'll read into those.

Are you seeing a variable number of clicks after each movement of the encoder, or the same number every time? I assume you mean that you turn the encoder one click only and that you are using a clone of a typical Bourns type rotary encoder, with 16 or 20 detents.

A couple of things about these encoders: they are quadrature encoders, so they will generate 4 pulses per click. I think your ISR should only provide one output per detent.
The other is that they are notoriously bouncy.
The Bourns PEC11 data sheet provides a circuit diagram for filtering, and they typically have been making stuff for musical use. The circuit is very easy and cheap: a couple of 10k resistors and a 0.01 uf capacitor. I've had good luck with anywhere from 1k to 10k and 1 to 0.1 uF caps, so the exact numbers are really not that critical.

On a side note, if the encoder you have is not a bare part but on a PCB, there are 3 setups possible: the encoder with no additional circuitry but the pins brought out on a header; the encoder with 10k pullup resistors one each for the 3 pins; and the third type with the resistors and capacitors already built in. This last type had virtually no switch bounce I could see on my scope. YMMV.
As you know, on many of the usual sites, there is no clear indication of the 'color' of the device. But careful perusal of the photos can help.

1 Like

Please provide a link to where you got the code

I don;t have a link it came on a CD, I bought this... https://www.amazon.co.uk/gp/product/B01IUY62RM/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

and the lessons/code examples were on a CD included.

I found the lessons/example code very helpful and far better written than I expected. That Interrupt is lifted verbatim from the final lesson which tells you how to control a stepper motor with a rotary encoder.

Should add I'm not trying to argue and say it is right, I just thought it was funny so far the only bit of code that is pointless/won't do anything is not something I have written.

Thanks I did order some more encoders so
I will check those out see how they are built.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.