Help needed simultaneous throttle control with GPS data display

Hello everybody,
I need some advice from more knowledgeable forum members about the approach to the following.
I want to control electric motor on a boat with arduino and to display some data on 3.5" tft lcd screen.
I want to implement the following features:

  1. Throttle control by potentiometer - reading the pot value and set the PWM for controlling the motor ESC.
  2. Obtain the momentary power consumption - reading the current flow through the motor by using ACS758 current sensor.
  3. Measuring the main battery voltage
  4. Measuring the speed of the boat - using NEO-6M GPS or QUECTEL L80 GPS

I want all the data to be displayed on a 3.5" tft display:

  • throttle position
  • power consumption
  • battery voltage
  • speed over ground

The problem I face is the time for reading the gps sentence. In all examples of gps code it is around 1-2 seconds which is too much for efficient throttle control.
One approach I am considering is to use separate arduinos - one for the throttle control, current sensor and battery voltage and one for the gps data. But I have no idea how to display data from the first one on the second one which will control the tft display.

Any ideas will be appreciated.

Seems the key would be how much time can there be between throttle readings for the performance and responsiveness to be acceptable.

I would write the program to do everything except actually read the GPS, but still send a speed to the display. Have a configurable delay where the program would normally read the GPS.

By changing the delay you would know how much time you actually have to read the GPS.

I actually started first from the gps readings. I have an assembly form Arduino Mega, 3.5" inch display and NEO-6M and it really takes 1-2 seconds to read the gps sentence.
I can post the code if it will be of any help.

You will need to configure the GPS module to update at faster than the standard one second interval, and either increase the baud rate and/or disable some of the data coming form the GPS so that you only get the specific NMEA sentences that you need. I would also highly recommend using a hardware serial port for the GPS, software serial slows everything else down because of the intensive use of interrupts.

Are you using the GPS data for throttle control? If not, then the update rate of the GPS speed data has almost no relation to how often you can read the potentiometer and adjust the speed control.

I am fully aware of that, but its not the point, and there are things you can do about that. There is no point in trying a lot of code, changing the config of the GPS etc, until you know how much time you have to read the GPS.

Work out what delay the throttle reading can cope with.

Acceptable for me is throttle delay of 0.1-0.2 seconds.

OK, thats a start.

So you need to read the throttle every, say 150mS.

How long does the other code take, writing to display, controlling the motor etc take ?

Thank you for advice. Currently I am using 9600 baud rate for hardware serial (Serial 3) communication with the GPS. I will try higher baud rates.

As for the GPS data - I am not using it in any way to control the motor. But the delay to read the NMEA sentence is affecting the readings from the potentiometer.
Also I want to display the all the data on the TFT screen. And I want the throttle indication to be also displayed with minimal delay.

Sounds like you are using blocking code to read the GPS. Are you using TinyGPS or some other library, or processing the NMEA sentences with your own code?

#include <LCDWIKI_GUI.h> //Core graphics library
#include <LCDWIKI_KBV.h> //Hardware-specific library
#include <TinyGPS++.h>
TinyGPSPlus gps; // create gps object

//the definiens of 16bit mode as follow:
//if the IC model is known or the modules is unreadable,you can use this constructed function
LCDWIKI_KBV mylcd(ILI9486,40,38,39,-1,41); //model,cs,cd,wr,rd,reset

//define some colour values
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

void setup() 
{
  mylcd.Init_LCD();
  mylcd.Fill_Screen(BLACK);
  mylcd.Fill_Screen(0x0000); 
  Serial.begin(57600); // connect serial
  Serial.println("The GPS Received Signal:");
  Serial3.begin(38400); // connect gps sensor
}

void loop() 
{
  mylcd.Set_Rotation(2); 
  mylcd.Set_Text_Mode(0);
  mylcd.Set_Text_Back_colour(BLACK);
  mylcd.Set_Text_Size(4);
  mylcd.Set_Text_colour(GREEN);

  GPSFix(2000);

  Serial.println("LAT: "+String(gps.location.lat(),6));
  Serial.println("LON: "+String(gps.location.lng(),6));
  Serial.println("CRS: "+String(gps.course.deg(),1)+" deg");
  Serial.println("SPD: "+String(gps.speed.mph(),1)+" mph");
  Serial.println("N.SATS: "+String(gps.satellites.value()));

  mylcd.Print_String("LAT: "+String(gps.location.lat(),4), 0, 30);
  mylcd.Print_String("LON: "+String(gps.location.lng(),4), 0, 80);
  mylcd.Print_String("CRS: "+String(gps.course.deg(),1)+" deg", 0, 130);
  mylcd.Print_String("SPD: "+String(gps.speed.mph(),1)+" mph", 0, 180);
  mylcd.Print_String("N.SATS: "+String(gps.satellites.value()), 0, 230);

  
}

bool GPSFix(uint16_t ms)
{
  //waits a specified number of seconds for a fix, returns true for good fix

  uint8_t GPSchar;

  Serial.print(F("Wait GPS Fix "));
  Serial.print(ms/1000);
  Serial.println(F(" seconds"));

  while (millis() < millis() + ms)
  {
    if (Serial3.available() > 0)
    {
      GPSchar = Serial3.read();
      gps.encode(GPSchar);
      Serial.write(GPSchar);
    }

    if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.date.isUpdated())
    {
      return true;
    }
  }

  return false;
}

Just changed the baud rate to 38400

That code has nothing about reading a throttle.

Yes. The throttle reading will be anywhere before or after the GPSfix call, but it still has to wait for the GPSfix call to be processed. So each reading of the throttle and corresponding response will be delayed with the GPSfix delay.

Well, there's your problem. You wait for the location to be updated. Just process one or more characters:

bool GPSFix(uint16_t ms)
{
  uint8_t GPSchar;
  if (Serial3.available())
  {
    GPSchar = Serial3.read();
    Serial.write(GPSchar);
    if (gps.encode(GPSchar))
     return true;
  }
 return false;
}

If you are not keeping up with GPS input, change the 'if (Serial3.available())' to 'while (Serial3.available())'

In addition to what @johnwasser said, you also do not want to constantly update the display when the data has not changed. Save the value of the data (lat, long, course, speed, and number of satellites), and only print to the display when the data changes. It is also not necessary to print the text constantly, just the data.

You will have major problems with the display and the GPS working together, writing to the display is very slow, you will be overrunning the serial receive buffer for the GPS. I'm not at all familiar with the particular display library you are using, or whether a different library would be faster. You may in fact have throttle response problems from the display, it may be necessary to update the throttle between every print to the display.

I modified the code and changed the baud rate and the measurement frequency of the GPS module. Now the time to get a fix is around 60ms. Now I am searching for a way to disable some sentences which I don't need. I need the speed, course, Lat, Long and time.

#include <LCDWIKI_GUI.h> //Core graphics library
#include <LCDWIKI_KBV.h> //Hardware-specific library
#include <TinyGPS++.h>
TinyGPSPlus gps; // create gps object

LCDWIKI_KBV mylcd(ILI9486,40,38,39,-1,41); //model,cs,cd,wr,rd,reset

//define some color values
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
long count = 0;

void setup() 
{
  mylcd.Init_LCD();
  mylcd.Set_Rotation(2); 
  mylcd.Set_Text_Mode(0);
  mylcd.Set_Text_Back_colour(BLACK);
  mylcd.Set_Text_Size(4);
  mylcd.Set_Text_colour(GREEN);
  mylcd.Fill_Screen(BLACK);
  Serial.begin(115200); // connect serial monitor
  Serial3.begin(9600); // connect gps sensor on default baud rate
  delay(100);
  changeFrequency(); // change measurement frequency
  delay(100);
  changeBaudrate(); // change gps module baud rate 
}

void loop() 
{
  count++;
  //Counting main loops - just for debugging
  //Output on serial monitor
  Serial.println("LOOP No: "+String(count));
  //Output on LCD
  mylcd.Print_String("LOOP No: "+String(count), 0, 0);
  
  unsigned long TimeToFix=GPSFix(100);

  //Output on serial monitor
  Serial.println("TTF: "+String(TimeToFix)+" ms   ");
  Serial.println("LAT: "+String(gps.location.lat(),6)+"  ");
  Serial.println("LON: "+String(gps.location.lng(),6)+"  ");
  Serial.println("CRS: "+String(gps.course.deg(),1)+" deg   ");
  Serial.println("SPD: "+String(gps.speed.mph(),1)+" mph   ");
  Serial.println("N.SATS: "+String(gps.satellites.value())+"  ");

  //Output on LCD
  mylcd.Print_String("TTF: "+String(TimeToFix)+" ms   ", 0, 80); //Delay for GPS fix aquisition
  mylcd.Print_String("LAT: "+String(gps.location.lat(),4)+"  ", 0, 130);
  mylcd.Print_String("LON: "+String(gps.location.lng(),4)+"  ", 0, 180);
  mylcd.Print_String("CRS: "+String(gps.course.deg(),1)+" deg   ", 0, 230);
  mylcd.Print_String("SPD: "+String(gps.speed.mph(),1)+" mph   ", 0, 280);
  mylcd.Print_String("N.SATS: "+String(gps.satellites.value())+"  ", 0, 330);
  
}

unsigned long GPSFix(unsigned long ms)
{
  //waits a specified number of milliseconds for a fix, returns milliseconds for good fix

  uint8_t GPSchar;

  Serial.println("Waiting GPS Fix for "+String(ms/1000)+" seconds");
  unsigned long startFixTime=millis();

  while (millis() < startFixTime + ms)
  {
    while (Serial3.available() > 0)
    {
      GPSchar = Serial3.read();
      gps.encode(GPSchar);
      Serial.write(GPSchar);
    }

    if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.date.isUpdated())
    {
      return millis()-startFixTime;
    }
  }

  return millis()-startFixTime;
}

void changeFrequency() {
    byte packet[] = {
      0xB5, 0x62, 0x06, 0x08, 0x06, 0x00, 0x64, //Measuring Rate, hex 64 = dec 100 ms
      0x00, 0x01, 0x00, 0x01, 0x00, 0x7A, 0x12,
    };
    sendPacket(packet, sizeof(packet));
    delay(100);
}

void changeBaudrate() {
    byte baud9600[] = {
      0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00,
      0x00, 0xD0, 0x08, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00,
      0x23, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAE, 0x6A,
    };

    byte baud38400[] = {
      0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 
      0x00, 0xD0, 0x08, 0x00, 0x00, 0xF0, 0x87, 0x00, 0x00, 
      0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x24,
    };

    byte baud115200[] = {
      0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 
      0x00, 0xD0, 0x08, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 
      0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x7E,
    };


    sendPacket(baud115200, sizeof(baud115200));
    delay(100);
    Serial3.flush();
    Serial3.begin(115200); //set new baud rate on the port connected to gps module
}

void sendPacket(byte *packet, byte len) {
    for (byte i = 0; i < len; i++)
    {
        Serial3.write(packet[i]);
    }
}

Completed the code with potentiometer reading and current reading from ACS758. The actual current sensor is on its way. The time to read the GPS is around 65ms. The throttle response is acceptable.

#include <LCDWIKI_GUI.h> //Core graphics library
#include <LCDWIKI_KBV.h> //Hardware-specific library
#include <TinyGPS++.h>
TinyGPSPlus gps; // create gps object

#include <Servo.h>
Servo ESC;     // create servo object to control the ESC
float POTValue;  // value from the analog pin

LCDWIKI_KBV mylcd(ILI9486,40,38,39,-1,41); //model,cs,cd,wr,rd,reset

//define some colour values
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
long count = 0;
float ACSValue;
float arrACSValue[10];
float AMPS;

void setup() 
{
  mylcd.Init_LCD();
  mylcd.Set_Rotation(2); 
  mylcd.Set_Text_Mode(0);
  mylcd.Set_Text_Back_colour(BLACK);
  mylcd.Set_Text_Size(4);
  mylcd.Set_Text_colour(GREEN);
  mylcd.Fill_Screen(BLACK);
  Serial.begin(115200); // connect serial monitor
  Serial3.begin(9600); // connect gps sensor on default baud rate
  delay(100);
  changeFrequency(); // change measurement frequency
  delay(100);
  changeBaudrate(); // change gps module baud rate 
  // Attach the ESC on pin 9
  ESC.attach(9,1000,2000); // (pin, min pulse width, max pulse width in microseconds) 
}

void loop() 
{
  String strLine;

  //Measure current flow through motor
  ACSValue = analogRead(A0)*5/1023; //voltage read from ACS758
  //ACS758ECB-200B-PFF-T
  //Sensitivity = 10 mV/A
  //ACS voltage offset = 0.5*Vcc = 2.5 V
  float average_voltage = avgACSValue(ACSValue); //Stores the last measurement in array and retrieves average
  //Current = (ACSVOffset – (Arduino measured analog reading)) / Sensitivity
  float current=(2.5-average_voltage)/10;
  strLine = "CURR: "+String(current,1)+" A  ";
  Serial.println(strLine); //Output on serial monitor
  mylcd.Print_String(strLine, 0, 0); //Output on LCD

  //Measure potentiometer position
  POTValue = analogRead(A3);   // reads the value of the potentiometer (value between 0 and 1023)

  POTValue = map(POTValue, 0, 1023, 0, 180);   // scale it to use it with the servo library (value between 0 and 180)
  ESC.write(POTValue);    // Send the signal to the ESC
  strLine = "THROT: "+String(100*POTValue/180,1)+" %    ";
  Serial.println(strLine); //Output on serial monitor
  mylcd.Print_String(strLine, 0, 50); //Output on LCD
  
  unsigned long TimeToFix=GPSFix(80);

  strLine = "TTF: "+String(TimeToFix)+" ms   ";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 100); //Output on LCD
  strLine = "LOCATION:";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 150); //Output on LCD
  strLine = String(gps.location.rawLat().negative ? "S " : "N ")+String(gps.location.lat(),5)+"  ";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 200); //Output on LCD
  strLine = String(gps.location.rawLng().negative ? "W " : "E ")+String(gps.location.lng(),5)+"  ";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 250); //Output on LCD
  strLine = "CRS: "+String(gps.course.deg(),1)+" deg   ";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 300); //Output on LCD
  strLine = "SPD: "+String(gps.speed.mph(),1)+" mph   ";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 350); //Output on LCD
  strLine = "N.SATS: "+String(gps.satellites.value())+"   ";
    Serial.println(strLine); //Output on serial monitor
    mylcd.Print_String(strLine, 0, 400); //Output on LCD
}

unsigned long GPSFix(unsigned long ms)
{
  //waits a specified number of milliseconds for a fix, returns milliseconds for good fix

  uint8_t GPSchar;

  Serial.println("Waiting GPS Fix for "+String(ms/1000)+" seconds");
  unsigned long startFixTime=millis();

  while (millis() < startFixTime + ms)
  {
    while (Serial3.available() > 0)
    {
      GPSchar = Serial3.read();
      gps.encode(GPSchar);
      Serial.write(GPSchar);
    }

    if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.date.isUpdated())
    {
      return millis()-startFixTime;
    }
  }

  return millis()-startFixTime;
}

void changeFrequency() {
    byte packet[] = {
      0xB5, 0x62, 0x06, 0x08, 0x06, 0x00, 0x64, //Measuring Rate, hex 64 = dec 100 ms
      0x00, 0x01, 0x00, 0x01, 0x00, 0x7A, 0x12,
    };
    sendPacket(packet, sizeof(packet));
    delay(100);
}

void changeBaudrate() {
    byte baud9600[] = {
      0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00,
      0x00, 0xD0, 0x08, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00,
      0x23, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAE, 0x6A,
    };

    byte baud38400[] = {
      0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 
      0x00, 0xD0, 0x08, 0x00, 0x00, 0xF0, 0x87, 0x00, 0x00, 
      0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x24,
    };

    byte baud115200[] = {
      0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 
      0x00, 0xD0, 0x08, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 
      0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x7E,
    };


    sendPacket(baud115200, sizeof(baud115200));
    delay(100);
    Serial3.flush();
    Serial3.begin(115200); //set new baud rate on the port connected to gps module
}

void sendPacket(byte *packet, byte len) {
    for (byte i = 0; i < len; i++)
    {
        Serial3.write(packet[i]);
    }
}

float avgACSValue(float newValue)
{
  double sumACSValue = 0.0;
  for(int i = 10 - 1; i > 0; i--)
  {
    arrACSValue[i] = arrACSValue[i-1];
    sumACSValue += arrACSValue[i];
  }
  arrACSValue[0] = newValue;
  return sumACSValue/10; //return average of all elements

}

You don't have to use a GPS library. It's pretty easy to just read each sentence, identify it and extract the fields you want. Once you've got the parameters you want you can stop reading the sentences. I use software serial to receive the GPS output. I start software serial immediately before I want to get the GPS data and end it when I've got what I want.

Would you share some code? I am not so proficient with the serial reading. What are you looking for in the serial? How you decide when the sentence started and finished?

The coding is easy--that's not your problem. You need a plan.

There is a function to read serial until a certain character is received. So you can read until the new line appears and that's the end of the sentence. Then you examine the first six characters. If I'm wanting the RMC sentence then the first six chars will be "$GPRMC". Then I can extract lat, long, time. That's just an example. Don't focus on that, just trust that it can be done.

As others have said, you must know how much time it takes to drive each of your attached modules.

Why not draw a flow chart showing all the tasks that you want to do in the sequence you want? Make it high level (no code). For now, allow yourself 1000 ms to accomplish everything, say 200ms to get your GPS fields and 700ms to do everything else. Then it repeats. The coding's easy once you've got a plan.

The following shows how to extract the VTG sentence from the GPS output:

  haveVTG = false;
  Serial.begin(9600);

while !haveVTG {
sentence = Serial.readStringUntil('\n') + '\n'; //restore new line char
  if (sentence.startsWith("$GPVTG")) {
    VTGsentence = sentence;
    haveVTG = true;
    Serial.end; //not interested in rest of GPS output
  }
}

The following shows how to extract the speed field from the VTG sentence:

void GetSpeed() {
  //the speed field is the 8th in the VTG sentence;
  //need to determine positions of 7th & 8th commas
  int posAlpha, posBeta = -1;
  String speedWithoutDecimalPoint;
  boolean haveSpeed = true;

  for (int i = 1; i <= 8; i++) {
    posAlpha = posBeta;
    posBeta = VTGsentence.indexOf(',', posAlpha + 1);
  }
  //get speed as a string, will be in kph with three decimal places
  speedField = VTGsentence.substring(posAlpha + 1, posBeta);

  if (speedField != "") //a null value indiates no fix achieved
  {
    haveSpeed = true;
    speedAsString = speedField;
  }