Go Down

Topic: Parsing an ascii string from serial input (Read 3952 times) previous topic - next topic

41south

Hi all,
Could some of you fine people please steer me in the right direction.

I have started a project to build an antenna rotator for Amatuer radio use. It will be driven by a sattelite tracking program suppling simple ascii commands via a comm port. I am trying to take this a few steps at a time and so far have the data being displayed on an lcd screen  :)

My next step is to try and parse the data so I can use each of 2 numbers to move servo motors to their correct position.

The ascii data is in the format of "AZxxx.x[space]ELyyy.yCRLF". (AZ = Azumith degress, EL = Elevation degrees)
The first issue is that the xxx.x and yyy.y are variable length (i.e it may be 1.0, 23.0, 123.0) but they are never negative. The constants in the string are the AZ, EL, the decimal point, the [space] and the crlf at the end. The number after the decimal point is always zero.

What I need to end up with is 2 pieces of data to be passed to the rest of the code, I guess in variables that could be named something like AzData and ElData. I don't need decimal accuracy so whole numbers would work great. The data is updated by the pc couple of seconds.

The very basic code so far is;
Code: [Select]
// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup(){
    // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // initialize the serial communications:
  Serial.begin(9600);
}

void loop()
{
  // when characters arrive over the serial port...
  if (Serial.available()) {
    // wait a bit for the entire message to arrive
    delay(100);
    // clear the screen
    lcd.clear();
    // read all the available characters
    while (Serial.available() > 0) {
      // display each character to the LCD
      lcd.write(Serial.read());
    }
  }
}


Many thanks
Colin.

PaulS

How to parse the data depends on the method that is used to collect the data. Storing the data in a char array allows the use of strtok() to parse the character array.

Storing the data in a String object allows the use of indexOf() and substring() methods to get the numbers as strings. Then, use toCharArray to convert the String objects to char arrays.

Once the numbers are isolated as char arrays (tokens), they can be converted to numbers. Ignoring the decimal point (change it to a NULL) is easy, and converting the integer to a number, using atoi() is trivial.

41south

#2
Feb 02, 2011, 11:07 pm Last Edit: Feb 02, 2011, 11:21 pm by 41south Reason: 1
Ok, so if I understand you right I need to;

Declare a char array along the lines of;

char inputData[17]                  //Using a max 16 char of data and 1 for the null

Then read my serial data into the array along the lines of;

if(Serial.available() > 16) {
       for(int j = 0; j < 16; j++){
          iputData[j] = Serial.read();
       }

Then I can work on parsing it out?


PaulS

Quote
Then read my serial data into the array along the lines of;

Yes, except don't use i. Use j. The [ i ] doesn't paste well in the forum (it's interpreted as italics start here).

Quote
Then I can work on parsing it out?

Exactly.

41south

#4
Feb 02, 2011, 11:26 pm Last Edit: Feb 03, 2011, 03:22 am by 41south Reason: 1
I just changed those "i" characters  :)

Thanks very much for your help. At first I looked at your initial reply and went "what the....!" But after I read it a couple of times I was able to go away and actually find the answers - great stuff. I have to say at 50 years old my brain hurts just from this little bit  :~ My last foray into programming was with VB many (too many) years ago  :smiley-roll-sweat:

Mark S

The better way to do it is to watch for CR (0x0d) and then when you get that go parse the data


Code: [Select]

int   dataIndex = 0;
char  dataInputBuffer[32];

loop()
{
char theChar;

if (Serial.available())
{
theChar = Serial.read();
if ((theChar == 0x0d) || (dataIndex > 31))
{
dataInputBuffer[dataIndex] = 0; // null terminate the string
ParseData(dataInputBuffer);
dataIndex = 0;
}
else if (theChar >= 0x20)
{
dataInputBuffer[dataIndex] = theChar;
dataIndex++;
}
}
}


41south

Thanks for that - I think I understand most of it  :smiley-slim:

I'm having trouble getting my head around the variable length of my data. What I do know is that whatever is between the "AZ" and the "." needs to become one token and whatever is between "EL" and "." is another.
Anything else can be discarded.

Each of these will be used later to drive 2 stepper motors.

PaulS

If you have read all the data, and stored it in a char array, find the first occurrence of AZ (strstr()).

Code: [Select]
char array[24];
// some code to populate the array
char *az = strstr(array, "AZ");


Then, increment az by the length of the searched for string:
Code: [Select]
az += strlen("AZ");
az now points to the first character after the letters AZ. Suppose array contained "xxxAZ123.45 EL67.890\n\r". az now would point to the 1.

Then, call strtok with az as input and either space or period as the delimiter.
Code: [Select]
char *azval = strtok(az, ".");
azval will now contain 123.

int valAZ = atoi(azval);

If you change the delimiter to space:
Code: [Select]
char *azval = strtok(az, " ");
azval will now contain 123.45.

You could then parse this to get the 123 and 45 as separate strings, and convert each to integer, and divide the second integer appropriately (by 10 for each character in the string), and add it to the first integer to get a float.

The same process can then be used to extract the EL value.


41south

Fantastic, thanks very much for the help. I thought I may be needing the strstr()function, so at least I was thinking in the right direction even if I couldn't quite work it out.  :~

Go Up