How to control the off time of LED using timer interrupt

/* code with timer interrupt that will create an interruption each 
 *  1s using timer1 and prescalar of 256.
Calculations (for 500ms): 
  System clock 16 Mhz and Prescalar 256;
  Timer 1 speed = 16Mhz/256 = 62.5 Khz    
  Pulse time = 1/62.5 Khz =  16us  
  Count up to = 1000ms / 16us = 62500 (so this is the value the OCR register should have)*/  
bool LED_STATE = true;
int count = 0;

void setup() {
  pinMode(13, OUTPUT);        //Set the pin to be OUTPUT
  cli();                      //stop interrupts for till we make the settings
  /*1. First we reset the control register to amke sure we start with everything disabled.*/
  TCCR1A = 0;                 // Reset entire TCCR1A to 0 
  TCCR1B = 0;                 // Reset entire TCCR1B to 0
 
  /*2. We set the prescalar to the desired value by changing the CS10 CS12 and CS12 bits. */  
  TCCR1B |= B00000100;        //Set CS12 to 1 so we get prescalar 256  
  
  /*3. We enable compare match mode on register A*/
  TIMSK1 |= B00000010;        //Set OCIE1A to 1 so we enable compare match A 
  
  /*4. Set the value of register A to 62500*/
  OCR1A = 62500;             //Finally we set compare register A to this value  
  sei();                     //Enable back the interrupts
}

void loop() {
  // put your main code here, to run repeatedly:
}

//With the settings above, this IRS will trigger each 1s.
ISR(TIMER1_COMPA_vect)
{
  count += 1;
  TCNT1  = 0;                  //First, set the timer back to 0 so it resets for next interrupt
  if (count >= 6)
  {  
  LED_STATE = !LED_STATE;      //Invert LED state
  digitalWrite(13,LED_STATE);  //Write new state to the LED on pin D5
  count = 0;
  }
}

I have this code above, I have use timer interrupt to turn ON the LED for 6 seconds, but I want it to turn off for 15 minutes and then on again for 6 secs and off for 15 min., this cycle should last for 2 hours. (But I know that my code for now turns LED ON for 6 secs, off for 6 secs)

It is still not in the code but I want this timer to only start when a button (BUTTON_A) is pushed. Also, another button (BUTTON_B) to stop the operation if BUTTON_B is pushed.

Anyone who can direct me to implement this, would be of great help. Thanks.

is there any reason why you need to play with timer interrupts instead of using millis()? I mean an interval of 6 seconds or 15 minutes is nothing crucial which should need an interrupt at all..

based on what I searched, isn't millis() a blocking timer? I tried to use timer interrupt because there could be a time that when button_b is pressed, the operation of LED should stop. (actually I will be controlling a solenoid valve, but for now I am ofc trying the basic and understandable for me so I use LED as my output)

millis() is nothing else than a timer based counter running in the background.
using the concept of the "Blink Without Delay" example from the IDE you can do all sort of not blocking blinking on your Arduino.

for sure you can use this also with a button to switch on/off the blinking at all.

1 Like

Thanks for the input about millis, I tried to search about it and implement it on my code. I was able to run this code where if button is push LED lights up for 6 seconds then off for 1 minute. But I can't seem to stop the operation when it's more than 3 minutes.

const int LED = 13; // Define the pin where your solenoid valve is connected
const int buttonAPin = 10;  // Define the pin for Push Button A

bool valveOpen = false;     // Track if the valve is currently open
bool buttonAPressed = false; // Track if Button A is pressed
unsigned long startTime;    // Record the start time of the cycle
unsigned long currentTime;  // Record the current time
unsigned long openTime = 6000; // 6 seconds in milliseconds
unsigned long closeTime = 66000; //306000; // 5 minutes // 900000; // 15 minutes in milliseconds
unsigned long cycleDuration = 180000; //7200000; // 2 hours in milliseconds
bool initialCycle = true; // Tracks inital cycle

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(buttonAPin, INPUT_PULLUP);
  Serial.begin(9600);
}

void loop() {
int count = 0;

  // Read the state of Push Button A
  buttonAPressed = digitalRead(buttonAPin) == LOW;

  // Check if Button A is pressed and it's the initial cycle or if the valve is currently closed
  if (buttonAPressed && !valveOpen)
  {
    Serial.println("Button A is pressed");
    // Record the start time
    startTime = millis();
    // Open the valve
    digitalWrite(LED, HIGH);
    valveOpen = true;
  }

  // Check if the valve is open and it's time to close it
  currentTime = millis();
  if (valveOpen && currentTime - startTime >= openTime) // openTime = 6 seconds
  {
    // Close the valve
    digitalWrite(LED, LOW);
    valveOpen = false;
  }

  // Check if the initial 15-minute close period has passed
  if (currentTime - startTime >= closeTime)             // closeTime = 1 minute
  {
    // Record the start time for the next cycle
    startTime = currentTime;
    // Open the valve
    digitalWrite(LED, HIGH);
    valveOpen = true;
    count += 1;
  }

  // Check if the 2-hour cycle has completed
  // for now, it was set to 3 minutes
  if (currentTime - startTime >= cycleDuration) 
  {
    // Reset the cycle
    valveOpen = false;
    digitalWrite(LED, LOW);
  }
}

These are my final requirements btw:

  1. Push Button A - 2 hours mode (5 seconds open, 15 minutes close cycle for 2 hours)
  2. Push Button B - 8 hours mode (5 seconds open, 15 minutes close cycle for 8 hours)
  3. Push Button C - stop the operation

But for now with my code above, I am stuck in automating the permanent OFF of LED when it reached the cycleDuration which I set approximately for 3 minutes.

What is the programme's task in real life?

you could use:

  • a separte flag like "mode"
  • each button should set the flag to either 0, 1, or 2

In the lines which process the "run effect" you react on the flag and use 2h/5s,

just as an idea:
1LED 3 Buttons - Wokwi ESP32, STM32, Arduino Simulator

/*
    https://forum.arduino.cc/t/how-to-control-the-off-time-of-led-using-timer-interrupt/1177480/7
    2023-10-12 by noiasca
    sketch
*/

const uint8_t ledPin = 13;         // Define the pin where your solenoid valve is connected
const uint8_t buttonAPin = 10;     // Define the pin for Push Button A
const uint8_t buttonBPin = 11;
const uint8_t buttonStopPin = 12;
uint8_t ledState = LOW;            // Track if the valve is currently open
uint32_t startTime = 0;            // Record the start time of the cycle
uint32_t previousMillis = 0;       // track millis for blink sequence
//                            A     B
const uint32_t openTime[] =  {500, 1000};   // in milliseconds
const uint32_t closeTime[] = {1000, 2000}; // in milliseconds
const uint32_t runTime[] =   {6000, 12000};

enum Mode {A, B, IDLE} mode = IDLE;   // 3 defined modes, in this case IDLE needs to be the last mode

// run the finite state machine to handle the LED
void runFSM() {
  if (mode == IDLE) return;           // nothing to do - return early
  uint32_t currentMillis = millis();  // Record the current time
  if (ledState == HIGH && currentMillis - previousMillis > openTime[mode]) {
    //Serial.println(F("switch to LOW"));
    previousMillis = currentMillis;
    ledState = LOW;
    digitalWrite(ledPin, ledState);
  }
  else if (ledState == LOW && currentMillis - previousMillis > closeTime[mode]) {
    //Serial.println(F("switch to HIGH"));
    previousMillis = currentMillis;
    ledState = HIGH;
    digitalWrite(ledPin, ledState);
  }
  else if (currentMillis - startTime > runTime[mode]) {
    Serial.println(F("runtime end"));
    mode = IDLE;
    ledState = LOW;
    digitalWrite(ledPin, ledState);
  }
}

void readButton() {
  if (mode == IDLE && digitalRead(buttonAPin) == LOW) {
    Serial.println(F("Button A is pressed"));
    mode = A;
    startTime = millis(); // Record the start time
    previousMillis = millis();
    ledState = HIGH;
    digitalWrite(ledPin, ledState);
  }
  if (mode == IDLE && digitalRead(buttonBPin) == LOW) {
    Serial.println(F("Button B is pressed"));
    mode = B;
    startTime = millis(); // Record the start time
    previousMillis = millis();
    ledState = HIGH;
    digitalWrite(ledPin, ledState);
  }
  if (digitalRead(buttonStopPin) == LOW) {
    Serial.println(F("Button Stop is pressed"));
    mode = IDLE;
    ledState = LOW;
    digitalWrite(ledPin, ledState);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonAPin, INPUT_PULLUP);
  pinMode(buttonBPin, INPUT_PULLUP);
  pinMode(buttonStopPin, INPUT_PULLUP);
}

void loop() {
  readButton();
  runFSM();
}
//

might be buggy ...
could be improved even more
test on your own.

Maybe this example is helpful:

class Timer {
public:
  void start() { timeStamp = millis(); }
  bool operator()(const uint32_t duration) const { return (millis() - timeStamp >= duration) ? true : false; }

private:
  uint32_t timeStamp {0};
};

enum class ProgramMode : byte { stop, modeA, modeB };
enum class LightStatus : byte { start, on, off };

constexpr uint8_t LED_PIN {4};

// constexpr uint32_t TIME_MODE_A {1000 * 3600UL * 2};   // 2 Hours
// constexpr uint32_t TIME_MODE_B {1000 * 3600UL * 8};   // 8 Hours

// constexpr uint32_t LIGHT_ON_MS {1000 * 5UL};          // 5 Seconds
// constexpr uint32_t LIGHT_OFF_MS {1000 * 60UL * 15};   // 15 Minutes

constexpr uint32_t TIME_MODE_A {1000 * 120UL};   // 2 Minutes
constexpr uint32_t TIME_MODE_B {1000 * 60UL};    // 1 Minute

constexpr uint32_t LIGHT_ON_MS {1000 * 2UL};
constexpr uint32_t LIGHT_OFF_MS {1000 * 4UL};

constexpr uint32_t mainIntervalls[3] {0, TIME_MODE_A, TIME_MODE_B};
constexpr uint32_t ledIntervalls[3] {0, LIGHT_ON_MS, LIGHT_OFF_MS};

Timer ledTimer;
Timer mainTimer;

LightStatus switchLight(LightStatus ls) {
  if (ledTimer(ledIntervalls[static_cast<byte>(ls)])) {
    ledTimer.start();
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    ls = (ls != LightStatus::on) ? LightStatus::on : LightStatus::off;
  }
  return ls;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  mainTimer.start();
}

void loop() {
  static LightStatus ledStatus {LightStatus::start};
  static ProgramMode mode {ProgramMode::modeB};

  // Depending on the pressed button, the variable "mode" must be set to either
  // ProgramMode::modeA, ProgramMode::modeB or ProgramMode::stop.
  // When the mode (A or B) is set, mainTimer.start() must also be executed (once).

  if (mainTimer(mainIntervalls[static_cast<byte>(mode)])) {
    if (digitalRead(LED_PIN)) { digitalWrite(LED_PIN, LOW); }
    mode = ProgramMode::stop;
  } else {
    ledStatus = switchLight(ledStatus);
  }
}

You can try it out here...

2 Likes

the timer works, thanks for this. But I'll be very honest that I can't seem to follow and fully understand the logic of some functions. I do get their general operation but if I try to understand each line and how is it being processed, it is where I am not confident of.

So I tried to add inside the loop where I poll the status of Button_A and Button_B and set the variable "mode" and execute the mainTimer.start() base on your comment.

#define Button_A      10
#define Button_B      11
#define Button_C      12

bool buttonApressed = false;
bool buttonBpressed = false;
bool buttonCpressed = false;

class Timer {
public:
  void start() { timeStamp = millis(); }
  bool operator()(const uint32_t duration) const { return (millis() - timeStamp >= duration) ? true : false; }

private:
  uint32_t timeStamp {0};
};

enum class ProgramMode : byte { stop, modeA, modeB };
enum class LightStatus : byte { start, on, off };

constexpr uint8_t LED_PIN {13};

// constexpr uint32_t TIME_MODE_A {1000 * 3600UL * 2};   // 2 Hours
// constexpr uint32_t TIME_MODE_B {1000 * 3600UL * 8};   // 8 Hours

// constexpr uint32_t LIGHT_ON_MS {1000 * 5UL};          // 5 Seconds
// constexpr uint32_t LIGHT_OFF_MS {1000 * 60UL * 15};   // 15 Minutes

constexpr uint32_t TIME_MODE_A {1000 * 120UL};   // 2 Minutes
constexpr uint32_t TIME_MODE_B {1000 * 60UL};    // 1 Minute

constexpr uint32_t LIGHT_ON_MS {1000 * 2UL};     // 
constexpr uint32_t LIGHT_OFF_MS {1000 * 4UL};

constexpr uint32_t mainIntervalls[3] {0, TIME_MODE_A, TIME_MODE_B};
constexpr uint32_t ledIntervalls[3] {0, LIGHT_ON_MS, LIGHT_OFF_MS};

Timer ledTimer;
Timer mainTimer;

LightStatus switchLight(LightStatus ls) {
  if (ledTimer(ledIntervalls[static_cast<byte>(ls)])) {
    ledTimer.start();
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    ls = (ls != LightStatus::on) ? LightStatus::on : LightStatus::off;
  }
  return ls;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  mainTimer.start();
}

void loop() {
  buttonApressed = digitalRead(Button_A) == LOW;
  buttonBpressed = digitalRead(Button_B) == LOW;
  buttonCpressed = digitalRead(Button_C) == LOW;

  static LightStatus ledStatus {LightStatus::start};
  // static ProgramMode mode {ProgramMode::modeB};

  // Depending on the pressed button, the variable "mode" must be set to either
  // ProgramMode::modeA, ProgramMode::modeB or ProgramMode::stop.
  // When the mode (A or B) is set, mainTimer.start() must also be executed (once).

  if (buttonApressed)
  {
    static ProgramMode mode {ProgramMode::modeA};
    mainTimer.start();
  }
  
  if (buttonBpressed)
  {
    static ProgramMode mode {ProgramMode::modeB};
    mainTimer.start();
  }

  // if (buttonCpressed)
  // {
  // static ProgramMode mode {ProgramMode::stop};  
  // mode = ProgramMode::stop;
  // }

  if (mainTimer(mainIntervalls[static_cast<byte>(mode)])) {
    if (digitalRead(LED_PIN)) { digitalWrite(LED_PIN, LOW); }
    mode = ProgramMode::stop;
  } else {
    ledStatus = switchLight(ledStatus);
  }
}

However, during the compilation, I get this error message which for now I can't really understand.

#define Button_A      10
#define Button_B      11
#define Button_C      12

bool buttonApressed = false;
bool buttonBpressed = false;
bool buttonCpressed = false;

class Timer {
public:
  void start() { timeStamp = millis(); }
  bool operator()(const uint32_t duration) const { return (millis() - timeStamp >= duration) ? true : false; }

private:
  uint32_t timeStamp {0};
};

enum class ProgramMode : byte { stop, modeA, modeB };
enum class LightStatus : byte { start, on, off };

constexpr uint8_t LED_PIN {13};

// constexpr uint32_t TIME_MODE_A {1000 * 3600UL * 2};   // 2 Hours
// constexpr uint32_t TIME_MODE_B {1000 * 3600UL * 8};   // 8 Hours

// constexpr uint32_t LIGHT_ON_MS {1000 * 5UL};          // 5 Seconds
// constexpr uint32_t LIGHT_OFF_MS {1000 * 60UL * 15};   // 15 Minutes

constexpr uint32_t TIME_MODE_A {1000 * 120UL};   // 2 Minutes
constexpr uint32_t TIME_MODE_B {1000 * 60UL};    // 1 Minute

constexpr uint32_t LIGHT_ON_MS {1000 * 2UL};     // 
constexpr uint32_t LIGHT_OFF_MS {1000 * 4UL};

constexpr uint32_t mainIntervalls[3] {0, TIME_MODE_A, TIME_MODE_B};
constexpr uint32_t ledIntervalls[3] {0, LIGHT_ON_MS, LIGHT_OFF_MS};

Timer ledTimer;
Timer mainTimer;

LightStatus switchLight(LightStatus ls) {
  if (ledTimer(ledIntervalls[static_cast<byte>(ls)])) {
    ledTimer.start();
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    ls = (ls != LightStatus::on) ? LightStatus::on : LightStatus::off;
  }
  return ls;
}

static ProgramMode mode {ProgramMode::modeA};

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  mainTimer.start();
}

void loop() {
  static LightStatus ledStatus {LightStatus::start};

  buttonApressed = digitalRead(Button_A) == LOW;
  buttonBpressed = digitalRead(Button_B) == LOW;
  buttonCpressed = digitalRead(Button_C) == LOW;

  // Depending on the pressed button, the variable "mode" must be set to either
  // ProgramMode::modeA, ProgramMode::modeB or ProgramMode::stop.
  // When the mode (A or B) is set, mainTimer.start() must also be executed (once).

  if (buttonApressed)
  {
    // mode = ProgramMode::modeA;
    mainTimer.start();
  }
  
  if (buttonBpressed)
  {
    mode = {ProgramMode::modeB};
    mainTimer.start();
  }

  // if (buttonCpressed)
  // {
  // static ProgramMode mode {ProgramMode::stop};  
  // mode = ProgramMode::stop;
  // }

  if (mainTimer(mainIntervalls[static_cast<byte>(mode)])) {
    if (digitalRead(LED_PIN)) { digitalWrite(LED_PIN, LOW); }
    mode = ProgramMode::stop;
  } else {
    ledStatus = switchLight(ledStatus);
  }
}

Here in the code above (I tied to move the static ProgramMode mode {ProgramMode::modeA} outside the loop ), I was able to light the LED when the Button_A is pressed, but it does not stop at the set time of 1 minute. It continuously turns on and off.

And I'm planning to add a counter at every time the turn OFF time is finished. So if I have it for 2 hours, the count must be a total of 8. It's like I am counting the cycle.

Your way of interrogating the buttons is a bit "strange". In addition, you do not take into account which status is currently active.

class Timer {
public:
  void start() { timeStamp = millis(); }
  bool operator()(const uint32_t duration) const { return (millis() - timeStamp >= duration) ? true : false; }

private:
  uint32_t timeStamp {0};
};

enum class ProgramMode : byte { stop, modeA, modeB };
enum class LightStatus : byte { start, on, off };

// constexpr uint32_t TIME_MODE_A {1000 * 3600UL * 2};   // 2 Hours
// constexpr uint32_t TIME_MODE_B {1000 * 3600UL * 8};   // 8 Hours

// constexpr uint32_t LIGHT_ON_MS {1000 * 5UL};          // 5 Seconds
// constexpr uint32_t LIGHT_OFF_MS {1000 * 60UL * 15};   // 15 Minutes

constexpr uint32_t TIME_MODE_A {1000 * 120UL};   // 2 Minutes
constexpr uint32_t TIME_MODE_B {1000 * 60UL};    // 1 Minute

constexpr uint32_t LIGHT_ON_MS {1000 * 2UL};
constexpr uint32_t LIGHT_OFF_MS {1000 * 1UL};

constexpr uint32_t mainIntervalls[3] {0, TIME_MODE_A, TIME_MODE_B};
constexpr uint32_t ledIntervalls[3] {0, LIGHT_ON_MS, LIGHT_OFF_MS};

constexpr uint8_t LED_PIN {4};
constexpr uint8_t BUTTON_MODE_A {5};
constexpr uint8_t BUTTON_MODE_B {6};
constexpr uint8_t BUTTON_STOP {7};

Timer mainTimer;
Timer ledTimer;

ProgramMode checkButtons(ProgramMode md, Timer &mt) {
  if (md == ProgramMode::stop) {   // Check Mode buttons only when mode "Stop" is active
    if (digitalRead(BUTTON_MODE_A) == LOW) {
      Serial.println("Start Mode A (2 Minutes)");
      md = ProgramMode::modeA;
      mt.start();
    } else if (digitalRead(BUTTON_MODE_B) == LOW) {
      Serial.println("Start Mode B (1 Minute)");
      md = ProgramMode::modeB;
      mt.start();
    }
  } else {   // Check Stop button only if modeA or modeB is active.
    if (digitalRead(BUTTON_STOP) == LOW) {
      md = ProgramMode::stop;
      Serial.println("Stop");
    }
  }
  return md;
}

LightStatus switchLight(LightStatus ls) {
  if (ledTimer(ledIntervalls[static_cast<byte>(ls)])) {
    ledTimer.start();
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    ls = (ls != LightStatus::on) ? LightStatus::on : LightStatus::off;
  }
  return ls;
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  pinMode(BUTTON_MODE_A, INPUT_PULLUP);
  pinMode(BUTTON_MODE_B, INPUT_PULLUP);
  pinMode(BUTTON_STOP, INPUT_PULLUP);
}

void loop() {
  static LightStatus ledStatus {LightStatus::start};
  static ProgramMode mode {ProgramMode::stop};

  mode = checkButtons(mode, mainTimer);
  if (mainTimer(mainIntervalls[static_cast<byte>(mode)])) {
    if (digitalRead(LED_PIN)) { digitalWrite(LED_PIN, LOW); }
    mode = ProgramMode::stop;
  } else {
    ledStatus = switchLight(ledStatus);
  }
}

You can test this here:

Green = Mode A, Blue Mode B and Red = Stop.
Every Mode can be stopped at ervery time.

1 Like

What is the programme's task in real life?

Thank you so much for this, it worked as expected. Will further decode how each line works to align it on how I code as I am not that of an expert in coding efficiently. Just learned the ternary operator when I try to understand the way you code it.

Have you worked with the Arduino Blink Without Delay example?

It does need to make all of the time variables in unsigned long but as is it works longer than necessary and provides a simple do more than one thing at a time lesson. The less wrinkles, the shorter the learning curve to a very fundamental step towards smooth automation.

In real world code, the time dimension is your ally.

Hi there. The final task would be it will open and close a solenoid valve base on the time that I set. But for testing in a simple manner, I just tried to interface with and LED.

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