1

I am using arduino IDE. I have an SD logger program writing x and y coords to one file on SD. The x/y point pair is converted from GPS Decimal Degrees, and the saved x/y pair is per line with line data separated by a comma. X is always the first number set, then comma, then y. Max line is 8 bytes. All x/y number points written will be greater than zero (0). The plan is to redraw the x/y coords to a tft (thereby redrawing the saved map)from SD or from a buffer of string obtained from the SD file. there is only one SD file containing all the x/y pairs. File size is up to 400,000 bytes (about 10 hours of saved data at 1 x/y pair per second) which has not been an issue.

I can easily plot the realtime GPS converted to x/y data points to TFT and get a cool map of my trail.TFT Map IS NOT to scale, and that is not important. What is import is being able to redraw the x/y points to a cleared TFT map screen, from SD or a buffer. I have spent much time reading on this site and others, but can't seem to figure out the code.

I have no issue seeing these x/y lines on SD, or from a buffer, when written to serial monitor.

What I can't figure out is how to read the SD file line by line, parse the characters by using the comma delimiter, and convert them to x and y int, then write to tft.

any GPS file saved to SD, or from SD to a buffer, EACH line looks like this: 230,123

possible range for x is always 3 digits, possible range for y could be 1-239.

anyone have any thoughts? I will post my code if needed, but how I get my data file from the SD is a simple call to read SD (or buffer...)

EDIT here is my code: in the loop, you can see where a button press call the SD or buffer data, which needs to be parsed and converted to new SDx and new SDy...

#include <SD.h>
#include <TinyGPS.h>
#include <Adafruit_GFX.h>
#include "Adafruit_ILI9341.h"

#define TFT_CS    8      // TFT CS  pin is connected to arduino pin 8
#define TFT_RST   9      // TFT RST pin is connected to arduino pin 9
#define TFT_DC    10     // TFT DC  pin is connected to arduino pin 10

// initialize ILI9341 TFT library
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

TinyGPS gps;

bool  oneTimePosition = true;
int long oneTimeLat, oneTimeLong;

unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 1000;

#define  R 6367000 // Radius earth in m

int long x ;
int long y ;
int long homex;
int long homey;

int dx;
int dy;

//not used yet
#define MAP_WIDTH 210
#define MAP_HEIGHT 240
#define MAP_CENTERX 215
#define MAP_CENTERY 120

//map x/y boundaries
#define MAP_X_MIN 110
#define MAP_X_MAX 320
#define MAP_Y_MIN 0
#define MAP_Y_MAX 240

#define width 210
#define height 240


#define  SCALINGBUTTON 5
int counter = 0;
int scaleButtonState = LOW;



File myFile;


//not used
char str[7];
const char s[2] = ",";
char *token;

String buffer;



void clearMap() {
  tft.fillRoundRect(110, 0, 210, 240, 10, ILI9341_BLACK);
}



void drawMap() { //draw a map w/dotted cross
  tft.drawRoundRect(110, 0, 210, 240, 10, ILI9341_GREEN);
  for (int x = 55; x < 160; x++) {
    tft.drawPixel((2 * x), 120, ILI9341_GREEN);
  }
  for (int y = 0; y < 120; y++) {
    tft.drawPixel(215, (2 * y), ILI9341_GREEN);
  }
  tft.setCursor(210, 2);
  tft.fillRect(210, 2, 10, 14, ILI9341_BLACK);
  tft.setTextColor(ILI9341_GREEN);  tft.setTextSize(2);
  tft.println("N");
}



void drawSetup() {
  tft.setCursor(0, 200);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
  tft.print("ZOOM I/O : ");
  tft.setCursor(0, 215);
  tft.print("RADIANS X: ");
  tft.setCursor(0, 230);
  tft.print("RADIANS y: ");

  tft.setCursor(0, 165);
  tft.print("X: ");
  tft.setCursor(0, 180);
  tft.print("Y: ");

  tft.setCursor(0, 45);
  tft.print("SATELITES: ");
  tft.setCursor(0, 60);
  tft.print("PRECISION: ");
}



void setup()
{

  Serial.begin(115200);
  Serial1.begin(9600);//my GPS device uses 9600 baud, using teensy Serial1


  Serial.print("Initializing SD card...");

  // see if the card is present and can be initialized:
  if (!SD.begin(BUILTIN_SDCARD)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");
  Serial.println("x,y");
  SD.remove("GPS");//remove prior GPS SD file

  myFile = SD.open("GPS", FILE_WRITE);


  //pinMode(scaleButtonPin, INPUT);
  pinMode(SCALINGBUTTON, INPUT_PULLDOWN);

  tft.begin();
  tft.setRotation(3);
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(40, 100);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(3);
  tft.println("GPS ACTIVATED");

  delay(2000);
  tft.fillScreen(ILI9341_BLACK);

  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_GREEN);  tft.setTextSize(1);
  tft.print("CURRENT POSITION: ");
  tft.setCursor(0, 90);
  tft.print("INITIAL POSITION: ");
  tft.setCursor(0, 150);
  tft.print("CARTESIAN COORD: ");


  //clear radians tft X Y ...
  tft.fillRect(60, 200, 40, 60, ILI9341_BLACK);
  tft.setCursor(0, 200);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
  tft.print("ZOOM I/O : ");
  tft.setCursor(0, 215);
  tft.print("RADIANS X: ");
  tft.setCursor(0, 230);
  tft.print("RADIANS y: ");


  drawMap();

  startMillis = millis();
}

void loop() {

  scaleButtonState = digitalRead(SCALINGBUTTON);

  if (scaleButtonState == HIGH) {
    counter++;
    clearMap();
    drawMap();

    // re-open the file for reading:
    myFile = SD.open("GPS");
    if (myFile) {
      //Serial.println("GPS:");

      drawMap();

      // read from the file until there's nothing else in it:
      while (myFile.available()) {
        //Serial.write(myFile.read());

        //while (myFile.available()) {
        buffer = myFile.readStringUntil('\n');

        Serial.println(buffer); //debugging
        /*
          this is where I need to parse each line and convert to
          int and recreate the x/y positions from SD or buffer
          //do some action here with recreated x/y for EACH line parsed
          //zoom in or out with new x/y
          // if (((SDdx > MAP_X_MIN) && (SDdx < MAP_X_MAX)) && ((SDdy > MAP_Y_MIN) && (SDdy < MAP_Y_MAX))) {
          // tft.drawPixel( SDdx, SDdy, ILI9341_WHITE);
        */
      }

      // close the file:
      myFile.close();
    } else {
      // if the file didn't open, print an error:
      Serial.println("error opening test.txt");
    }

    if (counter > 3) {
      counter = 0;
    }
    tft.fillRect(60, 200, 40, 60, ILI9341_BLACK);
    tft.setCursor(0, 200);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("ZOOM I/O : ");//
    tft.println(counter);

    scaleButtonState = LOW;
  }


  bool newData = false;
  unsigned long chars;
  unsigned short sentences, failed;

  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (Serial1.available())
    {
      char c = Serial1.read();
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData) {

    float flat, flon;
    unsigned long age;
    gps.f_get_position(&flat, &flon, &age);

    tft.fillRect(20, 15, 90, 10, ILI9341_BLACK);
    tft.setCursor(0, 15);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("LAT: ");
    tft.println(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 6);

    tft.fillRect(20, 30, 90, 10, ILI9341_BLACK);
    tft.setCursor(0, 30);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("LON: ");
    tft.println(flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 6);

    //record initial home position and display data on tft
    if (oneTimePosition == true)  {
      tft.setCursor(0, 105);
      tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
      tft.print("LAT: ");
      tft.println(flat, 6);
      tft.setCursor(0, 120);
      tft.print("LON: ");
      tft.println(flon, 6);

      float radiansX = ( flat * (asin(1)) / 90 );
      float radiansY = ( flon * (asin(1)) / 90 );

      homex = R * radiansY * cos(radiansX);
      homey = R * radiansX;

      oneTimePosition = false;
    }

    //for real time GPS to x/y for TFT draw pixel
    float radiansX = ( flat * (asin(1)) / 90 );
    float radiansY = ( flon * (asin(1)) / 90 );

    x = R * radiansY * cos(radiansX);
    y = R * radiansX;

    if (homex != x) {
      dx = (x - homex);
      dx = dx + 215;
    }

    if (homey != y) {
      dy = (y - homey)  ;
      dy = -dy + 120;//reverse N/S here
    }


    myFile = SD.open("GPS", FILE_WRITE);

    // if the file opened okay, write to it:
    if (myFile) {
      //Serial.print("Writing to GPS...");
      //myFile.print ("dx=");
      //myFile.print (", ");
      myFile.print (dx);
      myFile.print (", ");
      //myFile.print ("dy=");
      myFile.println(dy);
      //myFile.println(" ");

      // close the file:
      myFile.close();
      // Serial.println("done.");
    } else {
      // if the file didn't open, print an error:
      Serial.println("error opening test.txt");
    }

    //draw the newest x,y coord pixel every second 
    //only draw pixels within map boundaries
    if (((dx > MAP_X_MIN) && (dx < MAP_X_MAX)) && ((dy > MAP_Y_MIN) && (dy < MAP_Y_MAX))) {
      tft.drawPixel( dx, dy, ILI9341_WHITE);
    }


    //clear radians tft X Y ...
    tft.fillRect(60, 200, 40, 60, ILI9341_BLACK);
    tft.setCursor(0, 200);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("ZOOM I/O : ");
    tft.println(counter);
    tft.setCursor(0, 215);
    tft.print("RADIANS X: ");
    tft.println(radiansX);
    tft.setCursor(0, 230);
    tft.print("RADIANS y: ");
    tft.println(radiansY);

    //update and show current X Y ...
    tft.fillRect(10, 165, 90, 30, ILI9341_BLACK);
    tft.setCursor(0, 165);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("X: ");
    tft.println(dx);
    tft.setCursor(0, 180);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("Y: ");
    tft.println(dy);

    //update and show precision and satellites
    tft.fillRect(60, 45, 40, 10, ILI9341_BLACK);
    tft.setCursor(0, 45);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("SATELITES: ");
    tft.println(gps.satellites());
    tft.fillRect(60, 60, 40, 10, ILI9341_BLACK);
    tft.setCursor(0, 60);
    tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
    tft.print("PRECISION: ");
    tft.println(gps.hdop());
  }
}

  /*
      //for scaling
        if (homex != x) {
          dx = (x - homex);
          //calculate dx/dy here for range
          if (counter == 0) { // ~  50 meter radius
            dx =  dx;
          }
          else if (counter == 1) { // ~  5 mile map radius
            dx =  dx / 100;
          }
          else if (counter == 2) { // ~ 15 mile map radius
            dx =  dx / 300;
          }
          else if (counter == 3) { // ~ 30 mile map radius
            dx =  dx / 500;
          }
          dx =  dx + 215;
        }


        if (homey != y) {
          dy = (y - homey);
          //calculate dx/dy here for range
          if (counter == 0) { // ~  50 meter radius
            dy =  dy;
          }
          else if (counter == 1) { // ~  5 mile map radius
            dy =  dy / 100;
          }
          else if (counter == 2) { // ~ 15 mile map radius
            dy =  dy / 300;
          }
          else if (counter == 3) { // ~ 30 mile map radius
            dy =  dy / 500;
          }
          dy = -dy + 120;//'-' to reverse N / S map plot
        }
    */
XRAD
  • 17
  • 3

2 Answers2

0

Thank you Heto! you wrote a very smooth piece of code which works like a champ. I did not realize that buffer = myFile.readStringUntil('\n'); was producing a "line by line" string(s). I thought it was one long string which had to be parsed....And I kept trying to write char = [sizeof(buffer)] which of course does not work (which leads to not thinking of pointing to each string created either). The function now scales and redraws very nicely. I have added a larger TFT hence the HX8357. I am glad I came here for help! Excellent website.

// re-open the file for reading:
myFile = SD.open("GPS");
if (myFile) {
  //Serial.println("GPS:");

  drawMap();

  // read from the file until there's nothing else in it:
  while (myFile.available()) {
    //Serial.write(myFile.read());

    buffer = myFile.readStringUntil('\n');

    Serial.println(buffer); //debugging compare to scaled x/y

    char *x_str;
    x_str = strtok((char*)buffer.c_str(), ",");
    int SDdx = atoi(x_str);

    char *y_str;
    y_str = strtok(NULL, ",");
    int SDdy = atoi(y_str);

    //for scaling
    if (counter == 1) { // ~  5 mile map radius
      SDdx =  SDdx / 100;
      SDdx =  SDdx + 215;
    }
    else if (counter == 2) { // ~ 15 mile map radius
      SDdx =  SDdx / 300;
      SDdx =  SDdx + 215;
    }
    else if (counter == 3) { // ~ 30 mile map radius
      SDdx =  SDdx / 500;
      SDdx =  SDdx + 215;
    }

    //calculate dx/dy here for range
    if (counter == 1) { // ~  5 mile map radius
      SDdy =  SDdy / 100;
      SDdy = -SDdy + 120;//'-' to reverse N / S map plot
    }
    else if (counter == 2) { // ~ 15 mile map radius
      SDdy =  SDdy / 300;
      SDdy = -SDdy + 120;//'-' to reverse N / S map plot
    }
    else if (counter == 3) { // ~ 30 mile map radius
      SDdy =  SDdy / 500;
      SDdy = -SDdy + 120;//'-' to reverse N / S map plot
    }



    if (((SDdx > MAP_X_MIN) && (SDdx < MAP_X_MAX)) && ((SDdy > MAP_Y_MIN) && (SDdy < MAP_Y_MAX))) {
      tft.drawPixel( SDdx, SDdy, HX8357_WHITE);
    }
    
    //compare to initial buffer x/y
    Serial.print("SDdx: ");  
    Serial.println(SDdx);  
    Serial.print("SDdy: ");  
    Serial.println(SDdy); 

  }

  // close the file:
  myFile.close();
XRAD
  • 17
  • 3
-2

It will be nice if you post your code. Some references that you can use:

  1. Reading line-by-line from SDCard(Arduino) - check this answer:

Arduino reading SD file line by line C++

  1. Parse the characters using the comma delimiter, you could use strtok:

http://www.cplusplus.com/reference/cstring/strtok/

  1. Transform char array to int, you could use atoi:

https://www.cplusplus.com/reference/cstdlib/atoi/

EDIT:

Something like this should do it:

....
String buffer = "211,123"; // you already have this
char *x_str;
x_str = strtok((char*)buffer.c_str(),",");
int SDdx = atoi(x_str);
char *y_str;
y_str = strtok(NULL,",");
int SDdy = atoi(y_str);
tft.drawPixel( SDdx, SDdy, ILI9341_WHITE); // you already have this
Heto
  • 590
  • 4
  • 9
  • Thank you very much! I have read all of those, and many more. it's combining all of those and creating a working function to obtain the parsed data (with what "I think"? I should use, the comma delimiter)... I can parse one line, but not one after the other ... YES! I will add my code as soon as get home! – XRAD Jan 20 '22 at 19:07
  • yes, you need to combine them and create your own function. – Heto Jan 20 '22 at 19:16
  • 1
    That is just fantastic!! TY!!!! – XRAD Jan 21 '22 at 01:21
  • @XRAD I'm happy that it works :). – Heto Jan 21 '22 at 09:04