LCD screen changes not quick enough

Hi all,

My first post on the forum :slight_smile:
I have been using the arduino (duemilanove) for a while now. Great piece of kit. I recently programmed some gps code that is displayed on a lcd screen. I use a button (5V --> switch --> resistor to gnd and other wire to input pin) to switch between the various screens. However, if I press the switch, I want to change the screen immediately and not like it is now, after a few seconds of holding the switch. I quit sure it has something to do with the fact that is caught in the loop and that I have to hold the button till the loop passes by. I probably need to think outside the box on this one but I have trouble finding 'the edge of the box'.

Here's the code:

// Include libraries
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <LiquidCrystal.h>


#define rxPIN 1             //Serial info arrives here
#define txPIN 2             //Not used

const int buttonPin = 11;   //Button input pin

int screen = 0;             //Start screens of with No 0; the startup screen
int buttonState = 0;        //record button state
int lastButtonState = 0;    //record last button state

double lat = 0;             //Get lat and lon as a double (instead of float)
double lon = 0;

long lastDebounceTime = 0;  //debounce timer for switch
long debounceDelay = 50;    //the delay to reduce noise

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

TinyGPS gps;

static bool feedgps();
static void gpsdump(TinyGPS &gps);

SoftwareSerial ss(0, 1);


void setup(){
  
  lcd.begin(16,2);     //LCD is 16 char by 2 lines
  ss.begin(4800);      //Make connection to GPS serial
  Serial.begin(9600);  //Should it be necessary to debug anything we start the serial
  
}


void loop(){  
  
  screenChange();
    
  bool newdata = false;
  unsigned long start = millis();
  
  // Every second we print an update
  while (millis() - start < 1000){
    
    if (feedgps())
    newdata = true;
  }
  
  gpsdump(gps);
  
}


//Define the gpsdump function
static void gpsdump(TinyGPS &gps){
  
  float flat, flon;
  
  int year;
  byte month, day, hour, minute, second, hundredths;
  
  unsigned long fix_age;
  gps.f_get_position(&flat, &flon, &fix_age);  //Get out lat, lon and the age
  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths);
  
  //If the fix is invalid, we want to print the initializing (startup) screen
  if (fix_age == TinyGPS::GPS_INVALID_AGE){
    printStartUp();
  }
  
  //If the fix gets to old, we want to know by showing the lost sats screen
  else if (fix_age > 5000){
    lostSats();
  }
  
  //If the fix is valid and young enough, AND no other screen option has been chosen yet, say that option are available
  else if (fix_age != TinyGPS::GPS_INVALID_AGE && fix_age < 5000 && screen != 1 && screen != 2 && screen != 3 && screen != 4){
    chooseOption();
  }
  
  //If we push the button and the value is now 1, show coords
  else if (screen == 1){
    printCoordinates(flat, flon);
  }
  
  //If we push the button and the value is now 2, show speed
  else if (screen == 2){
    printSpeed();
  }
  
  //If we push the button and the value is now 3, show date and time
  else if (screen == 3){
    printDateTime(year, month, day, hour, minute, second);
  }
  
  //If we push the button and the value is now 4, show the 'number of satellite' screen
  else if (screen == 4){
    printSat();
  }
  
}


//Define the option screen
void chooseOption(){
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Data available,");
  lcd.setCursor(0, 1);
  lcd.print("push button");
  
}


//Define the coords screen (screen 1)
void printCoordinates(float flat, float flon){
  
  //Value are printen in a N/S and E/W format, rather than a +ve or -ve value
  
  double slat;  //declare new doubles in case lat or lon are negative
  double wlon;
  
  lat = flat;   //Assign value to the doubles
  lon = flon;
  
  lcd.clear();
  lcd.setCursor(0, 0);
  
  if (lat > 0){                //print of the lat if is a +ve value
    lcd.print("Lat:  ");
    if (lat < 10){             //Usualy, the N/S format comes in xx.xxxx
      lcd.print("0");          //We want a single 0 upfront when N/S is only a single digit
    }
    lcd.print(lat, 5);
    lcd.setCursor(15, 0);
    lcd.print("N");            //+ve are N(orth)
  }
  else{                        //multipli by -1 for South value
    slat = (lat * -1);
    lcd.print("Lat:  ");
    if (slat < 10){
      lcd.print("0");
    }
    lcd.print(slat, 5);
    lcd.setCursor(15, 0);
    lcd.print("S");            //initial -ve are S(outh)
  }
  
  lcd.setCursor(0, 1);
  
  if (lon > 0){                //print of if it is a +ve value
    lcd.print("Lon: ");
    if (lon < 10){             //Usualy, the E/W format comes in xxx.xxxxx
      lcd.print("00");         //We want double 0 upfront if E/W is only a single digit
    }
    else if (lon < 100){
      lcd.print("0");          //We want a single 0 upfront if E/W is two digits
    }
    lcd.print(lon, 5);
    lcd.setCursor(15, 1);
    lcd.print("E");            //+ve are E(ast)
  }
  else{                        // multipli by -1 for a West value
    wlon = (lon * -1);
    lcd.print("Lon: ");
    if (wlon < 10){
      lcd.print("00");
    }
    else if (wlon < 100){
      lcd.print("0");
    }
    lcd.print(wlon, 5);
    lcd.setCursor(15, 1);
    lcd.print("W");            //initial -ve are W(est)
  }
  
  feedgps();
  
}


//Define the speed screen (screen 2)
void printSpeed(){
  
  double currentSpd = gps.f_speed_kmph();
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Speed: ");
  lcd.print(currentSpd, 1);
  lcd.print("km/h");
  
  feedgps();
  
}


//Define the date and time screen (screen 3)
void printDateTime(int year, byte byteMonth, byte byteDay, byte byteHour, byte byteMinute, byte byteSecond){
  
  int timeDiff = 2;
  
  int month = (int) byteMonth;
  int day = (int) byteDay;
  int hour = (int) byteHour;
  int minute = (int) byteMinute;
  int second = (int) byteSecond;

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Date: ");
  if (day < 10){
    lcd.print("0");
  }
  lcd.print(day);
  lcd.print("/");
  
  if (month < 10){
    lcd.print("0");
  }
  lcd.print(month);
  lcd.print("/");
  lcd.print(year);
  
  lcd.setCursor(0, 1);
    
  
  int localHour = hour + timeDiff;
  
  if (localHour < 0){
    localHour = localHour + 24;
  }
  
  lcd.print("Time: ");
  if (localHour < 10){
    lcd.print("0");
  }
  lcd.print(localHour);
  lcd.print(":");
  
  if (minute < 10){
    lcd.print("0");
  }
  lcd.print(minute);
  
  feedgps();
  
}


//Define the 'number of satellite' screen (screen 4)
void printSat(){
  
  int sat = gps.satellites();
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Satellites:  ");
  lcd.print(sat);

}


//Define the 'feedgps' function
static bool feedgps(){
  
  while (ss.available()){
    
    if (gps.encode(ss.read()))
      return true;
  }
  return false;

}


//Define the screen number 
void screenChange(){
  
  int reading = digitalRead(buttonPin);  //Read button state
  
  if (reading != lastButtonState){                     //Check whether it has changed due to noise or switching
    lastDebounceTime = millis();
  }
  
  if ((millis() - lastDebounceTime) > debounceDelay){  //Check whether it is noise or a switch
    if (reading != buttonState){
      buttonState = reading;
      if (buttonState == HIGH){
        if (screen < 4)                                //Check if 'screen' is below 4 since 4 is highest
        screen++;                                      //Yes, add 1 to the 'screen'
        else {
        screen = 1;                                    //Otherwise return '1' as '0' is the setup screen
        }
      }
    }
  }
  
  
  lastButtonState = reading;

}


//Define the startup screen
void printStartUp(){
    
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Initializing,");
  lcd.setCursor(0, 1);
  lcd.print("please wait.....");
  
}


//Define the lost Sats screen
void lostSats(){
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Satellites lost,");
  lcd.setCursor(0, 1);
  lcd.print("please wait.....");
  
}

It might not be the best code in the world but at least it works :wink:
I suspect I have to use either a 'while' function to read a buttonpress and process this immediately or the loop must be setup entirely different.
The loop starts of with calling for the void screenChange which processes button inputs and the gets the GPS data and processes it. As I said, all this probably is the cause of the whole delay as it takes a while to process all of the functions called forward...

Hope someone can shine some light on this...

How long does it take you main loop() to process? You can put a timer in it if you need to, to see.

Hi Jackwp,

Using:

 unsigned int time = 0;
   time = micros();
   
  <-- the main loop as it is shown earlier -->
   
   time = micros() - time;
   
   Serial.print("Screen displayed ");
   Serial.print(screen);
   Serial.print("; time in micros ");
   Serial.println(time, DEC);
   delay(1000);

I got these values out of the Serial monitor:
Screen displayed 0; time in micros 29636
Screen displayed 0; time in micros 37356
Screen displayed 0; time in micros 29416
Screen displayed 0; time in micros 12072
Screen displayed 0; time in micros 5220
Screen displayed 0; time in micros 28144
Screen displayed 1; time in micros 57368
Screen displayed 1; time in micros 31568
Screen displayed 1; time in micros 32992
Screen displayed 1; time in micros 33808
Screen displayed 1; time in micros 32176
Screen displayed 2; time in micros 61124
Screen displayed 2; time in micros 60840
Screen displayed 2; time in micros 24944
Screen displayed 2; time in micros 23752
Screen displayed 2; time in micros 58000
Screen displayed 2; time in micros 24352
Screen displayed 2; time in micros 24252
Screen displayed 3; time in micros 29264
Screen displayed 3; time in micros 28984
Screen displayed 3; time in micros 22224
Screen displayed 3; time in micros 29040
Screen displayed 3; time in micros 28972
Screen displayed 3; time in micros 25236
Screen displayed 3; time in micros 29480
Screen displayed 3; time in micros 29156
Screen displayed 4; time in micros 25180
Screen displayed 4; time in micros 25276
Screen displayed 4; time in micros 55684
Screen displayed 4; time in micros 24628
Screen displayed 4; time in micros 54832
Screen displayed 4; time in micros 54856
Screen displayed 4; time in micros 25144
Screen displayed 4; time in micros 24420
Screen displayed 4; time in micros 56152
Screen displayed 4; time in micros 25192

The times do vary quit a bit although it is not the time I have to wait until the screen finally changes. I want it to be a quick button press and a screen change. Maybe I need to interrupt whatever is processing, change the screen and then just loop the loop. Don't know how to do such code though...

Maybe int is not big enough.
change

unsigned int time = 0;

to

unsigned long time = 0;

and see what the numbers are.

I see you have
delay(1000); in the code. That is going to give you a one second delay just in it's self. Remove it.

I thought I give myself a shot to actually copy the code out of the serial monitor. As the delay is outside de time start till time display I thought it wouldn't count towards the total time...

However, never discard good advise and I now came up with this:
Screen displayed 0; time in micros 1013576
Screen displayed 0; time in micros 1012888
Screen displayed 0; time in micros 1011872
Screen displayed 0; time in micros 1011872
Screen displayed 0; time in micros 1012892
Screen displayed 0; time in micros 1058708
Screen displayed 0; time in micros 1060028
Screen displayed 0; time in micros 1011264
Screen displayed 0; time in micros 1059280
Screen displayed 0; time in micros 1011624
Screen displayed 0; time in micros 1057644
Screen displayed 0; time in micros 1012220
Screen displayed 1; time in micros 1014308
Screen displayed 1; time in micros 1014940
Screen displayed 1; time in micros 1025800
Screen displayed 1; time in micros 1014316
Screen displayed 1; time in micros 1015960
Screen displayed 1; time in micros 1077536
Screen displayed 1; time in micros 1014876
Screen displayed 1; time in micros 1015964
Screen displayed 2; time in micros 1043860
Screen displayed 2; time in micros 1043860
Screen displayed 2; time in micros 1029532
Screen displayed 2; time in micros 1035848
Screen displayed 2; time in micros 1007420
Screen displayed 2; time in micros 1046536
Screen displayed 2; time in micros 1007980
Screen displayed 2; time in micros 1006736
Screen displayed 3; time in micros 1012760
Screen displayed 3; time in micros 1027208
Screen displayed 3; time in micros 1012920
Screen displayed 3; time in micros 1012884
Screen displayed 3; time in micros 1065952
Screen displayed 3; time in micros 1012128
Screen displayed 3; time in micros 1011864
Screen displayed 3; time in micros 1070632
Screen displayed 3; time in micros 1071860
Screen displayed 4; time in micros 1038440
Screen displayed 4; time in micros 1012912
Screen displayed 4; time in micros 1006856
Screen displayed 4; time in micros 1037968
Screen displayed 4; time in micros 1008368
Screen displayed 4; time in micros 1006740
Screen displayed 4; time in micros 1007780
Screen displayed 4; time in micros 1008892
Screen displayed 4; time in micros 1007668

So it takes about a second to process the loop I guess since it also takes time to write the times to the serial monitor (115200 baud selected). This is more or less indeed the time I have to press the button in order to change the screen (1,5 secs might be a closer value but human timing is not accurate :wink: )

However, I just want it do go past the debounceDelay value when that button is pressed and then instantly change the screen. Still unable to locate the end of the box I'm thinking inside in...

Inside or outside, look for where the delay is. that loop( ) should take less that 1/1000 of a second. There is a long delay in there somewhere.

Implementing the timer in every loop I get this:

ScrnCnge time: 12
FeedGps: 28
FeedGps: 33046356
FeedGps: 2648
FeedGps: 33047736
FeedGps: 7300
FeedGps: 33049124
FeedGps: 10936
FeedGps: 33050564
FeedGps: 14548
FeedGps: 33053276
FeedGps: 19528
FeedGps: 33058184

FeedGps: 33710048
FeedGps: 385572
FeedGps: 33711492
FeedGps: 387188
FeedGps: 33712936
FeedGps: 388804
FeedGps: 33714380
FeedGps: 390420
FeedGps: 33715828
FeedGps: 392032
Coords: 15508
GpsDump: 16432
Loop: 1427296

It looks like calling feedgps(); at the end of printing the lat/lon etc. takes up a bit of time. Having deleted those, the script still runs fine bit the loop time remains the same:

Loop: 1014692
Loop: 1012220
Loop: 1059924
Loop: 1057572
Loop: 1011412

Maybe it is the fact that I have it printing an update every 1 second in that 'while' loop...?

That could be it. Why don't you try speeding that up to ever 1/10 second?

yes, during this second, the button state is not checked,

  // Every second we print an update
  while (millis() - start < 1000){
    
    if (feedgps())
    newdata = true;
  }

that means that you may have to keep the button pressed during 1s, depending on when it is pressed, and if you add the 'dumpgps()' time , it might be more than 1s (which sometimes seems to last.... "a few seconds" :wink: )

It was indeed the 'while' function that did it. It had the delay of a 1000 millis in there because the gps only sends an update every 1 second so there is no need to check in between.

I now have to stop the lcd flickering, that shouldn't be to hard. It clears itself every time through the loop. I just need to change that to the first time the screen is changed. After that, only the value needs updating, not the whole screen.

Thanks guys for looking into this!

It was indeed the 'while' function that did it. It had the delay of a 1000 millis in there because the gps only sends an update every 1 second so there is no need to check in between.

While the GPS only sends data once a second, that does NOT mean that you have to wait that whole second to read the data. Read whatever is present (99.9% of the time that will be nothing) on every pass through loop, and accumulate it (as feedgps() does). When the sentence is complete, use the data. You are not checking that now.

Hi PaulS,

I get what you're saying but I'm lacking the skills to code that at the moment as I'm still learning C++.
Just as I tried to use Switch --> Case x, Case x to display the lcd screens but I couldn't get my head around it. Well, that is, not around how to do the values it needs to print. The switch function itself was not the problem.

If you can point me in the right direction with some code great, otherwise I leave it for now and wait until my skills have gone better :slight_smile:

Thanks,
Arjan

I get what you're saying but I'm lacking the skills to code that at the moment as I'm still learning C++.

Change the code as follows:
~~ while (millis() - start < 1000){~~

if (feedgps())
newdata = true;
~~ }~~
There, that was easy, wasn't it?

That was very easy indeed. Thanks a lot. Learning every day :slight_smile:

arjan_hes:
I now have to stop the lcd flickering, that shouldn't be to hard. It clears itself every time through the loop. I just need to change that to the first time the screen is changed. After that, only the value needs updating, not the whole screen.

If a value can be different lengths ('100' vs '10' vs '1') then you can end up with leftover characters littering the screen. You may wish to write a function that takes a string and a maximum length, which then prints the string plus however many blank spaces are required to clean up the rest of the space (see strlen()).