LCD Displaying using Millis not Delay

Hi all, I'm quite new to programming but have limited knowledge which i have been puting to use.

I'm printing to LCD four values. two on the first screen, then delaying for 5 seconds before printing the two further values.

Initially i used the delay function, but as i got further through the code, it became apparent that the delay function was not the way forward as it also delayed other parts of the code from being implemented.

I have read up on using millis instead, however it doesn't seem to be working for me.

//this is declared outside setup and loop

//set the delay between the LCD display setting using millis (not the delay function)
unsigned long lcdTimeOn = 0;
unsigned long lcdTimeOff = 5000;

//declared inside setup


//  lcd
void doLcdMsg() {

  float temperature = getTemp();                 // create temperature variable

  pinMode(backLight, OUTPUT);            // backlight pin set as output
  digitalWrite(backLight, HIGH);         // backlight on
  lcd.begin(16, 2);                      // columns, rows
  lcd.clear();                           // start with blank screen
  lcd.setCursor(0, 0);                   // set cursor to column 0, row 0
  lcd.print("Coop Temp:");               // show "Temp"
  lcd.print (temperature) ;              // show temperature of interior coop
  lcd.print("C");                        // show "C"
  lcd.setCursor(0, 1);                   // set cursor to column 0, row 1
  lcd.print("Coop Door:");               // show "Coop Door"

  if (bottomSwitchPinVal == 0) {         // if coop door bottom switch is closed
    lcd.print("Closed");                 // display "Closed"
  }
  else  {lcd.print("Open");            // if coop door bottom switch is open display "Open"
 
      }

       if ((unsigned long)(millis() - lcdTimeOn) > lcdTimeOff) {     // delay 5 seconds

    lcdTimeOn = millis();
}       
  lcd.clear();                            // clear the screen
  
      if (relayFan == LOW){                 //relayFan set to LOW as Songle Relay is on with Gnd Voltage
      lcd.print("Cooling Fan: ON");         
      }                                     //display that Cooling fan is on or off
      else {lcd.print("Cooling Fan: OFF");
      }

      lcd.setCursor(0,1);

      if (relayHeat == LOW){                //relayHeater set to LOW as Songle Relay is on with Gnd Voltage
        lcd.print("Coop Heater: ON");
        }                                     //display that Cooling fan is on or off
       else {lcd.print("Coop Heater: OFF");
       
       } 
         if ((unsigned long)(millis() - lcdTimeOn) > lcdTimeOff) {     // delay 5 seconds

    lcdTimeOn = millis();
}

when i upload the above code the LCD just show " Coop Heater: Off" and below that Cooling Fan: Off and does not cycle through to show "Coop Temp" or Coop Door.

If i remove the code from after:

else  {lcd.print("Open");            // if coop door bottom switch is open display "Open"
 
      }

Then i get my first LCD display done correctly, so i know the issue is around the delay using millis.

Any help would be appreciated as i've come off night shifts and my head is aching!

Thanks Alex

does not cycle through to show “Coop Temp” or Coop Door.

The only place in the code you posted where you print “Coop Temp” is in the doLcdMsg() function but the code posted never calls that function.

Please post your complete program.

doLcdMsg() is called in the void loop.

Thanks Alex

MaverickAlex:
doLcdMsg() is called in the void loop.

If you say so. If you want help with why it doesn't work then show the whole sketch.

Apols, heres the full code so far:
it exceeds 9000chars which is why i didnt post it. too much to put in between the code tags.

chicken_coop_TEST.ino (18.9 KB)

    if (temperature <= 5) {                                      // if temp drops below 5c turn on heat lamp(s) relay LOW is +ve voltage on Songle relay
      digitalWrite(relayHeat, LOW);
    }
    else if (temperature > 5) {
      digitalWrite(relayHeat, HIGH);                        // if temp remains above 5c turn off heat lamp(s) relay HIGH is Gnd voltage on Songle relay
    }

If the temperature is not less than or equal 5, what are the odd that it is not greater than 5?

  doCoopHVACCool();
  doCoopHVACHeat();

Wouldn’t it make more sense to read the temperature and call ONE of these functions? How likely are you to be heating AND cooling at the same time?

       if ((unsigned long)(millis() - lcdTimeOn) > lcdTimeOff) {     // delay 5 seconds

    lcdTimeOn = millis();
}

This seems pointless. Nothing useful happens if some period of time has elapsed.

         if ((unsigned long)(millis() - lcdTimeOn) > lcdTimeOff) {     // delay 5 seconds

    lcdTimeOn = millis();
}

This seems pointless, too.

Your code could REALLY benefit from using Tools + Auto Format.

That indenting
looks like
a drunken
sailor was in charge
of the tab key.

Thanks for looking through. I know you'll find lots of ways to tidy up the code, (I've auto formatted now thanks)

Its the delay using millis in the lcd function that is my pressing issue ATM.

Thanks

Alex

Its the delay using millis in the lcd function that is my pressing issue ATM.

That is the wrong way of thinking. You should be thinking in terms of using millis() to determine when something should happen, not in terms of implementing a delay.

What have you changed, besides doing Auto Format?

Well for starters im immersing myself in millis again, to see where i've gone wrong.

Thanks
Alex

So, after looking through numerous LCD and Millis tutorials i have tried to change my code to the following:

before setup

//set the delay between the LCD display setting using millis (not the delay function)
unsigned long lcdScreenOneDelay = 0;
unsigned long lcdScreenOneDelaytimer = 5000;

unsigned long lcdScreenTwoDelay = 0;
unsigned long lcdScreenTwoDelaytimer = 5000;

in setup

//  lcd
void doLcdMsg() {

  if ((unsigned long)(millis() - lcdScreenOneDelay) > lcdScreenOneDelaytimer) {     // delay 5 seconds

    lcdScreenOneDelay = millis();

    float temperature = getTemp();                 // create temperature variable

    pinMode(backLight, OUTPUT);            // backlight pin set as output
    digitalWrite(backLight, HIGH);         // backlight on
    lcd.begin(16, 2);                      // columns, rows
    lcd.clear();                           // start with blank screen
    lcd.setCursor(0, 0);                   // set cursor to column 0, row 0
    lcd.print("Coop Temp:");               // show "Temp"
    lcd.print (temperature) ;              // show temperature of interior coop
    lcd.print("C");                        // show "C"
    lcd.setCursor(0, 1);                   // set cursor to column 0, row 1
    lcd.print("Coop Door:");               // show "Coop Door"

    if (bottomSwitchPinVal == 0) {         // if coop door bottom switch is closed
      lcd.print("Closed");                 // display "Closed"
    }
    else  {
      lcd.print("Open");                 // if coop door bottom switch is open display "Open"

    }
  }
}

void doLcdMsg2() {

  if ((unsigned long)(millis() - lcdScreenTwoDelay) > lcdScreenTwoDelaytimer) {     // delay 5 seconds

    lcdScreenTwoDelay = millis();
  }
  pinMode(backLight, OUTPUT);            // backlight pin set as output
  digitalWrite(backLight, HIGH);         // backlight on
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);

  if (relayFan == LOW) {
    lcd.print("Cooling Fan: ON");
  }
  else {
    lcd.print("Cooling Fan: OFF");

    lcd.setCursor(0, 1);
  }

  if (relayHeat == LOW) {
    lcd.print("Heater is: ON");
  }
  else {
    lcd.print("Heater Lamp: OFF");



  }
}

in loop

doLcdMsg();
  doLcdMsg2();[code/]

As you can see i created another function for the second LCD message, however now i get the cooling and heater lamp messages displayed but not temp and door status, although i think i see them flash on evry 5 seconds.

Any ideas where i'm missing the point?

Thanks Alex

You are on the right path with your millis() timers, but the display of the two screens is not coordinated and they are conflicting with each other and one is overwriting the other.

For multiple screen display, it is better to use one millis() timer to control the progression through the display screens. Take the timing out of the individual display functions.

Here's a model for code which uses the timer to set flags to cycle through your two screens. It is easily extended for three or more screens.

Include the screen switch variables in set up

unsigned long switchMillis = 0;
unsigned long interval = 5000; //5 seconds for each lcd screen
byte count = 0;

boolean firstScreenFlag = false;
boolean secondScreenFlag = false;

Then in loop

if (millis() - switchMillis > interval)
    {
      count++;

      if (count == 1)
        firstScreenFlag = true;

      if (count == 2)
       {
        secondScreenFlag = true;
        count = 0;
        }
 
      switchMillis += interval; //millis();
    }
  }

Then conditional tests for what screen to display

if (firstScreenFlag)
  {
    doLcdMsg();
    firstScreenFlag = false;
  }

if (secondScreenFlag)
  {
    doLcdMsg2();
    secondScreenFlag = false;
  }

I can't quite get my head around that, I've been at this for some time and i think its time for a rest.

Looking at that code it appears the way to go, so thanks for that. Hopefully a new day will help get a clearer view on it.

I still can't wrap my head around this part.

using cattledogs example i can't seem to make it work, it just sends my lcd blank.

I'm sure this is simple but it's not sinking in.

using:
in setup

//set the delay between the LCD display setting using millis (not the delay function)
unsigned long lcdScreenOneDelay = 0;
unsigned long lcdScreenOneDelaytimer = 5000;

unsigned long lcdScreenTwoDelay = 0;
unsigned long lcdScreenTwoDelaytimer = 5000;
void doLcdMsg() {

  if ((unsigned long)(millis() - lcdScreenOneDelay) > lcdScreenOneDelaytimer) {     // delay 5 seconds

    lcdScreenOneDelay = millis();

    float temperature = getTemp();                 // create temperature variable

    pinMode(backLight, OUTPUT);            // backlight pin set as output
    digitalWrite(backLight, HIGH);         // backlight on
    lcd.begin(16, 2);                      // columns, rows
    lcd.clear();                           // start with blank screen
    lcd.setCursor(0, 0);                   // set cursor to column 0, row 0
    lcd.print("Coop Temp:");               // show "Temp"
    lcd.print (temperature) ;              // show temperature of interior coop
    lcd.print("C");                        // show "C"
    lcd.setCursor(0, 1);                   // set cursor to column 0, row 1
    lcd.print("Coop Door:");               // show "Coop Door"

    if (bottomSwitchPinVal == 0) {         // if coop door bottom switch is closed
      lcd.print("Closed");                 // display "Closed"
    }
    else  {
      lcd.print("Open");                 // if coop door bottom switch is open display "Open"

    }
  }
}

void doLcdMsg2() {

  if ((unsigned long)(millis() - lcdScreenTwoDelay) > lcdScreenTwoDelaytimer) {     // delay 5 seconds

    lcdScreenTwoDelay = millis();
  }
  pinMode(backLight, OUTPUT);            // backlight pin set as output
  digitalWrite(backLight, HIGH);         // backlight on
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);

  if (relayFan == LOW) {
    lcd.print("Cooling Fan: ON");
  }
  else {
    lcd.print("Cooling Fan: OFF");

    lcd.setCursor(0, 1);
  }

  if (relayHeat == LOW) {
    lcd.print("Heater is: ON");
  }
  else {
    lcd.print("Heater Lamp: OFF");



  }
}

shows up both dolcdmsg1 & dolcdmsg2 the first stays on for 5 seconds before flashing off. Te second message flases for about 500ms before reverting back to first message.

As Cattledog states they are in conflict with each other.

Cattledog my apologies, i have tried to implement the timer as you state, but i'm afraid i ended up with so many errors i lost track.

I think i've filled my head with so many different tutorials/videos and have so many test sketches i can no longer see the wood for the trees.

An Axe would come in handy!

Many Thanks

Alex

Once again, snippets prove to be the downfall. Here is running code which hopefully will show you a path in the forest.

//my lcd set up
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(4, 2, 6, 7, 8, 9);

unsigned long switchMillis = 0;
unsigned long interval = 5000; //5 seconds for each lcd screen
byte count = 0;

boolean firstScreenFlag = false;
boolean secondScreenFlag = false;


void setup() {

  lcd.begin(16, 2);
  lcd.print("Alternating");
  lcd.setCursor(0, 1);
  lcd.print("Display Views");

}

void loop() {

  if (millis() - switchMillis > interval)
  {
    count++;

    if (count == 1)
      firstScreenFlag = true;

    if (count == 2)
    {
      secondScreenFlag = true;
      count = 0;
    }

    switchMillis += interval; //millis();
  }

  if (firstScreenFlag)
  {
    doLcdMsg();
    firstScreenFlag = false;
  }

  if (secondScreenFlag)
  {
    doLcdMsg2();
    secondScreenFlag = false;
  }
}

void doLcdMsg() {
  lcd.clear();
  lcd.print("Alex Screen One");
  lcd.setCursor(0, 1);
  lcd.print(millis());
}

void doLcdMsg2() {
  lcd.clear();
  lcd.print("Alex Screen Two");
  lcd.setCursor(0, 1);
  lcd.print (millis());
}

Success with that of sorts, and it's much easier to follow that way.

I say of sorts as that has introduced delays in other parts of the program.

Here's the revised code including your snippets(relevant bits only as nothing else has been changed from the original posted code)

outside setup.

//set the delay between the LCD display setting using millis (not the delay function)
unsigned long switchMillis = 0;
unsigned long interval = 5000; //5 seconds for each lcd screen
byte count = 0;

boolean firstScreenFlag = false;
boolean secondScreenFlag = false;

functions in setup

//  lcd screen displays
void doLcdMsg1() {

  float temperature = getTemp();                 // create temperature variable

  pinMode(backLight, OUTPUT);            // backlight pin set as output
  digitalWrite(backLight, HIGH);         // backlight on
  lcd.begin(16, 2);                      // columns, rows
  lcd.clear();                           // start with blank screen
  lcd.setCursor(0, 0);                   // set cursor to column 0, row 0
  lcd.print("Coop Temp:");               // show "Temp"
  lcd.print (temperature) ;              // show temperature of interior coop
  lcd.print("C");                        // show "C"
  lcd.setCursor(0, 1);                   // set cursor to column 0, row 1
  lcd.print("Coop Door:");               // show "Coop Door"

  if (bottomSwitchPinVal == 0) {         // if coop door bottom switch is closed
    lcd.print("Closed");                 // display "Closed"
  }
  else  {
    lcd.print("Open");            // if coop door bottom switch is open display "Open"

  }
}

void doLcdMsg2() {


  pinMode(backLight, OUTPUT);            // backlight pin set as output
  digitalWrite(backLight, HIGH);         // backlight on
  lcd.begin(16, 2);                      // columns, rows
  lcd.clear();                            // clear the screen

  if (relayFan == LOW) {                //relayFan set to LOW as Songle Relay is on with Gnd Voltage
    lcd.print("Cooling Fan: ON");
  }                                     //display that Cooling fan is on or off
  else {
    lcd.print("Cooling Fan: OFF");
  }

  lcd.setCursor(0, 1);

  if (relayHeat == LOW) {               //relayHeater set to LOW as Songle Relay is on with Gnd Voltage
    lcd.print("Coop Heater: ON");
  }                                     //display that Cooling fan is on or off
  else {
    lcd.print("Coop Heater: OFF");
  }
}

and the loop

void loop() {

  doReadPhotoCell();
  doCoopHVACCool();
  doCoopHVACHeat();
  doCoopDoor();
  doCoopDoorLed();
  doCoopInteriorLightDusk();

  // Delays for the LCD screen messages

  if (millis() - switchMillis > interval) 
  {
    count++;

    if (count == 1)
      firstScreenFlag = true;

    if (count == 2)
    {
      secondScreenFlag = true;
      count = 0;
    }

    switchMillis += interval; //millis();
  }

  if (firstScreenFlag)
  {
    doLcdMsg1();
    firstScreenFlag = false;
  }

  if (secondScreenFlag)
  {
    doLcdMsg2();
    secondScreenFlag = false;
  }
}

It works and switches screens as predicted. However it appears to introduce delays in the other functions. Ie. when my motor runs and trips the reed switch to stop, the led does not go out as it should and the motor continues to run.

Is it due to where the functions are called in the loop? I would have thought that they are looped so quickly that it wouldnt be a factor?

I tried reloading the old code (without the second lcd screen code) and it the program reverts to running fine.

Any ideas?

pinMode(backLight, OUTPUT);            // backlight pin set as output
  digitalWrite(backLight, HIGH);         // backlight on
  lcd.begin(16, 2);                      // columns, rows

For a start, get rid of these lines in the doLcdMsg1() and doLcdMsg2() functions. The back light should be on and the display initialized one time in setup.

To trouble shoot, I would run the two screen code with one or the other of the doLcdMsg() commands commented out. Which one is causing the problem? My bet will be on msg1.

I don't believe it is the timing of the two screen display which is causing problems in other parts of your code, but possibly this line may be blocking and causing problems.

float temperature = getTemp();                 // create temperature variable

Can you write a simplified version of your code which only has the two displays and the motor/led and reed switch bits.

Well, it appeared there was a glitch in the upload, third time is a charm. The display is working nicely now thank you very much for the assistance.

It’s very much appreciated.

I just need to find out why the display doesn’t change fom Cooling Fan OFF to Cooling Fan On,
same for heating on/off.

I know the pins are changing state from high to low as the relay kicks in at the correct temperatures.

At first i thought the problem might be because i declared them const byte?

const byte relayHeat = 5;                     // heat lamp relay set to digital pin 5
const byte relayFan = 6;                      // exhaust fan relay set to digital pin 6

so i changed them back to int. That wasn’t it.

in set up

pinMode(relayHeat, OUTPUT);   //set heat lamp relay output
  digitalWrite(relayHeat, HIGH); // set the heat relay to be off when starting the program as the songle 8 channel relay is High for Gnd Voltage
  pinMode(relayFan, OUTPUT);    //set exhaust fan relay output
  digitalWrite(relayFan, HIGH); // set the fan to be off when starting the program as the songle 8 channel relay is High for Gnd Voltage

The Songle Relay i’m using is Gnd Voltage HIGH, and so that the heater and fans don’t come on at startup i set the to output and HIGH.

In the functions although maybe not written in the best way, they work. When the temp gets to 25 deg C or above the Relay clicks and lights up, same for 5 degrees and below the heater relay clicks and lights up.

void doCoopHVACHeat() {

  float temperature = getTemp();                    // create temperature variable


  if ((unsigned long)(millis() - lastTempCheckTime) > TempCheckDelay) {    // check temperature every 10 minutes
    lastTempCheckTime =  millis();


    // if cold, turn on heat lamps
    if (temperature <= 5) {                                      // if temp drops below 5c turn on heat lamp(s) relay LOW is +ve voltage on Songle relay
      digitalWrite(relayHeat, LOW);
    }
    else if (temperature > 5) {
      digitalWrite(relayHeat, HIGH);                        // if temp remains above 5c turn off heat lamp(s) relay HIGH is Gnd voltage on Songle relay
    }
    if (SerialDisplay) {
      Serial.print(" Coop Temperature:");             // print out coop temperature
      Serial.println(temperature);                    // print out the temperature
      Serial.println(" Coop Heater is on");           // print out Coop Heater is on

    }
  }
}



// if hot, turn on cooling fans

void doCoopHVACCool() {

  float temperature = getTemp();                    // create temperature variable



  if ((unsigned long)(millis() - lastTempCheckTime) > TempCheckDelay) {    // check temperature every 10 minutes
    lastTempCheckTime = millis();

    if (temperature >= 25) {                                      // if temp rises above 25c turn on cooling fan(s) relay
      digitalWrite(relayFan, LOW);
    }

    else if (temperature < 25) {
      digitalWrite(relayFan, HIGH);
    }
    if (SerialDisplay) {
      Serial.print(" Coop Temperature:");             // print out coop temperature
      Serial.println(temperature);                          // print out the temperature
      
    }
  }
}

So that leaves the LCD Display function.

void doLcdMsg2() {

  lcd.clear();                            // clear the screen

  if (relayFan == LOW) {                //relayFan set to LOW as Songle Relay is on with Gnd Voltage
    lcd.print("Cooling Fan: ON");
  }                                     //display that Cooling fan is on or off
  else {
    lcd.print("Cooling Fan: OFF");
  }

  lcd.setCursor(0, 1);

  if (relayHeat == LOW) {               //relayHeater set to LOW as Songle Relay is on with Gnd Voltage
    lcd.print("Coop Heater: ON");
  }                                     //display that Cooling fan is on or off
  else {
    lcd.print("Coop Heater: OFF");
  }
}

in the loop

doCoopHVACCool();
  doCoopHVACHeat();

and of course not forgetting the lcd screens!!!

// Delays for the LCD screen messages

  if (millis() - switchMillis > interval) 
  {
    count++;

    if (count == 1)
      firstScreenFlag = true;

    if (count == 2)
    {
      secondScreenFlag = true;
      count = 0;
    }

    switchMillis += interval; //millis();
  }

  if (firstScreenFlag)
  {
    doLcdMsg1();
    firstScreenFlag = false;
  }

  if (secondScreenFlag)
  {
    doLcdMsg2();
    secondScreenFlag = false;
  }
}

I’m pretty sure it’s glaring obvious somewhere!!!

I’m pretty sure it’s glaring obvious somewhere!!!

You are confusing the pin state with the pin number. relayFan is a pin number and will always equal 6. The same for relayHeat always equal 5. These can’t be HIGH or LOW.

You want the conditional tests on pin state sensed by digitalRead().

void doLcdMsg2() {

  lcd.clear();                            // clear the screen

  if (digitalRead(relayFan) == LOW) {                //relayFan set to LOW as Songle Relay is on with Gnd Voltage
    lcd.print("Cooling Fan: ON");
  }                                     //display that Cooling fan is on or off
  else {
    lcd.print("Cooling Fan: OFF");
  }

  lcd.setCursor(0, 1);

  if (digitalRead(relayHeat) == LOW) {               //relayHeater set to LOW as Songle Relay is on with Gnd Voltage
    lcd.print("Coop Heater: ON");
  }                                     //display that Cooling fan is on or off
  else {
    lcd.print("Coop Heater: OFF");
  }
}

In the functions although maybe not written in the best way, they work. When the temp gets to 25 deg C or above the Relay clicks and lights up, same for 5 degrees and below the heater relay clicks and lights up.

Those functions do seem strange. I think you are checking the temperature every pass through the loop when you call the functions but only take action on the temperature value every 10 minutes. Are the chickens happy with that? :slight_smile:

You could put the temperature checking within the 10 minute conditional, and the two functions can be combined into a single function doCoopHVAC(). I think the bracketing is correct in what I’m suggesting, but please check.

void doCoopHVAC() {
  if ((millis() - lastTempCheckTime) > TempCheckDelay) {    // check temperature every 10 minutes
    lastTempCheckTime =  millis();
    float temperature = getTemp();

    // if cold, turn on heat lamps
    if (temperature <= 5) {                                      // if temp drops below 5c turn on heat lamp(s) relay LOW is +ve voltage on Songle relay
      digitalWrite(relayHeat, LOW);
    }
    else if (temperature > 5) {
      digitalWrite(relayHeat, HIGH);                        // if temp remains above 5c turn off heat lamp(s) relay HIGH is Gnd voltage on Songle relay
    }
    if (SerialDisplay) {
      Serial.print(" Coop Temperature:");             // print out coop temperature
      Serial.println(temperature);                    // print out the temperature
      Serial.println(" Coop Heater is on");           // print out Coop Heater is on
    }
    // if hot, turn on cooling fans

    if (temperature >= 25) {                                      // if temp rises above 25c turn on cooling fan(s) relay
      digitalWrite(relayFan, LOW);
    }

    else if (temperature < 25) {
      digitalWrite(relayFan, HIGH);
    }
    if (SerialDisplay) {
      Serial.print(" Coop Temperature:");             // print out coop temperature
      Serial.println(temperature);                          // print out the temperature
    }
  }
}

PERFECT* thats great, i should have noticed the digitalRead, i knew i was confusing the pin state with the pin number. but couldnt see how..

Everything is working great now. The reason i left the tempCheckDelay at 10 mins is because large temp variations of such a magnitude do not happen here certainly not in ten minutes, plus if i kept checking the temp every 10 seconds the heater or fans wouldnt have much time to have an efffect on the overall temp inside the coop.

Say it was 5 degrees and the heater turned on in 2 minutes the heater would be off as it would reach >5 pretty quick, so the relay would be on and off quicker than OJ's Glove.

At least thats how i see it at the moment. I'm sure there's a way around that, but for now i'm very happen. You've been a great help indeed and i've learned quite a bit thanks. Kudos and Karma

MaverickAlex:
Say it was 5 degrees and the heater turned on in 2 minutes the heater would be off as it would reach >5 pretty quick, so the relay would be on and off quicker than OJ's Glove.

That is why all thermostats have hysteresis. It's also very easy to implement in software.