Voltage triggered variable delay shutdown timer

This is my first post after lurking and googling around for several months and being tempted to ask for help on more than one occasion but resisted and researched and tried things until I have some working code.

My project is to make an automatic shutdown for my dashcam with a variable delay that will fit in a car 12v usb adapter.

I have previously made a "hardware" system using a voltage sensing relay and 12v to 5v buck converters. It works OK but how long the camera runs after the ignition is switched off (for parking protection) is unpredictable due to the state of battery charge, short runs, cold temperatures etc and of course it runs down the car's battery -camera draws 600mA when running, base current of the hardware system is about 60mA.

In order to realize my project I will be using an attiny85 for the final deployment but testing was done on an UNO R3. A voltage divider, a MOSFET, a buck converter, an LED and a button make up the rest of the hardware.

The program works by sensing the external voltage and triggering the MOSFET when the target voltage is achieved or exceeded, the LED is lit. When the external voltage drops below the lower limit the MOSFET remains ON until the timer has expired. The timer length is determined by the number of button pushes (4 max) multiplied by the delay. When the delay has timed out the MOSFET goes OFF and set the counter to 1.

The code below works but I feel there may be a better way of doing it so I would appreciate any comments or suggestions for improvement.

const int  buttonPin = 2;    // the pin that the pushbutton is attached to
const int MOSFETPin = 8;       // the pin that the LED is attached to

float denominator;
int resistor1 = 990;
int resistor2 = 326;

// Variables that will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

void setup() {
  Serial.begin(9600);

  pinMode(buttonPin, INPUT); // initialize the button pin as a input:

  pinMode(MOSFETPin, OUTPUT); // initialize the MOSFET as an output:

  denominator = (float)resistor2 / (resistor1 + resistor2);
  pinMode(1, OUTPUT);
}


void loop() {

  buttonState = digitalRead(buttonPin); // read the pushbutton input pin:


  if (buttonState != lastButtonState) { // compare the buttonState to its previous state

    if (buttonState == HIGH) { // if the state has changed, increment the counter

      buttonPushCounter++; // if the current state is HIGH then the button went from off to on:
      Serial.println("on");
      Serial.print("number of button pushes: ");
      Serial.println(buttonPushCounter);
      if (buttonPushCounter == 5) {
        buttonPushCounter = 1;  // reset the counter added by me to limit time delay options
      }


    } else {
      Serial.println("off");

    }

    delay(50); // Delay a little bit to avoid bouncing

  }

  lastButtonState = buttonState; // save the current state as the last state, for next time through the loop

  float voltage;

  voltage = analogRead(A3); //Obtain RAW voltage data


  voltage = (voltage / 1024) * 5.0; //Convert to actual voltage (0 - 5 Vdc)

  //Convert to voltage before divider
  //  Divide by divider = multiply
  //  Divide by 1/3 = multiply by 3
  voltage = voltage / denominator;

  //Output to serial
  Serial.print("Volts: ");
  Serial.println(voltage);

  //Delay to make serial out readable
  delay(1000);
  if (voltage >= 3.9) digitalWrite(MOSFETPin, HIGH);

  if (voltage <= 3.8)
  {
    delay(buttonPushCounter * 1000UL * 10UL);
    digitalWrite(MOSFETPin, LOW);
    buttonPushCounter = 1;
  }


}

This part is wrong...

  //Delay to make serial out readable

delay(1000);

The comment should read "//Delay to make button unusable"

You would have to hold the button down for about 1 second because the code spends a second in this delay, not looking at the button. It also delays your system's response to the low-voltage condition because it's stuck in this delay.

Look at the tutorial on using millis() for timing. (I think it may be in the "Programming" sub-forum.) I can see three places you need to use millis:

  1. Restricting the Serial output
  2. Debouncing the button (look up "debounce" on your favourite search engine)
  3. Timing the off-timer

I'll give you the solution for #1. You need to do some more reading to understand what choices are available to you for solving the other timing problems.

The problem of restricting Serial output is very common. I have a simple little block that I drop in anywhere that I need to add some Serial output to a process which is running at high speed. This code below should replace everything in your code from //Output to Serial down to the delay.

  static unsigned long lastSerialPrint;
  const unsigned long serialPrintInterval = 1000; //milliseconds
  if(millis() - lastSerialPrint > serialPrintInterval) {
    lastSerialPrint = millis();

    //Output to serial
    Serial.print("Volts: ");
    Serial.println(voltage);
  }

The keyword "static" is important. By declaring a variable here, it's local to the function. In this case, the loop() function. Normally local variables are forgotten after the function ends. But a static variable is stored and keeps its value even when the function is not running.

Thank you MorganS for taking the time to reply. Once the code is ported to the Attiny85 all the references to serial will be removed but your guidance will be most useful for my other projects. I will investigate the timing issue and substitute millis()

Be aware that most modern cars turn off the 12v ports after 15-20 mins - check that first

Thanks, the socket stays live all the time even when the eco function shut off the engine at traffic lights

Depending how long it takes for your dashcam to start recording when powered up, maybe you could just cycle it every few seconds to greatly extend the coverage time.

MorganS:
This part is wrong...
The comment should read "//Delay to make button unusable"

You would have to hold the button down for about 1 second because the code spends a second in this delay, not looking at the button. It also delays your system's response to the low-voltage condition because it's stuck in this delay.

Look at the tutorial on using millis() for timing. (I think it may be in the "Programming" sub-forum.) I can see three places you need to use millis:

  1. Restricting the Serial output
  2. Debouncing the button (look up "debounce" on your favourite search engine)
  3. Timing the off-timer

I'll give you the solution for #1. You need to do some more reading to understand what choices are available to you for solving the other timing problems.

The problem of restricting Serial output is very common. I have a simple little block that I drop in anywhere that I need to add some Serial output to a process which is running at high speed. This code below should replace everything in your code from //Output to Serial down to the delay.

  static unsigned long lastSerialPrint;

const unsigned long serialPrintInterval = 1000; //milliseconds
 if(millis() - lastSerialPrint > serialPrintInterval) {
   lastSerialPrint = millis();

//Output to serial
   Serial.print("Volts: ");
   Serial.println(voltage);
 }




The keyword "static" is important. By declaring a variable here, it's local to the function. In this case, the loop() function. Normally local variables are forgotten after the function ends. But a `static` variable is stored and keeps its value even when the function is not running.

I have done a lot of looking into how I could replace the MOSFET delay off-timer with millis() but I keep running into the same problem, the off-timer is varied by the number of button presses. do I make 4 timers and call the value depending upon the buton presses ?

First count the button presses. Remember that in a variable.

Then if the timer is triggered, look at that variable to decide how long it should time for.

Been some time since I posted an update and the "tough love" in this group pays off. We must find our own solutions and hence learn. Here is the very different code I ended up with after a lot of looking and trying. I will remove all the serial code lines and rename LED to MOSFET and change the voltage thresholds before I upload this to my Attiny85.

Thanks to MorganS and the others who guided me

//Global Variables
const byte buttonPin = 2; // our button pin
const byte LED = 12; // LED
const int voltPin = A3; //Voltage divider input

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

unsigned long buttonPushedMillis; // when button was released
unsigned long ledTurnedOnAt; // when led was turned on
unsigned long turnOnDelay = 100; // wait to turn on LED
unsigned long turnOffDelay = 1000; // turn off LED after this time
unsigned long turnOffDelay_2 = 5000; // turn off LED after this time
unsigned long turnOffDelay_3 = 10000; // turn off LED after this time

bool ledReady = false; // flag for when button is let go
bool ledState = false; // for LED is on or not.

//Variables for voltage divider
float denominator;
int resistor1 = 990;
int resistor2 = 326;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  denominator = (float)resistor2 / (resistor1 + resistor2);
  //Serial.begin(9600);

}

void loop() {
  // get the time at the start of this loop()
  unsigned long currentMillis = millis();

  float voltage;
  //Obtain RAW voltage data
  voltage = analogRead(A3);

  //Convert to actual voltage (0 - 5 Vdc)
  voltage = (voltage / 1024) * 5.0;

  //Convert to voltage before divider
  //  Divide by divider = multiply
  //  Divide by 1/3 = multiply by 3
  voltage = voltage / denominator;

  if (voltage >= 3.9) digitalWrite(LED, HIGH);

  //---------------------
  // read the pushbutton input pin:
  buttonState = digitalRead(buttonPin);
  //---------------------

  // check the button
  if (digitalRead(buttonPin) == LOW) {
    // update the time when button was pushed
    buttonPushedMillis = currentMillis;
    ledReady = true;
  }
  //---------------------

  // compare the buttonState to its previous state
  if (buttonState != lastButtonState) {
    // if the state has changed, increment the counter
    if (buttonState == LOW)
    {
      // if the current state is HIGH then the button went from off to on:
      buttonPushCounter++;

      if (buttonPushCounter == 4) {
        buttonPushCounter = 0;  // reset the counter added by me to limit time delay options
      }
     // Serial.print("Volts: ");
    //  Serial.println(voltage);
    //  Serial.println("on");
    //  Serial.print("number of button pushes: ");
    //  Serial.println(buttonPushCounter);

    } else {
      // if the current state is LOW then the button went from on to off:
     // Serial.println("off");
    }
    // Delay a little for serial print
    //delay(50);
  }
  // save the current state as the last state, for next time through the loop
  lastButtonState = buttonState;

 
  //---------------------


  // make sure this code isn't checked until after button has been let go
  if (ledReady)
  {
    //this is typical millis code here:
    if ((unsigned long)(currentMillis - buttonPushedMillis) >= turnOnDelay) {
      // okay, enough time has passed since the button was let go.
      digitalWrite(LED, HIGH);
      // setup our next "state"
      ledState = true;
      // save when the LED turned on
      ledTurnedOnAt = currentMillis;
      // wait for next button press
      ledReady = false;
    }
  }

  // see if we are watching for the time to turn off LED
  if (ledState && buttonPushCounter == 1 && voltage <= 3.8) {
    // okay, led on, check for now long
    if ((unsigned long)(currentMillis - ledTurnedOnAt) >= turnOffDelay) {
      ledState = false;
      digitalWrite(LED, LOW);

    }
  }

  else if (ledState && buttonPushCounter == 2 && voltage <= 3.8) {
    // okay, led on, check for now long
    if ((unsigned long)(currentMillis - ledTurnedOnAt) >= turnOffDelay_2) {
      ledState = false;
      digitalWrite(LED, LOW);

    }
  }

  else if (ledState && buttonPushCounter == 3 && voltage <= 3.8) {
    // okay, led on, check for now long
    if ((unsigned long)(currentMillis - ledTurnedOnAt) >= turnOffDelay_3) {
      ledState = false;
      digitalWrite(LED, LOW);

    }
  }
    else if (voltage <= 3.8)
    {
      digitalWrite(LED, LOW);
    }
    }